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:
139 if (FirstChild == null)
141 if (FirstChild == LastChild)
142 return FirstChild.NodeType != XmlNodeType.Comment ?
143 FirstChild.InnerText :
146 StringBuilder builder = null;
147 AppendChildValues (ref builder);
148 return builder == null ? String.Empty : builder.ToString ();
152 if (! (this is XmlDocumentFragment))
153 throw new InvalidOperationException ("This node is read only. Cannot be modified.");
155 AppendChild (OwnerDocument.CreateTextNode (value));
159 private void AppendChildValues (ref StringBuilder builder)
161 XmlNode node = FirstChild;
163 while (node != null) {
164 switch (node.NodeType) {
165 case XmlNodeType.Text:
166 case XmlNodeType.CDATA:
167 case XmlNodeType.SignificantWhitespace:
168 case XmlNodeType.Whitespace:
170 builder = new StringBuilder ();
171 builder.Append (node.Value);
174 node.AppendChildValues (ref builder);
175 node = node.NextSibling;
179 public virtual string InnerXml {
181 StringWriter sw = new StringWriter ();
182 XmlTextWriter xtw = new XmlTextWriter (sw);
184 WriteContentTo (xtw);
186 return sw.GetStringBuilder ().ToString ();
190 throw new InvalidOperationException ("This node is readonly or doesn't have any children.");
194 public virtual bool IsReadOnly {
197 XmlNode curNode = this;
200 switch (curNode.NodeType)
202 case XmlNodeType.EntityReference:
203 case XmlNodeType.Entity:
206 case XmlNodeType.Attribute:
207 curNode = ((XmlAttribute)curNode).OwnerElement;
211 curNode = curNode.ParentNode;
215 while (curNode != null) ;
221 [System.Runtime.CompilerServices.IndexerName("Item")]
222 public virtual XmlElement this [string name] {
224 for (int i = 0; i < ChildNodes.Count; i++) {
225 XmlNode node = ChildNodes [i];
226 if ((node.NodeType == XmlNodeType.Element) &&
227 (node.Name == name)) {
228 return (XmlElement) node;
236 [System.Runtime.CompilerServices.IndexerName("Item")]
237 public virtual XmlElement this [string localname, string ns] {
239 for (int i = 0; i < ChildNodes.Count; i++) {
240 XmlNode node = ChildNodes [i];
241 if ((node.NodeType == XmlNodeType.Element) &&
242 (node.LocalName == localname) &&
243 (node.NamespaceURI == ns)) {
244 return (XmlElement) node;
252 public virtual XmlNode LastChild {
254 IHasXmlChildNode l = this as IHasXmlChildNode;
255 return l == null ? null : l.LastLinkedChild;
259 public abstract string LocalName { get; }
261 public abstract string Name { get; }
263 public virtual string NamespaceURI {
264 get { return String.Empty; }
267 public virtual XmlNode NextSibling {
271 public abstract XmlNodeType NodeType { get; }
273 internal virtual XPathNodeType XPathNodeType {
275 throw new InvalidOperationException ("Can not get XPath node type from " + this.GetType ().ToString ());
279 public virtual string OuterXml {
281 StringWriter sw = new StringWriter ();
282 XmlTextWriter xtw = new XmlTextWriter (sw);
286 return sw.ToString ();
290 public virtual XmlDocument OwnerDocument {
291 get { return ownerDocument; }
294 public virtual XmlNode ParentNode {
295 get { return parentNode; }
298 public virtual string Prefix {
299 get { return String.Empty; }
303 public virtual XmlNode PreviousSibling {
307 public virtual string Value {
309 set { throw new InvalidOperationException ("This node does not have a value"); }
312 internal virtual string XmlLang {
314 if(Attributes != null)
315 for (int i = 0; i < Attributes.Count; i++) {
316 XmlAttribute attr = Attributes [i];
317 if(attr.Name == "xml:lang")
320 return (ParentNode != null) ? ParentNode.XmlLang : OwnerDocument.XmlLang;
324 internal virtual XmlSpace XmlSpace {
326 if(Attributes != null) {
327 for (int i = 0; i < Attributes.Count; i++) {
328 XmlAttribute attr = Attributes [i];
329 if(attr.Name == "xml:space") {
331 case "preserve": return XmlSpace.Preserve;
332 case "default": return XmlSpace.Default;
338 return (ParentNode != null) ? ParentNode.XmlSpace : OwnerDocument.XmlSpace;
343 public virtual IXmlSchemaInfo SchemaInfo {
353 public virtual XmlNode AppendChild (XmlNode newChild)
355 // AppendChild(n) is equivalent to InsertBefore(n, null)
356 return InsertBefore (newChild, null);
359 internal XmlNode AppendChild (XmlNode newChild, bool checkNodeType)
361 return InsertBefore (newChild, null, checkNodeType, true);
364 public virtual XmlNode Clone ()
366 // By MS document, it is equivalent to CloneNode(true).
367 return this.CloneNode (true);
370 public abstract XmlNode CloneNode (bool deep);
373 public virtual XPathNavigator CreateNavigator ()
375 // XmlDocument has overriden definition, so it is safe
376 // to use OwnerDocument here.
377 return OwnerDocument.CreateNavigator (this);
380 public XPathNavigator CreateNavigator ()
382 XmlDocument document = this.NodeType == XmlNodeType.Document ?
383 this as XmlDocument : this.ownerDocument;
384 return document.CreateNavigator (this);
388 public IEnumerator GetEnumerator ()
390 return ChildNodes.GetEnumerator ();
393 public virtual string GetNamespaceOfPrefix (string prefix)
397 throw new ArgumentNullException ("prefix");
400 return XmlNamespaceManager.XmlnsXml;
402 return XmlNamespaceManager.XmlnsXmlns;
408 case XmlNodeType.Attribute:
409 node = ((XmlAttribute) this).OwnerElement;
413 case XmlNodeType.Element:
421 while (node != null) {
422 if (node.Prefix == prefix)
423 return node.NamespaceURI;
424 if (node.NodeType == XmlNodeType.Element &&
425 ((XmlElement) node).HasAttributes) {
426 int count = node.Attributes.Count;
427 for (int i = 0; i < count; i++) {
428 XmlAttribute attr = node.Attributes [i];
429 if (prefix == attr.LocalName && attr.Prefix == "xmlns"
430 || attr.Name == "xmlns" && prefix == String.Empty)
434 node = node.ParentNode;
439 public virtual string GetPrefixOfNamespace (string namespaceURI)
442 switch (namespaceURI) {
443 case XmlNamespaceManager.XmlnsXml:
444 return XmlNamespaceManager.PrefixXml;
445 case XmlNamespaceManager.XmlnsXmlns:
446 return XmlNamespaceManager.PrefixXmlns;
452 case XmlNodeType.Attribute:
453 node = ((XmlAttribute) this).OwnerElement;
455 case XmlNodeType.Element:
463 while (node != null) {
464 if (node.NodeType == XmlNodeType.Element &&
465 ((XmlElement) node).HasAttributes) {
466 for (int i = 0; i < node.Attributes.Count; i++) {
467 XmlAttribute attr = node.Attributes [i];
468 if (attr.Prefix == "xmlns" && attr.Value == namespaceURI)
469 return attr.LocalName;
470 else if (attr.Name == "xmlns" && attr.Value == namespaceURI)
474 node = node.ParentNode;
479 object ICloneable.Clone ()
484 IEnumerator IEnumerable.GetEnumerator ()
486 return GetEnumerator ();
489 public virtual XmlNode InsertAfter (XmlNode newChild, XmlNode refChild)
491 // InsertAfter(n1, n2) is equivalent to InsertBefore(n1, n2.PreviousSibling).
493 // I took this way because current implementation
494 // Calling InsertBefore() in this method is faster than
495 // the counterpart, since NextSibling is faster than
496 // PreviousSibling (these children are forward-only list).
497 XmlNode argNode = null;
498 if (refChild != null)
499 argNode = refChild.NextSibling;
500 else if (FirstChild != null)
501 argNode = FirstChild;
502 return InsertBefore (newChild, argNode);
505 public virtual XmlNode InsertBefore (XmlNode newChild, XmlNode refChild)
507 return InsertBefore (newChild, refChild, true, true);
510 // check for the node to be one of node ancestors
511 internal bool IsAncestor (XmlNode newChild)
513 XmlNode currNode = this.ParentNode;
514 while(currNode != null)
516 if(currNode == newChild)
518 currNode = currNode.ParentNode;
523 internal XmlNode InsertBefore (XmlNode newChild, XmlNode refChild, bool checkNodeType, bool raiseEvent)
526 CheckNodeInsertion (newChild, refChild);
528 if (newChild == refChild)
531 IHasXmlChildNode l = (IHasXmlChildNode) this;
533 XmlDocument ownerDoc = (NodeType == XmlNodeType.Document) ? (XmlDocument) this : OwnerDocument;
536 ownerDoc.onNodeInserting (newChild, this);
538 if (newChild.ParentNode != null)
539 newChild.ParentNode.RemoveChild (newChild, checkNodeType);
541 if (newChild.NodeType == XmlNodeType.DocumentFragment) {
542 // This recursively invokes events. (It is compatible with MS implementation.)
544 while (newChild.FirstChild != null) {
545 var c = this.InsertBefore (newChild.FirstChild, refChild);
550 XmlLinkedNode newLinkedChild = (XmlLinkedNode) newChild;
551 newLinkedChild.parentNode = this;
553 if (refChild == null) {
554 // newChild is the last child:
555 // * set newChild as NextSibling of the existing lastchild
556 // * set LastChild = newChild
557 // * set NextSibling of newChild as FirstChild
558 if (l.LastLinkedChild != null) {
559 XmlLinkedNode formerFirst = (XmlLinkedNode) FirstChild;
560 l.LastLinkedChild.NextLinkedSibling = newLinkedChild;
561 l.LastLinkedChild = newLinkedChild;
562 newLinkedChild.NextLinkedSibling = formerFirst;
564 l.LastLinkedChild = newLinkedChild;
565 l.LastLinkedChild.NextLinkedSibling = newLinkedChild; // FirstChild
568 // newChild is not the last child:
569 // * if newchild is first, then set next of lastchild is newChild.
570 // otherwise, set next of previous sibling to newChild
571 // * set next of newChild to refChild
572 XmlLinkedNode prev = refChild.PreviousSibling as XmlLinkedNode;
574 l.LastLinkedChild.NextLinkedSibling = newLinkedChild;
576 prev.NextLinkedSibling = newLinkedChild;
577 newLinkedChild.NextLinkedSibling = refChild as XmlLinkedNode;
579 switch (newChild.NodeType) {
580 case XmlNodeType.EntityReference:
581 ((XmlEntityReference) newChild).SetReferencedEntityContent ();
583 case XmlNodeType.Entity:
585 case XmlNodeType.DocumentType:
590 ownerDoc.onNodeInserted (newChild, newChild.ParentNode);
595 private void CheckNodeInsertion (XmlNode newChild, XmlNode refChild)
597 XmlDocument ownerDoc = (NodeType == XmlNodeType.Document) ? (XmlDocument) this : OwnerDocument;
599 if (NodeType != XmlNodeType.Element &&
600 NodeType != XmlNodeType.Attribute &&
601 NodeType != XmlNodeType.Document &&
602 NodeType != XmlNodeType.DocumentFragment)
603 throw new InvalidOperationException (String.Format ("Node cannot be appended to current node {0}.", NodeType));
606 case XmlNodeType.Attribute:
607 switch (newChild.NodeType) {
608 case XmlNodeType.Text:
609 case XmlNodeType.EntityReference:
612 throw new InvalidOperationException (String.Format (
613 "Cannot insert specified type of node {0} as a child of this node {1}.",
614 newChild.NodeType, NodeType));
617 case XmlNodeType.Element:
618 switch (newChild.NodeType) {
619 case XmlNodeType.Attribute:
620 case XmlNodeType.Document:
621 case XmlNodeType.DocumentType:
622 case XmlNodeType.Entity:
623 case XmlNodeType.Notation:
624 case XmlNodeType.XmlDeclaration:
625 throw new InvalidOperationException ("Cannot insert specified type of node as a child of this node.");
631 throw new InvalidOperationException ("The node is readonly.");
633 if (newChild.OwnerDocument != ownerDoc)
634 throw new ArgumentException ("Can't append a node created by another document.");
636 if (refChild != null) {
637 if (refChild.ParentNode != this)
638 throw new ArgumentException ("The reference node is not a child of this node.");
641 if(this == ownerDoc && ownerDoc.DocumentElement != null && (newChild is XmlElement) && newChild != ownerDoc.DocumentElement)
642 throw new XmlException ("multiple document element not allowed.");
644 // checking validity finished. then appending...
647 if (newChild == this || IsAncestor (newChild))
648 throw new ArgumentException("Cannot insert a node or any ancestor of that node as a child of itself.");
652 public virtual void Normalize ()
654 StringBuilder tmpBuilder = new StringBuilder ();
655 int count = this.ChildNodes.Count;
657 for (int i = 0; i < count; i++) {
658 XmlNode c = ChildNodes [i];
659 switch (c.NodeType) {
660 case XmlNodeType.Text:
661 case XmlNodeType.Whitespace:
662 case XmlNodeType.SignificantWhitespace:
663 tmpBuilder.Append (c.Value);
667 NormalizeRange (start, i, tmpBuilder);
668 // Continue to normalize from next node.
674 NormalizeRange (start, count, tmpBuilder);
678 private void NormalizeRange (int start, int i, StringBuilder tmpBuilder)
681 // If Texts and Whitespaces are mixed, Text takes precedence to remain.
682 // i.e. Whitespace should be removed.
683 for (int j = start; j < i; j++) {
684 XmlNode keep = ChildNodes [j];
685 if (keep.NodeType == XmlNodeType.Text) {
689 else if (keep.NodeType == XmlNodeType.SignificantWhitespace)
691 // but don't break up to find Text nodes.
695 for (int del = start; del < keepPos; del++)
696 RemoveChild (ChildNodes [start]);
697 int rest = i - keepPos - 1;
698 for (int del = 0; del < rest; del++) {
699 RemoveChild (ChildNodes [start + 1]);
704 ChildNodes [start].Value = tmpBuilder.ToString ();
705 // otherwise nothing to be normalized
707 tmpBuilder.Length = 0;
710 public virtual XmlNode PrependChild (XmlNode newChild)
712 return InsertAfter (newChild, null);
715 public virtual void RemoveAll ()
717 if (Attributes != null)
718 Attributes.RemoveAll ();
720 for (XmlNode node = FirstChild; node != null; node = next) {
721 next = node.NextSibling;
726 public virtual XmlNode RemoveChild (XmlNode oldChild)
728 return RemoveChild (oldChild, true);
731 private void CheckNodeRemoval ()
733 if (NodeType != XmlNodeType.Attribute &&
734 NodeType != XmlNodeType.Element &&
735 NodeType != XmlNodeType.Document &&
736 NodeType != XmlNodeType.DocumentFragment)
737 throw new ArgumentException (String.Format ("This {0} node cannot remove its child.", NodeType));
740 throw new ArgumentException (String.Format ("This {0} node is read only.", NodeType));
743 internal XmlNode RemoveChild (XmlNode oldChild, bool checkNodeType)
745 if (oldChild == null)
746 throw new NullReferenceException ();
747 XmlDocument ownerDoc = (NodeType == XmlNodeType.Document) ? (XmlDocument)this : OwnerDocument;
748 if(oldChild.ParentNode != this)
749 throw new ArgumentException ("The node to be removed is not a child of this node.");
752 ownerDoc.onNodeRemoving (oldChild, oldChild.ParentNode);
757 IHasXmlChildNode l = (IHasXmlChildNode) this;
759 if (Object.ReferenceEquals (l.LastLinkedChild, l.LastLinkedChild.NextLinkedSibling) && Object.ReferenceEquals (l.LastLinkedChild, oldChild))
760 // If there is only one children, simply clear.
761 l.LastLinkedChild = null;
763 XmlLinkedNode oldLinkedChild = (XmlLinkedNode) oldChild;
764 XmlLinkedNode beforeLinkedChild = l.LastLinkedChild;
765 XmlLinkedNode firstChild = (XmlLinkedNode) FirstChild;
767 while (Object.ReferenceEquals (beforeLinkedChild.NextLinkedSibling, l.LastLinkedChild) == false &&
768 Object.ReferenceEquals (beforeLinkedChild.NextLinkedSibling, oldLinkedChild) == false)
769 beforeLinkedChild = beforeLinkedChild.NextLinkedSibling;
771 if (Object.ReferenceEquals (beforeLinkedChild.NextLinkedSibling, oldLinkedChild) == false)
772 throw new ArgumentException ();
774 beforeLinkedChild.NextLinkedSibling = oldLinkedChild.NextLinkedSibling;
776 // Each derived class may have its own l.LastLinkedChild, so we must set it explicitly.
777 if (oldLinkedChild.NextLinkedSibling == firstChild)
778 l.LastLinkedChild = beforeLinkedChild;
780 oldLinkedChild.NextLinkedSibling = null;
784 ownerDoc.onNodeRemoved (oldChild, oldChild.ParentNode);
785 oldChild.parentNode = null; // clear parent 'after' above logic.
790 public virtual XmlNode ReplaceChild (XmlNode newChild, XmlNode oldChild)
792 if(oldChild.ParentNode != this)
793 throw new ArgumentException ("The node to be removed is not a child of this node.");
795 if (newChild == this || IsAncestor (newChild))
796 throw new InvalidOperationException("Cannot insert a node or any ancestor of that node as a child of itself.");
798 XmlNode next = oldChild.NextSibling;
799 RemoveChild (oldChild);
800 InsertBefore (newChild, next);
804 // WARNING: don't use this member outside XmlAttribute nodes.
805 internal XmlElement AttributeOwnerElement {
806 get { return (XmlElement) parentNode; }
807 set { parentNode = value; }
810 internal void SearchDescendantElements (string name, bool matchAll, ArrayList list)
812 for (XmlNode n = FirstChild; n != null; n = n.NextSibling) {
813 if (n.NodeType != XmlNodeType.Element)
815 if (matchAll || n.Name == name)
817 n.SearchDescendantElements (name, matchAll, list);
821 internal void SearchDescendantElements (string name, bool matchAllName, string ns, bool matchAllNS, ArrayList list)
823 for (XmlNode n = FirstChild; n != null; n = n.NextSibling) {
824 if (n.NodeType != XmlNodeType.Element)
826 if ((matchAllName || n.LocalName == name)
827 && (matchAllNS || n.NamespaceURI == ns))
829 n.SearchDescendantElements (name, matchAllName, ns, matchAllNS, list);
833 public XmlNodeList SelectNodes (string xpath)
835 return SelectNodes (xpath, null);
838 public XmlNodeList SelectNodes (string xpath, XmlNamespaceManager nsmgr)
840 XPathNavigator nav = CreateNavigator ();
841 XPathExpression expr = nav.Compile (xpath);
843 expr.SetContext (nsmgr);
844 XPathNodeIterator iter = nav.Select (expr);
846 ArrayList rgNodes = new ArrayList ();
847 while (iter.MoveNext ())
849 rgNodes.Add (((IHasXmlNode) iter.Current).GetNode ());
851 return new XmlNodeArrayList (rgNodes);
853 return new XmlIteratorNodeList (this as XmlDocument ?? ownerDocument, iter);
856 public XmlNode SelectSingleNode (string xpath)
858 return SelectSingleNode (xpath, null);
861 public XmlNode SelectSingleNode (string xpath, XmlNamespaceManager nsmgr)
863 XPathNavigator nav = CreateNavigator ();
864 XPathExpression expr = nav.Compile (xpath);
866 expr.SetContext (nsmgr);
867 XPathNodeIterator iter = nav.Select (expr);
868 if (!iter.MoveNext ())
870 return (iter.Current as IHasXmlNode).GetNode ();
873 public virtual bool Supports (string feature, string version)
875 if (String.Compare (feature, "xml", true, CultureInfo.InvariantCulture) == 0 // not case-sensitive
876 && (String.Compare (version, "1.0", true, CultureInfo.InvariantCulture) == 0
877 || String.Compare (version, "2.0", true, CultureInfo.InvariantCulture) == 0))
883 public abstract void WriteContentTo (XmlWriter w);
885 public abstract void WriteTo (XmlWriter w);
887 // It parses this and all the ancestor elements,
888 // find 'xmlns' declarations, stores and then return them.
889 internal XmlNamespaceManager ConstructNamespaceManager ()
891 XmlDocument doc = this is XmlDocument ? (XmlDocument)this : this.OwnerDocument;
892 XmlNamespaceManager nsmgr = new XmlNamespaceManager (doc.NameTable);
893 XmlElement el = null;
894 switch(this.NodeType) {
895 case XmlNodeType.Attribute:
896 el = ((XmlAttribute)this).OwnerElement;
898 case XmlNodeType.Element:
899 el = this as XmlElement;
902 el = this.ParentNode as XmlElement;
907 for (int i = 0; i < el.Attributes.Count; i++) {
908 XmlAttribute attr = el.Attributes [i];
909 if(attr.Prefix == "xmlns") {
910 if (nsmgr.LookupNamespace (attr.LocalName) != attr.Value)
911 nsmgr.AddNamespace (attr.LocalName, attr.Value);
912 } else if(attr.Name == "xmlns") {
913 if(nsmgr.LookupNamespace (String.Empty) != attr.Value)
914 nsmgr.AddNamespace (String.Empty, attr.Value);
917 // When reached to document, then it will set null value :)
918 el = el.ParentNode as XmlElement;