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);
242 public virtual XmlNode Clone ()
244 // By MS document, it is equivalent to CloneNode(true).
245 return this.CloneNode (true);
248 public abstract XmlNode CloneNode (bool deep);
251 public XPathNavigator CreateNavigator ()
253 return new XmlDocumentNavigator (this);
256 public IEnumerator GetEnumerator ()
258 return new XmlNodeListChildren (this).GetEnumerator ();
261 [MonoTODO("performance problem.")]
262 public virtual string GetNamespaceOfPrefix (string prefix)
264 XmlNamespaceManager nsmgr = ConstructNamespaceManager ();
265 return nsmgr.LookupNamespace (prefix);
268 [MonoTODO("performance problem.")]
269 public virtual string GetPrefixOfNamespace (string namespaceURI)
271 XmlNamespaceManager nsmgr = ConstructNamespaceManager ();
272 string ns = nsmgr.LookupPrefix (namespaceURI);
273 return (ns != null) ? ns : String.Empty;
276 object ICloneable.Clone ()
281 IEnumerator IEnumerable.GetEnumerator ()
283 return GetEnumerator ();
286 public virtual XmlNode InsertAfter (XmlNode newChild, XmlNode refChild)
288 // I assume that insertAfter(n1, n2) equals to InsertBefore(n1, n2.PreviousSibling).
290 // I took this way because rather than calling InsertAfter() from InsertBefore()
291 // because current implementation of 'NextSibling' looks faster than 'PreviousSibling'.
292 XmlNode argNode = null;
294 argNode = refChild.NextSibling;
295 else if(ChildNodes.Count > 0)
296 argNode = FirstChild;
297 return InsertBefore (newChild, argNode);
300 [MonoTODO("If inserted node is entity reference, then check conforming entity. Wait for DTD implementation.")]
301 public virtual XmlNode InsertBefore (XmlNode newChild, XmlNode refChild)
303 XmlDocument ownerDoc = (NodeType == XmlNodeType.Document) ? (XmlDocument)this : OwnerDocument;
305 if (NodeType == XmlNodeType.Document ||
306 NodeType == XmlNodeType.Element ||
307 NodeType == XmlNodeType.Attribute ||
308 NodeType == XmlNodeType.DocumentFragment) {
310 throw new ArgumentException ("The specified node is readonly.");
312 if (newChild.OwnerDocument != ownerDoc)
313 throw new ArgumentException ("Can't append a node created by another document.");
315 if (refChild != null && newChild.OwnerDocument != refChild.OwnerDocument)
316 throw new ArgumentException ("argument nodes are on the different documents.");
318 // This check is done by MS.NET 1.0, but isn't done for MS.NET 1.1.
319 // Skip this check in the meantime...
320 // if(this == ownerDoc && ownerDoc.DocumentElement != null && (newChild is XmlElement))
321 // throw new XmlException ("multiple document element not allowed.");
323 // checking validity finished. then appending...
325 ownerDoc.onNodeInserting (newChild, this);
327 if(newChild.ParentNode != null)
328 newChild.ParentNode.RemoveChild (newChild);
330 if(newChild.NodeType == XmlNodeType.DocumentFragment) {
331 int x = newChild.ChildNodes.Count;
332 for(int i=0; i<x; i++) {
333 XmlNode n = newChild.ChildNodes [0];
334 this.InsertBefore (n, refChild); // recursively invokes events. (It is compatible with MS implementation.)
338 XmlLinkedNode newLinkedChild = (XmlLinkedNode) newChild;
339 XmlLinkedNode lastLinkedChild = LastLinkedChild;
341 newLinkedChild.parentNode = this;
343 if(refChild == null) {
345 // * set nextSibling of previous lastchild to newChild
346 // * set lastchild = newChild
347 // * set next of newChild to firstChild
348 if(LastLinkedChild != null) {
349 XmlLinkedNode formerFirst = FirstChild as XmlLinkedNode;
350 LastLinkedChild.NextLinkedSibling = newLinkedChild;
351 LastLinkedChild = newLinkedChild;
352 newLinkedChild.NextLinkedSibling = formerFirst;
355 LastLinkedChild = newLinkedChild;
356 LastLinkedChild.NextLinkedSibling = newLinkedChild; // FirstChild
360 // append not last, so:
361 // * if newchild is first, then set next of lastchild is newChild.
362 // otherwise, set next of previous sibling to newChild
363 // * set next of newChild to refChild
364 XmlLinkedNode prev = refChild.PreviousSibling as XmlLinkedNode;
366 LastLinkedChild.NextLinkedSibling = newLinkedChild;
368 prev.NextLinkedSibling = newLinkedChild;
369 newLinkedChild.NextLinkedSibling = refChild as XmlLinkedNode;
371 ownerDoc.onNodeInserted (newChild, newChild.ParentNode);
376 throw new InvalidOperationException ();
380 public virtual void Normalize ()
382 throw new NotImplementedException ();
385 public virtual XmlNode PrependChild (XmlNode newChild)
387 return InsertAfter (newChild, null);
390 public virtual void RemoveAll ()
393 for (XmlNode node = FirstChild; node != null; node = next) {
394 next = node.NextSibling;
399 public virtual XmlNode RemoveChild (XmlNode oldChild)
401 XmlDocument ownerDoc = (NodeType == XmlNodeType.Document) ? (XmlDocument)this : OwnerDocument;
402 if(oldChild.ParentNode != this)
403 throw new XmlException ("specified child is not child of this node.");
405 ownerDoc.onNodeRemoving (oldChild, oldChild.ParentNode);
407 if (NodeType == XmlNodeType.Document || NodeType == XmlNodeType.Element || NodeType == XmlNodeType.Attribute || NodeType == XmlNodeType.DocumentFragment) {
409 throw new ArgumentException ();
411 if (Object.ReferenceEquals (LastLinkedChild, LastLinkedChild.NextLinkedSibling) && Object.ReferenceEquals (LastLinkedChild, oldChild))
412 LastLinkedChild = null;
414 XmlLinkedNode oldLinkedChild = (XmlLinkedNode)oldChild;
415 XmlLinkedNode beforeLinkedChild = LastLinkedChild;
417 while (!Object.ReferenceEquals (beforeLinkedChild.NextLinkedSibling, LastLinkedChild) && !Object.ReferenceEquals (beforeLinkedChild.NextLinkedSibling, oldLinkedChild))
418 beforeLinkedChild = beforeLinkedChild.NextLinkedSibling;
420 if (!Object.ReferenceEquals (beforeLinkedChild.NextLinkedSibling, oldLinkedChild))
421 throw new ArgumentException ();
423 beforeLinkedChild.NextLinkedSibling = oldLinkedChild.NextLinkedSibling;
424 oldLinkedChild.NextLinkedSibling = null;
427 ownerDoc.onNodeRemoved (oldChild, oldChild.ParentNode);
428 oldChild.parentNode = null; // clear parent 'after' above logic.
433 throw new ArgumentException ();
436 public virtual XmlNode ReplaceChild (XmlNode newChild, XmlNode oldChild)
438 if(oldChild.ParentNode != this)
439 throw new InvalidOperationException ("oldChild is not a child of this node.");
440 XmlNode parent = this.ParentNode;
441 while(parent != null) {
442 if(newChild == parent)
443 throw new InvalidOperationException ("newChild is ancestor of this node.");
444 parent = parent.ParentNode;
446 foreach(XmlNode n in ChildNodes) {
448 XmlNode prev = oldChild.PreviousSibling;
449 RemoveChild (oldChild);
450 InsertAfter (newChild, prev);
457 public XmlNodeList SelectNodes (string xpath)
459 return SelectNodes (xpath, null);
463 public XmlNodeList SelectNodes (string xpath, XmlNamespaceManager nsmgr)
465 XPathNavigator nav = CreateNavigator ();
466 XPathExpression expr = nav.Compile (xpath);
468 expr.SetContext (nsmgr);
469 XPathNodeIterator iter = nav.Select (expr);
470 ArrayList rgNodes = new ArrayList ();
471 while (iter.MoveNext ())
473 rgNodes.Add (((XmlDocumentNavigator) iter.Current).Node);
475 return new XmlNodeArrayList (rgNodes);
478 public XmlNode SelectSingleNode (string xpath)
480 return SelectSingleNode (xpath, null);
484 public XmlNode SelectSingleNode (string xpath, XmlNamespaceManager nsmgr)
486 XPathNavigator nav = CreateNavigator ();
487 XPathExpression expr = nav.Compile (xpath);
489 expr.SetContext (nsmgr);
490 XPathNodeIterator iter = nav.Select (expr);
491 if (!iter.MoveNext ())
493 return ((XmlDocumentNavigator) iter.Current).Node;
496 internal void SetParentNode (XmlNode parent)
502 public virtual bool Supports (string feature, string version)
504 throw new NotImplementedException ();
507 public abstract void WriteContentTo (XmlWriter w);
509 public abstract void WriteTo (XmlWriter w);
511 // It parses this and all the ancestor elements,
512 // find 'xmlns' declarations, stores and then return them.
514 internal XmlNamespaceManager ConstructNamespaceManager ()
516 XmlDocument doc = this is XmlDocument ? (XmlDocument)this : this.OwnerDocument;
517 XmlNamespaceManager nsmgr = new XmlNamespaceManager (doc.NameTable);
518 XmlElement el = null;
519 switch(this.NodeType) {
520 case XmlNodeType.Attribute:
521 el = ((XmlAttribute)this).OwnerElement;
523 case XmlNodeType.Element:
524 el = this as XmlElement;
527 el = this.ParentNode as XmlElement;
532 foreach(XmlAttribute attr in el.Attributes) {
533 if(attr.Prefix == "xmlns" || (attr.Name == "xmlns" && attr.Prefix == String.Empty)) {
534 if(nsmgr.LookupNamespace (attr.LocalName) == null )
535 nsmgr.AddNamespace (attr.LocalName, attr.Value);
538 // When reached to document, then it will set null value :)
539 el = el.ParentNode as XmlElement;