######################################################################## # # File Name: Node.py # # """ Implements the basic tree structure of DOM WWW: http://4suite.com/4DOM e-mail: support@4suite.com Copyright (c) 2000 Fourthought Inc, USA. All Rights Reserved. See http://4suite.com/COPYRIGHT for license and copyright information """ import os, os.path, sys xml_path1= os.path.join(os.getenv("BALBES_ROOT"),"PyXML-0.8.4/xml/dom/") xml_path2= os.path.join(os.getenv("BALBES_ROOT"),"PyXML-0.8.4/xml/dom/ext") xml_path3= os.path.join(os.getenv("BALBES_ROOT"),"PyXML-0.8.4/xml/dom/ext/reader") xml_path4= os.path.join(os.getenv("BALBES_ROOT"),"PyXML-0.8.4/xml/utils/") sys.path.append(xml_path1) sys.path.append(xml_path2) sys.path.append(xml_path3) sys.path.append(xml_path4) from DOMImplementation import implementation import Event from xml.dom import Node from dom_init import EMPTY_NAMESPACE from dom_init import NoModificationAllowedErr from dom_init import NamespaceErr from dom_init import NotFoundErr from dom_init import NotSupportedErr from dom_init import HierarchyRequestErr from dom_init import WrongDocumentErr from dom_init import InvalidCharacterErr # from xml.dom import UnspecifiedEventTypeErr from dom_init import UnspecifiedEventTypeErr from dom_init import XML_NAMESPACE import re, copy #FIXME: should allow combining characters: fix when Python gets Unicode g_pattPrefix = re.compile(r'[a-zA-Z_][\w\.\-_]*\Z') _namePattern = None def get_name_pattern(): # delay creating pattern until it is really needed global _namePattern if _namePattern: return _namePattern try: unicode # See whether we have Unicode support except NameError: _namePattern = re.compile('[a-zA-Z_:][\w\.\-_:]*\Z') else: #import xml.utils.characters import characters _namePattern = re.compile(characters.Name+'\Z') return _namePattern class FtNode(Event.EventTarget, Node): """ Encapsulates the pieces that DOM builds on the basic tree structure, Which is implemented by composition of TreeNode """ nodeType = None # Children that this node is allowed to have _allowedChildren = [] def __init__(self, ownerDocument, namespaceURI=EMPTY_NAMESPACE, prefix=None, localName=None): Event.EventTarget.__init__(self) self.__dict__['__nodeName'] = None self.__dict__['__nodeValue'] = None self.__dict__['__parentNode'] = None self.__dict__['__childNodes'] = None self.__dict__['__previousSibling'] = None self.__dict__['__nextSibling'] = None self.__dict__['__attributes'] = None self.__dict__['__ownerDocument'] = ownerDocument self.__dict__['__namespaceURI'] = namespaceURI self.__dict__['__prefix'] = prefix self.__dict__['__localName'] = localName self.__dict__['__childNodes'] = implementation._4dom_createNodeList([]) self.__dict__['__readOnly'] = 0 ### Attribute Access Methods -- Node.attr ### def __getattr__(self, name): attrFunc = self._readComputedAttrs.get(name) if attrFunc: return attrFunc(self) else: return getattr(FtNode, name) def __setattr__(self, name, value): #Make sure attribute is not read-only if name in self.__class__._readOnlyAttrs: raise NoModificationAllowedErr() #If it's computed execute that function attrFunc = self.__class__._writeComputedAttrs.get(name) if attrFunc: attrFunc(self, value) #Otherwise, just set the attribute else: self.__dict__[name] = value ### Attribute Methods -- Node._get_attr() ### def _get_nodeName(self): return self.__dict__['__nodeName'] def _get_nodeValue(self): return self.__dict__['__nodeValue'] def _set_nodeValue(self,value): self.__dict__['__nodeValue'] = value def _get_nodeType(self): return getattr(self.__class__, 'nodeType') def _get_parentNode(self): return self.__dict__['__parentNode'] def _get_childNodes(self): return self.__dict__['__childNodes'] def _get_firstChild(self): cn = self.__dict__['__childNodes'] return cn and cn[0] or None def _get_lastChild(self): cn = self.__dict__['__childNodes'] return cn and cn[-1] or None def _get_previousSibling(self): return self.__dict__['__previousSibling'] def _get_nextSibling(self): return self.__dict__['__nextSibling'] def _get_ownerDocument(self): return self.__dict__['__ownerDocument'] def _get_attributes(self): return self.__dict__['__attributes'] def _get_namespaceURI(self): return self.__dict__['__namespaceURI'] def _get_prefix(self): return self.__dict__['__prefix'] def _set_prefix(self, value): # Check for invalid characters if not get_name_pattern().match(value): raise InvalidCharacterErr() if (self.__dict__['__namespaceURI'] is None or ':' in value or (value == 'xml' and self.__dict__['__namespaceURI'] != XML_NAMESPACE)): raise NamespaceErr() self.__dict__['__prefix'] = value self.__dict__['__nodeName'] = '%s:%s' % ( value, self.__dict__['__localName']) def _get_localName(self): return self.__dict__['__localName'] ### Methods ### def insertBefore(self, newChild, refChild): if refChild is None: return self.appendChild(newChild) elif newChild.nodeType == Node.DOCUMENT_FRAGMENT_NODE: while newChild.firstChild: self.insertBefore(newChild.firstChild, refChild) else: #Make sure the newChild is all it is cracked up to be self._4dom_validateNode(newChild) #Make sure the refChild is indeed our child try: index = self.__dict__['__childNodes'].index(refChild) except: raise NotFoundErr() #Remove from old parent if newChild.parentNode != None: newChild.parentNode.removeChild(newChild); #Insert it self.__dict__['__childNodes'].insert(index, newChild) #Update the child caches newChild._4dom_setHierarchy(self, refChild.previousSibling, refChild) newChild._4dom_fireMutationEvent('DOMNodeInserted',relatedNode=self) self._4dom_fireMutationEvent('DOMSubtreeModified') return newChild def replaceChild(self, newChild, oldChild): if newChild.nodeType == Node.DOCUMENT_FRAGMENT_NODE: refChild = oldChild.nextSibling self.removeChild(oldChild) self.insertBefore(newChild, refChild) else: self._4dom_validateNode(newChild) #Make sure the oldChild is indeed our child try: index = self.__dict__['__childNodes'].index(oldChild) except: raise NotFoundErr() self.__dict__['__childNodes'][index] = newChild if newChild.parentNode is not None: newChild.parentNode.removeChild(newChild) newChild._4dom_setHierarchy(self, oldChild.previousSibling, oldChild.nextSibling) oldChild._4dom_fireMutationEvent('DOMNodeRemoved',relatedNode=self) oldChild._4dom_setHierarchy(None, None, None) newChild._4dom_fireMutationEvent('DOMNodeInserted',relatedNode=self) self._4dom_fireMutationEvent('DOMSubtreeModified') return oldChild def removeChild(self, childNode): #Make sure the childNode is indeed our child #FIXME: more efficient using list.remove() try: self.__dict__['__childNodes'].remove(childNode) except: raise NotFoundErr() childNode._4dom_fireMutationEvent('DOMNodeRemoved',relatedNode=self) self._4dom_fireMutationEvent('DOMSubtreeModified') # Adjust caches prev = childNode.previousSibling next = childNode.nextSibling if prev: prev.__dict__['__nextSibling'] = next if next: next.__dict__['__previousSibling'] = prev childNode._4dom_setHierarchy(None, None, None) return childNode def appendChild(self, newChild): if newChild.nodeType == Node.DOCUMENT_FRAGMENT_NODE: while newChild.childNodes: self.appendChild(newChild.childNodes[0]) else: self._4dom_validateNode(newChild) # Remove from old parent if newChild.parentNode != None: newChild.parentNode.removeChild(newChild); last = self.lastChild self.childNodes.append(newChild) newChild._4dom_setHierarchy(self, last, None) newChild._4dom_fireMutationEvent('DOMNodeInserted',relatedNode=self) self._4dom_fireMutationEvent('DOMSubtreeModified') return newChild def hasChildNodes(self): return self.__dict__['__childNodes'].length != 0 def cloneNode(self, deep, newOwner=None, readOnly=0): # Get constructor values clone = self._4dom_clone(newOwner or self.ownerDocument) # Set when cloning EntRef children readOnly and clone._4dom_setReadOnly(readOnly) # Copy the child nodes if deep if deep and self.nodeType != Node.ATTRIBUTE_NODE: # Children of EntRefs are cloned readOnly if self.nodeType == Node.ENTITY_REFERENCE_NODE: readOnly = 1 for child in self.childNodes: new_child = child.cloneNode(1, newOwner, readOnly) clone.appendChild(new_child) return clone def normalize(self): # This one needs to join all adjacent text nodes node = self.firstChild while node: if node.nodeType == Node.TEXT_NODE: next = node.nextSibling while next and next.nodeType == Node.TEXT_NODE: node.appendData(next.data) node.parentNode.removeChild(next) next = node.nextSibling if not node.length: # Remove any empty text nodes node.parentNode.removeChild(node) elif node.nodeType == Node.ELEMENT_NODE: for attr in node.attributes: attr.normalize() node.normalize() node = node.nextSibling def supports(self, feature, version): return implementation.hasFeature(feature,version) # # Event Target interface implementation # def dispatchEvent(self, evt): if not evt.type: raise UnspecifiedEventTypeErr() # the list of my ancestors for capture or bubbling # we are lazy, so we initialize this list only if required if evt._4dom_propagate and \ (evt.eventPhase == evt.CAPTURING_PHASE or evt.bubbles): ancestors = [self] while ancestors[-1].parentNode : ancestors.append(ancestors[-1].parentNode) # event capture if evt._4dom_propagate and evt.eventPhase == evt.CAPTURING_PHASE : ancestors.reverse() for a in ancestors[:-1]: evt.currentTarget = a for captor in a.capture_listeners[evt.type]: captor.handleEvent(evt) if not evt._4dom_propagate: break # let's put back the list in the right order # and move on to the next phase ancestors.reverse() evt.eventPhase = evt.AT_TARGET # event handling by the target if evt._4dom_propagate and evt.eventPhase == evt.AT_TARGET : evt.currentTarget = self for listener in self.listeners[evt.type]: listener.handleEvent(evt) # prepare for the next phase, if necessary if evt.bubbles: evt.eventPhase = evt.BUBBLING_PHASE # event bubbling if evt._4dom_propagate and evt.eventPhase == evt.BUBBLING_PHASE : for a in ancestors[1:]: evt.currentTarget = a for listener in a.listeners[evt.type]: listener.handleEvent(evt) if not evt._4dom_propagate: break return evt._4dom_preventDefaultCalled ### Unsupported, undocumented DOM Level 3 methods ### ### documented in the Python binding ### def isSameNode(self, other): return self == other ### Internal Methods ### #Functions not defined in the standard #All are fourthought internal functions #and should only be called by you if you specifically #don't want your program to run :) def _4dom_setattr(self, name, value): self.__dict__[name] = value def _4dom_fireMutationEvent(self,eventType,target=None, relatedNode=None,prevValue=None, newValue=None,attrName=None,attrChange=None): if self.supports('MutationEvents', 2.0): evt = self.ownerDocument.createEvent(eventType) evt.target = target or self evt.initMutationEvent(eventType,evt.eventSpec[eventType],0, relatedNode,prevValue,newValue,attrName) evt.attrChange = attrChange evt.target.dispatchEvent(evt) def _4dom_validateNode(self, newNode): if not newNode.nodeType in self.__class__._allowedChildren: raise HierarchyRequestErr() self._4dom_raiseIfAncestor(newNode) if self.ownerDocument != newNode.ownerDocument: raise WrongDocumentErr() def _4dom_raiseIfAncestor(self, node): "Helper function that raises if node is an ancestor of self or self." n = self if n is node: raise HierarchyRequestErr() if node.hasChildNodes(): while n is not None: n = n.parentNode if n is node: raise HierarchyRequestErr() def _4dom_setHierarchy(self, parent, previous, next): self.__dict__['__parentNode'] = parent if previous: previous.__dict__['__nextSibling'] = self self.__dict__['__previousSibling'] = previous self.__dict__['__nextSibling'] = next if next: next.__dict__['__previousSibling'] = self return def _4dom_setParentNode(self, parent): self.__dict__['__parentNode'] = parent def _4dom_setNextSibling(self,next): self.__dict__['__nextSibling'] = next def _4dom_setPreviousSibling(self,prev): self.__dict__['__previousSibling'] = prev def _4dom_setOwnerDocument(self, owner): self.__dict__['__ownerDocument'] = owner def _4dom_setReadOnly(self, flag): self.__dict__['__readOnly'] = flag ### Helper Functions For Cloning ### def _4dom_clone(self, owner): raise NotSupportedErr('Subclass must override') def __getinitargs__(self): return (self.__dict__['__ownerDocument'], self.__dict__['__namespaceURI'], self.__dict__['__prefix'], self.__dict__['__localName'] ) def __getstate__(self): return self.__dict__['__childNodes'] def __setstate__(self, children): self.__dict__['__childNodes'].extend(list(children)) prev = None for child in children: child._4dom_setHierarchy(self, prev, None) prev = child ### Attribute Access Mappings ### _readComputedAttrs = {'nodeName':_get_nodeName, 'nodeValue':_get_nodeValue, 'nodeType':_get_nodeType, 'parentNode':_get_parentNode, 'childNodes':_get_childNodes, 'firstChild':_get_firstChild, 'lastChild':_get_lastChild, 'previousSibling':_get_previousSibling, 'nextSibling':_get_nextSibling, 'attributes':_get_attributes, 'ownerDocument':_get_ownerDocument, 'namespaceURI':_get_namespaceURI, 'prefix':_get_prefix, 'localName':_get_localName } _writeComputedAttrs = {'nodeValue':_set_nodeValue, 'prefix':_set_prefix } # Create the read-only list of attributes _readOnlyAttrs = filter(lambda k,m=_writeComputedAttrs: not m.has_key(k), _readComputedAttrs.keys())