diff --git a/source/kxml/xml.d b/source/kxml/xml.d index 94855d2..1bbf804 100755 --- a/source/kxml/xml.d +++ b/source/kxml/xml.d @@ -178,6 +178,7 @@ class XmlNode protected string _name; protected string[string] _attributes; protected XmlNode[] _children; + protected XmlNode _parent; @@ -191,6 +192,37 @@ class XmlNode _name = name; } + /// Clone the state of an XmlNode from another + XmlNode cloneNode(XmlNode node) + { + _docroot = node._docroot; + _name = node._name; + _attributes = node._attributes.dup; + removeChildren; + _parent = null; + foreach(child; node._children) + addChild(child.duplicate); + return this; + } + + /// Duplicate an XmlNode or derivatives + XmlNode duplicate() + { + if (auto ucdata = cast(UCData)this) { + return (new UCData).cloneUCData(ucdata); + } else if (auto cdata = cast(CData)this) { + return (new CData).cloneCData(cdata); + } else if (auto xmlPi = cast(XmlPI)this) { + return (new XmlPI).cloneXmlPI(xmlPi); + } else if (auto xmlComment = cast(XmlComment)this) { + return (new XmlComment).cloneXmlComment(xmlComment); + } else if (auto xmlDoc = cast(XmlDocument)this) { + return (new XmlDocument).cloneXmlDocument(xmlDoc); + } else { + return (new XmlNode).cloneNode(this); + } + } + /// Get the name of this XmlNode. string getName() { return _name; @@ -249,8 +281,16 @@ class XmlNode return this; } + /// Get the parent node (or null if root). + XmlNode getParent() + { + return _parent; + } + /// Add a child node. XmlNode addChild(XmlNode newNode) { + if (newNode._parent) throw new Exception("Child already has a parent"); + newNode._parent = this; // let's bump things by increments of 10 to make them more efficient if (_children.length+1%10==0) { _children.length = _children.length + 10; @@ -268,15 +308,16 @@ class XmlNode } /// Remove the child with the same reference as what was given. - /// Returns: The number of children removed. + /// Returns: The number of children removed (can only be 1 or 0). size_t removeChild(XmlNode remove) { size_t len = _children.length; for (size_t i = 0;i<_children.length;i++) if (_children[i] is remove) { // we matched it, so remove it - // don't return true yet, since we're removing all references to it, not just the first one _children = _children[0..i]~_children[i+1..$]; + remove._parent = null; + return 1; } - return len - _children.length; + return 0; } /// Add a child Node of cdata (text). @@ -322,6 +363,7 @@ class XmlNode _children.length = 0; _attributes = null; _name = null; + _parent = null; // put back in the pool of available XmlNode nodes if possible if (_docroot) { _docroot.xmlNodes.length = _docroot.xmlNodes.length + 1; @@ -331,6 +373,9 @@ class XmlNode /// This function removes all child nodes from the current node XmlNode removeChildren() { + foreach(child;_children) { + child._parent = null; + } _children.length = 0; return this; } @@ -438,6 +483,9 @@ class XmlNode /// Add array of nodes directly into this node as children. void addChildren(XmlNode[]newChildren) { + foreach(child; newChildren) { + child._parent = this; + } // let's bump things by increments of 10 to make them more efficient if (_children.length+newChildren.length%10 < newChildren.length) { _children.length = _children.length + 10; @@ -1005,6 +1053,7 @@ class XmlNode /// Index override for replacing children. XmlNode opIndexAssign(XmlNode x,int childnum) { if (childnum > _children.length) throw new Exception("Child element assignment is outside of array bounds"); + if (x._parent) throw new Exception("Child already has a parent"); _children[childnum] = x; return this; } @@ -1022,6 +1071,15 @@ class CData : XmlNode this(){} + /// Clone the state of a CData from another + CData cloneCData(CData cdata) + { + _docroot = cdata._docroot; + _parent = null; + _cdata = cdata._cdata; + return this; + } + /// Get CData string associated with this object. /// Returns: Parsed Character Data with decoded XML entities override string getCData() { @@ -1042,6 +1100,7 @@ class CData : XmlNode _docroot.cdataNodes[$-1] = this; } _cdata = null; + _parent = null; } /// This outputs escaped XML entities for use on the network or in a document. @@ -1114,6 +1173,12 @@ class CData : XmlNode /// A specialization of CData for nodes class UCData : CData { + /// Clone the state of a UCData from another + UCData cloneUCData(UCData ucdata) + { + return cast(UCData)(cast(CData)this).cloneCData(cast(CData)ucdata); + } + /// Get CData string associated with this object. /// Returns: Unparsed Character Data override string getCData() { @@ -1134,6 +1199,7 @@ class UCData : CData { _docroot.ucdataNodes[$-1] = this; } _cdata = null; + _parent = null; } /// This outputs escaped XML entities for use on the network or in a document. @@ -1151,6 +1217,16 @@ class XmlPI : XmlNode { super(name); } + /// Clone the state of an XmlPI from another + XmlPI cloneXmlPI(XmlPI xmlPi) + { + _name = xmlPi._name; + _attributes = xmlPi._attributes.dup; + _parent = null; + _docroot = xmlPi._docroot; + return this; + } + /// This node can't have children, and so can't have CData. /// Should this throw an exception? override string getCData() { @@ -1164,9 +1240,10 @@ class XmlPI : XmlNode { /// This function resets the node to a default state override void reset() { - // put back in the pool of available CData nodes if possible + // put back in the pool of available xmlPINodes if possible _name = null; _attributes = null; + _parent = null; if (_docroot) { _docroot.xmlPINodes.length = _docroot.xmlPINodes.length + 1; _docroot.xmlPINodes[$-1] = this; @@ -1222,10 +1299,20 @@ class XmlComment : XmlNode { return null; } + /// Clone the state of an XmlComment from another + XmlComment cloneXmlComment(XmlComment comment) + { + _comment = comment._comment; + _parent = null; + _docroot = comment._docroot; + return this; + } + /// This function resets the node to a default state override void reset() { // put back in the pool of available XmlComment nodes if possible _comment = null; + _parent = null; if (_docroot) { _docroot.xmlCommentNodes.length = _docroot.xmlCommentNodes.length + 1; _docroot.xmlCommentNodes[$-1] = this; @@ -1351,6 +1438,14 @@ class XmlDocument:XmlNode { super(); } + /// Clone the state of an XmlDocument from another + XmlDocument cloneXmlDocument(XmlDocument document) + { + reset; + foreach(child; document._children) + addChild(child.duplicate); + return this; + } /// This static opCall should be used when creating new XmlDocuments for use static XmlDocument opCall(string constring,bool preserveWS = false) { @@ -1630,6 +1725,14 @@ unittest { searchlist = xml.parseXPath(`//td[.="Text 2.3"]`); assert(searchlist.length == 1); + xmlstring = `Text with child ` ~ + ` and more text. content.]]>`; + + logline("kxml.xml XmlNode.duplicate test\n"); + xml = readDocument(xmlstring, true); + assert(xml.toString == xmlstring); + auto dupXml = xml.duplicate; + assert(dupXml.toString == xmlstring); } version(XML_main) {