diff options
Diffstat (limited to 'lib/pure/xmldom.nim')
-rw-r--r-- | lib/pure/xmldom.nim | 244 |
1 files changed, 162 insertions, 82 deletions
diff --git a/lib/pure/xmldom.nim b/lib/pure/xmldom.nim index 12578a793..4e9d721d7 100644 --- a/lib/pure/xmldom.nim +++ b/lib/pure/xmldom.nim @@ -9,33 +9,30 @@ import strutils -## This module implements the XML DOM Level 2 +## 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 -#DOMString = String -#DOMTimeStamp = int16 ?? -#DECLARATIONS +#http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html #Exceptions type - EDOMException* = object of E_Base #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) + EDOMException* = object of E_Base ## 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) template newException(exceptn, message: expr): expr = block: # open a new scope @@ -65,24 +62,24 @@ type Feature = tuple[name: string, version: string] PDOMImplementation* = ref DOMImplementation DOMImplementation = object - Features: seq[Feature] #Read-Only + Features: seq[Feature] # Read-Only PNode* = ref Node Node = object - attributes: seq[PAttr] #Read-only - childNodes*: seq[PNode] #Read-only - FLocalName: string #Read-only - FNamespaceURI: string #Read-only - FNodeName: string #Read-only + 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 + 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 + FTagName: string # Read-only PCharacterData = ref CharacterData CharacterData = object of Node @@ -90,15 +87,15 @@ type PDocument* = ref Document Document = object of Node - FImplementation: PDOMImplementation #Read-only - FDocumentElement: PElement #Read-only + FImplementation: PDOMImplementation # Read-only + FDocumentElement: PElement # Read-only PAttr* = ref Attr Attr = object of Node - FName: string #Read-only - FSpecified: bool #Read-only + FName: string # Read-only + FSpecified: bool # Read-only value*: string - FOwnerElement: PElement #Read-only + FOwnerElement: PElement # Read-only PDocumentFragment* = ref DocumentFragment DocumentFragment = object of Node @@ -115,18 +112,18 @@ type PProcessingInstruction* = ref ProcessingInstruction ProcessingInstruction = object of Node data*: string - FTarget: string #Read-only + FTarget: string # Read-only -#DOMImplementation +# DOMImplementation proc getDOM*(): PDOMImplementation = - ##Returns a DOMImplementation + ## Returns a DOMImplementation var DOMImpl: PDOMImplementation new(DOMImpl) DOMImpl.Features = @[(name: "core", version: "2.0"), (name: "core", version: "1.0"), (name: "XML", version: "2.0")] return DOMImpl proc createDocument*(dom: PDOMImplementation, namespaceURI: string, qualifiedName: string): PDocument = - ##Creates an XML Document object of the specified type with its document element. + ## Creates an XML Document object of the specified type with its document element. var doc: PDocument new(doc) doc.FNamespaceURI = namespaceURI @@ -142,8 +139,9 @@ proc createDocument*(dom: PDOMImplementation, namespaceURI: string, qualifiedNam 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. + ## 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 @@ -153,7 +151,7 @@ proc createDocument*(dom: PDOMImplementation, n: PElement): PDocument = return doc proc hasFeature*(dom: PDOMImplementation, feature: string, version: string = ""): bool = - ##Returns ``true`` if this ``version`` of the DomImplementation implements ``feature``, otherwise ``false`` + ## Returns ``true`` if this ``version`` of the DomImplementation implements ``feature``, otherwise ``false`` for iName, iVersion in items(dom.Features): if iName == feature: if version == "": @@ -164,8 +162,8 @@ proc hasFeature*(dom: PDOMImplementation, feature: string, version: string = "") return False -#Document -#Attributes +# Document +# Attributes proc implementation*(doc: PDocument): PDOMImplementation = return doc.FImplementation @@ -173,9 +171,9 @@ proc implementation*(doc: PDocument): PDOMImplementation = proc documentElement*(doc: PDocument): PElement = return doc.FDocumentElement -#Internal procedures +# Internal procedures proc findNodes(nl: PNode, name: string): seq[PNode] = - #Made for getElementsByTagName + # Made for getElementsByTagName var r: seq[PNode] = @[] if nl.childNodes == nil: return @[] if nl.childNodes.len() == 0: return @[] @@ -192,7 +190,7 @@ proc findNodes(nl: PNode, name: string): seq[PNode] = return r proc findNodesNS(nl: PNode, namespaceURI: string, localName: string): seq[PNode] = - #Made for getElementsByTagNameNS + # Made for getElementsByTagNameNS var r: seq[PNode] = @[] if nl.childNodes == nil: return @[] if nl.childNodes.len() == 0: return @[] @@ -211,10 +209,10 @@ proc findNodesNS(nl: PNode, namespaceURI: string, localName: string): seq[PNode] #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. + ## 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 + # Check if name contains illegal characters if illegalChars in name: raise newException(EInvalidCharacterErr, "Invalid character") @@ -230,12 +228,12 @@ proc createAttribute*(doc: PDocument, name: string): PAttr = return AttrNode proc createAttributeNS*(doc: PDocument, namespaceURI: string, qualifiedName: string): PAttr = - ##Creates an attribute of the given qualified name and namespace URI + ## Creates an attribute of the given qualified name and namespace URI - #Check if name contains illegal characters + # Check if name contains illegal characters if illegalChars in namespaceURI or illegalChars in qualifiedName: raise newException(EInvalidCharacterErr, "Invalid character") - #Exceptions + # Exceptions if qualifiedName.contains(':'): if namespaceURI == nil or namespaceURI == "": raise newException(ENamespaceErr, "When qualifiedName contains a prefix namespaceURI cannot be nil") @@ -264,17 +262,17 @@ proc createAttributeNS*(doc: PDocument, namespaceURI: string, qualifiedName: str return AttrNode proc createCDATASection*(doc: PDocument, data: string): PCDATASection = - ##Creates a CDATASection node whose value is the specified string. + ## 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.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. + ## Creates a Comment node given the specified string. var Comm: PComment new(Comm) Comm.data = data @@ -284,15 +282,15 @@ proc createComment*(doc: PDocument, data: string): PComment = return Comm proc createDocumentFragment*(doc: PDocument): PDocumentFragment = - ##Creates an empty DocumentFragment object. + ## 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. + ## Creates an element of the type specified. - #Check if name contains illegal characters + # Check if name contains illegal characters if illegalChars in tagName: raise newException(EInvalidCharacterErr, "Invalid character") @@ -311,7 +309,7 @@ proc createElement*(doc: PDocument, tagName: string): PElement = return elNode proc createElementNS*(doc: PDocument, namespaceURI: string, qualifiedName: string): PElement = - ##Creates an element of the given qualified name and namespace URI. + ## Creates an element of the given qualified name and namespace URI. if qualifiedName.contains(':'): if namespaceURI == nil or namespaceURI == "": raise newException(ENamespaceErr, "When qualifiedName contains a prefix namespaceURI cannot be nil") @@ -319,7 +317,7 @@ proc createElementNS*(doc: PDocument, namespaceURI: string, qualifiedName: strin 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 + # Check if name contains illegal characters if illegalChars in namespaceURI or illegalChars in qualifiedName: raise newException(EInvalidCharacterErr, "Invalid character") @@ -342,7 +340,7 @@ proc createElementNS*(doc: PDocument, namespaceURI: string, qualifiedName: strin return elNode proc createProcessingInstruction*(doc: PDocument, target: string, data: string): PProcessingInstruction = - ##Creates a ProcessingInstruction node given the specified name and data strings. + ## Creates a ProcessingInstruction node given the specified name and data strings. #Check if name contains illegal characters if illegalChars in target: @@ -356,7 +354,7 @@ proc createProcessingInstruction*(doc: PDocument, target: string, data: string): return PI proc createTextNode*(doc: PDocument, data: string): PText = #Propably TextNode - ##Creates a Text node given the specified string. + ## Creates a Text node given the specified string. var txtNode: PText new(txtNode) txtNode.data = data @@ -371,8 +369,8 @@ discard """proc getElementById*(doc: PDocument, elementId: string): PElement = #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. + ## 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. var result: seq[PNode] = @[] if doc.FDocumentElement.FNodeName == tagName or tagName == "*": result.add(doc.FDocumentElement) @@ -381,8 +379,8 @@ proc getElementsByTagName*(doc: PDocument, tagName: string): seq[PNode] = return result 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. + ## 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. var result: seq[PNode] = @[] if doc.FDocumentElement.FLocalName == localName or localName == "*": if doc.FDocumentElement.FNamespaceURI == namespaceURI or namespaceURI == "*": @@ -450,57 +448,76 @@ proc importNode*(doc: PDocument, importedNode: PNode, deep: bool): PNode = # Node # Attributes -proc Attributes*(n: PNode): seq[PAttr] = - if n.attributes == nil: n.attributes = @[] # Initialize the sequence if it's nil - return n.attributes proc firstChild*(n: PNode): PNode = + ## Returns this node's first child + if n.childNodes.len() > 0: return n.childNodes[0] else: return nil proc lastChild*(n: PNode): PNode = + ## Returns this node's last child + if 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 nextSibling*(n: PNode): PNode = + ## Returns the next sibling of this node + 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: # HAVE TO TEST this line, not sure if ``==`` will work + 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 + 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: # HAVE TO TEST this line, not sure if ``==`` will work + if n.FParentNode.childNodes[i] == n: return n.FParentNode.childNodes[i - 1] return nil proc `prefix=`*(n: var PNode, value: string) = + ## Modifies the prefix of this node + # Setter # Check if name contains illegal characters if illegalChars in value: @@ -532,8 +549,11 @@ 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. - # TODO - Check if n contains newChild - # TODO - Exceptions + # Check if n contains newChild + if n.childNodes != nil: + 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: @@ -542,6 +562,9 @@ proc appendChild*(n: PNode, newChild: PNode) = 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 n.childNodes == nil: n.childNodes = @[] newChild.FParentNode = n @@ -604,10 +627,43 @@ proc isSupported*(n: PNode, feature: string, version: string): bool = ## feature and that feature is supported by this node. return n.FOwnerDocument.FImplementation.hasFeature(feature, version) +proc isEmpty(s: string): bool = + + if s == "" or s == nil: + return True + for i in items(s): + if i != ' ': + return False + return True + proc normalize*(n: PNode) = - ## Puts all Text nodes in the full depth of the sub-tree underneath this Node + ## Merges all seperated TextNodes together, and removes any empty TextNodes + var curTextNode: PNode = nil + var i: int = 0 - # TODO + var newChildNodes: seq[PNode] = @[] + while True: + if 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 curTextNode == nil: + 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. @@ -791,26 +847,32 @@ proc setNamedItemNS*(NList: var seq[PAttr], arg: PAttr): PAttr = NList[index] = arg return item # Return the replaced node -# TODO - Maybe implement a ChildlessNode!^ - # 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 @@ -960,11 +1022,29 @@ proc setAttributeNS*(el: PElement, namespaceURI, localName, value: string) = 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.copy(0, offset) + TextNode.data = left + var right: string = TextNode.data.copy(offset, TextNode.data.len()) + + if TextNode.FParentNode != nil: + 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 - # TODO - need insert(seq[T]) # ProcessingInstruction -proc target*(PI: PProcessingInstruction): string = +proc target*(PI: PProcessingInstruction): string = + ## Returns the Processing Instructions target + return PI.FTarget |