2 // Mono.Xml.XPath.XPathEditableDocument
5 // Atsushi Enomoto <atsushi@ximian.com>
9 // Yet another implementation of XPathEditableNavigator.
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 public class XPathEditableDocument
51 : IXPathNavigable, IXPathEditable,
52 IRevertibleChangeTracking, IChangeTracking, IXmlSerializable
55 public static void Main ()
59 XmlDocument doc = new XmlDocument ();
60 XPathEditableDocument pd = new XPathEditableDocument (doc);
61 XPathEditableNavigator nav = pd.CreateEditor ();
62 IChangeTracking xp = pd;
64 XPathDocument doc = new XPathDocument ();
65 XPathEditableNavigator nav = doc.CreateEditor ();
66 IChangeTracking xp = doc;
68 doc.LoadXml ("<root/>");
69 nav.MoveToFirstChild (); // root
70 XmlWriter w = nav.AppendChild ();
71 Console.WriteLine (((IChangeTracking) xp).IsChanged);
72 w.WriteElementString ("foo", "foo_text");
73 w.WriteElementString ("bar", "bar_text");
74 w.WriteStartElement ("hoge");
75 w.WriteAttributeString ("fuga", "fugafuga");
76 w.WriteAttributeString ("unya", "unyaunya");
77 w.WriteFullEndElement ();
80 w = nav.CreateAttributes ();
81 w.WriteStartAttribute ("namara");
82 w.WriteString ("mokera");
83 w.WriteEndAttribute ();
84 w.WriteAttributeString ("beccho", "giccho");
88 nav.MoveToFirstChild ();
89 nav.MoveToFirstChild ();
90 nav.DeleteCurrent (); // delete foo
91 Console.WriteLine (nav.Name);
93 Console.WriteLine (nav.Name);
94 Console.WriteLine (nav.MoveToFirstAttribute ());
95 nav.DeleteCurrent (); // delete fuga
97 doc.Save (Console.Out);
98 } catch (Exception ex) {
99 Console.WriteLine (ex);
104 XmlDocument document;
106 ArrayList changes = new ArrayList ();
108 public XPathEditableDocument (XmlDocument doc)
113 public XPathNavigator CreateNavigator ()
115 return document.CreateNavigator ();
118 public XPathEditableNavigator CreateEditor ()
120 return new XmlDocumentEditableNavigator (this);
123 public XmlWriter CreateWriter ()
125 return CreateEditor ().AppendChild ();
128 public bool HasChanges ()
133 #region IRevertibleChangeTracking/IChangeTracking
134 public bool IsChanged {
135 get { return changes.Count != 0; }
138 public void AcceptChanges ()
143 public void RejectChanges ()
145 for (int i = changes.Count - 1; i >= 0; i--) {
146 Insertion ins = changes [i] as Insertion;
148 ins.ParentNode.RemoveChild (ins.InsertedNode);
152 Removal rem = changes [i] as Removal;
154 if (rem.RemovedNode.NodeType == XmlNodeType.Attribute) {
155 XmlElement el = (XmlElement) rem.OwnerNode;
156 el.SetAttributeNode ((XmlAttribute) rem.RemovedNode);
159 rem.OwnerNode.InsertBefore (rem.RemovedNode, rem.NextSibling);
162 AttributeUpdate au = changes [i] as AttributeUpdate;
164 if (au.OldAttribute != null)
165 au.Element.SetAttributeNode (au.OldAttribute);
167 au.Element.RemoveAttributeNode (au.NewAttribute);
175 #region IXmlSerializable
176 public void WriteXml (XmlWriter writer)
178 throw new NotImplementedException ();
181 public void ReadXml (XmlReader reader)
183 throw new NotImplementedException ();
186 public XmlSchema GetSchema ()
188 throw new NotImplementedException ();
192 internal bool DeleteNode (XmlNode node)
194 Removal rem = new Removal ();
195 if (node.NodeType == XmlNodeType.Attribute) {
196 XmlAttribute attr = node as XmlAttribute;
197 rem.OwnerNode = attr.OwnerElement;
198 rem.RemovedNode = node;
199 attr.OwnerElement.RemoveAttributeNode (attr);
202 rem.OwnerNode = node.ParentNode;
203 rem.NextSibling = node.NextSibling;
204 rem.RemovedNode = node;
205 node.ParentNode.RemoveChild (node);
206 return rem.NextSibling != null;
210 internal XmlWriter CreateInsertionWriter (XmlNode owner, XmlNode previousSibling)
212 return new XmlDocumentInsertionWriter (owner, previousSibling, this);
215 internal XmlWriter CreateAttributesWriter (XmlNode owner)
217 return new XmlDocumentAttributeWriter (owner, this);
220 internal void AttributeUpdate (XmlElement element, XmlAttribute oldAttr, XmlAttribute newAttr)
222 AttributeUpdate au = new AttributeUpdate ();
223 au.Element = element;
224 au.OldAttribute = oldAttr;
225 au.NewAttribute = newAttr;
229 internal void AppendChild (XmlNode parent, XmlNode child)
231 Insertion ins = new Insertion ();
232 ins.ParentNode = parent;
233 ins.InsertedNode = child;
238 public class XmlDocumentInsertionWriter : XmlWriter
241 XmlNode previousSibling;
242 XPathEditableDocument document;
243 Stack nodeStack = new Stack ();
245 public XmlDocumentInsertionWriter (XmlNode owner, XmlNode previousSibling, XPathEditableDocument doc)
247 this.current = (XmlNode) owner;
249 throw new InvalidOperationException ();
250 this.previousSibling = previousSibling;
252 state = WriteState.Content;
256 XmlAttribute attribute;
258 public override WriteState WriteState {
259 get { return state; }
262 public override void Close ()
266 public override void Flush ()
270 public override string LookupPrefix (string ns)
272 return current.GetPrefixOfNamespace (ns);
275 public override void WriteStartAttribute (string prefix, string name, string ns)
277 if (state != WriteState.Content)
278 throw new InvalidOperationException ("Current state is not inside element. Cannot start attribute.");
279 attribute = current.OwnerDocument.CreateAttribute (prefix, name, ns);
280 state = WriteState.Attribute;
283 public override void WriteProcessingInstruction (string name, string value)
285 XmlProcessingInstruction pi = current.OwnerDocument.CreateProcessingInstruction (name, value);
286 current.AppendChild (pi);
287 document.AppendChild (current, pi);
290 public override void WriteComment (string text)
292 XmlComment comment = current.OwnerDocument.CreateComment (text);
293 current.AppendChild (comment);
294 document.AppendChild (current, comment);
297 public override void WriteCData (string text)
299 XmlCDataSection cdata = current.OwnerDocument.CreateCDataSection (text);
300 current.AppendChild (cdata);
301 document.AppendChild (current, cdata);
304 public override void WriteStartElement (string prefix, string name, string ns)
306 if (current.OwnerDocument == null)
307 throw new Exception ("Should not happen.");
308 XmlElement el = current.OwnerDocument.CreateElement (prefix, name, ns);
309 current.AppendChild (el);
310 document.AppendChild (current, el);
311 nodeStack.Push (current);
315 public override void WriteEndElement ()
317 if (nodeStack.Count == 0)
318 throw new InvalidOperationException ("No element is opened.");
319 current = nodeStack.Pop () as XmlNode;
322 public override void WriteFullEndElement ()
325 XmlElement el = current as XmlElement;
330 public override void WriteDocType (string name, string pubid, string systemId, string intsubset)
332 throw new NotSupportedException ();
335 public override void WriteStartDocument ()
337 throw new NotSupportedException ();
340 public override void WriteStartDocument (bool standalone)
342 throw new NotSupportedException ();
345 public override void WriteEndDocument ()
347 throw new NotSupportedException ();
350 public override void WriteBase64 (byte [] data, int start, int length)
352 WriteString (Convert.ToBase64String (data, start, length));
355 public override void WriteRaw (char [] raw, int start, int length)
357 throw new NotSupportedException ();
360 public override void WriteRaw (string raw)
362 throw new NotSupportedException ();
365 public override void WriteSurrogateCharEntity (char msb, char lsb)
367 throw new NotSupportedException ();
370 public override void WriteCharEntity (char c)
372 throw new NotSupportedException ();
375 public override void WriteEntityRef (string entname)
377 if (state != WriteState.Attribute)
378 throw new InvalidOperationException ("Current state is not inside attribute. Cannot write attribute value.");
379 attribute.AppendChild (attribute.OwnerDocument.CreateEntityReference (entname));
382 public override void WriteChars (char [] data, int start, int length)
384 WriteString (new string (data, start, length));
387 public override void WriteString (string text)
389 if (attribute != null)
390 attribute.Value += text;
392 current.AppendChild (current.OwnerDocument.CreateTextNode (text));
395 public override void WriteWhitespace (string text)
397 if (state != WriteState.Attribute)
398 current.AppendChild (current.OwnerDocument.CreateTextNode (text));
399 else if (attribute.ChildNodes.Count == 0)
400 attribute.AppendChild (attribute.OwnerDocument.CreateWhitespace (text));
402 attribute.Value += text;
405 public override void WriteEndAttribute ()
407 XmlElement element = current as XmlElement;
408 if (state != WriteState.Attribute || element == null)
409 throw new InvalidOperationException ("Current state is not inside attribute. Cannot close attribute.");
410 document.AttributeUpdate (element, element.SetAttributeNode (attribute), attribute);
412 state = WriteState.Content;
416 public class XmlDocumentAttributeWriter : XmlWriter
419 XPathEditableDocument document;
421 public XmlDocumentAttributeWriter (XmlNode owner, XPathEditableDocument doc)
423 element = owner as XmlElement;
425 throw new ArgumentException ("To write attributes, current node must be an element.");
426 state = WriteState.Content;
431 XmlAttribute attribute;
433 public override WriteState WriteState {
434 get { return state; }
437 public override void Close ()
441 public override void Flush ()
445 public override string LookupPrefix (string ns)
447 return element.GetPrefixOfNamespace (ns);
450 public override void WriteStartAttribute (string prefix, string name, string ns)
452 if (state != WriteState.Content)
453 throw new InvalidOperationException ("Current state is not inside element. Cannot start attribute.");
454 attribute = element.OwnerDocument.CreateAttribute (prefix, name, ns);
455 state = WriteState.Attribute;
458 public override void WriteProcessingInstruction (string name, string value)
460 throw new NotSupportedException ();
463 public override void WriteComment (string text)
465 throw new NotSupportedException ();
468 public override void WriteCData (string text)
470 throw new NotSupportedException ();
473 public override void WriteStartElement (string prefix, string name, string ns)
475 throw new NotSupportedException ();
478 public override void WriteEndElement ()
480 throw new NotSupportedException ();
483 public override void WriteFullEndElement ()
485 throw new NotSupportedException ();
488 public override void WriteDocType (string name, string pubid, string systemId, string intsubset)
490 throw new NotSupportedException ();
493 public override void WriteStartDocument ()
495 throw new NotSupportedException ();
498 public override void WriteStartDocument (bool standalone)
500 throw new NotSupportedException ();
503 public override void WriteEndDocument ()
505 throw new NotSupportedException ();
508 public override void WriteBase64 (byte [] data, int start, int length)
510 throw new NotSupportedException ();
513 public override void WriteRaw (char [] raw, int start, int length)
515 throw new NotSupportedException ();
518 public override void WriteRaw (string raw)
520 throw new NotSupportedException ();
523 public override void WriteSurrogateCharEntity (char msb, char lsb)
525 throw new NotSupportedException ();
528 public override void WriteCharEntity (char c)
530 throw new NotSupportedException ();
533 public override void WriteEntityRef (string entname)
535 if (state != WriteState.Attribute)
536 throw new InvalidOperationException ("Current state is not inside attribute. Cannot write attribute value.");
537 attribute.AppendChild (attribute.OwnerDocument.CreateEntityReference (entname));
540 public override void WriteChars (char [] data, int start, int length)
542 WriteString (new string (data, start, length));
545 public override void WriteString (string text)
547 if (state != WriteState.Attribute)
548 throw new InvalidOperationException ("Current state is not inside attribute. Cannot write attribute value.");
549 attribute.Value += text;
552 public override void WriteWhitespace (string text)
554 if (state != WriteState.Attribute)
555 throw new InvalidOperationException ("Current state is not inside attribute. Cannot write attribute value.");
556 if (attribute.ChildNodes.Count == 0)
557 attribute.AppendChild (attribute.OwnerDocument.CreateWhitespace (text));
559 attribute.Value += text;
562 public override void WriteEndAttribute ()
564 if (state != WriteState.Attribute)
565 throw new InvalidOperationException ("Current state is not inside attribute. Cannot close attribute.");
566 document.AttributeUpdate (element, element.SetAttributeNode (attribute), attribute);
568 state = WriteState.Content;
572 public class Insertion
574 // AppendChild : last child / true
575 // InsertBefore : current node / false
576 // InsertAfter : current node / true
577 // PrependChild : first child / false
578 public XmlNode ParentNode;
579 public XmlNode InsertedNode;
580 public bool Afterward;
585 public XmlNode OwnerNode;
586 public XmlNode NextSibling;
587 public XmlNode RemovedNode;
590 public class AttributeUpdate
592 public XmlElement Element;
593 public XmlAttribute NewAttribute;
594 public XmlAttribute OldAttribute;
597 public class XmlDocumentEditableNavigator : XPathEditableNavigator, IHasXmlNode
599 XPathEditableDocument document;
600 XPathNavigator navigator;
602 public XmlDocumentEditableNavigator (XPathEditableDocument doc)
605 navigator = doc.CreateNavigator ();
608 public XmlDocumentEditableNavigator (XmlDocumentEditableNavigator nav)
610 document = nav.document;
611 navigator = nav.navigator.Clone ();
614 public override string BaseURI {
615 get { return navigator.BaseURI; }
618 public override bool IsEmptyElement {
619 get { return navigator.IsEmptyElement; }
622 public override string LocalName {
623 get { return navigator.LocalName; }
626 public override XmlNameTable NameTable {
627 get { return navigator.NameTable; }
630 public override string Name {
631 get { return navigator.Name; }
634 public override string NamespaceURI {
635 get { return navigator.NamespaceURI; }
638 public override XPathNodeType NodeType {
639 get { return navigator.NodeType; }
642 public override string Prefix {
643 get { return navigator.Prefix; }
646 public override string Value {
647 get { return navigator.Value; }
650 public override XPathNavigator Clone ()
652 return new XmlDocumentEditableNavigator (this);
655 public override XPathNavigator CreateNavigator ()
657 return navigator.Clone ();
660 public XmlNode GetNode ()
662 return ((IHasXmlNode) navigator).GetNode ();
665 public override bool IsSamePosition (XPathNavigator other)
667 XmlDocumentEditableNavigator nav = other as XmlDocumentEditableNavigator;
669 return navigator.IsSamePosition (nav.navigator);
671 return navigator.IsSamePosition (nav);
674 public override bool MoveTo (XPathNavigator other)
676 XmlDocumentEditableNavigator nav = other as XmlDocumentEditableNavigator;
678 return navigator.MoveTo (nav.navigator);
680 return navigator.MoveTo (nav);
683 public override bool MoveToFirstAttribute ()
685 return navigator.MoveToFirstAttribute ();
688 public override bool MoveToFirstChild ()
690 return navigator.MoveToFirstChild ();
693 public override bool MoveToFirstNamespace (XPathNamespaceScope scope)
695 return navigator.MoveToFirstNamespace (scope);
698 public override bool MoveToId (string id)
700 return navigator.MoveToId (id);
703 public override bool MoveToNext ()
705 return navigator.MoveToNext ();
708 public override bool MoveToNextAttribute ()
710 return navigator.MoveToNextAttribute ();
713 public override bool MoveToNextNamespace (XPathNamespaceScope scope)
715 return navigator.MoveToNextNamespace (scope);
718 public override bool MoveToParent ()
720 return navigator.MoveToParent ();
723 public override bool MoveToPrevious ()
725 return navigator.MoveToPrevious ();
728 public override void Validate (XmlSchemaSet schemas, ValidationEventHandler handler)
730 throw new NotImplementedException ();
732 // FIXME: use handler
733 XmlReaderSettings settings = new XmlReaderSettings ();
734 settings.Schemas.Add (schemas);
735 settings.NameTable = this.NameTable;
736 settings.XsdValidate = true;
737 settings.DtdValidate = false;
738 XmlReader xvr = XmlReader.Create (new XPathNavigatorReader (this), settings);
744 public override XmlWriter AppendChild ()
746 XmlNode n = ((IHasXmlNode) navigator).GetNode ();
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 DeleteCurrent ()
766 XmlNode n = ((IHasXmlNode) navigator).GetNode ();
767 if (!navigator.MoveToNext ())
768 navigator.MoveToParent ();
769 return document.DeleteNode (n);
772 public override void SetValue (object 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 // FIXME: Hmm, it does not look like using it.
779 w.WriteFromObject (value);