**** Merged from MCS ****
[mono-project.git] / mcs / class / System.XML / Mono.Xml.XPath / XPathEditableDocument.cs
bloba7a5679219c1c5ac5af2f3307ffbf481ace8316c
1 //
2 // Mono.Xml.XPath.XPathEditableDocument
3 //
4 // Author:
5 // Atsushi Enomoto <atsushi@ximian.com>
6 //
7 // (C)2004 Novell Inc.
8 //
9 // Yet another implementation of editable XPathNavigator.
10 // (Even runnable under MS.NET 2.0)
12 // By rewriting XPathEditableDocument.CreateNavigator() as just to
13 // create XmlDocumentNavigator, XmlDocumentEditableNavigator could be used
14 // as to implement XmlDocument.CreateNavigator().
18 // Permission is hereby granted, free of charge, to any person obtaining
19 // a copy of this software and associated documentation files (the
20 // "Software"), to deal in the Software without restriction, including
21 // without limitation the rights to use, copy, modify, merge, publish,
22 // distribute, sublicense, and/or sell copies of the Software, and to
23 // permit persons to whom the Software is furnished to do so, subject to
24 // the following conditions:
25 //
26 // The above copyright notice and this permission notice shall be
27 // included in all copies or substantial portions of the Software.
28 //
29 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
30 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
31 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
32 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
33 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
34 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
35 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
37 #if NET_2_0
39 using System;
40 using System.Collections;
41 using System.ComponentModel;
42 using System.IO;
43 using System.Xml;
44 using System.Xml.Schema;
45 using System.Xml.XPath;
46 using System.Xml.Serialization;
48 namespace Mono.Xml.XPath
50 internal class XPathEditableDocument : IXPathNavigable
53 public static void Main ()
55 try {
56 #if true
57 XmlDocument doc = new XmlDocument ();
58 XPathEditableDocument pd = new XPathEditableDocument (doc);
59 XPathNavigator nav = pd.CreateNavigator ();
60 IChangeTracking xp = pd;
61 #else
62 XPathDocument doc = new XPathDocument ();
63 XPathNavigator nav = doc.CreateNavigator ();
64 IChangeTracking xp = doc;
65 #endif
66 doc.LoadXml ("<root/>");
67 nav.MoveToFirstChild (); // root
68 XmlWriter w = nav.AppendChild ();
69 Console.WriteLine (((IChangeTracking) xp).IsChanged);
70 w.WriteElementString ("foo", "foo_text");
71 w.WriteElementString ("bar", "bar_text");
72 w.WriteStartElement ("hoge");
73 w.WriteAttributeString ("fuga", "fugafuga");
74 w.WriteAttributeString ("unya", "unyaunya");
75 w.WriteFullEndElement ();
76 w.Close ();
78 w = nav.CreateAttributes ();
79 w.WriteStartAttribute ("namara");
80 w.WriteString ("mokera");
81 w.WriteEndAttribute ();
82 w.WriteAttributeString ("beccho", "giccho");
83 w.Close ();
85 nav.MoveToRoot ();
86 nav.MoveToFirstChild ();
87 nav.MoveToFirstChild ();
88 nav.DeleteSelf (); // delete foo
89 Console.WriteLine (nav.Name);
90 nav.MoveToNext ();
91 Console.WriteLine (nav.Name);
92 Console.WriteLine (nav.MoveToFirstAttribute ());
93 nav.DeleteSelf (); // delete fuga
95 doc.Save (Console.Out);
96 } catch (Exception ex) {
97 Console.WriteLine (ex);
102 XmlNode node;
104 ArrayList changes = new ArrayList ();
106 public XPathEditableDocument (XmlNode node)
108 this.node = node;
111 public virtual bool CanEdit {
112 get { return true; }
115 public XmlNode Node {
116 get { return node; }
119 public XPathNavigator CreateNavigator ()
121 return new XmlDocumentEditableNavigator (this);
124 public XmlWriter CreateWriter ()
126 return CreateNavigator ().AppendChild ();
129 public bool HasChanges ()
131 return IsChanged;
134 #region IRevertibleChangeTracking/IChangeTracking
135 public bool IsChanged {
136 get { return changes.Count != 0; }
139 public void AcceptChanges ()
141 changes.Clear ();
144 public void RejectChanges ()
146 for (int i = changes.Count - 1; i >= 0; i--) {
147 Insertion ins = changes [i] as Insertion;
148 if (ins != null) {
149 ins.ParentNode.RemoveChild (ins.InsertedNode);
150 continue;
153 Removal rem = changes [i] as Removal;
154 if (rem != null) {
155 if (rem.RemovedNode.NodeType == XmlNodeType.Attribute) {
156 XmlElement el = (XmlElement) rem.OwnerNode;
157 el.SetAttributeNode ((XmlAttribute) rem.RemovedNode);
159 else
160 rem.OwnerNode.InsertBefore (rem.RemovedNode, rem.NextSibling);
161 continue;
163 AttributeUpdate au = changes [i] as AttributeUpdate;
164 if (au != null) {
165 if (au.OldAttribute != null)
166 au.Element.SetAttributeNode (au.OldAttribute);
167 else
168 au.Element.RemoveAttributeNode (au.NewAttribute);
169 continue;
172 changes.Clear ();
174 #endregion
176 #region IXmlSerializable
177 public void WriteXml (XmlWriter writer)
179 throw new NotImplementedException ();
182 public void ReadXml (XmlReader reader)
184 throw new NotImplementedException ();
187 public XmlSchema GetSchema ()
189 throw new NotImplementedException ();
191 #endregion
193 internal bool DeleteNode (XmlNode node)
195 Removal rem = new Removal ();
196 if (node.NodeType == XmlNodeType.Attribute) {
197 XmlAttribute attr = node as XmlAttribute;
198 rem.OwnerNode = attr.OwnerElement;
199 rem.RemovedNode = node;
200 attr.OwnerElement.RemoveAttributeNode (attr);
201 return false;
202 } else {
203 rem.OwnerNode = node.ParentNode;
204 rem.NextSibling = node.NextSibling;
205 rem.RemovedNode = node;
206 node.ParentNode.RemoveChild (node);
207 return rem.NextSibling != null;
211 internal XmlWriter CreateInsertionWriter (XmlNode owner, XmlNode previousSibling)
213 return new XmlDocumentInsertionWriter (owner, previousSibling, this);
216 internal XmlWriter CreateAttributesWriter (XmlNode owner)
218 return new XmlDocumentAttributeWriter (owner, this);
221 internal void AttributeUpdate (XmlElement element, XmlAttribute oldAttr, XmlAttribute newAttr)
223 AttributeUpdate au = new AttributeUpdate ();
224 au.Element = element;
225 au.OldAttribute = oldAttr;
226 au.NewAttribute = newAttr;
227 changes.Add (au);
230 internal void AppendChild (XmlNode parent, XmlNode child)
232 Insertion ins = new Insertion ();
233 ins.ParentNode = parent;
234 ins.InsertedNode = child;
235 changes.Add (ins);
239 internal class XmlDocumentInsertionWriter : XmlWriter
241 XmlNode current;
242 XmlNode previousSibling;
243 XPathEditableDocument document;
244 Stack nodeStack = new Stack ();
246 public XmlDocumentInsertionWriter (XmlNode owner, XmlNode previousSibling, XPathEditableDocument doc)
248 this.current = (XmlNode) owner;
249 if (current == null)
250 throw new InvalidOperationException ();
251 this.previousSibling = previousSibling;
252 this.document = doc;
253 state = WriteState.Content;
256 WriteState state;
257 XmlAttribute attribute;
259 public override WriteState WriteState {
260 get { return state; }
263 public override void Close ()
267 public override void Flush ()
271 public override string LookupPrefix (string ns)
273 return current.GetPrefixOfNamespace (ns);
276 public override void WriteStartAttribute (string prefix, string name, string ns)
278 if (state != WriteState.Content)
279 throw new InvalidOperationException ("Current state is not inside element. Cannot start attribute.");
280 attribute = current.OwnerDocument.CreateAttribute (prefix, name, ns);
281 state = WriteState.Attribute;
284 public override void WriteProcessingInstruction (string name, string value)
286 XmlProcessingInstruction pi = current.OwnerDocument.CreateProcessingInstruction (name, value);
287 current.AppendChild (pi);
288 document.AppendChild (current, pi);
291 public override void WriteComment (string text)
293 XmlComment comment = current.OwnerDocument.CreateComment (text);
294 current.AppendChild (comment);
295 document.AppendChild (current, comment);
298 public override void WriteCData (string text)
300 XmlCDataSection cdata = current.OwnerDocument.CreateCDataSection (text);
301 current.AppendChild (cdata);
302 document.AppendChild (current, cdata);
305 public override void WriteStartElement (string prefix, string name, string ns)
307 if (current.OwnerDocument == null)
308 throw new Exception ("Should not happen.");
309 XmlElement el = current.OwnerDocument.CreateElement (prefix, name, ns);
310 current.AppendChild (el);
311 document.AppendChild (current, el);
312 nodeStack.Push (current);
313 current = el;
316 public override void WriteEndElement ()
318 if (nodeStack.Count == 0)
319 throw new InvalidOperationException ("No element is opened.");
320 current = nodeStack.Pop () as XmlNode;
323 public override void WriteFullEndElement ()
325 WriteEndElement ();
326 XmlElement el = current as XmlElement;
327 if (el != null)
328 el.IsEmpty = false;
331 public override void WriteDocType (string name, string pubid, string systemId, string intsubset)
333 throw new NotSupportedException ();
336 public override void WriteStartDocument ()
338 throw new NotSupportedException ();
341 public override void WriteStartDocument (bool standalone)
343 throw new NotSupportedException ();
346 public override void WriteEndDocument ()
348 throw new NotSupportedException ();
351 public override void WriteBase64 (byte [] data, int start, int length)
353 WriteString (Convert.ToBase64String (data, start, length));
356 public override void WriteRaw (char [] raw, int start, int length)
358 throw new NotSupportedException ();
361 public override void WriteRaw (string raw)
363 throw new NotSupportedException ();
366 public override void WriteSurrogateCharEntity (char msb, char lsb)
368 throw new NotSupportedException ();
371 public override void WriteCharEntity (char c)
373 throw new NotSupportedException ();
376 public override void WriteEntityRef (string entname)
378 if (state != WriteState.Attribute)
379 throw new InvalidOperationException ("Current state is not inside attribute. Cannot write attribute value.");
380 attribute.AppendChild (attribute.OwnerDocument.CreateEntityReference (entname));
383 public override void WriteChars (char [] data, int start, int length)
385 WriteString (new string (data, start, length));
388 public override void WriteString (string text)
390 if (attribute != null)
391 attribute.Value += text;
392 else {
393 XmlText t = current.OwnerDocument.CreateTextNode (text);
394 current.AppendChild (t);
395 document.AppendChild (current, t);
399 public override void WriteWhitespace (string text)
401 if (state != WriteState.Attribute)
402 current.AppendChild (current.OwnerDocument.CreateTextNode (text));
403 else if (attribute.ChildNodes.Count == 0)
404 attribute.AppendChild (attribute.OwnerDocument.CreateWhitespace (text));
405 else
406 attribute.Value += text;
409 public override void WriteEndAttribute ()
411 XmlElement element = current as XmlElement;
412 if (state != WriteState.Attribute || element == null)
413 throw new InvalidOperationException ("Current state is not inside attribute. Cannot close attribute.");
414 document.AttributeUpdate (element, element.SetAttributeNode (attribute), attribute);
415 attribute = null;
416 state = WriteState.Content;
420 internal class XmlDocumentAttributeWriter : XmlWriter
422 XmlElement element;
423 XPathEditableDocument document;
425 public XmlDocumentAttributeWriter (XmlNode owner, XPathEditableDocument doc)
427 element = owner as XmlElement;
428 if (element == null)
429 throw new ArgumentException ("To write attributes, current node must be an element.");
430 state = WriteState.Content;
431 document = doc;
434 WriteState state;
435 XmlAttribute attribute;
437 public override WriteState WriteState {
438 get { return state; }
441 public override void Close ()
445 public override void Flush ()
449 public override string LookupPrefix (string ns)
451 return element.GetPrefixOfNamespace (ns);
454 public override void WriteStartAttribute (string prefix, string name, string ns)
456 if (state != WriteState.Content)
457 throw new InvalidOperationException ("Current state is not inside element. Cannot start attribute.");
458 attribute = element.OwnerDocument.CreateAttribute (prefix, name, ns);
459 state = WriteState.Attribute;
462 public override void WriteProcessingInstruction (string name, string value)
464 throw new NotSupportedException ();
467 public override void WriteComment (string text)
469 throw new NotSupportedException ();
472 public override void WriteCData (string text)
474 throw new NotSupportedException ();
477 public override void WriteStartElement (string prefix, string name, string ns)
479 throw new NotSupportedException ();
482 public override void WriteEndElement ()
484 throw new NotSupportedException ();
487 public override void WriteFullEndElement ()
489 throw new NotSupportedException ();
492 public override void WriteDocType (string name, string pubid, string systemId, string intsubset)
494 throw new NotSupportedException ();
497 public override void WriteStartDocument ()
499 throw new NotSupportedException ();
502 public override void WriteStartDocument (bool standalone)
504 throw new NotSupportedException ();
507 public override void WriteEndDocument ()
509 throw new NotSupportedException ();
512 public override void WriteBase64 (byte [] data, int start, int length)
514 throw new NotSupportedException ();
517 public override void WriteRaw (char [] raw, int start, int length)
519 throw new NotSupportedException ();
522 public override void WriteRaw (string raw)
524 throw new NotSupportedException ();
527 public override void WriteSurrogateCharEntity (char msb, char lsb)
529 throw new NotSupportedException ();
532 public override void WriteCharEntity (char c)
534 throw new NotSupportedException ();
537 public override void WriteEntityRef (string entname)
539 if (state != WriteState.Attribute)
540 throw new InvalidOperationException ("Current state is not inside attribute. Cannot write attribute value.");
541 attribute.AppendChild (attribute.OwnerDocument.CreateEntityReference (entname));
544 public override void WriteChars (char [] data, int start, int length)
546 WriteString (new string (data, start, length));
549 public override void WriteString (string text)
551 if (state != WriteState.Attribute)
552 throw new InvalidOperationException ("Current state is not inside attribute. Cannot write attribute value.");
553 attribute.Value += text;
556 public override void WriteWhitespace (string text)
558 if (state != WriteState.Attribute)
559 throw new InvalidOperationException ("Current state is not inside attribute. Cannot write attribute value.");
560 if (attribute.ChildNodes.Count == 0)
561 attribute.AppendChild (attribute.OwnerDocument.CreateWhitespace (text));
562 else
563 attribute.Value += text;
566 public override void WriteEndAttribute ()
568 if (state != WriteState.Attribute)
569 throw new InvalidOperationException ("Current state is not inside attribute. Cannot close attribute.");
570 document.AttributeUpdate (element, element.SetAttributeNode (attribute), attribute);
571 attribute = null;
572 state = WriteState.Content;
576 internal class Insertion
578 // AppendChild : last child / true
579 // InsertBefore : current node / false
580 // InsertAfter : current node / true
581 // PrependChild : first child / false
582 public XmlNode ParentNode;
583 public XmlNode InsertedNode;
584 public bool Afterward;
587 internal class Removal
589 public XmlNode OwnerNode;
590 public XmlNode NextSibling;
591 public XmlNode RemovedNode;
594 internal class AttributeUpdate
596 public XmlElement Element;
597 public XmlAttribute NewAttribute;
598 public XmlAttribute OldAttribute;
601 internal class XmlDocumentEditableNavigator : XPathNavigator, IHasXmlNode
603 static readonly bool isXmlDocumentNavigatorImpl;
605 static XmlDocumentEditableNavigator ()
607 isXmlDocumentNavigatorImpl =
608 (typeof (XmlDocumentEditableNavigator).Assembly
609 == typeof (XmlDocument).Assembly);
612 XPathEditableDocument document;
613 XPathNavigator navigator;
615 public XmlDocumentEditableNavigator (XPathEditableDocument doc)
617 document = doc;
618 if (isXmlDocumentNavigatorImpl)
619 navigator = new XmlDocumentNavigator (doc.Node);
620 else
621 navigator = doc.CreateNavigator ();
624 public XmlDocumentEditableNavigator (XmlDocumentEditableNavigator nav)
626 document = nav.document;
627 navigator = nav.navigator.Clone ();
630 public override string BaseURI {
631 get { return navigator.BaseURI; }
634 public override bool IsEmptyElement {
635 get { return navigator.IsEmptyElement; }
638 public override string LocalName {
639 get { return navigator.LocalName; }
642 public override XmlNameTable NameTable {
643 get { return navigator.NameTable; }
646 public override string Name {
647 get { return navigator.Name; }
650 public override string NamespaceURI {
651 get { return navigator.NamespaceURI; }
654 public override XPathNodeType NodeType {
655 get { return navigator.NodeType; }
658 public override string Prefix {
659 get { return navigator.Prefix; }
662 public override string Value {
663 get { return navigator.Value; }
666 public override XPathNavigator Clone ()
668 return new XmlDocumentEditableNavigator (this);
671 public override XPathNavigator CreateNavigator ()
673 return navigator.Clone ();
676 public XmlNode GetNode ()
678 return ((IHasXmlNode) navigator).GetNode ();
681 public override bool IsSamePosition (XPathNavigator other)
683 XmlDocumentEditableNavigator nav = other as XmlDocumentEditableNavigator;
684 if (nav != null)
685 return navigator.IsSamePosition (nav.navigator);
686 else
687 return navigator.IsSamePosition (nav);
690 public override bool MoveTo (XPathNavigator other)
692 XmlDocumentEditableNavigator nav = other as XmlDocumentEditableNavigator;
693 if (nav != null)
694 return navigator.MoveTo (nav.navigator);
695 else
696 return navigator.MoveTo (nav);
699 public override bool MoveToFirstAttribute ()
701 return navigator.MoveToFirstAttribute ();
704 public override bool MoveToFirstChild ()
706 return navigator.MoveToFirstChild ();
709 public override bool MoveToFirstNamespace (XPathNamespaceScope scope)
711 return navigator.MoveToFirstNamespace (scope);
714 public override bool MoveToId (string id)
716 return navigator.MoveToId (id);
719 public override bool MoveToNext ()
721 return navigator.MoveToNext ();
724 public override bool MoveToNextAttribute ()
726 return navigator.MoveToNextAttribute ();
729 public override bool MoveToNextNamespace (XPathNamespaceScope scope)
731 return navigator.MoveToNextNamespace (scope);
734 public override bool MoveToParent ()
736 return navigator.MoveToParent ();
739 public override bool MoveToPrevious ()
741 return navigator.MoveToPrevious ();
744 public override XmlWriter AppendChild ()
746 XmlNode n = ((IHasXmlNode) navigator).GetNode ();
747 if (n == null)
748 throw new InvalidOperationException ("Should not happen.");
749 return document.CreateInsertionWriter (n, null);
752 public override XmlWriter InsertBefore ()
754 XmlNode n = ((IHasXmlNode) navigator).GetNode ();
755 return document.CreateInsertionWriter (n.ParentNode, n.PreviousSibling);
758 public override XmlWriter CreateAttributes ()
760 XmlNode n = ((IHasXmlNode) navigator).GetNode ();
761 return document.CreateInsertionWriter (n, null);
764 public override bool DeleteSelf ()
766 XmlNode n = ((IHasXmlNode) navigator).GetNode ();
767 if (!navigator.MoveToNext ())
768 navigator.MoveToParent ();
769 return document.DeleteNode (n);
772 public override void SetValue (string value)
774 XmlNode n = ((IHasXmlNode) navigator).GetNode ();
775 foreach (XmlNode c in n.ChildNodes)
776 document.DeleteNode (c);
777 XmlWriter w = document.CreateInsertionWriter (n, null);
778 w.WriteValue (value);
779 w.Close ();
784 #endif