5 // Kral Ferch <kral_ferch@hotmail.com>
6 // Atsushi Enomoto <ginga@kit.hi-ho.ne.jp>
9 // (C) 2002 Atsushi Enomoto
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 using System.Collections;
35 using System.Globalization;
38 using System.Xml.XPath;
39 using System.Diagnostics;
40 using System.Xml.Schema;
44 public abstract class XmlNode : ICloneable, IEnumerable, IXPathNavigable
46 static EmptyNodeList emptyList = new EmptyNodeList ();
48 class EmptyNodeList : XmlNodeList
50 static IEnumerator emptyEnumerator = new object [0].GetEnumerator ();
52 public override int Count {
56 public override IEnumerator GetEnumerator ()
58 return emptyEnumerator;
61 public override XmlNode Item (int index)
69 XmlDocument ownerDocument;
71 XmlNodeListChildren childNodes;
77 internal XmlNode (XmlDocument ownerDocument)
79 this.ownerDocument = ownerDocument;
86 public virtual XmlAttributeCollection Attributes {
90 public virtual string BaseURI {
92 // Isn't it conformant to W3C XML Base Recommendation?
93 // As far as I tested, there are not...
94 return (ParentNode != null) ? ParentNode.ChildrenBaseURI : String.Empty;
98 internal virtual string ChildrenBaseURI {
104 public virtual XmlNodeList ChildNodes {
106 IHasXmlChildNode l = this as IHasXmlChildNode;
109 if (childNodes == null)
110 childNodes = new XmlNodeListChildren (l);
115 public virtual XmlNode FirstChild {
117 IHasXmlChildNode l = this as IHasXmlChildNode;
118 XmlLinkedNode c = (l == null) ?
119 null : l.LastLinkedChild;
120 return c == null ? null : c.NextLinkedSibling;
124 public virtual bool HasChildNodes {
125 get { return LastChild != null; }
128 public virtual string InnerText {
131 case XmlNodeType.Text:
132 case XmlNodeType.CDATA:
133 case XmlNodeType.SignificantWhitespace:
134 case XmlNodeType.Whitespace:
137 if (FirstChild == null)
139 if (FirstChild == LastChild)
140 return FirstChild.NodeType != XmlNodeType.Comment ?
141 FirstChild.InnerText :
144 StringBuilder builder = null;
145 AppendChildValues (ref builder);
146 return builder == null ? String.Empty : builder.ToString ();
150 if (! (this is XmlDocumentFragment))
151 throw new InvalidOperationException ("This node is read only. Cannot be modified.");
153 AppendChild (OwnerDocument.CreateTextNode (value));
157 private void AppendChildValues (ref StringBuilder builder)
159 XmlNode node = FirstChild;
161 while (node != null) {
162 switch (node.NodeType) {
163 case XmlNodeType.Text:
164 case XmlNodeType.CDATA:
165 case XmlNodeType.SignificantWhitespace:
166 case XmlNodeType.Whitespace:
168 builder = new StringBuilder ();
169 builder.Append (node.Value);
172 node.AppendChildValues (ref builder);
173 node = node.NextSibling;
177 public virtual string InnerXml {
179 StringWriter sw = new StringWriter ();
180 XmlTextWriter xtw = new XmlTextWriter (sw);
182 WriteContentTo (xtw);
184 return sw.GetStringBuilder ().ToString ();
188 throw new InvalidOperationException ("This node is readonly or doesn't have any children.");
192 public virtual bool IsReadOnly {
195 XmlNode curNode = this;
198 switch (curNode.NodeType)
200 case XmlNodeType.EntityReference:
201 case XmlNodeType.Entity:
204 case XmlNodeType.Attribute:
205 curNode = ((XmlAttribute)curNode).OwnerElement;
209 curNode = curNode.ParentNode;
213 while (curNode != null) ;
219 [System.Runtime.CompilerServices.IndexerName("Item")]
220 public virtual XmlElement this [string name] {
222 for (int i = 0; i < ChildNodes.Count; i++) {
223 XmlNode node = ChildNodes [i];
224 if ((node.NodeType == XmlNodeType.Element) &&
225 (node.Name == name)) {
226 return (XmlElement) node;
234 [System.Runtime.CompilerServices.IndexerName("Item")]
235 public virtual XmlElement this [string localname, string ns] {
237 for (int i = 0; i < ChildNodes.Count; i++) {
238 XmlNode node = ChildNodes [i];
239 if ((node.NodeType == XmlNodeType.Element) &&
240 (node.LocalName == localname) &&
241 (node.NamespaceURI == ns)) {
242 return (XmlElement) node;
250 public virtual XmlNode LastChild {
252 IHasXmlChildNode l = this as IHasXmlChildNode;
253 return l == null ? null : l.LastLinkedChild;
257 public abstract string LocalName { get; }
259 public abstract string Name { get; }
261 public virtual string NamespaceURI {
262 get { return String.Empty; }
265 public virtual XmlNode NextSibling {
269 public abstract XmlNodeType NodeType { get; }
271 internal virtual XPathNodeType XPathNodeType {
273 throw new InvalidOperationException ("Can not get XPath node type from " + this.GetType ().ToString ());
277 public virtual string OuterXml {
279 StringWriter sw = new StringWriter ();
280 XmlTextWriter xtw = new XmlTextWriter (sw);
284 return sw.ToString ();
288 public virtual XmlDocument OwnerDocument {
289 get { return ownerDocument; }
292 public virtual XmlNode ParentNode {
293 get { return parentNode; }
296 public virtual string Prefix {
297 get { return String.Empty; }
301 public virtual XmlNode PreviousSibling {
305 public virtual string Value {
307 set { throw new InvalidOperationException ("This node does not have a value"); }
310 internal virtual string XmlLang {
312 if(Attributes != null)
313 for (int i = 0; i < Attributes.Count; i++) {
314 XmlAttribute attr = Attributes [i];
315 if(attr.Name == "xml:lang")
318 return (ParentNode != null) ? ParentNode.XmlLang : OwnerDocument.XmlLang;
322 internal virtual XmlSpace XmlSpace {
324 if(Attributes != null) {
325 for (int i = 0; i < Attributes.Count; i++) {
326 XmlAttribute attr = Attributes [i];
327 if(attr.Name == "xml:space") {
329 case "preserve": return XmlSpace.Preserve;
330 case "default": return XmlSpace.Default;
336 return (ParentNode != null) ? ParentNode.XmlSpace : OwnerDocument.XmlSpace;
340 public virtual IXmlSchemaInfo SchemaInfo {
349 public virtual XmlNode AppendChild (XmlNode newChild)
351 // AppendChild(n) is equivalent to InsertBefore(n, null)
352 return InsertBefore (newChild, null);
355 internal XmlNode AppendChild (XmlNode newChild, bool checkNodeType)
357 return InsertBefore (newChild, null, checkNodeType, true);
360 public virtual XmlNode Clone ()
362 // By MS document, it is equivalent to CloneNode(true).
363 return this.CloneNode (true);
366 public abstract XmlNode CloneNode (bool deep);
368 public virtual XPathNavigator CreateNavigator ()
370 // XmlDocument has overriden definition, so it is safe
371 // to use OwnerDocument here.
372 return OwnerDocument.CreateNavigator (this);
375 public IEnumerator GetEnumerator ()
377 return ChildNodes.GetEnumerator ();
380 public virtual string GetNamespaceOfPrefix (string prefix)
384 throw new ArgumentNullException ("prefix");
386 return XmlNamespaceManager.XmlnsXml;
388 return XmlNamespaceManager.XmlnsXmlns;
393 case XmlNodeType.Attribute:
394 node = ((XmlAttribute) this).OwnerElement;
398 case XmlNodeType.Element:
406 while (node != null) {
407 if (node.Prefix == prefix)
408 return node.NamespaceURI;
409 if (node.NodeType == XmlNodeType.Element &&
410 ((XmlElement) node).HasAttributes) {
411 int count = node.Attributes.Count;
412 for (int i = 0; i < count; i++) {
413 XmlAttribute attr = node.Attributes [i];
414 if (prefix == attr.LocalName && attr.Prefix == "xmlns"
415 || attr.Name == "xmlns" && prefix == String.Empty)
419 node = node.ParentNode;
424 public virtual string GetPrefixOfNamespace (string namespaceURI)
426 switch (namespaceURI) {
427 case XmlNamespaceManager.XmlnsXml:
428 return XmlNamespaceManager.PrefixXml;
429 case XmlNamespaceManager.XmlnsXmlns:
430 return XmlNamespaceManager.PrefixXmlns;
435 case XmlNodeType.Attribute:
436 node = ((XmlAttribute) this).OwnerElement;
438 case XmlNodeType.Element:
446 while (node != null) {
447 if (node.NodeType == XmlNodeType.Element &&
448 ((XmlElement) node).HasAttributes) {
449 for (int i = 0; i < node.Attributes.Count; i++) {
450 XmlAttribute attr = node.Attributes [i];
451 if (attr.Prefix == "xmlns" && attr.Value == namespaceURI)
452 return attr.LocalName;
453 else if (attr.Name == "xmlns" && attr.Value == namespaceURI)
457 node = node.ParentNode;
462 object ICloneable.Clone ()
467 IEnumerator IEnumerable.GetEnumerator ()
469 return GetEnumerator ();
472 public virtual XmlNode InsertAfter (XmlNode newChild, XmlNode refChild)
474 // InsertAfter(n1, n2) is equivalent to InsertBefore(n1, n2.PreviousSibling).
476 // I took this way because current implementation
477 // Calling InsertBefore() in this method is faster than
478 // the counterpart, since NextSibling is faster than
479 // PreviousSibling (these children are forward-only list).
480 XmlNode argNode = null;
481 if (refChild != null)
482 argNode = refChild.NextSibling;
483 else if (FirstChild != null)
484 argNode = FirstChild;
485 return InsertBefore (newChild, argNode);
488 public virtual XmlNode InsertBefore (XmlNode newChild, XmlNode refChild)
490 return InsertBefore (newChild, refChild, true, true);
493 // check for the node to be one of node ancestors
494 internal bool IsAncestor (XmlNode newChild)
496 XmlNode currNode = this.ParentNode;
497 while(currNode != null)
499 if(currNode == newChild)
501 currNode = currNode.ParentNode;
506 internal XmlNode InsertBefore (XmlNode newChild, XmlNode refChild, bool checkNodeType, bool raiseEvent)
509 CheckNodeInsertion (newChild, refChild);
511 if (newChild == refChild)
514 IHasXmlChildNode l = (IHasXmlChildNode) this;
516 XmlDocument ownerDoc = (NodeType == XmlNodeType.Document) ? (XmlDocument) this : OwnerDocument;
519 ownerDoc.onNodeInserting (newChild, this);
521 if (newChild.ParentNode != null)
522 newChild.ParentNode.RemoveChild (newChild, checkNodeType);
524 if (newChild.NodeType == XmlNodeType.DocumentFragment) {
525 // This recursively invokes events. (It is compatible with MS implementation.)
527 while (newChild.FirstChild != null) {
528 var c = this.InsertBefore (newChild.FirstChild, refChild);
533 XmlLinkedNode newLinkedChild = (XmlLinkedNode) newChild;
534 newLinkedChild.parentNode = this;
536 if (refChild == null) {
537 // newChild is the last child:
538 // * set newChild as NextSibling of the existing lastchild
539 // * set LastChild = newChild
540 // * set NextSibling of newChild as FirstChild
541 if (l.LastLinkedChild != null) {
542 XmlLinkedNode formerFirst = (XmlLinkedNode) FirstChild;
543 l.LastLinkedChild.NextLinkedSibling = newLinkedChild;
544 l.LastLinkedChild = newLinkedChild;
545 newLinkedChild.NextLinkedSibling = formerFirst;
547 l.LastLinkedChild = newLinkedChild;
548 l.LastLinkedChild.NextLinkedSibling = newLinkedChild; // FirstChild
551 // newChild is not the last child:
552 // * if newchild is first, then set next of lastchild is newChild.
553 // otherwise, set next of previous sibling to newChild
554 // * set next of newChild to refChild
555 XmlLinkedNode prev = refChild.PreviousSibling as XmlLinkedNode;
557 l.LastLinkedChild.NextLinkedSibling = newLinkedChild;
559 prev.NextLinkedSibling = newLinkedChild;
560 newLinkedChild.NextLinkedSibling = refChild as XmlLinkedNode;
562 switch (newChild.NodeType) {
563 case XmlNodeType.EntityReference:
564 ((XmlEntityReference) newChild).SetReferencedEntityContent ();
566 case XmlNodeType.Entity:
568 case XmlNodeType.DocumentType:
573 ownerDoc.onNodeInserted (newChild, newChild.ParentNode);
578 private void CheckNodeInsertion (XmlNode newChild, XmlNode refChild)
580 XmlDocument ownerDoc = (NodeType == XmlNodeType.Document) ? (XmlDocument) this : OwnerDocument;
582 if (NodeType != XmlNodeType.Element &&
583 NodeType != XmlNodeType.Attribute &&
584 NodeType != XmlNodeType.Document &&
585 NodeType != XmlNodeType.DocumentFragment)
586 throw new InvalidOperationException (String.Format ("Node cannot be appended to current node {0}.", NodeType));
589 case XmlNodeType.Attribute:
590 switch (newChild.NodeType) {
591 case XmlNodeType.Text:
592 case XmlNodeType.EntityReference:
595 throw new InvalidOperationException (String.Format (
596 "Cannot insert specified type of node {0} as a child of this node {1}.",
597 newChild.NodeType, NodeType));
600 case XmlNodeType.Element:
601 switch (newChild.NodeType) {
602 case XmlNodeType.Attribute:
603 case XmlNodeType.Document:
604 case XmlNodeType.DocumentType:
605 case XmlNodeType.Entity:
606 case XmlNodeType.Notation:
607 case XmlNodeType.XmlDeclaration:
608 throw new InvalidOperationException ("Cannot insert specified type of node as a child of this node.");
614 throw new InvalidOperationException ("The node is readonly.");
616 if (newChild.OwnerDocument != ownerDoc)
617 throw new ArgumentException ("Can't append a node created by another document.");
619 if (refChild != null) {
620 if (refChild.ParentNode != this)
621 throw new ArgumentException ("The reference node is not a child of this node.");
624 if(this == ownerDoc && ownerDoc.DocumentElement != null && (newChild is XmlElement) && newChild != ownerDoc.DocumentElement)
625 throw new XmlException ("multiple document element not allowed.");
627 // checking validity finished. then appending...
630 if (newChild == this || IsAncestor (newChild))
631 throw new ArgumentException("Cannot insert a node or any ancestor of that node as a child of itself.");
635 public virtual void Normalize ()
637 StringBuilder tmpBuilder = new StringBuilder ();
638 int count = this.ChildNodes.Count;
640 for (int i = 0; i < count; i++) {
641 XmlNode c = ChildNodes [i];
642 switch (c.NodeType) {
643 case XmlNodeType.Text:
644 case XmlNodeType.Whitespace:
645 case XmlNodeType.SignificantWhitespace:
646 tmpBuilder.Append (c.Value);
650 NormalizeRange (start, i, tmpBuilder);
651 // Continue to normalize from next node.
657 NormalizeRange (start, count, tmpBuilder);
661 private void NormalizeRange (int start, int i, StringBuilder tmpBuilder)
664 // If Texts and Whitespaces are mixed, Text takes precedence to remain.
665 // i.e. Whitespace should be removed.
666 for (int j = start; j < i; j++) {
667 XmlNode keep = ChildNodes [j];
668 if (keep.NodeType == XmlNodeType.Text) {
672 else if (keep.NodeType == XmlNodeType.SignificantWhitespace)
674 // but don't break up to find Text nodes.
678 for (int del = start; del < keepPos; del++)
679 RemoveChild (ChildNodes [start]);
680 int rest = i - keepPos - 1;
681 for (int del = 0; del < rest; del++) {
682 RemoveChild (ChildNodes [start + 1]);
687 ChildNodes [start].Value = tmpBuilder.ToString ();
688 // otherwise nothing to be normalized
690 tmpBuilder.Length = 0;
693 public virtual XmlNode PrependChild (XmlNode newChild)
695 return InsertAfter (newChild, null);
698 public virtual void RemoveAll ()
700 if (Attributes != null)
701 Attributes.RemoveAll ();
703 for (XmlNode node = FirstChild; node != null; node = next) {
704 next = node.NextSibling;
709 public virtual XmlNode RemoveChild (XmlNode oldChild)
711 return RemoveChild (oldChild, true);
714 private void CheckNodeRemoval ()
716 if (NodeType != XmlNodeType.Attribute &&
717 NodeType != XmlNodeType.Element &&
718 NodeType != XmlNodeType.Document &&
719 NodeType != XmlNodeType.DocumentFragment)
720 throw new ArgumentException (String.Format ("This {0} node cannot remove its child.", NodeType));
723 throw new ArgumentException (String.Format ("This {0} node is read only.", NodeType));
726 internal XmlNode RemoveChild (XmlNode oldChild, bool checkNodeType)
728 if (oldChild == null)
729 throw new NullReferenceException ();
730 XmlDocument ownerDoc = (NodeType == XmlNodeType.Document) ? (XmlDocument)this : OwnerDocument;
731 if(oldChild.ParentNode != this)
732 throw new ArgumentException ("The node to be removed is not a child of this node.");
735 ownerDoc.onNodeRemoving (oldChild, oldChild.ParentNode);
740 IHasXmlChildNode l = (IHasXmlChildNode) this;
742 if (Object.ReferenceEquals (l.LastLinkedChild, l.LastLinkedChild.NextLinkedSibling) && Object.ReferenceEquals (l.LastLinkedChild, oldChild))
743 // If there is only one children, simply clear.
744 l.LastLinkedChild = null;
746 XmlLinkedNode oldLinkedChild = (XmlLinkedNode) oldChild;
747 XmlLinkedNode beforeLinkedChild = l.LastLinkedChild;
748 XmlLinkedNode firstChild = (XmlLinkedNode) FirstChild;
750 while (Object.ReferenceEquals (beforeLinkedChild.NextLinkedSibling, l.LastLinkedChild) == false &&
751 Object.ReferenceEquals (beforeLinkedChild.NextLinkedSibling, oldLinkedChild) == false)
752 beforeLinkedChild = beforeLinkedChild.NextLinkedSibling;
754 if (Object.ReferenceEquals (beforeLinkedChild.NextLinkedSibling, oldLinkedChild) == false)
755 throw new ArgumentException ();
757 beforeLinkedChild.NextLinkedSibling = oldLinkedChild.NextLinkedSibling;
759 // Each derived class may have its own l.LastLinkedChild, so we must set it explicitly.
760 if (oldLinkedChild.NextLinkedSibling == firstChild)
761 l.LastLinkedChild = beforeLinkedChild;
763 oldLinkedChild.NextLinkedSibling = null;
767 ownerDoc.onNodeRemoved (oldChild, oldChild.ParentNode);
768 oldChild.parentNode = null; // clear parent 'after' above logic.
773 public virtual XmlNode ReplaceChild (XmlNode newChild, XmlNode oldChild)
775 if(oldChild.ParentNode != this)
776 throw new ArgumentException ("The node to be removed is not a child of this node.");
778 if (newChild == this || IsAncestor (newChild))
779 throw new InvalidOperationException("Cannot insert a node or any ancestor of that node as a child of itself.");
781 XmlNode next = oldChild.NextSibling;
782 RemoveChild (oldChild);
783 InsertBefore (newChild, next);
787 // WARNING: don't use this member outside XmlAttribute nodes.
788 internal XmlElement AttributeOwnerElement {
789 get { return (XmlElement) parentNode; }
790 set { parentNode = value; }
793 internal void SearchDescendantElements (string name, bool matchAll, ArrayList list)
795 for (XmlNode n = FirstChild; n != null; n = n.NextSibling) {
796 if (n.NodeType != XmlNodeType.Element)
798 if (matchAll || n.Name == name)
800 n.SearchDescendantElements (name, matchAll, list);
804 internal void SearchDescendantElements (string name, bool matchAllName, string ns, bool matchAllNS, ArrayList list)
806 for (XmlNode n = FirstChild; n != null; n = n.NextSibling) {
807 if (n.NodeType != XmlNodeType.Element)
809 if ((matchAllName || n.LocalName == name)
810 && (matchAllNS || n.NamespaceURI == ns))
812 n.SearchDescendantElements (name, matchAllName, ns, matchAllNS, list);
816 public XmlNodeList SelectNodes (string xpath)
818 return SelectNodes (xpath, null);
821 public XmlNodeList SelectNodes (string xpath, XmlNamespaceManager nsmgr)
823 XPathNavigator nav = CreateNavigator ();
824 XPathExpression expr = nav.Compile (xpath);
826 expr.SetContext (nsmgr);
827 XPathNodeIterator iter = nav.Select (expr);
829 ArrayList rgNodes = new ArrayList ();
830 while (iter.MoveNext ())
832 rgNodes.Add (((IHasXmlNode) iter.Current).GetNode ());
834 return new XmlNodeArrayList (rgNodes);
836 return new XmlIteratorNodeList (this as XmlDocument ?? ownerDocument, iter);
839 public XmlNode SelectSingleNode (string xpath)
841 return SelectSingleNode (xpath, null);
844 public XmlNode SelectSingleNode (string xpath, XmlNamespaceManager nsmgr)
846 XPathNavigator nav = CreateNavigator ();
847 XPathExpression expr = nav.Compile (xpath);
849 expr.SetContext (nsmgr);
850 XPathNodeIterator iter = nav.Select (expr);
851 if (!iter.MoveNext ())
853 return (iter.Current as IHasXmlNode).GetNode ();
856 public virtual bool Supports (string feature, string version)
858 if (String.Compare (feature, "xml", true, CultureInfo.InvariantCulture) == 0 // not case-sensitive
859 && (String.Compare (version, "1.0", true, CultureInfo.InvariantCulture) == 0
860 || String.Compare (version, "2.0", true, CultureInfo.InvariantCulture) == 0))
866 public abstract void WriteContentTo (XmlWriter w);
868 public abstract void WriteTo (XmlWriter w);
870 // It parses this and all the ancestor elements,
871 // find 'xmlns' declarations, stores and then return them.
872 internal XmlNamespaceManager ConstructNamespaceManager ()
874 XmlDocument doc = this is XmlDocument ? (XmlDocument)this : this.OwnerDocument;
875 XmlNamespaceManager nsmgr = new XmlNamespaceManager (doc.NameTable);
876 XmlElement el = null;
877 switch(this.NodeType) {
878 case XmlNodeType.Attribute:
879 el = ((XmlAttribute)this).OwnerElement;
881 case XmlNodeType.Element:
882 el = this as XmlElement;
885 el = this.ParentNode as XmlElement;
890 for (int i = 0; i < el.Attributes.Count; i++) {
891 XmlAttribute attr = el.Attributes [i];
892 if(attr.Prefix == "xmlns") {
893 if (nsmgr.LookupNamespace (attr.LocalName) != attr.Value)
894 nsmgr.AddNamespace (attr.LocalName, attr.Value);
895 } else if(attr.Name == "xmlns") {
896 if(nsmgr.LookupNamespace (String.Empty) != attr.Value)
897 nsmgr.AddNamespace (String.Empty, attr.Value);
900 // When reached to document, then it will set null value :)
901 el = el.ParentNode as XmlElement;