# # # Nim's Runtime Library # (c) Copyright 2010 Dominik Picheta # # See the file "copying.txt", included in this # distribution, for details about the copyright. # import strutils ## This module implements XML DOM Level 2 Core ## specification (http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html) #http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html #Exceptions type EDOMException* = object of ValueError ## Base exception object for all DOM Exceptions EDOMStringSizeErr* = object of EDOMException ## If the specified range of text does not fit into a DOMString ## Currently not used(Since DOMString is just string) EHierarchyRequestErr* = object of EDOMException ## If any node is inserted somewhere it doesn't belong EIndexSizeErr* = object of EDOMException ## If index or size is negative, or greater than the allowed value EInuseAttributeErr* = object of EDOMException ## If an attempt is made to add an attribute that is already in use elsewhere EInvalidAccessErr* = object of EDOMException ## If a parameter or an operation is not supported by the underlying object. EInvalidCharacterErr* = object of EDOMException ## This exception is raised when a string parameter contains an illegal character EInvalidModificationErr* = object of EDOMException ## If an attempt is made to modify the type of the underlying object. EInvalidStateErr* = object of EDOMException ## If an attempt is made to use an object that is not, or is no longer, usable. ENamespaceErr* = object of EDOMException ## If an attempt is made to create or change an object in a way which is incorrect with regard to namespaces. ENotFoundErr* = object of EDOMException ## If an attempt is made to reference a node in a context where it does not exist ENotSupportedErr* = object of EDOMException ## If the implementation does not support the requested type of object or operation. ENoDataAllowedErr* = object of EDOMException ## If data is specified for a node which does not support data ENoModificationAllowedErr* = object of EDOMException ## If an attempt is made to modify an object where modifications are not allowed ESyntaxErr* = object of EDOMException ## If an invalid or illegal string is specified. EWrongDocumentErr* = object of EDOMException ## If a node is used in a different document than the one that created it (that doesn't support it) const ElementNode* = 1 AttributeNode* = 2 TextNode* = 3 CDataSectionNode* = 4 ProcessingInstructionNode* = 7 CommentNode* = 8 DocumentNode* = 9 DocumentFragmentNode* = 11 # Nodes which are childless - Not sure about AttributeNode childlessObjects = {DocumentNode, AttributeNode, TextNode, CDataSectionNode, ProcessingInstructionNode, CommentNode} # Illegal characters illegalChars = {'>', '<', '&', '"'} type Feature = tuple[name: string, version: string] PDOMImplementation* = ref DOMImplementation DOMImplementation = object features: seq[Feature] # Read-Only PNode* = ref Node Node = object of RootObj attributes*: seq[PAttr] childNodes*: seq[PNode] fLocalName: string # Read-only fNamespaceURI: string # Read-only fNodeName: string # Read-only nodeValue*: string fNodeType: int # Read-only fOwnerDocument: PDocument # Read-Only fParentNode: PNode # Read-Only prefix*: string # Setting this should change some values... TODO! PElement* = ref Element Element = object of Node fTagName: string # Read-only PCharacterData* = ref CharacterData CharacterData = object of Node data*: string PDocument* = ref Document Document = object of Node fImplementation: PDOMImplementation # Read-only fDocumentElement: PElement # Read-only PAttr* = ref Attr Attr = object of Node fName: string # Read-only fSpecified: bool # Read-only value*: string fOwnerElement: PElement # Read-only PDocumentFragment* = ref DocumentFragment DocumentFragment = object of Node PText* = ref Text Text = object of CharacterData PComment* = ref Comment Comment = object of CharacterData PCDataSection* = ref CDataSection CDataSection = object of Text PProcessingInstruction* = ref ProcessingInstruction ProcessingInstruction = object of Node data*: string fTarget: string # Read-only # DOMImplementation proc getDOM*(): PDOMImplementation = ## Returns a DOMImplementation new(result) result.features = @[(name: "core", version: "2.0"), (name: "core", version: "1.0"), (name: "XML", version: "2.0")] proc createDocument*(dom: PDOMImplementation, namespaceURI: string, qualifiedName: string): PDocument = ## Creates an XML Document object of the specified type with its document element. var doc: PDocument new(doc) doc.fNamespaceURI = namespaceURI doc.fImplementation = dom var elTag: PElement new(elTag) elTag.fTagName = qualifiedName elTag.fNodeName = qualifiedName doc.fDocumentElement = elTag doc.fNodeType = DocumentNode return doc proc createDocument*(dom: PDOMImplementation, n: PElement): PDocument = ## Creates an XML Document object of the specified type with its document element. # This procedure is not in the specification, it's provided for the parser. var doc: PDocument new(doc) doc.fDocumentElement = n doc.fImplementation = dom doc.fNodeType = DocumentNode return doc proc hasFeature*(dom: PDOMImplementation, feature: string, version: string = ""): bool = ## Returns ``true`` if this ``version`` of the DomImplementation implements ``feature``, otherwise ``false`` for iName, iVersion in items(dom.features): if iName == feature: if version == "": return true else: if iVersion == version: return true return false # Document # Attributes proc implementation*(doc: PDocument): PDOMImplementation = return doc.fImplementation proc documentElement*(doc: PDocument): PElement = return doc.fDocumentElement # Internal procedures proc findNodes(nl: PNode, name: string): seq[PNode] = # Made for getElementsByTagName var r: seq[PNode] = @[] if isNil(nl.childNodes): return @[] if nl.childNodes.len() == 0: return @[] for i in items(nl.childNodes): if i.fNodeType == ElementNode: if i.fNodeName == name or name == "*": r.add(i) if not isNil(i.childNodes): if i.childNodes.len() != 0: r.add(findNodes(i, name)) return r proc findNodesNS(nl: PNode, namespaceURI: string, localName: string): seq[PNode] = # Made for getElementsByTagNameNS var r: seq[PNode] = @[] if isNil(nl.childNodes): return @[] if nl.childNodes.len() == 0: return @[] for i in items(nl.childNodes): if i.fNodeType == ElementNode: if (i.fNamespaceURI == namespaceURI or namespaceURI == "*") and (i.fLocalName == localName or localName == "*"): r.add(i) if not isNil(i.childNodes): if i.childNodes.len() != 0: r.add(findNodesNS(i, namespaceURI, localName)) return r #Procedures proc createAttribute*(doc: PDocument, name: string): PAttr = ## Creates an Attr of the given name. Note that the Attr instance can then be set on an Element using the setAttributeNode method. ## To create an attribute with a qualified name and namespace URI, use the createAttributeNS method. # Check if name contains illegal characters if illegalChars in name: raise newException(EInvalidCharacterErr, "Invalid character") var attrNode: PAttr new(attrNode) attrNode.fName = name attrNode.fNodeName = name attrNode.fLocalName = nil attrNode.prefix = nil attrNode.fNamespaceURI = nil attrNode.value = "" attrNode.fSpecified = false return attrNode proc createAttributeNS*(doc: PDocument, namespaceURI: string, qualifiedName: string): PAttr = ## Creates an attribute of the given qualified name and namespace URI # Check if name contains illegal characters if illegalChars in namespaceURI or illegalChars in qualifiedName: raise newException(EInvalidCharacterErr, "Invalid character") # Exceptions if qualifiedName.contains(':'): if isNil(namespaceURI): raise newException(ENamespaceErr, "When qualifiedName contains a prefix namespaceURI cannot be nil") elif qualifiedName.split(':')[0].toLower() == "xml" and namespaceURI != "http://www.w3.org/XML/1998/namespace": raise newException(ENamespaceErr, "When the namespace prefix is \"xml\" namespaceURI has to be \"http://www.w3.org/XML/1998/namespace\"") elif qualifiedName.split(':')[1].toLower() == "xmlns" and namespaceURI != "http://www.w3.org/2000/xmlns/": raise newException(ENamespaceErr, "When the namespace prefix is \"xmlns\" namespaceURI has to be \"http://www.w3.org/2000/xmlns/\"") var attrNode: PAttr new(attrNode) attrNode.fName = qualifiedName attrNode.fNodeName = qualifiedName attrNode.fSpecified = false attrNode.fNamespaceURI = namespaceURI if qualifiedName.contains(':'): attrNode.prefix = qualifiedName.split(':')[0] attrNode.fLocalName = qualifiedName.split(':')[1] else: attrNode.prefix = nil attrNode.fLocalName = qualifiedName attrNode.value = "" attrNode.fNodeType = AttributeNode return attrNode proc createCDATASection*(doc: PDocument, data: string): PCDataSection = ## Creates a CDATASection node whose value is the specified string. var cData: PCDataSection new(cData) cData.data = data cData.nodeValue = data cData.fNodeName = "#text" # Not sure about this, but this is technically a TextNode cData.fNodeType = CDataSectionNode return cData proc createComment*(doc: PDocument, data: string): PComment = ## Creates a Comment node given the specified string. var comm: PComment new(comm) comm.data = data comm.nodeValue = data comm.fNodeType = CommentNode return comm proc createDocumentFragment*(doc: PDocument): PDocumentFragment = ## Creates an empty DocumentFragment object. var df: PDocumentFragment new(df) return df proc createElement*(doc: PDocument, tagName: string): PElement = ## Creates an element of the type specified. # Check if name contains illegal characters if illegalChars in tagName: raise newException(EInvalidCharacterErr, "Invalid character") var elNode: PElement new(elNode) elNode.fTagName = tagName elNode.fNodeName = tagName elNode.fLocalName = nil elNode.prefix = nil elNode.fNamespaceURI = nil elNode.childNodes = @[] elNode.attributes = @[] elNode.fNodeType = ElementNode return elNode proc createElementNS*(doc: PDocument, namespaceURI: string, qualifiedName: string): PElement = ## Creates an element of the given qualified name and namespace URI. if qualifiedName.contains(':'): if isNil(namespaceURI): raise newException(ENamespaceErr, "When qualifiedName contains a prefix namespaceURI cannot be nil") elif qualifiedName.split(':')[0].toLower() == "xml" and namespaceURI != "http://www.w3.org/XML/1998/namespace": raise newException(ENamespaceErr, "When the namespace prefix is \"xml\" namespaceURI has to be \"http://www.w3.org/XML/1998/namespace\"") # Check if name contains illegal characters if illegalChars in namespaceURI or illegalChars in qualifiedName: raise newException(EInvalidCharacterErr, "Invalid character") var elNode: PElement new(elNode) elNode.fTagName = qualifiedName elNode.fNodeName = qualifiedName if qualifiedName.contains(':'): elNode.prefix = qualifiedName.split(':')[0] elNode.fLocalName = qualifiedName.split(':')[1] else: elNode.prefix = nil elNode.fLocalName = qualifiedName elNode.fNamespaceURI = namespaceURI elNode.childNodes = @[] elNode.attributes = @[] elNode.fNodeType = ElementNode return elNode proc createProcessingInstruction*(doc: PDocument, target: string, data: string): PProcessingInstruction = ## Creates a ProcessingInstruction node given the specified name and data strings. #Check if name contains illegal characters if illegalChars in target: raise newException(EInvalidCharacterErr, "Invalid character") var pi: PProcessingInstruction new(pi) pi.fTarget = target pi.data = data pi.fNodeType = ProcessingInstructionNode return pi proc createTextNode*(doc: PDocument, data: string): PText = #Propably TextNode ## Creates a Text node given the specified string. var txtNode: PText new(txtNode) txtNode.data = data txtNode.nodeValue = data txtNode.fNodeName = "#text" txtNode.fNodeType = TextNode return txtNode discard """proc getElementById*(doc: PDocument, elementId: string): PElement = ##Returns the ``Element`` whose ID is given by ``elementId``. If no such element exists, returns ``nil`` #TODO""" proc getElementsByTagName*(doc: PDocument, tagName: string): seq[PNode] = ## Returns a NodeList of all the Elements with a given tag name in ## the order in which they are encountered in a preorder traversal of the Document tree. result = @[] if doc.fDocumentElement.fNodeName == tagName or tagName == "*": result.add(doc.fDocumentElement) result.add(doc.fDocumentElement.findNodes(tagName)) proc getElementsByTagNameNS*(doc: PDocument, namespaceURI: string, localName: string): seq[PNode] = ## Returns a NodeList of all the Elements with a given localName and namespaceURI ## in the order in which they are encountered in a preorder traversal of the Document tree. result = @[] if doc.fDocumentElement.fLocalName == localName or localName == "*": if doc.fDocumentElement.fNamespaceURI == namespaceURI or namespaceURI == "*": result.add(doc.fDocumentElement) result.add(doc.fDocumentElement.findNodesNS(namespaceURI, localName)) proc importNode*(doc: PDocument, importedNode: PNode, deep: bool): PNode = ## Imports a node from another document to this document case importedNode.fNodeType of AttributeNode: var nAttr: PAttr = PAttr(importedNode) nAttr.fOwnerDocument = doc nAttr.fParentNode = nil nAttr.fOwnerElement = nil nAttr.fSpecified = true return nAttr of DocumentFragmentNode: var n: PNode new(n) n = importedNode n.fOwnerDocument = doc n.fParentNode = nil n.fOwnerDocument = doc n.fParentNode = nil var tmp: seq[PNode] = n.childNodes n.childNodes = @[] if deep: for i in low(tmp.len())..high(tmp.len()): n.childNodes.add(importNode(doc, tmp[i], deep)) return n of ElementNode: var n: PNode new(n) n = importedNode n.fOwnerDocument = doc n.fParentNode = nil var tmpA: seq[PAttr] = n.attributes n.attributes = @[] # Import the Element node's attributes for i in low(tmpA.len())..high(tmpA.len()): n.attributes.add(PAttr(importNode(doc, tmpA[i], deep))) # Import the childNodes var tmp: seq[PNode] = n.childNodes n.childNodes = @[] if deep: for i in low(tmp.len())..high(tmp.len()): n.childNodes.add(importNode(doc, tmp[i], deep)) return n of ProcessingInstructionNode, TextNode, CDataSectionNode, CommentNode: var n: PNode new(n) n = importedNode n.fOwnerDocument = doc n.fParentNode = nil return n else: raise newException(ENotSupportedErr, "The type of node being imported is not supported") # Node # Attributes proc firstChild*(n: PNode): PNode = ## Returns this node's first child if not isNil(n.childNodes) and n.childNodes.len() > 0: return n.childNodes[0] else: return nil proc lastChild*(n: PNode): PNode = ## Returns this node's last child if not isNil(n.childNodes) and n.childNodes.len() > 0: return n.childNodes[n.childNodes.len() - 1] else: return nil proc localName*(n: PNode): string = ## Returns this nodes local name return n.fLocalName proc namespaceURI*(n: PNode): string = ## Returns this nodes namespace URI return n.fNamespaceURI proc `namespaceURI=`*(n: PNode, value: string) = n.fNamespaceURI = value proc nextSibling*(n: PNode): PNode = ## Returns the next sibling of this node if isNil(n.fParentNode) or isNil(n.fParentNode.childNodes): return nil var nLow: int = low(n.fParentNode.childNodes) var nHigh: int = high(n.fParentNode.childNodes) for i in nLow..nHigh: if n.fParentNode.childNodes[i] == n: return n.fParentNode.childNodes[i + 1] return nil proc nodeName*(n: PNode): string = ## Returns the name of this node return n.fNodeName proc nodeType*(n: PNode): int = ## Returns the type of this node return n.fNodeType proc ownerDocument*(n: PNode): PDocument = ## Returns the owner document of this node return n.fOwnerDocument proc parentNode*(n: PNode): PNode = ## Returns the parent node of this node return n.fParentNode proc previousSibling*(n: PNode): PNode = ## Returns the previous sibling of this node if isNil(n.fParentNode) or isNil(n.fParentNode.childNodes): return nil var nLow: int = low(n.fParentNode.childNodes) var nHigh: int = high(n.fParentNode.childNodes) for i in nLow..nHigh: if n.fParentNode.childNodes[i] == n: return n.fParentNode.childNodes[i - 1] return nil proc `prefix=`*(n: PNode, value: string) = ## Modifies the prefix of this node # Setter # Check if name contains illegal characters if illegalChars in value: raise newException(EInvalidCharacterErr, "Invalid character") if isNil(n.fNamespaceURI): raise newException(ENamespaceErr, "namespaceURI cannot be nil") elif value.toLower() == "xml" and n.fNamespaceURI != "http://www.w3.org/XML/1998/namespace": raise newException(ENamespaceErr, "When the namespace prefix is \"xml\" namespaceURI has to be \"http://www.w3.org/XML/1998/namespace\"") elif value.toLower() == "xmlns" and n.fNamespaceURI != "http://www.w3.org/2000/xmlns/": raise newException(ENamespaceErr, "When the namespace prefix is \"xmlns\" namespaceURI has to be \"http://www.w3.org/2000/xmlns/\"") elif value.toLower() == "xmlns" and n.fNodeType == AttributeNode: raise newException(ENamespaceErr, "An AttributeNode cannot have a prefix of \"xmlns\"") n.fNodeName = value & ":" & n.fLocalName if n.nodeType == ElementNode: var el: PElement = PElement(n) el.fTagName = value & ":" & n.fLocalName elif n.nodeType == AttributeNode: var attr: PAttr = PAttr(n) attr.fName = value & ":" & n.fLocalName # Procedures proc appendChild*(n: PNode, newChild: PNode) = ## Adds the node newChild to the end of the list of children of this node. ## If the newChild is already in the tree, it is first removed. # Check if n contains newChild if not isNil(n.childNodes): for i in low(n.childNodes)..high(n.childNodes): if n.childNodes[i] == newChild: raise newException(EHierarchyRequestErr, "The node to append is already in this nodes children.") # Check if newChild is from this nodes document if n.fOwnerDocument != newChild.fOwnerDocument: raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") if n == newChild: raise newException(EHierarchyRequestErr, "You can't add a node into itself") if n.nodeType in childlessObjects: raise newException(ENoModificationAllowedErr, "Cannot append children to a childless node") if isNil(n.childNodes): n.childNodes = @[] newChild.fParentNode = n for i in low(n.childNodes)..high(n.childNodes): if n.childNodes[i] == newChild: n.childNodes[i] = newChild n.childNodes.add(newChild) proc cloneNode*(n: PNode, deep: bool): PNode = ## Returns a duplicate of this node, if ``deep`` is `true`, Element node's children are copied case n.fNodeType of AttributeNode: var newNode: PAttr new(newNode) newNode = PAttr(n) newNode.fSpecified = true newNode.fOwnerElement = nil return newNode of ElementNode: var newNode: PElement new(newNode) newNode = PElement(n) # Import the childNodes var tmp: seq[PNode] = n.childNodes n.childNodes = @[] if deep and not isNil(tmp): for i in low(tmp.len())..high(tmp.len()): n.childNodes.add(cloneNode(tmp[i], deep)) return newNode else: var newNode: PNode new(newNode) newNode = n return newNode proc hasAttributes*(n: PNode): bool = ## Returns whether this node (if it is an element) has any attributes. return not isNil(n.attributes) and n.attributes.len() > 0 proc hasChildNodes*(n: PNode): bool = ## Returns whether this node has any children. return not isNil(n.childNodes) and n.childNodes.len() > 0 proc insertBefore*(n: PNode, newChild: PNode, refChild: PNode): PNode = ## Inserts the node ``newChild`` before the existing child node ``refChild``. ## If ``refChild`` is nil, insert ``newChild`` at the end of the list of children. # Check if newChild is from this nodes document if n.fOwnerDocument != newChild.fOwnerDocument: raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") if isNil(n.childNodes): n.childNodes = @[] for i in low(n.childNodes)..high(n.childNodes): if n.childNodes[i] == refChild: n.childNodes.insert(newChild, i - 1) return n.childNodes.add(newChild) proc isSupported*(n: PNode, feature: string, version: string): bool = ## Tests whether the DOM implementation implements a specific ## feature and that feature is supported by this node. return n.fOwnerDocument.fImplementation.hasFeature(feature, version) proc isEmpty(s: string): bool = if isNil(s) or s == "": return true for i in items(s): if i != ' ': return false return true proc normalize*(n: PNode) = ## Merges all separated TextNodes together, and removes any empty TextNodes var curTextNode: PNode = nil var i: int = 0 var newChildNodes: seq[PNode] = @[] while true: if isNil(n.childNodes) or i >= n.childNodes.len: break if n.childNodes[i].nodeType == TextNode: #If the TextNode is empty, remove it if PText(n.childNodes[i]).data.isEmpty(): inc(i) if isNil(curTextNode): curTextNode = n.childNodes[i] else: PText(curTextNode).data.add(PText(n.childNodes[i]).data) curTextNode.nodeValue.add(PText(n.childNodes[i]).data) inc(i) else: newChildNodes.add(curTextNode) newChildNodes.add(n.childNodes[i]) curTextNode = nil inc(i) n.childNodes = newChildNodes proc removeChild*(n: PNode, oldChild: PNode): PNode = ## Removes the child node indicated by ``oldChild`` from the list of children, and returns it. if not isNil(n.childNodes): for i in low(n.childNodes)..high(n.childNodes): if n.childNodes[i] == oldChild: result = n.childNodes[i] n.childNodes.delete(i) return raise newException(ENotFoundErr, "Node not found") proc replaceChild*(n: PNode, newChild: PNode, oldChild: PNode): PNode = ## Replaces the child node ``oldChild`` with ``newChild`` in the list of children, and returns the ``oldChild`` node. # Check if newChild is from this nodes document if n.fOwnerDocument != newChild.fOwnerDocument: raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") if not isNil(n.childNodes): for i in low(n.childNodes)..high(n.childNodes): if n.childNodes[i] == oldChild: result = n.childNodes[i] n.childNodes[i] = newChild return raise newException(ENotFoundErr, "Node not found") # NamedNodeMap proc getNamedItem*(nList: seq[PNode], name: string): PNode = ## Retrieves a node specified by ``name``. If this node cannot be found returns ``nil`` for i in items(nList): if i.nodeName() == name: return i return nil proc getNamedItem*(nList: seq[PAttr], name: string): PAttr = ## Retrieves a node specified by ``name``. If this node cannot be found returns ``nil`` for i in items(nList): if i.nodeName() == name: return i return nil proc getNamedItemNS*(nList: seq[PNode], namespaceURI: string, localName: string): PNode = ## Retrieves a node specified by ``localName`` and ``namespaceURI``. If this node cannot be found returns ``nil`` for i in items(nList): if i.namespaceURI() == namespaceURI and i.localName() == localName: return i return nil proc getNamedItemNS*(nList: seq[PAttr], namespaceURI: string, localName: string): PAttr = ## Retrieves a node specified by ``localName`` and ``namespaceURI``. If this node cannot be found returns ``nil`` for i in items(nList): if i.namespaceURI() == namespaceURI and i.localName() == localName: return i return nil proc item*(nList: seq[PNode], index: int): PNode = ## Returns the ``index`` th item in the map. ## If ``index`` is greater than or equal to the number of nodes in this map, this returns ``nil``. if index >= nList.len(): return nil else: return nList[index] proc removeNamedItem*(nList: var seq[PNode], name: string): PNode = ## Removes a node specified by ``name`` ## Raises the ``ENotFoundErr`` exception, if the node was not found for i in low(nList)..high(nList): if nList[i].fNodeName == name: result = nList[i] nList.delete(i) return raise newException(ENotFoundErr, "Node not found") proc removeNamedItemNS*(nList: var seq[PNode], namespaceURI: string, localName: string): PNode = ## Removes a node specified by local name and namespace URI for i in low(nList)..high(nList): if nList[i].fLocalName == localName and nList[i].fNamespaceURI == namespaceURI: result = nList[i] nList.delete(i) return raise newException(ENotFoundErr, "Node not found") proc setNamedItem*(nList: var seq[PNode], arg: PNode): PNode = ## Adds ``arg`` as a ``Node`` to the ``NList`` ## If a node with the same name is already present in this map, it is replaced by the new one. if not isNil(nList): if nList.len() > 0: #Check if newChild is from this nodes document if nList[0].fOwnerDocument != arg.fOwnerDocument: raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") #Exceptions End var item: PNode = nList.getNamedItem(arg.nodeName()) if isNil(item): nList.add(arg) return nil else: # Node with the same name exists var index: int = 0 for i in low(nList)..high(nList): if nList[i] == item: index = i break nList[index] = arg return item # Return the replaced node proc setNamedItem*(nList: var seq[PAttr], arg: PAttr): PAttr = ## Adds ``arg`` as a ``Node`` to the ``NList`` ## If a node with the same name is already present in this map, it is replaced by the new one. if not isNil(nList): if nList.len() > 0: # Check if newChild is from this nodes document if nList[0].fOwnerDocument != arg.fOwnerDocument: raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") if not isNil(arg.fOwnerElement): raise newException(EInuseAttributeErr, "This attribute is in use by another element, use cloneNode") # Exceptions end var item: PAttr = nList.getNamedItem(arg.nodeName()) if isNil(item): nList.add(arg) return nil else: # Node with the same name exists var index: int = 0 for i in low(nList)..high(nList): if nList[i] == item: index = i break nList[index] = arg return item # Return the replaced node proc setNamedItemNS*(nList: var seq[PNode], arg: PNode): PNode = ## Adds a node using its ``namespaceURI`` and ``localName`` if not isNil(nList): if nList.len() > 0: # Check if newChild is from this nodes document if nList[0].fOwnerDocument != arg.fOwnerDocument: raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") #Exceptions end var item: PNode = nList.getNamedItemNS(arg.namespaceURI(), arg.localName()) if isNil(item): nList.add(arg) return nil else: # Node with the same name exists var index: int = 0 for i in low(nList)..high(nList): if nList[i] == item: index = i break nList[index] = arg return item # Return the replaced node proc setNamedItemNS*(nList: var seq[PAttr], arg: PAttr): PAttr = ## Adds a node using its ``namespaceURI`` and ``localName`` if not isNil(nList): if nList.len() > 0: # Check if newChild is from this nodes document if nList[0].fOwnerDocument != arg.fOwnerDocument: raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") if not isNil(arg.fOwnerElement): raise newException(EInuseAttributeErr, "This attribute is in use by another element, use cloneNode") # Exceptions end var item: PAttr = nList.getNamedItemNS(arg.namespaceURI(), arg.localName()) if isNil(item): nList.add(arg) return nil else: # Node with the same name exists var index: int = 0 for i in low(nList)..high(nList): if nList[i] == item: index = i break nList[index] = arg return item # Return the replaced node # CharacterData - Decided to implement this, # Didn't add the procedures, because you can just edit .data # Attr # Attributes proc name*(a: PAttr): string = ## Returns the name of the Attribute return a.fName proc specified*(a: PAttr): bool = ## Specifies whether this attribute was specified in the original document return a.fSpecified proc ownerElement*(a: PAttr): PElement = ## Returns this Attributes owner element return a.fOwnerElement # Element # Attributes proc tagName*(el: PElement): string = ## Returns the Element Tag Name return el.fTagName # Procedures proc getAttribute*(el: PElement, name: string): string = ## Retrieves an attribute value by ``name`` if isNil(el.attributes): return nil var attribute = el.attributes.getNamedItem(name) if not isNil(attribute): return attribute.value else: return nil proc getAttributeNS*(el: PElement, namespaceURI: string, localName: string): string = ## Retrieves an attribute value by ``localName`` and ``namespaceURI`` if isNil(el.attributes): return nil var attribute = el.attributes.getNamedItemNS(namespaceURI, localName) if not isNil(attribute): return attribute.value else: return nil proc getAttributeNode*(el: PElement, name: string): PAttr = ## Retrieves an attribute node by ``name`` ## To retrieve an attribute node by qualified name and namespace URI, use the `getAttributeNodeNS` method if isNil(el.attributes): return nil return el.attributes.getNamedItem(name) proc getAttributeNodeNS*(el: PElement, namespaceURI: string, localName: string): PAttr = ## Retrieves an `Attr` node by ``localName`` and ``namespaceURI`` if isNil(el.attributes): return nil return el.attributes.getNamedItemNS(namespaceURI, localName) proc getElementsByTagName*(el: PElement, name: string): seq[PNode] = ## Returns a `NodeList` of all descendant `Elements` of ``el`` with a given tag ``name``, ## in the order in which they are encountered in a preorder traversal of this `Element` tree ## If ``name`` is `*`, returns all descendant of ``el`` result = el.findNodes(name) proc getElementsByTagNameNS*(el: PElement, namespaceURI: string, localName: string): seq[PNode] = ## Returns a `NodeList` of all the descendant Elements with a given ## ``localName`` and ``namespaceURI`` in the order in which they are ## encountered in a preorder traversal of this Element tree result = el.findNodesNS(namespaceURI, localName) proc hasAttribute*(el: PElement, name: string): bool = ## Returns ``true`` when an attribute with a given ``name`` is specified ## on this element , ``false`` otherwise. if isNil(el.attributes): return false return not isNil(el.attributes.getNamedItem(name)) proc hasAttributeNS*(el: PElement, namespaceURI: string, localName: string): bool = ## Returns ``true`` when an attribute with a given ``localName`` and ## ``namespaceURI`` is specified on this element , ``false`` otherwise if isNil(el.attributes): return false return not isNil(el.attributes.getNamedItemNS(namespaceURI, localName)) proc removeAttribute*(el: PElement, name: string) = ## Removes an attribute by ``name`` if not isNil(el.attributes): for i in low(el.attributes)..high(el.attributes): if el.attributes[i].fName == name: el.attributes.delete(i) proc removeAttributeNS*(el: PElement, namespaceURI: string, localName: string) = ## Removes an attribute by ``localName`` and ``namespaceURI`` if not isNil(el.attributes): for i in low(el.attributes)..high(el.attributes): if el.attributes[i].fNamespaceURI == namespaceURI and el.attributes[i].fLocalName == localName: el.attributes.delete(i) proc removeAttributeNode*(el: PElement, oldAttr: PAttr): PAttr = ## Removes the specified attribute node ## If the attribute node cannot be found raises ``ENotFoundErr`` if not isNil(el.attributes): for i in low(el.attributes)..high(el.attributes): if el.attributes[i] == oldAttr: result = el.attributes[i] el.attributes.delete(i) return raise newException(ENotFoundErr, "oldAttr is not a member of el's Attributes") proc setAttributeNode*(el: PElement, newAttr: PAttr): PAttr = ## Adds a new attribute node, if an attribute with the same `nodeName` is ## present, it is replaced by the new one and the replaced attribute is ## returned, otherwise ``nil`` is returned. # Check if newAttr is from this nodes document if el.fOwnerDocument != newAttr.fOwnerDocument: raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") if not isNil(newAttr.fOwnerElement): raise newException(EInuseAttributeErr, "This attribute is in use by another element, use cloneNode") # Exceptions end if isNil(el.attributes): el.attributes = @[] return el.attributes.setNamedItem(newAttr) proc setAttributeNodeNS*(el: PElement, newAttr: PAttr): PAttr = ## Adds a new attribute node, if an attribute with the localName and ## namespaceURI of ``newAttr`` is present, it is replaced by the new one ## and the replaced attribute is returned, otherwise ``nil`` is returned. # Check if newAttr is from this nodes document if el.fOwnerDocument != newAttr.fOwnerDocument: raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") if not isNil(newAttr.fOwnerElement): raise newException(EInuseAttributeErr, "This attribute is in use by another element, use cloneNode") # Exceptions end if isNil(el.attributes): el.attributes = @[] return el.attributes.setNamedItemNS(newAttr) proc setAttribute*(el: PElement, name: string, value: string) = ## Adds a new attribute, as specified by ``name`` and ``value`` ## If an attribute with that name is already present in the element, its ## value is changed to be that of the value parameter ## Raises the EInvalidCharacterErr if the specified ``name`` contains ## illegal characters var attrNode = el.fOwnerDocument.createAttribute(name) # Check if name contains illegal characters if illegalChars in name: raise newException(EInvalidCharacterErr, "Invalid character") discard el.setAttributeNode(attrNode) # Set the info later, the setAttributeNode checks # if FOwnerElement is nil, and if it isn't it raises an exception attrNode.fOwnerElement = el attrNode.fSpecified = true attrNode.value = value proc setAttributeNS*(el: PElement, namespaceURI, localName, value: string) = ## Adds a new attribute, as specified by ``namespaceURI``, ``localName`` ## and ``value``. # Check if name contains illegal characters if illegalChars in namespaceURI or illegalChars in localName: raise newException(EInvalidCharacterErr, "Invalid character") var attrNode = el.fOwnerDocument.createAttributeNS(namespaceURI, localName) discard el.setAttributeNodeNS(attrNode) # Set the info later, the setAttributeNode checks # if FOwnerElement is nil, and if it isn't it raises an exception attrNode.fOwnerElement = el attrNode.fSpecified = true attrNode.value = value # Text proc splitData*(textNode: PText, offset: int): PText = ## Breaks this node into two nodes at the specified offset, ## keeping both in the tree as siblings. if offset > textNode.data.len(): raise newException(EIndexSizeErr, "Index out of bounds") var left: string = textNode.data.substr(0, offset) textNode.data = left var right: string = textNode.data.substr(offset, textNode.data.len()) if not isNil(textNode.fParentNode) and not isNil(textNode.fParentNode.childNodes): for i in low(textNode.fParentNode.childNodes)..high(textNode.fParentNode.childNodes): if textNode.fParentNode.childNodes[i] == textNode: var newNode: PText = textNode.fOwnerDocument.createTextNode(right) textNode.fParentNode.childNodes.insert(newNode, i) return newNode else: var newNode: PText = textNode.fOwnerDocument.createTextNode(right) return newNode # ProcessingInstruction proc target*(pi: PProcessingInstruction): string = ## Returns the Processing Instructions target return pi.fTarget # --Other stuff-- # Writer proc addEscaped(s: string): string = result = "" for c in items(s): case c of '<': result.add("<") of '>': result.add(">") of '&': result.add("&") of '"': result.add(""") else: result.add(c) proc nodeToXml(n: PNode, indent: int = 0): string = result = spaces(indent) & "<" & n.nodeName if not isNil(n.attributes): for i in items(n.attributes): result.add(" " & i.name & "=\"" & addEscaped(i.value) & "\"") if isNil(n.childNodes) or n.childNodes.len() == 0: result.add("/>") # No idea why this doesn't need a \n :O else: # End the beginning of this tag result.add(">\n") for i in items(n.childNodes): case i.nodeType of ElementNode: result.add(nodeToXml(i, indent + 2)) of TextNode: result.add(spaces(indent * 2)) result.add(addEscaped(i.nodeValue)) of CDataSectionNode: result.add(spaces(indent * 2)) result.add("") of ProcessingInstructionNode: result.add(spaces(indent * 2)) result.add("") of CommentNode: result.add(spaces(indent * 2)) result.add("") else: continue result.add("\n") # Add the ending tag - result.add(spaces(indent) & "") proc `$`*(doc: PDocument): string = ## Converts a PDocument object into a string representation of it's XML result = "\n" result.add(nodeToXml(doc.documentElement))