5 // Kral Ferch <kral_ferch@hotmail.com>
6 // Atsushi Enomoto <ginga@kit.hi-ho.ne.jp>
9 // (C) 2002 Atsushi Enomoto
13 using System.Collections;
16 using System.Xml.XPath;
20 public abstract class XmlNode : ICloneable, IEnumerable, IXPathNavigable
24 XmlDocument ownerDocument;
31 internal XmlNode (XmlDocument ownerDocument)
33 this.ownerDocument = ownerDocument;
40 public virtual XmlAttributeCollection Attributes {
44 public virtual string BaseURI {
46 // Isn't it conformant to W3C XML Base Recommendation?
47 // As far as I tested, there are not...
48 return (ParentNode != null) ? ParentNode.BaseURI : OwnerDocument.BaseURI;
52 public virtual XmlNodeList ChildNodes {
54 return new XmlNodeListChildren (this);
58 public virtual XmlNode FirstChild {
60 if (LastChild != null) {
61 return LastLinkedChild.NextLinkedSibling;
69 public virtual bool HasChildNodes {
70 get { return LastChild != null; }
73 [MonoTODO("confirm whether this way is right for each not-overriden types.")]
74 public virtual string InnerText {
76 StringBuilder builder = new StringBuilder ();
77 AppendChildValues (this, builder);
78 return builder.ToString ();
81 set { throw new NotImplementedException (); }
84 private void AppendChildValues (XmlNode parent, StringBuilder builder)
86 XmlNode node = parent.FirstChild;
88 while (node != null) {
89 if (node.NodeType == XmlNodeType.Text)
90 builder.Append (node.Value);
91 AppendChildValues (node, builder);
92 node = node.NextSibling;
97 public virtual string InnerXml {
99 StringWriter sw = new StringWriter ();
100 XmlTextWriter xtw = new XmlTextWriter (sw);
102 WriteContentTo (xtw);
104 return sw.GetStringBuilder ().ToString ();
107 set { throw new NotImplementedException (); }
110 public virtual bool IsReadOnly {
111 get { return false; }
114 [System.Runtime.CompilerServices.IndexerName("Item")]
115 public virtual XmlElement this [string name] {
117 foreach (XmlNode node in ChildNodes) {
118 if ((node.NodeType == XmlNodeType.Element) &&
119 (node.Name == name)) {
120 return (XmlElement) node;
128 [System.Runtime.CompilerServices.IndexerName("Item")]
129 public virtual XmlElement this [string localname, string ns] {
131 foreach (XmlNode node in ChildNodes) {
132 if ((node.NodeType == XmlNodeType.Element) &&
133 (node.LocalName == localname) &&
134 (node.NamespaceURI == ns)) {
135 return (XmlElement) node;
143 public virtual XmlNode LastChild {
144 get { return LastLinkedChild; }
147 internal virtual XmlLinkedNode LastLinkedChild {
152 public abstract string LocalName { get; }
154 public abstract string Name { get; }
156 public virtual string NamespaceURI {
157 get { return String.Empty; }
160 public virtual XmlNode NextSibling {
164 public abstract XmlNodeType NodeType { get; }
166 internal virtual XPathNodeType XPathNodeType {
168 return (XPathNodeType) (-1);
172 public virtual string OuterXml {
174 StringWriter sw = new StringWriter ();
175 XmlTextWriter xtw = new XmlTextWriter (sw);
179 return sw.GetStringBuilder ().ToString ();
183 public virtual XmlDocument OwnerDocument {
184 get { return ownerDocument; }
187 public virtual XmlNode ParentNode {
188 get { return parentNode; }
191 public virtual string Prefix {
192 get { return String.Empty; }
196 public virtual XmlNode PreviousSibling {
200 public virtual string Value {
202 set { throw new InvalidOperationException ("This node does not have a value"); }
205 internal virtual string XmlLang {
207 if(Attributes != null)
208 foreach(XmlAttribute attr in Attributes)
209 if(attr.Name == "xml:lang")
211 return (ParentNode != null) ? ParentNode.XmlLang : OwnerDocument.XmlLang;
215 internal virtual XmlSpace XmlSpace {
217 if(Attributes != null) {
218 foreach(XmlAttribute attr in Attributes) {
219 if(attr.Name == "xml:space") {
221 case "preserve": return XmlSpace.Preserve;
222 case "default": return XmlSpace.Default;
228 return (ParentNode != null) ? ParentNode.XmlSpace : OwnerDocument.XmlSpace;
236 public virtual XmlNode AppendChild (XmlNode newChild)
238 // I assume that AppendChild(n) equals to InsertAfter(n, this.LastChild) or InsertBefore(n, null)
239 return InsertBefore (newChild, null);
241 // Below are formerly used logic.
242 /* XmlDocument ownerDoc = (NodeType == XmlNodeType.Document) ? (XmlDocument)this : OwnerDocument;
244 if (NodeType == XmlNodeType.Document || NodeType == XmlNodeType.Element || NodeType == XmlNodeType.Attribute || NodeType == XmlNodeType.DocumentFragment) {
247 throw new ArgumentException ("The specified node is readonly.");
249 if (newChild.OwnerDocument != ownerDoc)
250 throw new ArgumentException ("Can't append a node created by another document.");
252 // checking validity finished. then appending...
254 ownerDoc.onNodeInserting (newChild, this);
256 if(newChild.ParentNode != null)
257 newChild.ParentNode.RemoveChild(newChild);
259 if(newChild.NodeType == XmlNodeType.DocumentFragment)
261 int x = newChild.ChildNodes.Count;
262 for(int i=0; i<x; i++)
264 // When this logic became to remove children in order, then index will have never to increments.
265 XmlNode n = newChild.ChildNodes [0];
266 this.AppendChild(n); // recursively invokes events. (It is compatible with MS implementation.)
271 XmlLinkedNode newLinkedChild = (XmlLinkedNode) newChild;
272 XmlLinkedNode lastLinkedChild = LastLinkedChild;
274 newLinkedChild.parentNode = this;
276 if (lastLinkedChild != null)
278 newLinkedChild.NextLinkedSibling = lastLinkedChild.NextLinkedSibling;
279 lastLinkedChild.NextLinkedSibling = newLinkedChild;
282 newLinkedChild.NextLinkedSibling = newLinkedChild;
284 LastLinkedChild = newLinkedChild;
286 ownerDoc.onNodeInserted (newChild, newChild.ParentNode);
291 throw new InvalidOperationException();
294 public virtual XmlNode Clone ()
296 // By MS document, it is equivalent to CloneNode(true).
297 return this.CloneNode (true);
300 public abstract XmlNode CloneNode (bool deep);
303 public XPathNavigator CreateNavigator ()
305 return new XmlDocumentNavigator (this);
308 public IEnumerator GetEnumerator ()
310 return new XmlNodeListChildren (this).GetEnumerator ();
313 [MonoTODO("performance problem.")]
314 public virtual string GetNamespaceOfPrefix (string prefix)
316 XmlNamespaceManager nsmgr = ConstructNamespaceManager ();
317 return nsmgr.LookupNamespace (prefix);
320 [MonoTODO("performance problem.")]
321 public virtual string GetPrefixOfNamespace (string namespaceURI)
323 XmlNamespaceManager nsmgr = ConstructNamespaceManager ();
324 return nsmgr.LookupPrefix (namespaceURI);
327 object ICloneable.Clone ()
332 IEnumerator IEnumerable.GetEnumerator ()
334 return GetEnumerator ();
337 public virtual XmlNode InsertAfter (XmlNode newChild, XmlNode refChild)
339 // I assume that insertAfter(n1, n2) equals to InsertBefore(n1, n2.PreviousSibling).
341 // I took this way because rather than calling InsertAfter() from InsertBefore()
342 // because current implementation of 'NextSibling' looks faster than 'PreviousSibling'.
343 XmlNode argNode = null;
345 argNode = refChild.NextSibling;
346 else if(ChildNodes.Count > 0)
347 argNode = FirstChild;
348 return InsertBefore (newChild, argNode);
351 [MonoTODO("If inserted node is entity reference, then check conforming entity. Wait for DTD implementation.")]
352 public virtual XmlNode InsertBefore (XmlNode newChild, XmlNode refChild)
354 XmlDocument ownerDoc = (NodeType == XmlNodeType.Document) ? (XmlDocument)this : OwnerDocument;
356 if (NodeType == XmlNodeType.Document ||
357 NodeType == XmlNodeType.Element ||
358 NodeType == XmlNodeType.Attribute ||
359 NodeType == XmlNodeType.DocumentFragment) {
361 throw new ArgumentException ("The specified node is readonly.");
363 if (newChild.OwnerDocument != ownerDoc)
364 throw new ArgumentException ("Can't append a node created by another document.");
366 if (refChild != null && newChild.OwnerDocument != refChild.OwnerDocument)
367 throw new ArgumentException ("argument nodes are on the different documents.");
369 if (refChild != null && this == ownerDoc &&
370 ownerDoc.DocumentElement != null &&
371 (newChild is XmlElement ||
372 newChild is XmlCharacterData ||
373 newChild is XmlEntityReference))
374 throw new XmlException ("cannot insert this node to this position.");
376 // checking validity finished. then appending...
378 ownerDoc.onNodeInserting (newChild, this);
380 if(newChild.ParentNode != null)
381 newChild.ParentNode.RemoveChild (newChild);
383 if(newChild.NodeType == XmlNodeType.DocumentFragment) {
384 int x = newChild.ChildNodes.Count;
385 for(int i=0; i<x; i++) {
386 XmlNode n = newChild.ChildNodes [0];
387 this.InsertBefore (n, refChild); // recursively invokes events. (It is compatible with MS implementation.)
391 XmlLinkedNode newLinkedChild = (XmlLinkedNode) newChild;
392 XmlLinkedNode lastLinkedChild = LastLinkedChild;
394 newLinkedChild.parentNode = this;
396 if(refChild == null) {
398 // * set nextSibling of previous lastchild to newChild
399 // * set lastchild = newChild
400 // * set next of newChild to firstChild
401 if(LastLinkedChild != null) {
402 XmlLinkedNode formerFirst = FirstChild as XmlLinkedNode;
403 LastLinkedChild.NextLinkedSibling = newLinkedChild;
404 LastLinkedChild = newLinkedChild;
405 newLinkedChild.NextLinkedSibling = formerFirst;
408 LastLinkedChild = newLinkedChild;
409 LastLinkedChild.NextLinkedSibling = newLinkedChild; // FirstChild
413 // append not last, so:
414 // * if newchild is first, then set next of lastchild is newChild.
415 // otherwise, set next of previous sibling to newChild
416 // * set next of newChild to refChild
417 XmlLinkedNode prev = refChild.PreviousSibling as XmlLinkedNode;
419 LastLinkedChild.NextLinkedSibling = newLinkedChild;
421 prev.NextLinkedSibling = newLinkedChild;
422 newLinkedChild.NextLinkedSibling = refChild as XmlLinkedNode;
424 ownerDoc.onNodeInserted (newChild, newChild.ParentNode);
429 throw new InvalidOperationException ();
433 public virtual void Normalize ()
435 throw new NotImplementedException ();
438 public virtual XmlNode PrependChild (XmlNode newChild)
440 return InsertAfter (newChild, null);
443 public virtual void RemoveAll ()
446 for (XmlNode node = FirstChild; node != null; node = next) {
447 next = node.NextSibling;
452 public virtual XmlNode RemoveChild (XmlNode oldChild)
454 XmlDocument ownerDoc = (NodeType == XmlNodeType.Document) ? (XmlDocument)this : OwnerDocument;
455 if(oldChild.ParentNode != this)
456 throw new XmlException ("specified child is not child of this node.");
458 ownerDoc.onNodeRemoving (oldChild, oldChild.ParentNode);
460 if (NodeType == XmlNodeType.Document || NodeType == XmlNodeType.Element || NodeType == XmlNodeType.Attribute || NodeType == XmlNodeType.DocumentFragment) {
462 throw new ArgumentException ();
464 if (Object.ReferenceEquals (LastLinkedChild, LastLinkedChild.NextLinkedSibling) && Object.ReferenceEquals (LastLinkedChild, oldChild))
465 LastLinkedChild = null;
467 XmlLinkedNode oldLinkedChild = (XmlLinkedNode)oldChild;
468 XmlLinkedNode beforeLinkedChild = LastLinkedChild;
470 while (!Object.ReferenceEquals (beforeLinkedChild.NextLinkedSibling, LastLinkedChild) && !Object.ReferenceEquals (beforeLinkedChild.NextLinkedSibling, oldLinkedChild))
471 beforeLinkedChild = beforeLinkedChild.NextLinkedSibling;
473 if (!Object.ReferenceEquals (beforeLinkedChild.NextLinkedSibling, oldLinkedChild))
474 throw new ArgumentException ();
476 beforeLinkedChild.NextLinkedSibling = oldLinkedChild.NextLinkedSibling;
477 oldLinkedChild.NextLinkedSibling = null;
480 ownerDoc.onNodeRemoved (oldChild, oldChild.ParentNode);
481 oldChild.parentNode = null; // clear parent 'after' above logic.
486 throw new ArgumentException ();
490 public virtual XmlNode ReplaceChild (XmlNode newChild, XmlNode oldChild)
492 throw new NotImplementedException ();
495 public XmlNodeList SelectNodes (string xpath)
497 return SelectNodes (xpath, null);
501 public XmlNodeList SelectNodes (string xpath, XmlNamespaceManager nsmgr)
503 XPathNavigator nav = CreateNavigator ();
504 XPathExpression expr = nav.Compile (xpath);
506 expr.SetContext (nsmgr);
507 XPathNodeIterator iter = nav.Select (expr);
508 ArrayList rgNodes = new ArrayList ();
509 while (iter.MoveNext ())
511 rgNodes.Add (((XmlDocumentNavigator) iter.Current).Node);
513 return new XmlNodeArrayList (rgNodes);
516 public XmlNode SelectSingleNode (string xpath)
518 return SelectSingleNode (xpath, null);
522 public XmlNode SelectSingleNode (string xpath, XmlNamespaceManager nsmgr)
524 XPathNavigator nav = CreateNavigator ();
525 XPathExpression expr = nav.Compile (xpath);
527 expr.SetContext (nsmgr);
528 XPathNodeIterator iter = nav.Select (expr);
529 if (!iter.MoveNext ())
531 return ((XmlDocumentNavigator) iter.Current).Node;
534 internal void SetParentNode (XmlNode parent)
540 public virtual bool Supports (string feature, string version)
542 throw new NotImplementedException ();
545 public abstract void WriteContentTo (XmlWriter w);
547 public abstract void WriteTo (XmlWriter w);
549 // It parses this and all the ancestor elements,
550 // find 'xmlns' declarations, stores and then return them.
552 internal XmlNamespaceManager ConstructNamespaceManager ()
554 XmlDocument doc = this is XmlDocument ? (XmlDocument)this : this.OwnerDocument;
555 XmlNamespaceManager nsmgr = new XmlNamespaceManager (doc.NameTable);
556 XmlElement el = null;
557 switch(this.NodeType) {
558 case XmlNodeType.Attribute:
559 el = ((XmlAttribute)this).OwnerElement;
561 case XmlNodeType.Element:
562 el = this as XmlElement;
565 el = this.ParentNode as XmlElement;
570 foreach(XmlAttribute attr in el.Attributes) {
571 if(attr.Prefix == "xmlns" || (attr.Name == "xmlns" && attr.Prefix == String.Empty)) {
572 if(nsmgr.LookupNamespace (attr.LocalName) == null )
573 nsmgr.AddNamespace (attr.LocalName, attr.Value);
576 // When reached to document, then it will set null value :)
577 el = el.ParentNode as XmlElement;