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 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
);
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 ()
326 XmlElement el
= current
as XmlElement
;
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
;
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
));
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
);
416 state
= WriteState
.Content
;
420 internal class XmlDocumentAttributeWriter
: XmlWriter
423 XPathEditableDocument document
;
425 public XmlDocumentAttributeWriter (XmlNode owner
, XPathEditableDocument doc
)
427 element
= owner
as XmlElement
;
429 throw new ArgumentException ("To write attributes, current node must be an element.");
430 state
= WriteState
.Content
;
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
));
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
);
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
)
618 if (isXmlDocumentNavigatorImpl
)
619 navigator
= new XmlDocumentNavigator (doc
.Node
);
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
;
685 return navigator
.IsSamePosition (nav
.navigator
);
687 return navigator
.IsSamePosition (nav
);
690 public override bool MoveTo (XPathNavigator other
)
692 XmlDocumentEditableNavigator nav
= other
as XmlDocumentEditableNavigator
;
694 return navigator
.MoveTo (nav
.navigator
);
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 ();
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);