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.Xml.Schema;
45 public abstract class XmlNode : ICloneable, IEnumerable, IXPathNavigable
49 XmlDocument ownerDocument;
51 XmlLinkedNode lastLinkedChild;
52 XmlNodeListChildren childNodes;
58 internal XmlNode (XmlDocument ownerDocument)
60 this.ownerDocument = ownerDocument;
67 public virtual XmlAttributeCollection Attributes {
71 public virtual string BaseURI {
73 // Isn't it conformant to W3C XML Base Recommendation?
74 // As far as I tested, there are not...
75 return (ParentNode != null) ? ParentNode.ChildrenBaseURI : String.Empty;
79 protected virtual string ChildrenBaseURI {
85 public virtual XmlNodeList ChildNodes {
87 if (childNodes == null)
88 childNodes = new XmlNodeListChildren (this);
93 public virtual XmlNode FirstChild {
95 if (LastChild != null) {
96 return LastLinkedChild.NextLinkedSibling;
104 public virtual bool HasChildNodes {
105 get { return LastChild != null; }
108 public virtual string InnerText {
110 StringBuilder builder = new StringBuilder ();
111 AppendChildValues (this, builder);
112 return builder.ToString ();
115 set { throw new InvalidOperationException ("This node is read only. Cannot be modified."); }
118 private void AppendChildValues (XmlNode parent, StringBuilder builder)
120 XmlNode node = parent.FirstChild;
122 while (node != null) {
123 switch (node.NodeType) {
124 case XmlNodeType.Text:
125 case XmlNodeType.CDATA:
126 case XmlNodeType.SignificantWhitespace:
127 case XmlNodeType.Whitespace:
128 builder.Append (node.Value);
131 AppendChildValues (node, builder);
132 node = node.NextSibling;
136 public virtual string InnerXml {
138 StringWriter sw = new StringWriter ();
139 XmlTextWriter xtw = new XmlTextWriter (sw);
141 WriteContentTo (xtw);
143 return sw.GetStringBuilder ().ToString ();
147 throw new InvalidOperationException ("This node is readonly or doesn't have any children.");
151 public virtual bool IsReadOnly {
154 XmlNode curNode = this;
157 switch (curNode.NodeType)
159 case XmlNodeType.EntityReference:
160 case XmlNodeType.Entity:
163 case XmlNodeType.Attribute:
164 curNode = ((XmlAttribute)curNode).OwnerElement;
168 curNode = curNode.ParentNode;
172 while (curNode != null) ;
178 [System.Runtime.CompilerServices.IndexerName("Item")]
179 public virtual XmlElement this [string name] {
181 for (int i = 0; i < ChildNodes.Count; i++) {
182 XmlNode node = ChildNodes [i];
183 if ((node.NodeType == XmlNodeType.Element) &&
184 (node.Name == name)) {
185 return (XmlElement) node;
193 [System.Runtime.CompilerServices.IndexerName("Item")]
194 public virtual XmlElement this [string localname, string ns] {
196 for (int i = 0; i < ChildNodes.Count; i++) {
197 XmlNode node = ChildNodes [i];
198 if ((node.NodeType == XmlNodeType.Element) &&
199 (node.LocalName == localname) &&
200 (node.NamespaceURI == ns)) {
201 return (XmlElement) node;
209 public virtual XmlNode LastChild {
210 get { return LastLinkedChild; }
213 internal virtual XmlLinkedNode LastLinkedChild {
214 get { return lastLinkedChild; }
215 set { lastLinkedChild = value; }
218 public abstract string LocalName { get; }
220 public abstract string Name { get; }
222 public virtual string NamespaceURI {
223 get { return String.Empty; }
226 public virtual XmlNode NextSibling {
230 public abstract XmlNodeType NodeType { get; }
232 internal virtual XPathNodeType XPathNodeType {
234 throw new InvalidOperationException ("Can not get XPath node type from " + this.GetType ().ToString ());
238 public virtual string OuterXml {
240 StringWriter sw = new StringWriter ();
241 XmlTextWriter xtw = new XmlTextWriter (sw);
245 return sw.ToString ();
249 public virtual XmlDocument OwnerDocument {
250 get { return ownerDocument; }
253 public virtual XmlNode ParentNode {
254 get { return parentNode; }
257 public virtual string Prefix {
258 get { return String.Empty; }
262 public virtual XmlNode PreviousSibling {
266 public virtual string Value {
268 set { throw new InvalidOperationException ("This node does not have a value"); }
271 internal virtual string XmlLang {
273 if(Attributes != null)
274 for (int i = 0; i < Attributes.Count; i++) {
275 XmlAttribute attr = Attributes [i];
276 if(attr.Name == "xml:lang")
279 return (ParentNode != null) ? ParentNode.XmlLang : OwnerDocument.XmlLang;
283 internal virtual XmlSpace XmlSpace {
285 if(Attributes != null) {
286 for (int i = 0; i < Attributes.Count; i++) {
287 XmlAttribute attr = Attributes [i];
288 if(attr.Name == "xml:space") {
290 case "preserve": return XmlSpace.Preserve;
291 case "default": return XmlSpace.Default;
297 return (ParentNode != null) ? ParentNode.XmlSpace : OwnerDocument.XmlSpace;
302 [CLSCompliant(false)]
303 public virtual IXmlSchemaInfo SchemaInfo {
313 public virtual XmlNode AppendChild (XmlNode newChild)
315 // I assume that AppendChild(n) equals to InsertAfter(n, this.LastChild) or InsertBefore(n, null)
316 return InsertBefore (newChild, null);
319 public virtual XmlNode Clone ()
321 // By MS document, it is equivalent to CloneNode(true).
322 return this.CloneNode (true);
325 public abstract XmlNode CloneNode (bool deep);
327 public XPathNavigator CreateNavigator ()
329 XmlDocument document = this.NodeType == XmlNodeType.Document ?
330 this as XmlDocument : this.ownerDocument;
331 return document.CreateNavigator (this);
334 public IEnumerator GetEnumerator ()
336 return ChildNodes.GetEnumerator ();
339 public virtual string GetNamespaceOfPrefix (string prefix)
342 throw new ArgumentNullException ("prefix");
346 case XmlNodeType.Attribute:
347 node = ((XmlAttribute) this).OwnerElement;
351 case XmlNodeType.Element:
359 while (node != null) {
360 if (node.Prefix == prefix)
361 return node.NamespaceURI;
362 if (node.Attributes != null) {
363 int count = node.Attributes.Count;
364 for (int i = 0; i < count; i++) {
365 XmlAttribute attr = node.Attributes [i];
366 if (prefix == attr.LocalName && attr.Prefix == "xmlns"
367 || attr.Name == "xmlns" && prefix == String.Empty)
371 node = node.ParentNode;
376 public virtual string GetPrefixOfNamespace (string namespaceURI)
380 case XmlNodeType.Attribute:
381 node = ((XmlAttribute) this).OwnerElement;
383 case XmlNodeType.Element:
391 while (node != null && node.Attributes != null) {
392 for (int i = 0; i < Attributes.Count; i++) {
393 XmlAttribute attr = Attributes [i];
394 if (attr.Prefix == "xmlns" && attr.Value == namespaceURI)
395 return attr.LocalName;
396 else if (attr.Name == "xmlns" && attr.Value == namespaceURI)
399 node = node.ParentNode;
404 object ICloneable.Clone ()
409 IEnumerator IEnumerable.GetEnumerator ()
411 return GetEnumerator ();
414 public virtual XmlNode InsertAfter (XmlNode newChild, XmlNode refChild)
416 // InsertAfter(n1, n2) is equivalent to InsertBefore(n1, n2.PreviousSibling).
418 // I took this way because current implementation
419 // Calling InsertBefore() in this method is faster than
420 // the counterpart, since NextSibling is faster than
421 // PreviousSibling (these children are forward-only list).
422 XmlNode argNode = null;
423 if (refChild != null)
424 argNode = refChild.NextSibling;
425 else if (ChildNodes.Count > 0)
426 argNode = FirstChild;
427 return InsertBefore (newChild, argNode);
430 public virtual XmlNode InsertBefore (XmlNode newChild, XmlNode refChild)
432 return InsertBefore (newChild, refChild, true, true);
435 // check for the node to be one of node ancestors
436 internal bool IsAncestor (XmlNode newChild)
438 XmlNode currNode = this.ParentNode;
439 while(currNode != null)
441 if(currNode == newChild)
443 currNode = currNode.ParentNode;
448 internal XmlNode InsertBefore (XmlNode newChild, XmlNode refChild, bool checkNodeType, bool raiseEvent)
451 CheckNodeInsertion (newChild, refChild);
453 XmlDocument ownerDoc = (NodeType == XmlNodeType.Document) ? (XmlDocument) this : OwnerDocument;
456 ownerDoc.onNodeInserting (newChild, this);
458 if (newChild.ParentNode != null)
459 newChild.ParentNode.RemoveChild (newChild, checkNodeType);
461 if (newChild.NodeType == XmlNodeType.DocumentFragment) {
462 int x = newChild.ChildNodes.Count;
463 for (int i = 0; i < x; i++) {
464 XmlNode n = newChild.ChildNodes [0];
465 this.InsertBefore (n, refChild); // recursively invokes events. (It is compatible with MS implementation.)
469 XmlLinkedNode newLinkedChild = (XmlLinkedNode) newChild;
470 newLinkedChild.parentNode = this;
472 if (refChild == null) {
473 // newChild is the last child:
474 // * set newChild as NextSibling of the existing lastchild
475 // * set LastChild = newChild
476 // * set NextSibling of newChild as FirstChild
477 if (LastLinkedChild != null) {
478 XmlLinkedNode formerFirst = (XmlLinkedNode) FirstChild;
479 LastLinkedChild.NextLinkedSibling = newLinkedChild;
480 LastLinkedChild = newLinkedChild;
481 newLinkedChild.NextLinkedSibling = formerFirst;
483 LastLinkedChild = newLinkedChild;
484 LastLinkedChild.NextLinkedSibling = newLinkedChild; // FirstChild
487 // newChild is not the last child:
488 // * if newchild is first, then set next of lastchild is newChild.
489 // otherwise, set next of previous sibling to newChild
490 // * set next of newChild to refChild
491 XmlLinkedNode prev = refChild.PreviousSibling as XmlLinkedNode;
493 LastLinkedChild.NextLinkedSibling = newLinkedChild;
495 prev.NextLinkedSibling = newLinkedChild;
496 newLinkedChild.NextLinkedSibling = refChild as XmlLinkedNode;
498 switch (newChild.NodeType) {
499 case XmlNodeType.EntityReference:
500 ((XmlEntityReference) newChild).SetReferencedEntityContent ();
502 case XmlNodeType.Entity:
503 ((XmlEntity) newChild).SetEntityContent ();
505 case XmlNodeType.DocumentType:
506 foreach (XmlEntity ent in ((XmlDocumentType)newChild).Entities)
507 ent.SetEntityContent ();
512 ownerDoc.onNodeInserted (newChild, newChild.ParentNode);
517 private void CheckNodeInsertion (XmlNode newChild, XmlNode refChild)
519 XmlDocument ownerDoc = (NodeType == XmlNodeType.Document) ? (XmlDocument) this : OwnerDocument;
521 if (NodeType != XmlNodeType.Element &&
522 NodeType != XmlNodeType.Attribute &&
523 NodeType != XmlNodeType.Document &&
524 NodeType != XmlNodeType.DocumentFragment)
525 throw new InvalidOperationException (String.Format ("Node cannot be appended to current node {0}.", NodeType));
528 case XmlNodeType.Attribute:
529 switch (newChild.NodeType) {
530 case XmlNodeType.Text:
531 case XmlNodeType.EntityReference:
534 throw new InvalidOperationException (String.Format (
535 "Cannot insert specified type of node {0} as a child of this node {1}.",
536 newChild.NodeType, NodeType));
539 case XmlNodeType.Element:
540 switch (newChild.NodeType) {
541 case XmlNodeType.Attribute:
542 case XmlNodeType.Document:
543 case XmlNodeType.DocumentType:
544 case XmlNodeType.Entity:
545 case XmlNodeType.Notation:
546 case XmlNodeType.XmlDeclaration:
547 throw new InvalidOperationException ("Cannot insert specified type of node as a child of this node.");
553 throw new InvalidOperationException ("The node is readonly.");
555 if (newChild.OwnerDocument != ownerDoc)
556 throw new ArgumentException ("Can't append a node created by another document.");
558 if (refChild != null) {
559 if (refChild.ParentNode != this)
560 throw new ArgumentException ("The reference node is not a child of this node.");
563 if(this == ownerDoc && ownerDoc.DocumentElement != null && (newChild is XmlElement) && newChild != ownerDoc.DocumentElement)
564 throw new XmlException ("multiple document element not allowed.");
566 // checking validity finished. then appending...
569 if (newChild == this || IsAncestor (newChild))
570 throw new ArgumentException("Cannot insert a node or any ancestor of that node as a child of itself.");
574 public virtual void Normalize ()
576 StringBuilder tmpBuilder = new StringBuilder ();
577 int count = this.ChildNodes.Count;
579 for (int i = 0; i < count; i++) {
580 XmlNode c = ChildNodes [i];
581 switch (c.NodeType) {
582 case XmlNodeType.Text:
583 case XmlNodeType.Whitespace:
584 case XmlNodeType.SignificantWhitespace:
585 tmpBuilder.Append (c.Value);
589 NormalizeRange (start, i, tmpBuilder);
590 // Continue to normalize from next node.
596 NormalizeRange (start, count, tmpBuilder);
600 private void NormalizeRange (int start, int i, StringBuilder tmpBuilder)
603 // If Texts and Whitespaces are mixed, Text takes precedence to remain.
604 // i.e. Whitespace should be removed.
605 for (int j = start; j < i; j++) {
606 XmlNode keep = ChildNodes [j];
607 if (keep.NodeType == XmlNodeType.Text) {
611 else if (keep.NodeType == XmlNodeType.SignificantWhitespace)
613 // but don't break up to find Text nodes.
617 for (int del = start; del < keepPos; del++)
618 RemoveChild (ChildNodes [start]);
619 int rest = i - keepPos - 1;
620 for (int del = 0; del < rest; del++) {
621 RemoveChild (ChildNodes [start + 1]);
626 ChildNodes [start].Value = tmpBuilder.ToString ();
627 // otherwise nothing to be normalized
629 tmpBuilder.Length = 0;
632 public virtual XmlNode PrependChild (XmlNode newChild)
634 return InsertAfter (newChild, null);
637 public virtual void RemoveAll ()
639 if (Attributes != null)
640 Attributes.RemoveAll ();
642 for (XmlNode node = FirstChild; node != null; node = next) {
643 next = node.NextSibling;
648 public virtual XmlNode RemoveChild (XmlNode oldChild)
650 return RemoveChild (oldChild, true);
653 private void CheckNodeRemoval ()
655 if (NodeType != XmlNodeType.Attribute &&
656 NodeType != XmlNodeType.Element &&
657 NodeType != XmlNodeType.Document &&
658 NodeType != XmlNodeType.DocumentFragment)
659 throw new ArgumentException (String.Format ("This {0} node cannot remove its child.", NodeType));
662 throw new ArgumentException (String.Format ("This {0} node is read only.", NodeType));
665 internal XmlNode RemoveChild (XmlNode oldChild, bool checkNodeType)
667 if (oldChild == null)
668 throw new NullReferenceException ();
669 XmlDocument ownerDoc = (NodeType == XmlNodeType.Document) ? (XmlDocument)this : OwnerDocument;
670 if(oldChild.ParentNode != this)
671 throw new ArgumentException ("The node to be removed is not a child of this node.");
674 ownerDoc.onNodeRemoving (oldChild, oldChild.ParentNode);
679 if (Object.ReferenceEquals (LastLinkedChild, LastLinkedChild.NextLinkedSibling) && Object.ReferenceEquals (LastLinkedChild, oldChild))
680 // If there is only one children, simply clear.
681 LastLinkedChild = null;
683 XmlLinkedNode oldLinkedChild = (XmlLinkedNode) oldChild;
684 XmlLinkedNode beforeLinkedChild = LastLinkedChild;
685 XmlLinkedNode firstChild = (XmlLinkedNode) FirstChild;
687 while (Object.ReferenceEquals (beforeLinkedChild.NextLinkedSibling, LastLinkedChild) == false &&
688 Object.ReferenceEquals (beforeLinkedChild.NextLinkedSibling, oldLinkedChild) == false)
689 beforeLinkedChild = beforeLinkedChild.NextLinkedSibling;
691 if (Object.ReferenceEquals (beforeLinkedChild.NextLinkedSibling, oldLinkedChild) == false)
692 throw new ArgumentException ();
694 beforeLinkedChild.NextLinkedSibling = oldLinkedChild.NextLinkedSibling;
696 // Each derived class may have its own LastLinkedChild, so we must set it explicitly.
697 if (oldLinkedChild.NextLinkedSibling == firstChild)
698 this.LastLinkedChild = beforeLinkedChild;
700 oldLinkedChild.NextLinkedSibling = null;
704 ownerDoc.onNodeRemoved (oldChild, oldChild.ParentNode);
705 oldChild.parentNode = null; // clear parent 'after' above logic.
710 public virtual XmlNode ReplaceChild (XmlNode newChild, XmlNode oldChild)
712 if(oldChild.ParentNode != this)
713 throw new ArgumentException ("The node to be removed is not a child of this node.");
715 if (newChild == this || IsAncestor (newChild))
716 throw new ArgumentException("Cannot insert a node or any ancestor of that node as a child of itself.");
718 XmlNode next = oldChild.NextSibling;
719 RemoveChild (oldChild);
720 InsertBefore (newChild, next);
724 // WARNING: don't use this member outside XmlAttribute nodes.
725 internal XmlElement AttributeOwnerElement {
726 get { return (XmlElement) parentNode; }
727 set { parentNode = value; }
730 internal void SearchDescendantElements (string name, bool matchAll, ArrayList list)
732 for (int i = 0; i < ChildNodes.Count; i++) {
733 XmlNode n = ChildNodes [i];
734 if (n.NodeType != XmlNodeType.Element)
736 if (matchAll || n.Name == name)
738 n.SearchDescendantElements (name, matchAll, list);
742 internal void SearchDescendantElements (string name, bool matchAllName, string ns, bool matchAllNS, ArrayList list)
744 for (int i = 0; i < ChildNodes.Count; i++) {
745 XmlNode n = ChildNodes [i];
746 if (n.NodeType != XmlNodeType.Element)
748 if ((matchAllName || n.LocalName == name)
749 && (matchAllNS || n.NamespaceURI == ns))
751 n.SearchDescendantElements (name, matchAllName, ns, matchAllNS, list);
755 public XmlNodeList SelectNodes (string xpath)
757 return SelectNodes (xpath, null);
760 public XmlNodeList SelectNodes (string xpath, XmlNamespaceManager nsmgr)
762 XPathNavigator nav = CreateNavigator ();
763 XPathExpression expr = nav.Compile (xpath);
765 expr.SetContext (nsmgr);
766 XPathNodeIterator iter = nav.Select (expr);
767 ArrayList rgNodes = new ArrayList ();
768 while (iter.MoveNext ())
770 rgNodes.Add (((IHasXmlNode) iter.Current).GetNode ());
772 return new XmlNodeArrayList (rgNodes);
775 public XmlNode SelectSingleNode (string xpath)
777 return SelectSingleNode (xpath, null);
780 public XmlNode SelectSingleNode (string xpath, XmlNamespaceManager nsmgr)
782 XPathNavigator nav = CreateNavigator ();
783 XPathExpression expr = nav.Compile (xpath);
785 expr.SetContext (nsmgr);
786 XPathNodeIterator iter = nav.Select (expr);
787 if (!iter.MoveNext ())
789 return ((IHasXmlNode) iter.Current).GetNode ();
792 public virtual bool Supports (string feature, string version)
794 if (String.Compare (feature, "xml", true, CultureInfo.InvariantCulture) == 0 // not case-sensitive
795 && (String.Compare (version, "1.0", true, CultureInfo.InvariantCulture) == 0
796 || String.Compare (version, "2.0", true, CultureInfo.InvariantCulture) == 0))
802 public abstract void WriteContentTo (XmlWriter w);
804 public abstract void WriteTo (XmlWriter w);
806 // It parses this and all the ancestor elements,
807 // find 'xmlns' declarations, stores and then return them.
808 internal XmlNamespaceManager ConstructNamespaceManager ()
810 XmlDocument doc = this is XmlDocument ? (XmlDocument)this : this.OwnerDocument;
811 XmlNamespaceManager nsmgr = new XmlNamespaceManager (doc.NameTable);
812 XmlElement el = null;
813 switch(this.NodeType) {
814 case XmlNodeType.Attribute:
815 el = ((XmlAttribute)this).OwnerElement;
817 case XmlNodeType.Element:
818 el = this as XmlElement;
821 el = this.ParentNode as XmlElement;
826 for (int i = 0; i < el.Attributes.Count; i++) {
827 XmlAttribute attr = el.Attributes [i];
828 if(attr.Prefix == "xmlns") {
829 if (nsmgr.LookupNamespace (attr.LocalName) != attr.Value)
830 nsmgr.AddNamespace (attr.LocalName, attr.Value);
831 } else if(attr.Name == "xmlns") {
832 if(nsmgr.LookupNamespace (String.Empty) != attr.Value)
833 nsmgr.AddNamespace (String.Empty, attr.Value);
836 // When reached to document, then it will set null value :)
837 el = el.ParentNode as XmlElement;