Add some nil checks for xmldom (and clean up a bit)

This commit is contained in:
def 2014-07-30 23:23:02 +02:00
commit 2c6406703c

View file

@ -45,7 +45,7 @@ const
DocumentFragmentNode* = 11 DocumentFragmentNode* = 11
# Nodes which are childless - Not sure about AttributeNode # Nodes which are childless - Not sure about AttributeNode
childlessObjects = {DocumentNode, AttributeNode, TextNode, childlessObjects = {DocumentNode, AttributeNode, TextNode,
CDataSectionNode, ProcessingInstructionNode, CommentNode} CDataSectionNode, ProcessingInstructionNode, CommentNode}
# Illegal characters # Illegal characters
illegalChars = {'>', '<', '&', '"'} illegalChars = {'>', '<', '&', '"'}
@ -69,21 +69,21 @@ type
FOwnerDocument: PDocument # Read-Only FOwnerDocument: PDocument # Read-Only
FParentNode: PNode # Read-Only FParentNode: PNode # Read-Only
prefix*: string # Setting this should change some values... TODO! prefix*: string # Setting this should change some values... TODO!
PElement* = ref Element PElement* = ref Element
Element = object of Node Element = object of Node
FTagName: string # Read-only FTagName: string # Read-only
PCharacterData* = ref CharacterData PCharacterData* = ref CharacterData
CharacterData = object of Node CharacterData = object of Node
data*: string data*: string
PDocument* = ref Document PDocument* = ref Document
Document = object of Node Document = object of Node
FImplementation: PDOMImplementation # Read-only FImplementation: PDOMImplementation # Read-only
FDocumentElement: PElement # Read-only FDocumentElement: PElement # Read-only
PAttr* = ref Attr PAttr* = ref Attr
Attr = object of Node Attr = object of Node
FName: string # Read-only FName: string # Read-only
FSpecified: bool # Read-only FSpecified: bool # Read-only
@ -95,13 +95,13 @@ type
PText* = ref Text PText* = ref Text
Text = object of CharacterData Text = object of CharacterData
PComment* = ref comment PComment* = ref comment
Comment = object of CharacterData Comment = object of CharacterData
PCDataSection* = ref CDataSection PCDataSection* = ref CDataSection
CDataSection = object of Text CDataSection = object of Text
PProcessingInstruction* = ref ProcessingInstruction PProcessingInstruction* = ref ProcessingInstruction
ProcessingInstruction = object of Node ProcessingInstruction = object of Node
data*: string data*: string
@ -111,8 +111,8 @@ type
proc getDOM*(): PDOMImplementation = proc getDOM*(): PDOMImplementation =
## Returns a DOMImplementation ## Returns a DOMImplementation
new(result) new(result)
result.Features = @[(name: "core", version: "2.0"), result.Features = @[(name: "core", version: "2.0"),
(name: "core", version: "1.0"), (name: "core", version: "1.0"),
(name: "XML", version: "2.0")] (name: "XML", version: "2.0")]
proc createDocument*(dom: PDOMImplementation, namespaceURI: string, qualifiedName: string): PDocument = proc createDocument*(dom: PDOMImplementation, namespaceURI: string, qualifiedName: string): PDocument =
@ -121,28 +121,28 @@ proc createDocument*(dom: PDOMImplementation, namespaceURI: string, qualifiedNam
new(doc) new(doc)
doc.FNamespaceURI = namespaceURI doc.FNamespaceURI = namespaceURI
doc.FImplementation = dom doc.FImplementation = dom
var elTag: PElement var elTag: PElement
new(elTag) new(elTag)
elTag.FTagName = qualifiedName elTag.FTagName = qualifiedName
elTag.FNodeName = qualifiedName elTag.FNodeName = qualifiedName
doc.FDocumentElement = elTag doc.FDocumentElement = elTag
doc.FNodeType = DocumentNode doc.FNodeType = DocumentNode
return doc return doc
proc createDocument*(dom: PDOMImplementation, n: PElement): PDocument = proc createDocument*(dom: PDOMImplementation, n: PElement): 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.
# This procedure is not in the specification, it's provided for the parser. # This procedure is not in the specification, it's provided for the parser.
var doc: PDocument var doc: PDocument
new(doc) new(doc)
doc.FDocumentElement = n doc.FDocumentElement = n
doc.FImplementation = dom doc.FImplementation = dom
doc.FNodeType = DocumentNode doc.FNodeType = DocumentNode
return doc return doc
proc hasFeature*(dom: PDOMImplementation, feature: string, version: string = ""): bool = 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): for iName, iVersion in items(dom.Features):
@ -157,58 +157,58 @@ proc hasFeature*(dom: PDOMImplementation, feature: string, version: string = "")
# Document # Document
# Attributes # Attributes
proc implementation*(doc: PDocument): PDOMImplementation = proc implementation*(doc: PDocument): PDOMImplementation =
return doc.FImplementation return doc.FImplementation
proc documentElement*(doc: PDocument): PElement = proc documentElement*(doc: PDocument): PElement =
return doc.FDocumentElement return doc.FDocumentElement
# Internal procedures # Internal procedures
proc findNodes(nl: PNode, name: string): seq[PNode] = proc findNodes(nl: PNode, name: string): seq[PNode] =
# Made for getElementsByTagName # Made for getElementsByTagName
var r: seq[PNode] = @[] var r: seq[PNode] = @[]
if isNil(nl.childNodes): return @[] if nl.childNodes == nil: return @[]
if nl.childNodes.len() == 0: return @[] if nl.childNodes.len() == 0: return @[]
for i in items(nl.childNodes): for i in items(nl.childNodes):
if i.FNodeType == ElementNode: if i.FNodeType == ElementNode:
if i.FNodeName == name or name == "*": if i.FNodeName == name or name == "*":
r.add(i) r.add(i)
if not isNil(i.childNodes): if i.childNodes != nil:
if i.childNodes.len() != 0: if i.childNodes.len() != 0:
r.add(findNodes(i, name)) r.add(findNodes(i, name))
return r return r
proc findNodesNS(nl: PNode, namespaceURI: string, localName: string): seq[PNode] = proc findNodesNS(nl: PNode, namespaceURI: string, localName: string): seq[PNode] =
# Made for getElementsByTagNameNS # Made for getElementsByTagNameNS
var r: seq[PNode] = @[] var r: seq[PNode] = @[]
if isNil(nl.childNodes): return @[] if nl.childNodes == nil: return @[]
if nl.childNodes.len() == 0: return @[] if nl.childNodes.len() == 0: return @[]
for i in items(nl.childNodes): for i in items(nl.childNodes):
if i.FNodeType == ElementNode: if i.FNodeType == ElementNode:
if (i.FNamespaceURI == namespaceURI or namespaceURI == "*") and (i.FLocalName == localName or localName == "*"): if (i.FNamespaceURI == namespaceURI or namespaceURI == "*") and (i.FLocalName == localName or localName == "*"):
r.add(i) r.add(i)
if not isNil(i.childNodes): if i.childNodes != nil:
if i.childNodes.len() != 0: if i.childNodes.len() != 0:
r.add(findNodesNS(i, namespaceURI, localName)) r.add(findNodesNS(i, namespaceURI, localName))
return r return r
#Procedures #Procedures
proc createAttribute*(doc: PDocument, name: string): PAttr = 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. ## 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. ## 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: if illegalChars in name:
raise newException(EInvalidCharacterErr, "Invalid character") raise newException(EInvalidCharacterErr, "Invalid character")
var AttrNode: PAttr var AttrNode: PAttr
new(AttrNode) new(AttrNode)
AttrNode.FName = name AttrNode.FName = name
@ -222,7 +222,7 @@ proc createAttribute*(doc: PDocument, name: string): PAttr =
proc createAttributeNS*(doc: PDocument, namespaceURI: string, qualifiedName: string): PAttr = 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: if illegalChars in namespaceURI or illegalChars in qualifiedName:
raise newException(EInvalidCharacterErr, "Invalid character") raise newException(EInvalidCharacterErr, "Invalid character")
@ -231,12 +231,12 @@ proc createAttributeNS*(doc: PDocument, namespaceURI: string, qualifiedName: str
if namespaceURI == nil: if namespaceURI == nil:
raise newException(ENamespaceErr, "When qualifiedName contains a prefix namespaceURI cannot be nil") 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": elif qualifiedName.split(':')[0].toLower() == "xml" and namespaceURI != "http://www.w3.org/XML/1998/namespace":
raise newException(ENamespaceErr, raise newException(ENamespaceErr,
"When the namespace prefix is \"xml\" namespaceURI has to be \"http://www.w3.org/XML/1998/namespace\"") "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/": elif qualifiedName.split(':')[1].toLower() == "xmlns" and namespaceURI != "http://www.w3.org/2000/xmlns/":
raise newException(ENamespaceErr, raise newException(ENamespaceErr,
"When the namespace prefix is \"xmlns\" namespaceURI has to be \"http://www.w3.org/2000/xmlns/\"") "When the namespace prefix is \"xmlns\" namespaceURI has to be \"http://www.w3.org/2000/xmlns/\"")
var AttrNode: PAttr var AttrNode: PAttr
new(AttrNode) new(AttrNode)
AttrNode.FName = qualifiedName AttrNode.FName = qualifiedName
@ -250,7 +250,7 @@ proc createAttributeNS*(doc: PDocument, namespaceURI: string, qualifiedName: str
AttrNode.prefix = nil AttrNode.prefix = nil
AttrNode.FLocalName = qualifiedName AttrNode.FLocalName = qualifiedName
AttrNode.value = "" AttrNode.value = ""
AttrNode.FNodeType = AttributeNode AttrNode.FNodeType = AttributeNode
return AttrNode return AttrNode
@ -265,12 +265,12 @@ proc createCDATASection*(doc: PDocument, data: string): PCDATASection =
return CData return CData
proc createComment*(doc: PDocument, data: string): PComment = 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 var Comm: PComment
new(Comm) new(Comm)
Comm.data = data Comm.data = data
Comm.nodeValue = data Comm.nodeValue = data
Comm.FNodeType = CommentNode Comm.FNodeType = CommentNode
return Comm return Comm
@ -282,11 +282,11 @@ proc createDocumentFragment*(doc: PDocument): PDocumentFragment =
proc createElement*(doc: PDocument, tagName: string): PElement = 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: if illegalChars in tagName:
raise newException(EInvalidCharacterErr, "Invalid character") raise newException(EInvalidCharacterErr, "Invalid character")
var elNode: PElement var elNode: PElement
new(elNode) new(elNode)
elNode.FTagName = tagName elNode.FTagName = tagName
@ -296,9 +296,9 @@ proc createElement*(doc: PDocument, tagName: string): PElement =
elNode.FNamespaceURI = nil elNode.FNamespaceURI = nil
elNode.childNodes = @[] elNode.childNodes = @[]
elNode.attributes = @[] elNode.attributes = @[]
elNode.FNodeType = ElementNode elNode.FNodeType = ElementNode
return elNode return elNode
proc createElementNS*(doc: PDocument, namespaceURI: string, qualifiedName: string): PElement = proc createElementNS*(doc: PDocument, namespaceURI: string, qualifiedName: string): PElement =
@ -307,13 +307,13 @@ proc createElementNS*(doc: PDocument, namespaceURI: string, qualifiedName: strin
if namespaceURI == nil: if namespaceURI == nil:
raise newException(ENamespaceErr, "When qualifiedName contains a prefix namespaceURI cannot be nil") 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": elif qualifiedName.split(':')[0].toLower() == "xml" and namespaceURI != "http://www.w3.org/XML/1998/namespace":
raise newException(ENamespaceErr, raise newException(ENamespaceErr,
"When the namespace prefix is \"xml\" namespaceURI has to be \"http://www.w3.org/XML/1998/namespace\"") "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: if illegalChars in namespaceURI or illegalChars in qualifiedName:
raise newException(EInvalidCharacterErr, "Invalid character") raise newException(EInvalidCharacterErr, "Invalid character")
var elNode: PElement var elNode: PElement
new(elNode) new(elNode)
elNode.FTagName = qualifiedName elNode.FTagName = qualifiedName
@ -327,18 +327,18 @@ proc createElementNS*(doc: PDocument, namespaceURI: string, qualifiedName: strin
elNode.FNamespaceURI = namespaceURI elNode.FNamespaceURI = namespaceURI
elNode.childNodes = @[] elNode.childNodes = @[]
elNode.attributes = @[] elNode.attributes = @[]
elNode.FNodeType = ElementNode elNode.FNodeType = ElementNode
return elNode return elNode
proc createProcessingInstruction*(doc: PDocument, target: string, data: string): PProcessingInstruction = 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 #Check if name contains illegal characters
if illegalChars in target: if illegalChars in target:
raise newException(EInvalidCharacterErr, "Invalid character") raise newException(EInvalidCharacterErr, "Invalid character")
var PI: PProcessingInstruction var PI: PProcessingInstruction
new(PI) new(PI)
PI.FTarget = target PI.FTarget = target
@ -347,13 +347,13 @@ proc createProcessingInstruction*(doc: PDocument, target: string, data: string):
return PI return PI
proc createTextNode*(doc: PDocument, data: string): PText = #Propably TextNode 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 var txtNode: PText
new(txtNode) new(txtNode)
txtNode.data = data txtNode.data = data
txtNode.nodeValue = data txtNode.nodeValue = data
txtNode.FNodeName = "#text" txtNode.FNodeName = "#text"
txtNode.FNodeType = TextNode txtNode.FNodeType = TextNode
return txtNode return txtNode
@ -363,22 +363,22 @@ discard """proc getElementById*(doc: PDocument, elementId: string): PElement =
proc getElementsByTagName*(doc: PDocument, tagName: string): seq[PNode] = proc getElementsByTagName*(doc: PDocument, tagName: string): seq[PNode] =
## Returns a NodeList of all the Elements with a given tag name in ## 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. ## the order in which they are encountered in a preorder traversal of the Document tree.
var result: seq[PNode] = @[] var result: seq[PNode] = @[]
if doc.FDocumentElement.FNodeName == tagName or tagName == "*": if doc.FDocumentElement.FNodeName == tagName or tagName == "*":
result.add(doc.FDocumentElement) result.add(doc.FDocumentElement)
result.add(doc.FDocumentElement.findNodes(tagName)) result.add(doc.FDocumentElement.findNodes(tagName))
return result return result
proc getElementsByTagNameNS*(doc: PDocument, namespaceURI: string, localName: string): seq[PNode] = proc getElementsByTagNameNS*(doc: PDocument, namespaceURI: string, localName: string): seq[PNode] =
## Returns a NodeList of all the Elements with a given localName and namespaceURI ## 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. ## in the order in which they are encountered in a preorder traversal of the Document tree.
var result: seq[PNode] = @[] var result: seq[PNode] = @[]
if doc.FDocumentElement.FLocalName == localName or localName == "*": if doc.FDocumentElement.FLocalName == localName or localName == "*":
if doc.FDocumentElement.FNamespaceURI == namespaceURI or namespaceURI == "*": if doc.FDocumentElement.FNamespaceURI == namespaceURI or namespaceURI == "*":
result.add(doc.FDocumentElement) result.add(doc.FDocumentElement)
result.add(doc.FDocumentElement.findNodesNS(namespaceURI, localName)) result.add(doc.FDocumentElement.findNodesNS(namespaceURI, localName))
return result return result
@ -406,7 +406,7 @@ proc importNode*(doc: PDocument, importedNode: PNode, deep: bool): PNode =
if deep: if deep:
for i in low(tmp.len())..high(tmp.len()): for i in low(tmp.len())..high(tmp.len()):
n.childNodes.add(importNode(doc, tmp[i], deep)) n.childNodes.add(importNode(doc, tmp[i], deep))
return n return n
of ElementNode: of ElementNode:
var n: PNode var n: PNode
@ -414,7 +414,7 @@ proc importNode*(doc: PDocument, importedNode: PNode, deep: bool): PNode =
n = importedNode n = importedNode
n.FOwnerDocument = doc n.FOwnerDocument = doc
n.FParentNode = nil n.FParentNode = nil
var tmpA: seq[PAttr] = n.attributes var tmpA: seq[PAttr] = n.attributes
n.attributes = @[] n.attributes = @[]
# Import the Element node's attributes # Import the Element node's attributes
@ -426,7 +426,7 @@ proc importNode*(doc: PDocument, importedNode: PNode, deep: bool): PNode =
if deep: if deep:
for i in low(tmp.len())..high(tmp.len()): for i in low(tmp.len())..high(tmp.len()):
n.childNodes.add(importNode(doc, tmp[i], deep)) n.childNodes.add(importNode(doc, tmp[i], deep))
return n return n
of ProcessingInstructionNode, TextNode, CDataSectionNode, CommentNode: of ProcessingInstructionNode, TextNode, CDataSectionNode, CommentNode:
var n: PNode var n: PNode
@ -437,27 +437,27 @@ proc importNode*(doc: PDocument, importedNode: PNode, deep: bool): PNode =
return n return n
else: else:
raise newException(ENotSupportedErr, "The type of node being imported is not supported") raise newException(ENotSupportedErr, "The type of node being imported is not supported")
# Node # Node
# Attributes # Attributes
proc firstChild*(n: PNode): PNode = proc firstChild*(n: PNode): PNode =
## Returns this node's first child ## Returns this node's first child
if n.childNodes.len() > 0: if n.childNodes != nil and n.childNodes.len() > 0:
return n.childNodes[0] return n.childNodes[0]
else: else:
return nil return nil
proc lastChild*(n: PNode): PNode = proc lastChild*(n: PNode): PNode =
## Returns this node's last child ## Returns this node's last child
if n.childNodes.len() > 0: if n.childNodes != nil and n.childNodes.len() > 0:
return n.childNodes[n.childNodes.len() - 1] return n.childNodes[n.childNodes.len() - 1]
else: else:
return nil return nil
proc localName*(n: PNode): string = proc localName*(n: PNode): string =
## Returns this nodes local name ## Returns this nodes local name
@ -465,15 +465,17 @@ proc localName*(n: PNode): string =
proc namespaceURI*(n: PNode): string = proc namespaceURI*(n: PNode): string =
## Returns this nodes namespace URI ## Returns this nodes namespace URI
return n.FNamespaceURI return n.FNamespaceURI
proc `namespaceURI=`*(n: PNode, value: string) = proc `namespaceURI=`*(n: PNode, value: string) =
n.FNamespaceURI = value n.FNamespaceURI = value
proc nextSibling*(n: PNode): PNode = proc nextSibling*(n: PNode): PNode =
## Returns the next sibling of this node ## Returns the next sibling of this node
if n.FParentNode == nil or n.FParentNode.childNodes == nil:
return nil
var nLow: int = low(n.FParentNode.childNodes) var nLow: int = low(n.FParentNode.childNodes)
var nHigh: int = high(n.FParentNode.childNodes) var nHigh: int = high(n.FParentNode.childNodes)
for i in nLow..nHigh: for i in nLow..nHigh:
@ -500,17 +502,19 @@ proc parentNode*(n: PNode): PNode =
## Returns the parent node of this node ## Returns the parent node of this node
return n.FParentNode return n.FParentNode
proc previousSibling*(n: PNode): PNode = proc previousSibling*(n: PNode): PNode =
## Returns the previous sibling of this node ## Returns the previous sibling of this node
if n.FParentNode == nil or n.FParentNode.childNodes == nil:
return nil
var nLow: int = low(n.FParentNode.childNodes) var nLow: int = low(n.FParentNode.childNodes)
var nHigh: int = high(n.FParentNode.childNodes) var nHigh: int = high(n.FParentNode.childNodes)
for i in nLow..nHigh: for i in nLow..nHigh:
if n.FParentNode.childNodes[i] == n: if n.FParentNode.childNodes[i] == n:
return n.FParentNode.childNodes[i - 1] return n.FParentNode.childNodes[i - 1]
return nil return nil
proc `prefix=`*(n: PNode, value: string) = proc `prefix=`*(n: PNode, value: string) =
## Modifies the prefix of this node ## Modifies the prefix of this node
@ -522,10 +526,10 @@ proc `prefix=`*(n: PNode, value: string) =
if n.FNamespaceURI == nil: if n.FNamespaceURI == nil:
raise newException(ENamespaceErr, "namespaceURI cannot be nil") raise newException(ENamespaceErr, "namespaceURI cannot be nil")
elif value.toLower() == "xml" and n.FNamespaceURI != "http://www.w3.org/XML/1998/namespace": elif value.toLower() == "xml" and n.FNamespaceURI != "http://www.w3.org/XML/1998/namespace":
raise newException(ENamespaceErr, raise newException(ENamespaceErr,
"When the namespace prefix is \"xml\" namespaceURI has to be \"http://www.w3.org/XML/1998/namespace\"") "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/": elif value.toLower() == "xmlns" and n.FNamespaceURI != "http://www.w3.org/2000/xmlns/":
raise newException(ENamespaceErr, raise newException(ENamespaceErr,
"When the namespace prefix is \"xmlns\" namespaceURI has to be \"http://www.w3.org/2000/xmlns/\"") "When the namespace prefix is \"xmlns\" namespaceURI has to be \"http://www.w3.org/2000/xmlns/\"")
elif value.toLower() == "xmlns" and n.FNodeType == AttributeNode: elif value.toLower() == "xmlns" and n.FNodeType == AttributeNode:
raise newException(ENamespaceErr, "An AttributeNode cannot have a prefix of \"xmlns\"") raise newException(ENamespaceErr, "An AttributeNode cannot have a prefix of \"xmlns\"")
@ -543,33 +547,33 @@ proc `prefix=`*(n: PNode, value: string) =
proc appendChild*(n: PNode, newChild: PNode) = proc appendChild*(n: PNode, newChild: PNode) =
## Adds the node newChild to the end of the list of children of this node. ## 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. ## If the newChild is already in the tree, it is first removed.
# Check if n contains newChild # Check if n contains newChild
if not IsNil(n.childNodes): if n.childNodes != nil:
for i in low(n.childNodes)..high(n.childNodes): for i in low(n.childNodes)..high(n.childNodes):
if n.childNodes[i] == newChild: if n.childNodes[i] == newChild:
raise newException(EHierarchyRequestErr, "The node to append is already in this nodes children.") raise newException(EHierarchyRequestErr, "The node to append is already in this nodes children.")
# Check if newChild is from this nodes document # Check if newChild is from this nodes document
if n.FOwnerDocument != newChild.FOwnerDocument: if n.FOwnerDocument != newChild.FOwnerDocument:
raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.")
if n == newChild: if n == newChild:
raise newException(EHierarchyRequestErr, "You can't add a node into itself") raise newException(EHierarchyRequestErr, "You can't add a node into itself")
if n.nodeType in childlessObjects: if n.nodeType in childlessObjects:
raise newException(ENoModificationAllowedErr, "Cannot append children to a childless node") raise newException(ENoModificationAllowedErr, "Cannot append children to a childless node")
if isNil(n.childNodes): n.childNodes = @[] if n.childNodes == nil: n.childNodes = @[]
newChild.FParentNode = n newChild.FParentNode = n
for i in low(n.childNodes)..high(n.childNodes): for i in low(n.childNodes)..high(n.childNodes):
if n.childNodes[i] == newChild: if n.childNodes[i] == newChild:
n.childNodes[i] = newChild n.childNodes[i] = newChild
n.childNodes.add(newChild) n.childNodes.add(newChild)
proc cloneNode*(n: PNode, deep: bool): PNode = proc cloneNode*(n: PNode, deep: bool): PNode =
## Returns a duplicate of this node, if ``deep`` is `true`, Element node's children are copied ## Returns a duplicate of this node, if ``deep`` is `true`, Element node's children are copied
case n.FNodeType case n.FNodeType
of AttributeNode: of AttributeNode:
@ -586,7 +590,7 @@ proc cloneNode*(n: PNode, deep: bool): PNode =
# Import the childNodes # Import the childNodes
var tmp: seq[PNode] = n.childNodes var tmp: seq[PNode] = n.childNodes
n.childNodes = @[] n.childNodes = @[]
if deep: if deep and tmp != nil:
for i in low(tmp.len())..high(tmp.len()): for i in low(tmp.len())..high(tmp.len()):
n.childNodes.add(cloneNode(tmp[i], deep)) n.childNodes.add(cloneNode(tmp[i], deep))
return newNode return newNode
@ -597,29 +601,34 @@ proc cloneNode*(n: PNode, deep: bool): PNode =
return newNode return newNode
proc hasAttributes*(n: PNode): bool = proc hasAttributes*(n: PNode): bool =
## Returns whether this node (if it is an element) has any attributes. ## Returns whether this node (if it is an element) has any attributes.
return n.attributes.len() > 0 return n.attributes != nil and n.attributes.len() > 0
proc hasChildNodes*(n: PNode): bool = proc hasChildNodes*(n: PNode): bool =
## Returns whether this node has any children. ## Returns whether this node has any children.
return n.childNodes.len() > 0 return n.childNodes != nil and n.childNodes.len() > 0
proc insertBefore*(n: PNode, newChild: PNode, refChild: PNode): PNode = proc insertBefore*(n: PNode, newChild: PNode, refChild: PNode): PNode =
## Inserts the node ``newChild`` before the existing child node ``refChild``. ## Inserts the node ``newChild`` before the existing child node ``refChild``.
## If ``refChild`` is nil, insert ``newChild`` at the end of the list of children. ## If ``refChild`` is nil, insert ``newChild`` at the end of the list of children.
# Check if newChild is from this nodes document # Check if newChild is from this nodes document
if n.FOwnerDocument != newChild.FOwnerDocument: if n.FOwnerDocument != newChild.FOwnerDocument:
raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.")
if n.childNodes == nil:
n.ChildNodes = @[]
for i in low(n.childNodes)..high(n.childNodes): for i in low(n.childNodes)..high(n.childNodes):
if n.childNodes[i] == refChild: if n.childNodes[i] == refChild:
n.childNodes.insert(newChild, i - 1) n.childNodes.insert(newChild, i - 1)
return return
n.ChildNodes.add(newChild)
proc isSupported*(n: PNode, feature: string, version: string): bool = proc isSupported*(n: PNode, feature: string, version: string): bool =
## Tests whether the DOM implementation implements a specific ## Tests whether the DOM implementation implements a specific
## feature and that feature is supported by this node. ## feature and that feature is supported by this node.
return n.FOwnerDocument.FImplementation.hasFeature(feature, version) return n.FOwnerDocument.FImplementation.hasFeature(feature, version)
proc isEmpty(s: string): bool = proc isEmpty(s: string): bool =
@ -635,17 +644,17 @@ proc normalize*(n: PNode) =
## Merges all seperated TextNodes together, and removes any empty TextNodes ## Merges all seperated TextNodes together, and removes any empty TextNodes
var curTextNode: PNode = nil var curTextNode: PNode = nil
var i: int = 0 var i: int = 0
var newChildNodes: seq[PNode] = @[] var newChildNodes: seq[PNode] = @[]
while True: while True:
if i >= n.childNodes.len: if n.childNodes == nil or i >= n.childNodes.len:
break break
if n.childNodes[i].nodeType == TextNode: if n.childNodes[i].nodeType == TextNode:
#If the TextNode is empty, remove it #If the TextNode is empty, remove it
if PText(n.childNodes[i]).data.isEmpty(): if PText(n.childNodes[i]).data.isEmpty():
inc(i) inc(i)
if curTextNode == nil: if curTextNode == nil:
curTextNode = n.childNodes[i] curTextNode = n.childNodes[i]
else: else:
@ -656,35 +665,37 @@ proc normalize*(n: PNode) =
newChildNodes.add(curTextNode) newChildNodes.add(curTextNode)
newChildNodes.add(n.childNodes[i]) newChildNodes.add(n.childNodes[i])
curTextNode = nil curTextNode = nil
inc(i) inc(i)
n.childNodes = newChildNodes n.childNodes = newChildNodes
proc removeChild*(n: PNode, oldChild: PNode): PNode = proc removeChild*(n: PNode, oldChild: PNode): PNode =
## Removes the child node indicated by ``oldChild`` from the list of children, and returns it. ## Removes the child node indicated by ``oldChild`` from the list of children, and returns it.
for i in low(n.childNodes)..high(n.childNodes): if n.childNodes != nil:
if n.childNodes[i] == oldChild: for i in low(n.childNodes)..high(n.childNodes):
result = n.childNodes[i] if n.childNodes[i] == oldChild:
n.childNodes.delete(i) result = n.childNodes[i]
return result n.childNodes.delete(i)
return result
raise newException(ENotFoundErr, "Node not found") raise newException(ENotFoundErr, "Node not found")
proc replaceChild*(n: PNode, newChild: PNode, oldChild: PNode): PNode = 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. ## 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 # Check if newChild is from this nodes document
if n.FOwnerDocument != newChild.FOwnerDocument: if n.FOwnerDocument != newChild.FOwnerDocument:
raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.")
for i in low(n.childNodes)..high(n.childNodes): if n.childNodes != nil:
if n.childNodes[i] == oldChild: for i in low(n.childNodes)..high(n.childNodes):
result = n.childNodes[i] if n.childNodes[i] == oldChild:
n.childNodes[i] = newChild result = n.childNodes[i]
return result n.childNodes[i] = newChild
return result
raise newException(ENotFoundErr, "Node not found") raise newException(ENotFoundErr, "Node not found")
# NamedNodeMap # NamedNodeMap
proc getNamedItem*(NList: seq[PNode], name: string): PNode = proc getNamedItem*(NList: seq[PNode], name: string): PNode =
@ -693,22 +704,22 @@ proc getNamedItem*(NList: seq[PNode], name: string): PNode =
if i.nodeName() == name: if i.nodeName() == name:
return i return i
return nil return nil
proc getNamedItem*(NList: seq[PAttr], name: string): PAttr = proc getNamedItem*(NList: seq[PAttr], name: string): PAttr =
## Retrieves a node specified by ``name``. If this node cannot be found returns ``nil`` ## Retrieves a node specified by ``name``. If this node cannot be found returns ``nil``
for i in items(NList): for i in items(NList):
if i.nodeName() == name: if i.nodeName() == name:
return i return i
return nil return nil
proc getNamedItemNS*(NList: seq[PNode], namespaceURI: string, localName: string): PNode = 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`` ## Retrieves a node specified by ``localName`` and ``namespaceURI``. If this node cannot be found returns ``nil``
for i in items(NList): for i in items(NList):
if i.namespaceURI() == namespaceURI and i.localName() == localName: if i.namespaceURI() == namespaceURI and i.localName() == localName:
return i return i
return nil return nil
proc getNamedItemNS*(NList: seq[PAttr], namespaceURI: string, localName: string): PAttr = 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`` ## Retrieves a node specified by ``localName`` and ``namespaceURI``. If this node cannot be found returns ``nil``
for i in items(NList): for i in items(NList):
if i.NamespaceURI() == namespaceURI and i.LocalName() == localName: if i.NamespaceURI() == namespaceURI and i.LocalName() == localName:
@ -716,7 +727,7 @@ proc getNamedItemNS*(NList: seq[PAttr], namespaceURI: string, localName: string)
return nil return nil
proc item*(NList: seq[PNode], index: int): PNode = proc item*(NList: seq[PNode], index: int): PNode =
## Returns the ``index`` th item in the map. ## 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`` is greater than or equal to the number of nodes in this map, this returns ``nil``.
if index >= NList.len(): return nil if index >= NList.len(): return nil
else: return NList[index] else: return NList[index]
@ -729,9 +740,9 @@ proc removeNamedItem*(NList: var seq[PNode], name: string): PNode =
result = NList[i] result = NList[i]
NList.delete(i) NList.delete(i)
return result return result
raise newException(ENotFoundErr, "Node not found") raise newException(ENotFoundErr, "Node not found")
proc removeNamedItemNS*(NList: var seq[PNode], namespaceURI: string, localName: string): PNode = proc removeNamedItemNS*(NList: var seq[PNode], namespaceURI: string, localName: string): PNode =
## Removes a node specified by local name and namespace URI ## Removes a node specified by local name and namespace URI
for i in low(NList)..high(NList): for i in low(NList)..high(NList):
@ -739,19 +750,19 @@ proc removeNamedItemNS*(NList: var seq[PNode], namespaceURI: string, localName:
result = NList[i] result = NList[i]
NList.delete(i) NList.delete(i)
return result return result
raise newException(ENotFoundErr, "Node not found") raise newException(ENotFoundErr, "Node not found")
proc setNamedItem*(NList: var seq[PNode], arg: PNode): PNode = proc setNamedItem*(NList: var seq[PNode], arg: PNode): PNode =
## Adds ``arg`` as a ``Node`` to the ``NList`` ## 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 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 != nil:
if NList.len() > 0: if NList.len() > 0:
#Check if newChild is from this nodes document #Check if newChild is from this nodes document
if NList[0].FOwnerDocument != arg.FOwnerDocument: if NList[0].FOwnerDocument != arg.FOwnerDocument:
raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.")
#Exceptions End #Exceptions End
var item: PNode = NList.getNamedItem(arg.NodeName()) var item: PNode = NList.getNamedItem(arg.NodeName())
if item == nil: if item == nil:
NList.add(arg) NList.add(arg)
@ -765,19 +776,19 @@ proc setNamedItem*(NList: var seq[PNode], arg: PNode): PNode =
break break
NList[index] = arg NList[index] = arg
return item # Return the replaced node return item # Return the replaced node
proc setNamedItem*(NList: var seq[PAttr], arg: PAttr): PAttr = proc setNamedItem*(NList: var seq[PAttr], arg: PAttr): PAttr =
## Adds ``arg`` as a ``Node`` to the ``NList`` ## 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 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 != nil:
if NList.len() > 0: if NList.len() > 0:
# Check if newChild is from this nodes document # Check if newChild is from this nodes document
if NList[0].FOwnerDocument != arg.FOwnerDocument: if NList[0].FOwnerDocument != arg.FOwnerDocument:
raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.")
if arg.FOwnerElement != nil: if arg.FOwnerElement != nil:
raise newException(EInuseAttributeErr, "This attribute is in use by another element, use cloneNode") raise newException(EInuseAttributeErr, "This attribute is in use by another element, use cloneNode")
# Exceptions end # Exceptions end
var item: PAttr = NList.getNamedItem(arg.nodeName()) var item: PAttr = NList.getNamedItem(arg.nodeName())
if item == nil: if item == nil:
@ -792,16 +803,16 @@ proc setNamedItem*(NList: var seq[PAttr], arg: PAttr): PAttr =
break break
NList[index] = arg NList[index] = arg
return item # Return the replaced node return item # Return the replaced node
proc setNamedItemNS*(NList: var seq[PNode], arg: PNode): PNode = proc setNamedItemNS*(NList: var seq[PNode], arg: PNode): PNode =
## Adds a node using its ``namespaceURI`` and ``localName`` ## Adds a node using its ``namespaceURI`` and ``localName``
if not IsNil(NList): if NList != nil:
if NList.len() > 0: if NList.len() > 0:
# Check if newChild is from this nodes document # Check if newChild is from this nodes document
if NList[0].FOwnerDocument != arg.FOwnerDocument: if NList[0].FOwnerDocument != arg.FOwnerDocument:
raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.")
#Exceptions end #Exceptions end
var item: PNode = NList.getNamedItemNS(arg.namespaceURI(), arg.localName()) var item: PNode = NList.getNamedItemNS(arg.namespaceURI(), arg.localName())
if item == nil: if item == nil:
NList.add(arg) NList.add(arg)
@ -815,18 +826,18 @@ proc setNamedItemNS*(NList: var seq[PNode], arg: PNode): PNode =
break break
NList[index] = arg NList[index] = arg
return item # Return the replaced node return item # Return the replaced node
proc setNamedItemNS*(NList: var seq[PAttr], arg: PAttr): PAttr = proc setNamedItemNS*(NList: var seq[PAttr], arg: PAttr): PAttr =
## Adds a node using its ``namespaceURI`` and ``localName`` ## Adds a node using its ``namespaceURI`` and ``localName``
if not isNil(NList): if NList != nil:
if NList.len() > 0: if NList.len() > 0:
# Check if newChild is from this nodes document # Check if newChild is from this nodes document
if NList[0].FOwnerDocument != arg.FOwnerDocument: if NList[0].FOwnerDocument != arg.FOwnerDocument:
raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.") raise newException(EWrongDocumentErr, "This node belongs to a different document, use importNode.")
if arg.FOwnerElement != nil: if arg.FOwnerElement != nil:
raise newException(EInuseAttributeErr, "This attribute is in use by another element, use cloneNode") raise newException(EInuseAttributeErr, "This attribute is in use by another element, use cloneNode")
# Exceptions end # Exceptions end
var item: PAttr = NList.getNamedItemNS(arg.namespaceURI(), arg.localName()) var item: PAttr = NList.getNamedItemNS(arg.namespaceURI(), arg.localName())
if item == nil: if item == nil:
@ -841,8 +852,8 @@ proc setNamedItemNS*(NList: var seq[PAttr], arg: PAttr): PAttr =
break break
NList[index] = arg NList[index] = arg
return item # Return the replaced node return item # Return the replaced node
# CharacterData - Decided to implement this, # CharacterData - Decided to implement this,
# Didn't add the procedures, because you can just edit .data # Didn't add the procedures, because you can just edit .data
# Attr # Attr
@ -851,13 +862,13 @@ proc name*(a: PAttr): string =
## Returns the name of the Attribute ## Returns the name of the Attribute
return a.FName return a.FName
proc specified*(a: PAttr): bool = proc specified*(a: PAttr): bool =
## Specifies whether this attribute was specified in the original document ## Specifies whether this attribute was specified in the original document
return a.FSpecified return a.FSpecified
proc ownerElement*(a: PAttr): PElement = proc ownerElement*(a: PAttr): PElement =
## Returns this Attributes owner element ## Returns this Attributes owner element
return a.FOwnerElement return a.FOwnerElement
@ -873,6 +884,8 @@ proc tagName*(el: PElement): string =
# Procedures # Procedures
proc getAttribute*(el: PElement, name: string): string = proc getAttribute*(el: PElement, name: string): string =
## Retrieves an attribute value by ``name`` ## Retrieves an attribute value by ``name``
if el.attributes == nil:
return nil
var attribute = el.attributes.getNamedItem(name) var attribute = el.attributes.getNamedItem(name)
if attribute != nil: if attribute != nil:
return attribute.value return attribute.value
@ -881,19 +894,25 @@ proc getAttribute*(el: PElement, name: string): string =
proc getAttributeNS*(el: PElement, namespaceURI: string, localName: string): string = proc getAttributeNS*(el: PElement, namespaceURI: string, localName: string): string =
## Retrieves an attribute value by ``localName`` and ``namespaceURI`` ## Retrieves an attribute value by ``localName`` and ``namespaceURI``
if el.attributes == nil:
return nil
var attribute = el.attributes.getNamedItemNS(namespaceURI, localName) var attribute = el.attributes.getNamedItemNS(namespaceURI, localName)
if attribute != nil: if attribute != nil:
return attribute.value return attribute.value
else: else:
return nil return nil
proc getAttributeNode*(el: PElement, name: string): PAttr = proc getAttributeNode*(el: PElement, name: string): PAttr =
## Retrieves an attribute node by ``name`` ## Retrieves an attribute node by ``name``
## To retrieve an attribute node by qualified name and namespace URI, use the `getAttributeNodeNS` method ## To retrieve an attribute node by qualified name and namespace URI, use the `getAttributeNodeNS` method
if el.attributes == nil:
return nil
return el.attributes.getNamedItem(name) return el.attributes.getNamedItem(name)
proc getAttributeNodeNS*(el: PElement, namespaceURI: string, localName: string): PAttr = proc getAttributeNodeNS*(el: PElement, namespaceURI: string, localName: string): PAttr =
## Retrieves an `Attr` node by ``localName`` and ``namespaceURI`` ## Retrieves an `Attr` node by ``localName`` and ``namespaceURI``
if el.attributes == nil:
return nil
return el.attributes.getNamedItemNS(namespaceURI, localName) return el.attributes.getNamedItemNS(namespaceURI, localName)
proc getElementsByTagName*(el: PElement, name: string): seq[PNode] = proc getElementsByTagName*(el: PElement, name: string): seq[PNode] =
@ -909,103 +928,110 @@ proc getElementsByTagNameNS*(el: PElement, namespaceURI: string, localName: stri
result = el.findNodesNS(namespaceURI, localName) result = el.findNodesNS(namespaceURI, localName)
proc hasAttribute*(el: PElement, name: string): bool = proc hasAttribute*(el: PElement, name: string): bool =
## Returns ``true`` when an attribute with a given ``name`` is specified ## Returns ``true`` when an attribute with a given ``name`` is specified
## on this element , ``false`` otherwise. ## on this element , ``false`` otherwise.
if el.attributes == nil:
return false
return el.attributes.getNamedItem(name) != nil return el.attributes.getNamedItem(name) != nil
proc hasAttributeNS*(el: PElement, namespaceURI: string, localName: string): bool = proc hasAttributeNS*(el: PElement, namespaceURI: string, localName: string): bool =
## Returns ``true`` when an attribute with a given ``localName`` and ## Returns ``true`` when an attribute with a given ``localName`` and
## ``namespaceURI`` is specified on this element , ``false`` otherwise ## ``namespaceURI`` is specified on this element , ``false`` otherwise
if el.attributes == nil:
return false
return el.attributes.getNamedItemNS(namespaceURI, localName) != nil return el.attributes.getNamedItemNS(namespaceURI, localName) != nil
proc removeAttribute*(el: PElement, name: string) = proc removeAttribute*(el: PElement, name: string) =
## Removes an attribute by ``name`` ## Removes an attribute by ``name``
for i in low(el.attributes)..high(el.attributes): if el.attributes != nil:
if el.attributes[i].FName == name: for i in low(el.attributes)..high(el.attributes):
el.attributes.delete(i) if el.attributes[i].FName == name:
el.attributes.delete(i)
proc removeAttributeNS*(el: PElement, namespaceURI: string, localName: string) = proc removeAttributeNS*(el: PElement, namespaceURI: string, localName: string) =
## Removes an attribute by ``localName`` and ``namespaceURI`` ## Removes an attribute by ``localName`` and ``namespaceURI``
for i in low(el.attributes)..high(el.attributes): if el.attributes != nil:
if el.attributes[i].FNamespaceURI == namespaceURI and for i in low(el.attributes)..high(el.attributes):
el.attributes[i].FLocalName == localName: if el.attributes[i].FNamespaceURI == namespaceURI and
el.attributes.delete(i) el.attributes[i].FLocalName == localName:
el.attributes.delete(i)
proc removeAttributeNode*(el: PElement, oldAttr: PAttr): PAttr = proc removeAttributeNode*(el: PElement, oldAttr: PAttr): PAttr =
## Removes the specified attribute node ## Removes the specified attribute node
## If the attribute node cannot be found raises ``ENotFoundErr`` ## If the attribute node cannot be found raises ``ENotFoundErr``
for i in low(el.attributes)..high(el.attributes): if el.attributes != nil:
if el.attributes[i] == oldAttr: for i in low(el.attributes)..high(el.attributes):
result = el.attributes[i] if el.attributes[i] == oldAttr:
el.attributes.delete(i) result = el.attributes[i]
return result el.attributes.delete(i)
return result
raise newException(ENotFoundErr, "oldAttr is not a member of el's Attributes") raise newException(ENotFoundErr, "oldAttr is not a member of el's Attributes")
proc setAttributeNode*(el: PElement, newAttr: PAttr): PAttr = proc setAttributeNode*(el: PElement, newAttr: PAttr): PAttr =
## Adds a new attribute node, if an attribute with the same `nodeName` is ## 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 ## present, it is replaced by the new one and the replaced attribute is
## returned, otherwise ``nil`` is returned. ## returned, otherwise ``nil`` is returned.
# Check if newAttr is from this nodes document # Check if newAttr is from this nodes document
if el.FOwnerDocument != newAttr.FOwnerDocument: if el.FOwnerDocument != newAttr.FOwnerDocument:
raise newException(EWrongDocumentErr, raise newException(EWrongDocumentErr,
"This node belongs to a different document, use importNode.") "This node belongs to a different document, use importNode.")
if newAttr.FOwnerElement != nil: if newAttr.FOwnerElement != nil:
raise newException(EInuseAttributeErr, raise newException(EInuseAttributeErr,
"This attribute is in use by another element, use cloneNode") "This attribute is in use by another element, use cloneNode")
# Exceptions end # Exceptions end
if isNil(el.attributes): el.attributes = @[] if el.attributes == nil: el.attributes = @[]
return el.attributes.setNamedItem(newAttr) return el.attributes.setNamedItem(newAttr)
proc setAttributeNodeNS*(el: PElement, newAttr: PAttr): PAttr = proc setAttributeNodeNS*(el: PElement, newAttr: PAttr): PAttr =
## Adds a new attribute node, if an attribute with the localName and ## Adds a new attribute node, if an attribute with the localName and
## namespaceURI of ``newAttr`` is present, it is replaced by the new one ## namespaceURI of ``newAttr`` is present, it is replaced by the new one
## and the replaced attribute is returned, otherwise ``nil`` is returned. ## and the replaced attribute is returned, otherwise ``nil`` is returned.
# Check if newAttr is from this nodes document # Check if newAttr is from this nodes document
if el.FOwnerDocument != newAttr.FOwnerDocument: if el.FOwnerDocument != newAttr.FOwnerDocument:
raise newException(EWrongDocumentErr, raise newException(EWrongDocumentErr,
"This node belongs to a different document, use importNode.") "This node belongs to a different document, use importNode.")
if newAttr.FOwnerElement != nil: if newAttr.FOwnerElement != nil:
raise newException(EInuseAttributeErr, raise newException(EInuseAttributeErr,
"This attribute is in use by another element, use cloneNode") "This attribute is in use by another element, use cloneNode")
# Exceptions end # Exceptions end
if isNil(el.attributes): el.attributes = @[] if el.attributes == nil: el.attributes = @[]
return el.attributes.setNamedItemNS(newAttr) return el.attributes.setNamedItemNS(newAttr)
proc setAttribute*(el: PElement, name: string, value: string) = proc setAttribute*(el: PElement, name: string, value: string) =
## Adds a new attribute, as specified by ``name`` and ``value`` ## Adds a new attribute, as specified by ``name`` and ``value``
## If an attribute with that name is already present in the element, its ## If an attribute with that name is already present in the element, its
## value is changed to be that of the value parameter ## value is changed to be that of the value parameter
## Raises the EInvalidCharacterErr if the specified ``name`` contains ## Raises the EInvalidCharacterErr if the specified ``name`` contains
## illegal characters ## illegal characters
var AttrNode = el.FOwnerDocument.createAttribute(name) var AttrNode = el.FOwnerDocument.createAttribute(name)
# Check if name contains illegal characters # Check if name contains illegal characters
if illegalChars in name: if illegalChars in name:
raise newException(EInvalidCharacterErr, "Invalid character") raise newException(EInvalidCharacterErr, "Invalid character")
discard el.setAttributeNode(AttrNode) discard el.setAttributeNode(AttrNode)
# Set the info later, the setAttributeNode checks # Set the info later, the setAttributeNode checks
# if FOwnerElement is nil, and if it isn't it raises an exception # if FOwnerElement is nil, and if it isn't it raises an exception
AttrNode.FOwnerElement = el AttrNode.FOwnerElement = el
AttrNode.FSpecified = True AttrNode.FSpecified = True
AttrNode.value = value AttrNode.value = value
proc setAttributeNS*(el: PElement, namespaceURI, localName, value: string) = proc setAttributeNS*(el: PElement, namespaceURI, localName, value: string) =
## Adds a new attribute, as specified by ``namespaceURI``, ``localName`` ## Adds a new attribute, as specified by ``namespaceURI``, ``localName``
## and ``value``. ## and ``value``.
# Check if name contains illegal characters # Check if name contains illegal characters
if illegalChars in namespaceURI or illegalChars in localName: if illegalChars in namespaceURI or illegalChars in localName:
raise newException(EInvalidCharacterErr, "Invalid character") raise newException(EInvalidCharacterErr, "Invalid character")
var AttrNode = el.FOwnerDocument.createAttributeNS(namespaceURI, localName) var AttrNode = el.FOwnerDocument.createAttributeNS(namespaceURI, localName)
discard el.setAttributeNodeNS(AttrNode) discard el.setAttributeNodeNS(AttrNode)
# Set the info later, the setAttributeNode checks # Set the info later, the setAttributeNode checks
# if FOwnerElement is nil, and if it isn't it raises an exception # if FOwnerElement is nil, and if it isn't it raises an exception
@ -1013,19 +1039,19 @@ proc setAttributeNS*(el: PElement, namespaceURI, localName, value: string) =
AttrNode.FSpecified = True AttrNode.FSpecified = True
AttrNode.value = value AttrNode.value = value
# Text # Text
proc splitData*(TextNode: PText, offset: int): PText = proc splitData*(TextNode: PText, offset: int): PText =
## Breaks this node into two nodes at the specified offset, ## Breaks this node into two nodes at the specified offset,
## keeping both in the tree as siblings. ## keeping both in the tree as siblings.
if offset > TextNode.data.len(): if offset > TextNode.data.len():
raise newException(EIndexSizeErr, "Index out of bounds") raise newException(EIndexSizeErr, "Index out of bounds")
var left: string = TextNode.data.substr(0, offset) var left: string = TextNode.data.substr(0, offset)
TextNode.data = left TextNode.data = left
var right: string = TextNode.data.substr(offset, TextNode.data.len()) var right: string = TextNode.data.substr(offset, TextNode.data.len())
if TextNode.FParentNode != nil: if TextNode.FParentNode != nil and TextNode.FParentNode.childNodes != nil:
for i in low(TextNode.FParentNode.childNodes)..high(TextNode.FParentNode.childNodes): for i in low(TextNode.FParentNode.childNodes)..high(TextNode.FParentNode.childNodes):
if TextNode.FParentNode.childNodes[i] == TextNode: if TextNode.FParentNode.childNodes[i] == TextNode:
var newNode: PText = TextNode.FOwnerDocument.createTextNode(right) var newNode: PText = TextNode.FOwnerDocument.createTextNode(right)
@ -1042,10 +1068,10 @@ proc target*(PI: PProcessingInstruction): string =
return PI.FTarget return PI.FTarget
# --Other stuff-- # --Other stuff--
# Writer # Writer
proc addEscaped(s: string): string = proc addEscaped(s: string): string =
result = "" result = ""
for c in items(s): for c in items(s):
case c case c
@ -1057,10 +1083,11 @@ proc addEscaped(s: string): string =
proc nodeToXml(n: PNode, indent: int = 0): string = proc nodeToXml(n: PNode, indent: int = 0): string =
result = repeatChar(indent, ' ') & "<" & n.nodeName result = repeatChar(indent, ' ') & "<" & n.nodeName
for i in items(n.Attributes): if n.attributes != nil:
result.add(" " & i.name & "=\"" & addEscaped(i.value) & "\"") for i in items(n.attributes):
result.add(" " & i.name & "=\"" & addEscaped(i.value) & "\"")
if n.childNodes.len() == 0:
if n.childNodes == nil or n.childNodes.len() == 0:
result.add("/>") # No idea why this doesn't need a \n :O result.add("/>") # No idea why this doesn't need a \n :O
else: else:
# End the beginning of this tag # End the beginning of this tag