2 // Mono.Xml.XPath.XPathEditableDocument
5 // Atsushi Enomoto <atsushi@ximian.com>
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:
26 // The above copyright notice and this permission notice shall be
27 // included in all copies or substantial portions of the Software.
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.
40 using System.Collections;
41 using System.ComponentModel;
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 ()
57 XmlDocument doc = new XmlDocument ();
58 XPathEditableDocument pd = new XPathEditableDocument (doc);
59 XPathNavigator nav = pd.CreateNavigator ();
60 IChangeTracking xp = pd;
62 XPathDocument doc = new XPathDocument ();
63 XPathNavigator nav = doc.CreateNavigator ();
64 IChangeTracking xp = doc;
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 ();
78 w = nav.CreateAttributes ();
79 w.WriteStartAttribute ("namara");
80 w.WriteString ("mokera");
81 w.WriteEndAttribute ();
82 w.WriteAttributeString ("beccho", "giccho");
86 nav.MoveToFirstChild ();
87 nav.MoveToFirstChild ();
88 nav.DeleteSelf (); // delete foo
89 Console.WriteLine (nav.Name);
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);
104 ArrayList changes = new ArrayList ();
106 public XPathEditableDocument (XmlNode node)
111 public virtual bool CanEdit {
115 public XmlNode Node {
119 public XPathNavigator CreateNavigator ()
121 return new XmlDocumentEditableNavigator (this);
124 public XmlWriter CreateWriter ()
126 return CreateNavigator ().AppendChild ();
129 public bool HasChanges ()
134 #region IRevertibleChangeTracking/IChangeTracking
135 public bool IsChanged {
136 get { return changes.Count != 0; }
139 public void AcceptChanges ()
144 public void RejectChanges ()
146 for (int i = changes.Count - 1; i >= 0; i--) {
147 Insertion ins = changes [i] as Insertion;
149 ins.ParentNode.RemoveChild (ins.InsertedNode);
153 Removal rem = changes [i] as Removal;
155 if (rem.RemovedNode.NodeType == XmlNodeType.Attribute) {
156 XmlElement el = (XmlElement) rem.OwnerNode;
157 el.SetAttributeNode ((XmlAttribute) rem.RemovedNode);
160 rem.OwnerNode.InsertBefore (rem.RemovedNode, rem.NextSibling);
163 AttributeUpdate au = changes [i] as AttributeUpdate;
165 if (au.OldAttribute != null)
166 au.Element.SetAttributeNode (au.OldAttribute);
168 au.Element.RemoveAttributeNode (au.NewAttribute);
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 ();
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);
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;
230 internal void AppendChild (XmlNode parent, XmlNode child)
232 Insertion ins = new Insertion ();
233 ins.ParentNode = parent;
234 ins.InsertedNode = child;
239 internal class XmlDocumentInsertionWriter : XmlWriter
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;
250 throw new InvalidOperationException ();
251 this.previousSibling = previousSibling;
253 state = WriteState.Content;
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 XmlDocument doc = current.OwnerDocument;
309 doc = current as XmlDocument;
311 throw new SystemException ("Should not happen.");
312 XmlElement el = doc.CreateElement (prefix, name, ns);
313 current.AppendChild (el);
314 document.AppendChild (current, el);
315 nodeStack.Push (current);
319 public override void WriteEndElement ()
321 if (nodeStack.Count == 0)
322 throw new InvalidOperationException ("No element is opened.");
323 current = nodeStack.Pop () as XmlNode;
326 public override void WriteFullEndElement ()
329 XmlElement el = current as XmlElement;
334 public override void WriteDocType (string name, string pubid, string systemId, string intsubset)
336 throw new NotSupportedException ();
339 public override void WriteStartDocument ()
341 throw new NotSupportedException ();
344 public override void WriteStartDocument (bool standalone)
346 throw new NotSupportedException ();
349 public override void WriteEndDocument ()
351 throw new NotSupportedException ();
354 public override void WriteBase64 (byte [] data, int start, int length)
356 WriteString (Convert.ToBase64String (data, start, length));
359 public override void WriteRaw (char [] raw, int start, int length)
361 throw new NotSupportedException ();
364 public override void WriteRaw (string raw)
366 throw new NotSupportedException ();
369 public override void WriteSurrogateCharEntity (char msb, char lsb)
371 throw new NotSupportedException ();
374 public override void WriteCharEntity (char c)
376 throw new NotSupportedException ();
379 public override void WriteEntityRef (string entname)
381 if (state != WriteState.Attribute)
382 throw new InvalidOperationException ("Current state is not inside attribute. Cannot write attribute value.");
383 attribute.AppendChild (attribute.OwnerDocument.CreateEntityReference (entname));
386 public override void WriteChars (char [] data, int start, int length)
388 WriteString (new string (data, start, length));
391 public override void WriteString (string text)
393 if (attribute != null)
394 attribute.Value += text;
396 XmlText t = current.OwnerDocument.CreateTextNode (text);
397 current.AppendChild (t);
398 document.AppendChild (current, t);
402 public override void WriteWhitespace (string text)
404 if (state != WriteState.Attribute)
405 current.AppendChild (current.OwnerDocument.CreateTextNode (text));
406 else if (attribute.ChildNodes.Count == 0)
407 attribute.AppendChild (attribute.OwnerDocument.CreateWhitespace (text));
409 attribute.Value += text;
412 public override void WriteEndAttribute ()
414 XmlElement element = current as XmlElement;
415 if (state != WriteState.Attribute || element == null)
416 throw new InvalidOperationException ("Current state is not inside attribute. Cannot close attribute.");
417 document.AttributeUpdate (element, element.SetAttributeNode (attribute), attribute);
419 state = WriteState.Content;
423 internal class XmlDocumentAttributeWriter : XmlWriter
426 XPathEditableDocument document;
428 public XmlDocumentAttributeWriter (XmlNode owner, XPathEditableDocument doc)
430 element = owner as XmlElement;
432 throw new ArgumentException ("To write attributes, current node must be an element.");
433 state = WriteState.Content;
438 XmlAttribute attribute;
440 public override WriteState WriteState {
441 get { return state; }
444 public override void Close ()
448 public override void Flush ()
452 public override string LookupPrefix (string ns)
454 return element.GetPrefixOfNamespace (ns);
457 public override void WriteStartAttribute (string prefix, string name, string ns)
459 if (state != WriteState.Content)
460 throw new InvalidOperationException ("Current state is not inside element. Cannot start attribute.");
461 attribute = element.OwnerDocument.CreateAttribute (prefix, name, ns);
462 state = WriteState.Attribute;
465 public override void WriteProcessingInstruction (string name, string value)
467 throw new NotSupportedException ();
470 public override void WriteComment (string text)
472 throw new NotSupportedException ();
475 public override void WriteCData (string text)
477 throw new NotSupportedException ();
480 public override void WriteStartElement (string prefix, string name, string ns)
482 throw new NotSupportedException ();
485 public override void WriteEndElement ()
487 throw new NotSupportedException ();
490 public override void WriteFullEndElement ()
492 throw new NotSupportedException ();
495 public override void WriteDocType (string name, string pubid, string systemId, string intsubset)
497 throw new NotSupportedException ();
500 public override void WriteStartDocument ()
502 throw new NotSupportedException ();
505 public override void WriteStartDocument (bool standalone)
507 throw new NotSupportedException ();
510 public override void WriteEndDocument ()
512 throw new NotSupportedException ();
515 public override void WriteBase64 (byte [] data, int start, int length)
517 throw new NotSupportedException ();
520 public override void WriteRaw (char [] raw, int start, int length)
522 throw new NotSupportedException ();
525 public override void WriteRaw (string raw)
527 throw new NotSupportedException ();
530 public override void WriteSurrogateCharEntity (char msb, char lsb)
532 throw new NotSupportedException ();
535 public override void WriteCharEntity (char c)
537 throw new NotSupportedException ();
540 public override void WriteEntityRef (string entname)
542 if (state != WriteState.Attribute)
543 throw new InvalidOperationException ("Current state is not inside attribute. Cannot write attribute value.");
544 attribute.AppendChild (attribute.OwnerDocument.CreateEntityReference (entname));
547 public override void WriteChars (char [] data, int start, int length)
549 WriteString (new string (data, start, length));
552 public override void WriteString (string text)
554 if (state != WriteState.Attribute)
555 throw new InvalidOperationException ("Current state is not inside attribute. Cannot write attribute value.");
556 attribute.Value += text;
559 public override void WriteWhitespace (string text)
561 if (state != WriteState.Attribute)
562 throw new InvalidOperationException ("Current state is not inside attribute. Cannot write attribute value.");
563 if (attribute.ChildNodes.Count == 0)
564 attribute.AppendChild (attribute.OwnerDocument.CreateWhitespace (text));
566 attribute.Value += text;
569 public override void WriteEndAttribute ()
571 if (state != WriteState.Attribute)
572 throw new InvalidOperationException ("Current state is not inside attribute. Cannot close attribute.");
573 document.AttributeUpdate (element, element.SetAttributeNode (attribute), attribute);
575 state = WriteState.Content;
579 internal class Insertion
581 // AppendChild : last child / true
582 // InsertBefore : current node / false
583 // InsertAfter : current node / true
584 // PrependChild : first child / false
585 public XmlNode ParentNode;
586 public XmlNode InsertedNode;
587 public bool Afterward;
590 internal class Removal
592 public XmlNode OwnerNode;
593 public XmlNode NextSibling;
594 public XmlNode RemovedNode;
597 internal class AttributeUpdate
599 public XmlElement Element;
600 public XmlAttribute NewAttribute;
601 public XmlAttribute OldAttribute;
604 internal class XmlDocumentEditableNavigator : XPathNavigator, IHasXmlNode
606 static readonly bool isXmlDocumentNavigatorImpl;
608 static XmlDocumentEditableNavigator ()
610 isXmlDocumentNavigatorImpl =
611 (typeof (XmlDocumentEditableNavigator).Assembly
612 == typeof (XmlDocument).Assembly);
615 XPathEditableDocument document;
616 XPathNavigator navigator;
618 public XmlDocumentEditableNavigator (XPathEditableDocument doc)
621 if (isXmlDocumentNavigatorImpl)
622 navigator = new XmlDocumentNavigator (doc.Node);
624 navigator = doc.CreateNavigator ();
627 public XmlDocumentEditableNavigator (XmlDocumentEditableNavigator nav)
629 document = nav.document;
630 navigator = nav.navigator.Clone ();
633 public override string BaseURI {
634 get { return navigator.BaseURI; }
637 public override bool IsEmptyElement {
638 get { return navigator.IsEmptyElement; }
641 public override string LocalName {
642 get { return navigator.LocalName; }
645 public override XmlNameTable NameTable {
646 get { return navigator.NameTable; }
649 public override string Name {
650 get { return navigator.Name; }
653 public override string NamespaceURI {
654 get { return navigator.NamespaceURI; }
657 public override XPathNodeType NodeType {
658 get { return navigator.NodeType; }
661 public override string Prefix {
662 get { return navigator.Prefix; }
665 public override string Value {
666 get { return navigator.Value; }
669 public override XPathNavigator Clone ()
671 return new XmlDocumentEditableNavigator (this);
674 public override XPathNavigator CreateNavigator ()
676 return navigator.Clone ();
679 public XmlNode GetNode ()
681 return ((IHasXmlNode) navigator).GetNode ();
684 public override bool IsSamePosition (XPathNavigator other)
686 XmlDocumentEditableNavigator nav = other as XmlDocumentEditableNavigator;
688 return navigator.IsSamePosition (nav.navigator);
690 return navigator.IsSamePosition (nav);
693 public override bool MoveTo (XPathNavigator other)
695 XmlDocumentEditableNavigator nav = other as XmlDocumentEditableNavigator;
697 return navigator.MoveTo (nav.navigator);
699 return navigator.MoveTo (nav);
702 public override bool MoveToFirstAttribute ()
704 return navigator.MoveToFirstAttribute ();
707 public override bool MoveToFirstChild ()
709 return navigator.MoveToFirstChild ();
712 public override bool MoveToFirstNamespace (XPathNamespaceScope scope)
714 return navigator.MoveToFirstNamespace (scope);
717 public override bool MoveToId (string id)
719 return navigator.MoveToId (id);
722 public override bool MoveToNext ()
724 return navigator.MoveToNext ();
727 public override bool MoveToNextAttribute ()
729 return navigator.MoveToNextAttribute ();
732 public override bool MoveToNextNamespace (XPathNamespaceScope scope)
734 return navigator.MoveToNextNamespace (scope);
737 public override bool MoveToParent ()
739 return navigator.MoveToParent ();
742 public override bool MoveToPrevious ()
744 return navigator.MoveToPrevious ();
747 public override XmlWriter AppendChild ()
749 XmlNode n = ((IHasXmlNode) navigator).GetNode ();
751 throw new InvalidOperationException ("Should not happen.");
752 return document.CreateInsertionWriter (n, null);
755 public override XmlWriter InsertBefore ()
757 XmlNode n = ((IHasXmlNode) navigator).GetNode ();
758 return document.CreateInsertionWriter (n.ParentNode, n.PreviousSibling);
761 public override XmlWriter CreateAttributes ()
763 XmlNode n = ((IHasXmlNode) navigator).GetNode ();
764 return document.CreateInsertionWriter (n, null);
767 public override void DeleteSelf ()
769 XmlNode n = ((IHasXmlNode) navigator).GetNode ();
770 if (!navigator.MoveToNext ())
771 navigator.MoveToParent ();
772 document.DeleteNode (n);
775 public override void SetValue (string value)
777 XmlNode n = ((IHasXmlNode) navigator).GetNode ();
778 foreach (XmlNode c in n.ChildNodes)
779 document.DeleteNode (c);
780 XmlWriter w = document.CreateInsertionWriter (n, null);
781 w.WriteValue (value);