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;
40 using System.Diagnostics;
41 using System.Xml.Schema;
46 public abstract class XmlNode : ICloneable, IEnumerable, IXPathNavigable
48 static EmptyNodeList emptyList = new EmptyNodeList ();
50 class EmptyNodeList : XmlNodeList
52 static IEnumerator emptyEnumerator = new object [0].GetEnumerator ();
54 public override int Count {
58 public override IEnumerator GetEnumerator ()
60 return emptyEnumerator;
63 public override XmlNode Item (int index)
71 XmlDocument ownerDocument;
73 XmlNodeListChildren childNodes;
79 internal XmlNode (XmlDocument ownerDocument)
81 this.ownerDocument = ownerDocument;
88 public virtual XmlAttributeCollection Attributes {
92 public virtual string BaseURI {
94 // Isn't it conformant to W3C XML Base Recommendation?
95 // As far as I tested, there are not...
96 return (ParentNode != null) ? ParentNode.ChildrenBaseURI : String.Empty;
100 internal virtual string ChildrenBaseURI {
106 public virtual XmlNodeList ChildNodes {
108 IHasXmlChildNode l = this as IHasXmlChildNode;
111 if (childNodes == null)
112 childNodes = new XmlNodeListChildren (l);
117 public virtual XmlNode FirstChild {
119 IHasXmlChildNode l = this as IHasXmlChildNode;
120 XmlLinkedNode c = (l == null) ?
121 null : l.LastLinkedChild;
122 return c == null ? null : c.NextLinkedSibling;
126 public virtual bool HasChildNodes {
127 get { return LastChild != null; }
130 public virtual string InnerText {
133 case XmlNodeType.Text:
134 case XmlNodeType.CDATA:
135 case XmlNodeType.SignificantWhitespace:
136 case XmlNodeType.Whitespace:
140 if (FirstChild == null)
142 if (FirstChild == LastChild)
143 return FirstChild.NodeType != XmlNodeType.Comment ?
144 FirstChild.InnerText :
147 StringBuilder builder = null;
148 AppendChildValues (ref builder);
149 return builder == null ? String.Empty : builder.ToString ();
152 set { throw new InvalidOperationException ("This node is read only. Cannot be modified."); }
155 private void AppendChildValues (ref StringBuilder builder)
157 XmlNode node = FirstChild;
159 while (node != null) {
160 switch (node.NodeType) {
161 case XmlNodeType.Text:
162 case XmlNodeType.CDATA:
163 case XmlNodeType.SignificantWhitespace:
164 case XmlNodeType.Whitespace:
166 builder = new StringBuilder ();
167 builder.Append (node.Value);
170 node.AppendChildValues (ref builder);
171 node = node.NextSibling;
175 public virtual string InnerXml {
177 StringWriter sw = new StringWriter ();
178 XmlTextWriter xtw = new XmlTextWriter (sw);
180 WriteContentTo (xtw);
182 return sw.GetStringBuilder ().ToString ();
186 throw new InvalidOperationException ("This node is readonly or doesn't have any children.");
190 public virtual bool IsReadOnly {
193 XmlNode curNode = this;
196 switch (curNode.NodeType)
198 case XmlNodeType.EntityReference:
199 case XmlNodeType.Entity:
202 case XmlNodeType.Attribute:
203 curNode = ((XmlAttribute)curNode).OwnerElement;
207 curNode = curNode.ParentNode;
211 while (curNode != null) ;
217 [System.Runtime.CompilerServices.IndexerName("Item")]
218 public virtual XmlElement this [string name] {
220 for (int i = 0; i < ChildNodes.Count; i++) {
221 XmlNode node = ChildNodes [i];
222 if ((node.NodeType == XmlNodeType.Element) &&
223 (node.Name == name)) {
224 return (XmlElement) node;
232 [System.Runtime.CompilerServices.IndexerName("Item")]
233 public virtual XmlElement this [string localname, string ns] {
235 for (int i = 0; i < ChildNodes.Count; i++) {
236 XmlNode node = ChildNodes [i];
237 if ((node.NodeType == XmlNodeType.Element) &&
238 (node.LocalName == localname) &&
239 (node.NamespaceURI == ns)) {
240 return (XmlElement) node;
248 public virtual XmlNode LastChild {
250 IHasXmlChildNode l = this as IHasXmlChildNode;
251 return l == null ? null : l.LastLinkedChild;
255 public abstract string LocalName { get; }
257 public abstract string Name { get; }
259 public virtual string NamespaceURI {
260 get { return String.Empty; }
263 public virtual XmlNode NextSibling {
267 public abstract XmlNodeType NodeType { get; }
269 internal virtual XPathNodeType XPathNodeType {
271 throw new InvalidOperationException ("Can not get XPath node type from " + this.GetType ().ToString ());
275 public virtual string OuterXml {
277 StringWriter sw = new StringWriter ();
278 XmlTextWriter xtw = new XmlTextWriter (sw);
282 return sw.ToString ();
286 public virtual XmlDocument OwnerDocument {
287 get { return ownerDocument; }
290 public virtual XmlNode ParentNode {
291 get { return parentNode; }
294 public virtual string Prefix {
295 get { return String.Empty; }
299 public virtual XmlNode PreviousSibling {
303 public virtual string Value {
305 set { throw new InvalidOperationException ("This node does not have a value"); }
308 internal virtual string XmlLang {
310 if(Attributes != null)
311 for (int i = 0; i < Attributes.Count; i++) {
312 XmlAttribute attr = Attributes [i];
313 if(attr.Name == "xml:lang")
316 return (ParentNode != null) ? ParentNode.XmlLang : OwnerDocument.XmlLang;
320 internal virtual XmlSpace XmlSpace {
322 if(Attributes != null) {
323 for (int i = 0; i < Attributes.Count; i++) {
324 XmlAttribute attr = Attributes [i];
325 if(attr.Name == "xml:space") {
327 case "preserve": return XmlSpace.Preserve;
328 case "default": return XmlSpace.Default;
334 return (ParentNode != null) ? ParentNode.XmlSpace : OwnerDocument.XmlSpace;
339 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);
369 public virtual XPathNavigator CreateNavigator ()
371 // XmlDocument has overriden definition, so it is safe
372 // to use OwnerDocument here.
373 return OwnerDocument.CreateNavigator (this);
376 public XPathNavigator CreateNavigator ()
378 XmlDocument document = this.NodeType == XmlNodeType.Document ?
379 this as XmlDocument : this.ownerDocument;
380 return document.CreateNavigator (this);
384 public IEnumerator GetEnumerator ()
386 return ChildNodes.GetEnumerator ();
389 public virtual string GetNamespaceOfPrefix (string prefix)
393 throw new ArgumentNullException ("prefix");
396 return XmlNamespaceManager.XmlnsXml;
398 return XmlNamespaceManager.XmlnsXmlns;
404 case XmlNodeType.Attribute:
405 node = ((XmlAttribute) this).OwnerElement;
409 case XmlNodeType.Element:
417 while (node != null) {
418 if (node.Prefix == prefix)
419 return node.NamespaceURI;
420 if (node.NodeType == XmlNodeType.Element &&
421 ((XmlElement) node).HasAttributes) {
422 int count = node.Attributes.Count;
423 for (int i = 0; i < count; i++) {
424 XmlAttribute attr = node.Attributes [i];
425 if (prefix == attr.LocalName && attr.Prefix == "xmlns"
426 || attr.Name == "xmlns" && prefix == String.Empty)
430 node = node.ParentNode;
435 public virtual string GetPrefixOfNamespace (string namespaceURI)
438 switch (namespaceURI) {
439 case XmlNamespaceManager.XmlnsXml:
440 return XmlNamespaceManager.PrefixXml;
441 case XmlNamespaceManager.XmlnsXmlns:
442 return XmlNamespaceManager.PrefixXmlns;
448 case XmlNodeType.Attribute:
449 node = ((XmlAttribute) this).OwnerElement;
451 case XmlNodeType.Element:
459 while (node != null) {
460 if (node.NodeType == XmlNodeType.Element &&
461 ((XmlElement) node).HasAttributes) {
462 for (int i = 0; i < node.Attributes.Count; i++) {
463 XmlAttribute attr = node.Attributes [i];
464 if (attr.Prefix == "xmlns" && attr.Value == namespaceURI)
465 return attr.LocalName;
466 else if (attr.Name == "xmlns" && attr.Value == namespaceURI)
470 node = node.ParentNode;
475 object ICloneable.Clone ()
480 IEnumerator IEnumerable.GetEnumerator ()
482 return GetEnumerator ();
485 public virtual XmlNode InsertAfter (XmlNode newChild, XmlNode refChild)
487 // InsertAfter(n1, n2) is equivalent to InsertBefore(n1, n2.PreviousSibling).
489 // I took this way because current implementation
490 // Calling InsertBefore() in this method is faster than
491 // the counterpart, since NextSibling is faster than
492 // PreviousSibling (these children are forward-only list).
493 XmlNode argNode = null;
494 if (refChild != null)
495 argNode = refChild.NextSibling;
496 else if (FirstChild != null)
497 argNode = FirstChild;
498 return InsertBefore (newChild, argNode);
501 public virtual XmlNode InsertBefore (XmlNode newChild, XmlNode refChild)
503 return InsertBefore (newChild, refChild, true, true);
506 // check for the node to be one of node ancestors
507 internal bool IsAncestor (XmlNode newChild)
509 XmlNode currNode = this.ParentNode;
510 while(currNode != null)
512 if(currNode == newChild)
514 currNode = currNode.ParentNode;
519 internal XmlNode InsertBefore (XmlNode newChild, XmlNode refChild, bool checkNodeType, bool raiseEvent)
522 CheckNodeInsertion (newChild, refChild);
524 if (newChild == refChild)
527 IHasXmlChildNode l = (IHasXmlChildNode) this;
529 XmlDocument ownerDoc = (NodeType == XmlNodeType.Document) ? (XmlDocument) this : OwnerDocument;
532 ownerDoc.onNodeInserting (newChild, this);
534 if (newChild.ParentNode != null)
535 newChild.ParentNode.RemoveChild (newChild, checkNodeType);
537 if (newChild.NodeType == XmlNodeType.DocumentFragment) {
538 // This recursively invokes events. (It is compatible with MS implementation.)
539 while (newChild.FirstChild != null)
540 this.InsertBefore (newChild.FirstChild, refChild);
543 XmlLinkedNode newLinkedChild = (XmlLinkedNode) newChild;
544 newLinkedChild.parentNode = this;
546 if (refChild == null) {
547 // newChild is the last child:
548 // * set newChild as NextSibling of the existing lastchild
549 // * set LastChild = newChild
550 // * set NextSibling of newChild as FirstChild
551 if (l.LastLinkedChild != null) {
552 XmlLinkedNode formerFirst = (XmlLinkedNode) FirstChild;
553 l.LastLinkedChild.NextLinkedSibling = newLinkedChild;
554 l.LastLinkedChild = newLinkedChild;
555 newLinkedChild.NextLinkedSibling = formerFirst;
557 l.LastLinkedChild = newLinkedChild;
558 l.LastLinkedChild.NextLinkedSibling = newLinkedChild; // FirstChild
561 // newChild is not the last child:
562 // * if newchild is first, then set next of lastchild is newChild.
563 // otherwise, set next of previous sibling to newChild
564 // * set next of newChild to refChild
565 XmlLinkedNode prev = refChild.PreviousSibling as XmlLinkedNode;
567 l.LastLinkedChild.NextLinkedSibling = newLinkedChild;
569 prev.NextLinkedSibling = newLinkedChild;
570 newLinkedChild.NextLinkedSibling = refChild as XmlLinkedNode;
572 switch (newChild.NodeType) {
573 case XmlNodeType.EntityReference:
574 ((XmlEntityReference) newChild).SetReferencedEntityContent ();
576 case XmlNodeType.Entity:
578 case XmlNodeType.DocumentType:
583 ownerDoc.onNodeInserted (newChild, newChild.ParentNode);
588 private void CheckNodeInsertion (XmlNode newChild, XmlNode refChild)
590 XmlDocument ownerDoc = (NodeType == XmlNodeType.Document) ? (XmlDocument) this : OwnerDocument;
592 if (NodeType != XmlNodeType.Element &&
593 NodeType != XmlNodeType.Attribute &&
594 NodeType != XmlNodeType.Document &&
595 NodeType != XmlNodeType.DocumentFragment)
596 throw new InvalidOperationException (String.Format ("Node cannot be appended to current node {0}.", NodeType));
599 case XmlNodeType.Attribute:
600 switch (newChild.NodeType) {
601 case XmlNodeType.Text:
602 case XmlNodeType.EntityReference:
605 throw new InvalidOperationException (String.Format (
606 "Cannot insert specified type of node {0} as a child of this node {1}.",
607 newChild.NodeType, NodeType));
610 case XmlNodeType.Element:
611 switch (newChild.NodeType) {
612 case XmlNodeType.Attribute:
613 case XmlNodeType.Document:
614 case XmlNodeType.DocumentType:
615 case XmlNodeType.Entity:
616 case XmlNodeType.Notation:
617 case XmlNodeType.XmlDeclaration:
618 throw new InvalidOperationException ("Cannot insert specified type of node as a child of this node.");
624 throw new InvalidOperationException ("The node is readonly.");
626 if (newChild.OwnerDocument != ownerDoc)
627 throw new ArgumentException ("Can't append a node created by another document.");
629 if (refChild != null) {
630 if (refChild.ParentNode != this)
631 throw new ArgumentException ("The reference node is not a child of this node.");
634 if(this == ownerDoc && ownerDoc.DocumentElement != null && (newChild is XmlElement) && newChild != ownerDoc.DocumentElement)
635 throw new XmlException ("multiple document element not allowed.");
637 // checking validity finished. then appending...
640 if (newChild == this || IsAncestor (newChild))
641 throw new ArgumentException("Cannot insert a node or any ancestor of that node as a child of itself.");
645 public virtual void Normalize ()
647 StringBuilder tmpBuilder = new StringBuilder ();
648 int count = this.ChildNodes.Count;
650 for (int i = 0; i < count; i++) {
651 XmlNode c = ChildNodes [i];
652 switch (c.NodeType) {
653 case XmlNodeType.Text:
654 case XmlNodeType.Whitespace:
655 case XmlNodeType.SignificantWhitespace:
656 tmpBuilder.Append (c.Value);
660 NormalizeRange (start, i, tmpBuilder);
661 // Continue to normalize from next node.
667 NormalizeRange (start, count, tmpBuilder);
671 private void NormalizeRange (int start, int i, StringBuilder tmpBuilder)
674 // If Texts and Whitespaces are mixed, Text takes precedence to remain.
675 // i.e. Whitespace should be removed.
676 for (int j = start; j < i; j++) {
677 XmlNode keep = ChildNodes [j];
678 if (keep.NodeType == XmlNodeType.Text) {
682 else if (keep.NodeType == XmlNodeType.SignificantWhitespace)
684 // but don't break up to find Text nodes.
688 for (int del = start; del < keepPos; del++)
689 RemoveChild (ChildNodes [start]);
690 int rest = i - keepPos - 1;
691 for (int del = 0; del < rest; del++) {
692 RemoveChild (ChildNodes [start + 1]);
697 ChildNodes [start].Value = tmpBuilder.ToString ();
698 // otherwise nothing to be normalized
700 tmpBuilder.Length = 0;
703 public virtual XmlNode PrependChild (XmlNode newChild)
705 return InsertAfter (newChild, null);
708 public virtual void RemoveAll ()
710 if (Attributes != null)
711 Attributes.RemoveAll ();
713 for (XmlNode node = FirstChild; node != null; node = next) {
714 next = node.NextSibling;
719 public virtual XmlNode RemoveChild (XmlNode oldChild)
721 return RemoveChild (oldChild, true);
724 private void CheckNodeRemoval ()
726 if (NodeType != XmlNodeType.Attribute &&
727 NodeType != XmlNodeType.Element &&
728 NodeType != XmlNodeType.Document &&
729 NodeType != XmlNodeType.DocumentFragment)
730 throw new ArgumentException (String.Format ("This {0} node cannot remove its child.", NodeType));
733 throw new ArgumentException (String.Format ("This {0} node is read only.", NodeType));
736 internal XmlNode RemoveChild (XmlNode oldChild, bool checkNodeType)
738 if (oldChild == null)
739 throw new NullReferenceException ();
740 XmlDocument ownerDoc = (NodeType == XmlNodeType.Document) ? (XmlDocument)this : OwnerDocument;
741 if(oldChild.ParentNode != this)
742 throw new ArgumentException ("The node to be removed is not a child of this node.");
745 ownerDoc.onNodeRemoving (oldChild, oldChild.ParentNode);
750 IHasXmlChildNode l = (IHasXmlChildNode) this;
752 if (Object.ReferenceEquals (l.LastLinkedChild, l.LastLinkedChild.NextLinkedSibling) && Object.ReferenceEquals (l.LastLinkedChild, oldChild))
753 // If there is only one children, simply clear.
754 l.LastLinkedChild = null;
756 XmlLinkedNode oldLinkedChild = (XmlLinkedNode) oldChild;
757 XmlLinkedNode beforeLinkedChild = l.LastLinkedChild;
758 XmlLinkedNode firstChild = (XmlLinkedNode) FirstChild;
760 while (Object.ReferenceEquals (beforeLinkedChild.NextLinkedSibling, l.LastLinkedChild) == false &&
761 Object.ReferenceEquals (beforeLinkedChild.NextLinkedSibling, oldLinkedChild) == false)
762 beforeLinkedChild = beforeLinkedChild.NextLinkedSibling;
764 if (Object.ReferenceEquals (beforeLinkedChild.NextLinkedSibling, oldLinkedChild) == false)
765 throw new ArgumentException ();
767 beforeLinkedChild.NextLinkedSibling = oldLinkedChild.NextLinkedSibling;
769 // Each derived class may have its own l.LastLinkedChild, so we must set it explicitly.
770 if (oldLinkedChild.NextLinkedSibling == firstChild)
771 l.LastLinkedChild = beforeLinkedChild;
773 oldLinkedChild.NextLinkedSibling = null;
777 ownerDoc.onNodeRemoved (oldChild, oldChild.ParentNode);
778 oldChild.parentNode = null; // clear parent 'after' above logic.
783 public virtual XmlNode ReplaceChild (XmlNode newChild, XmlNode oldChild)
785 if(oldChild.ParentNode != this)
786 throw new ArgumentException ("The node to be removed is not a child of this node.");
788 if (newChild == this || IsAncestor (newChild))
789 throw new InvalidOperationException("Cannot insert a node or any ancestor of that node as a child of itself.");
791 XmlNode next = oldChild.NextSibling;
792 RemoveChild (oldChild);
793 InsertBefore (newChild, next);
797 // WARNING: don't use this member outside XmlAttribute nodes.
798 internal XmlElement AttributeOwnerElement {
799 get { return (XmlElement) parentNode; }
800 set { parentNode = value; }
803 internal void SearchDescendantElements (string name, bool matchAll, ArrayList list)
805 for (XmlNode n = FirstChild; n != null; n = n.NextSibling) {
806 if (n.NodeType != XmlNodeType.Element)
808 if (matchAll || n.Name == name)
810 n.SearchDescendantElements (name, matchAll, list);
814 internal void SearchDescendantElements (string name, bool matchAllName, string ns, bool matchAllNS, ArrayList list)
816 for (XmlNode n = FirstChild; n != null; n = n.NextSibling) {
817 if (n.NodeType != XmlNodeType.Element)
819 if ((matchAllName || n.LocalName == name)
820 && (matchAllNS || n.NamespaceURI == ns))
822 n.SearchDescendantElements (name, matchAllName, ns, matchAllNS, list);
826 public XmlNodeList SelectNodes (string xpath)
828 return SelectNodes (xpath, null);
831 public XmlNodeList SelectNodes (string xpath, XmlNamespaceManager nsmgr)
833 XPathNavigator nav = CreateNavigator ();
834 XPathExpression expr = nav.Compile (xpath);
836 expr.SetContext (nsmgr);
837 XPathNodeIterator iter = nav.Select (expr);
839 ArrayList rgNodes = new ArrayList ();
840 while (iter.MoveNext ())
842 rgNodes.Add (((IHasXmlNode) iter.Current).GetNode ());
844 return new XmlNodeArrayList (rgNodes);
846 return new XmlIteratorNodeList (iter);
849 public XmlNode SelectSingleNode (string xpath)
851 return SelectSingleNode (xpath, null);
854 public XmlNode SelectSingleNode (string xpath, XmlNamespaceManager nsmgr)
856 XPathNavigator nav = CreateNavigator ();
857 XPathExpression expr = nav.Compile (xpath);
859 expr.SetContext (nsmgr);
860 XPathNodeIterator iter = nav.Select (expr);
861 if (!iter.MoveNext ())
863 return ((IHasXmlNode) iter.Current).GetNode ();
866 public virtual bool Supports (string feature, string version)
868 if (String.Compare (feature, "xml", true, CultureInfo.InvariantCulture) == 0 // not case-sensitive
869 && (String.Compare (version, "1.0", true, CultureInfo.InvariantCulture) == 0
870 || String.Compare (version, "2.0", true, CultureInfo.InvariantCulture) == 0))
876 public abstract void WriteContentTo (XmlWriter w);
878 public abstract void WriteTo (XmlWriter w);
880 // It parses this and all the ancestor elements,
881 // find 'xmlns' declarations, stores and then return them.
882 internal XmlNamespaceManager ConstructNamespaceManager ()
884 XmlDocument doc = this is XmlDocument ? (XmlDocument)this : this.OwnerDocument;
885 XmlNamespaceManager nsmgr = new XmlNamespaceManager (doc.NameTable);
886 XmlElement el = null;
887 switch(this.NodeType) {
888 case XmlNodeType.Attribute:
889 el = ((XmlAttribute)this).OwnerElement;
891 case XmlNodeType.Element:
892 el = this as XmlElement;
895 el = this.ParentNode as XmlElement;
900 for (int i = 0; i < el.Attributes.Count; i++) {
901 XmlAttribute attr = el.Attributes [i];
902 if(attr.Prefix == "xmlns") {
903 if (nsmgr.LookupNamespace (attr.LocalName) != attr.Value)
904 nsmgr.AddNamespace (attr.LocalName, attr.Value);
905 } else if(attr.Name == "xmlns") {
906 if(nsmgr.LookupNamespace (String.Empty) != attr.Value)
907 nsmgr.AddNamespace (String.Empty, attr.Value);
910 // When reached to document, then it will set null value :)
911 el = el.ParentNode as XmlElement;