bda7695922584adbb683a4056547c94f871632aa
[mono.git] / mcs / class / System.XML / Mono.Xml.XPath / XPathEditableDocument.cs
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)
11 //
12 // By rewriting XPathEditableDocument.CreateNavigator() as just to 
13 // create XmlDocumentNavigator, XmlDocumentEditableNavigator could be used 
14 // as to implement XmlDocument.CreateNavigator().
15 //
16
17 //
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.
36 //
37 #if NET_2_0
38
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;
47
48 namespace Mono.Xml.XPath
49 {
50         internal class XPathEditableDocument : IXPathNavigable
51         {
52                 XmlNode node;
53
54                 public XPathEditableDocument (XmlNode node)
55                 {
56                         this.node = node;
57                 }
58
59                 public XmlNode Node {
60                         get { return node; }
61                 }
62
63                 public XPathNavigator CreateNavigator ()
64                 {
65                         return new XmlDocumentEditableNavigator (this);
66                 }
67         }
68
69         internal delegate void XmlWriterClosedEventHandler (
70                 XmlWriter writer);
71
72         internal class XmlDocumentInsertionWriter : XmlWriter
73         {
74                 XmlNode parent;
75                 XmlNode current;
76                 XmlNode nextSibling;
77                 Stack nodeStack = new Stack ();
78
79                 public XmlDocumentInsertionWriter (XmlNode owner, XmlNode nextSibling)
80                 {
81                         this.parent = (XmlNode) owner;
82                         if (parent == null)
83                                 throw new InvalidOperationException ();
84                         switch (parent.NodeType) {
85                         case XmlNodeType.Document:
86                                 current = ((XmlDocument) parent).CreateDocumentFragment ();
87                                 break;
88                         case XmlNodeType.DocumentFragment:
89                         case XmlNodeType.Element:
90                                 current = parent.OwnerDocument.CreateDocumentFragment ();
91                                 break;
92                         default:
93                                 throw new InvalidOperationException (String.Format ("Insertion into {0} node is not allowed.", parent.NodeType));
94                         }
95                         this.nextSibling = nextSibling;
96                         state = WriteState.Content;
97                 }
98
99                 WriteState state;
100                 XmlAttribute attribute;
101
102                 public override WriteState WriteState {
103                         get { return state; }
104                 }
105
106                 public override void Close ()
107                 {
108                         while (nodeStack.Count > 0) {
109                                 XmlNode n = nodeStack.Pop () as XmlNode;
110                                 n.AppendChild (current);
111                                 current = n;
112                         }
113                         parent.InsertBefore ((XmlDocumentFragment) current, nextSibling);
114                         if (Closed != null)
115                                 Closed (this);
116                 }
117
118                 internal event XmlWriterClosedEventHandler Closed;
119
120                 internal XmlNode AppendedFirstChild;
121
122                 public override void Flush ()
123                 {
124                 }
125
126                 public override string LookupPrefix (string ns)
127                 {
128                         return current.GetPrefixOfNamespace (ns);
129                 }
130
131                 public override void WriteStartAttribute (string prefix, string name, string ns)
132                 {
133                         if (state != WriteState.Content)
134                                 throw new InvalidOperationException ("Current state is not inside element. Cannot start attribute.");
135                         attribute = current.OwnerDocument.CreateAttribute (prefix, name, ns);
136                         state = WriteState.Attribute;
137                 }
138
139                 public override void WriteProcessingInstruction (string name, string value)
140                 {
141                         XmlProcessingInstruction pi = current.OwnerDocument.CreateProcessingInstruction (name, value);
142                         current.AppendChild (pi);
143                 }
144
145                 public override void WriteComment (string text)
146                 {
147                         XmlComment comment = current.OwnerDocument.CreateComment (text);
148                         current.AppendChild (comment);
149                 }
150
151                 public override void WriteCData (string text)
152                 {
153                         XmlCDataSection cdata = current.OwnerDocument.CreateCDataSection (text);
154                         current.AppendChild (cdata);
155                 }
156
157                 public override void WriteStartElement (string prefix, string name, string ns)
158                 {
159                         XmlElement el = current.OwnerDocument.CreateElement (prefix, name, ns);
160                         current.AppendChild (el);
161                         nodeStack.Push (current);
162                         current = el;
163                 }
164
165                 public override void WriteEndElement ()
166                 {
167                         if (nodeStack.Count == 0)
168                                 throw new InvalidOperationException ("No element is opened.");
169                         current = nodeStack.Pop () as XmlNode;
170                 }
171
172                 public override void WriteFullEndElement ()
173                 {
174                         XmlElement el = current as XmlElement;
175                         if (el != null)
176                                 el.IsEmpty = false;
177                         WriteEndElement ();
178                 }
179
180                 public override void WriteDocType (string name, string pubid, string systemId, string intsubset)
181                 {
182                         throw new NotSupportedException ();
183                 }
184
185                 public override void WriteStartDocument ()
186                 {
187                         throw new NotSupportedException ();
188                 }
189
190                 public override void WriteStartDocument (bool standalone)
191                 {
192                         throw new NotSupportedException ();
193                 }
194
195                 public override void WriteEndDocument ()
196                 {
197                         throw new NotSupportedException ();
198                 }
199
200                 public override void WriteBase64 (byte [] data, int start, int length)
201                 {
202                         WriteString (Convert.ToBase64String (data, start, length));
203                 }
204
205                 public override void WriteRaw (char [] raw, int start, int length)
206                 {
207                         throw new NotSupportedException ();
208                 }
209
210                 public override void WriteRaw (string raw)
211                 {
212                         throw new NotSupportedException ();
213                 }
214
215                 public override void WriteSurrogateCharEntity (char msb, char lsb)
216                 {
217                         throw new NotSupportedException ();
218                 }
219
220                 public override void WriteCharEntity (char c)
221                 {
222                         throw new NotSupportedException ();
223                 }
224
225                 public override void WriteEntityRef (string entname)
226                 {
227                         if (state != WriteState.Attribute)
228                                 throw new InvalidOperationException ("Current state is not inside attribute. Cannot write attribute value.");
229                         attribute.AppendChild (attribute.OwnerDocument.CreateEntityReference (entname));
230                 }
231
232                 public override void WriteChars (char [] data, int start, int length)
233                 {
234                         WriteString (new string (data, start, length));
235                 }
236
237                 public override void WriteString (string text)
238                 {
239                         if (attribute != null)
240                                 attribute.Value += text;
241                         else {
242                                 XmlText t = current.OwnerDocument.CreateTextNode (text);
243                                 current.AppendChild (t);
244                         }
245                 }
246
247                 public override void WriteWhitespace (string text)
248                 {
249                         if (state != WriteState.Attribute)
250                                 current.AppendChild (current.OwnerDocument.CreateTextNode (text));
251                         else if (attribute.ChildNodes.Count == 0)
252                                 attribute.AppendChild (attribute.OwnerDocument.CreateWhitespace (text));
253                         else
254                                 attribute.Value += text;
255                 }
256
257                 public override void WriteEndAttribute ()
258                 {
259                         XmlElement element = current as XmlElement;
260                         if (state != WriteState.Attribute || element == null)
261                                 throw new InvalidOperationException ("Current state is not inside attribute. Cannot close attribute.");
262                         element.SetAttributeNode (attribute);
263                         attribute = null;
264                         state = WriteState.Content;
265                 }
266         }
267
268         internal class XmlDocumentAttributeWriter : XmlWriter
269         {
270                 XmlElement element;
271
272                 public XmlDocumentAttributeWriter (XmlNode owner)
273                 {
274                         element = owner as XmlElement;
275                         if (element == null)
276                                 throw new ArgumentException ("To write attributes, current node must be an element.");
277                         state = WriteState.Content;
278                 }
279
280                 WriteState state;
281                 XmlAttribute attribute;
282
283                 public override WriteState WriteState {
284                         get { return state; }
285                 }
286
287                 public override void Close ()
288                 {
289                 }
290
291                 public override void Flush ()
292                 {
293                 }
294
295                 public override string LookupPrefix (string ns)
296                 {
297                         return element.GetPrefixOfNamespace (ns);
298                 }
299
300                 public override void WriteStartAttribute (string prefix, string name, string ns)
301                 {
302                         if (state != WriteState.Content)
303                                 throw new InvalidOperationException ("Current state is not inside element. Cannot start attribute.");
304                         attribute = element.OwnerDocument.CreateAttribute (prefix, name, ns);
305                         state = WriteState.Attribute;
306                 }
307
308                 public override void WriteProcessingInstruction (string name, string value)
309                 {
310                         throw new NotSupportedException ();
311                 }
312
313                 public override void WriteComment (string text)
314                 {
315                         throw new NotSupportedException ();
316                 }
317
318                 public override void WriteCData (string text)
319                 {
320                         throw new NotSupportedException ();
321                 }
322
323                 public override void WriteStartElement (string prefix, string name, string ns)
324                 {
325                         throw new NotSupportedException ();
326                 }
327
328                 public override void WriteEndElement ()
329                 {
330                         throw new NotSupportedException ();
331                 }
332
333                 public override void WriteFullEndElement ()
334                 {
335                         throw new NotSupportedException ();
336                 }
337
338                 public override void WriteDocType (string name, string pubid, string systemId, string intsubset)
339                 {
340                         throw new NotSupportedException ();
341                 }
342
343                 public override void WriteStartDocument ()
344                 {
345                         throw new NotSupportedException ();
346                 }
347
348                 public override void WriteStartDocument (bool standalone)
349                 {
350                         throw new NotSupportedException ();
351                 }
352
353                 public override void WriteEndDocument ()
354                 {
355                         throw new NotSupportedException ();
356                 }
357
358                 public override void WriteBase64 (byte [] data, int start, int length)
359                 {
360                         throw new NotSupportedException ();
361                 }
362
363                 public override void WriteRaw (char [] raw, int start, int length)
364                 {
365                         throw new NotSupportedException ();
366                 }
367
368                 public override void WriteRaw (string raw)
369                 {
370                         throw new NotSupportedException ();
371                 }
372
373                 public override void WriteSurrogateCharEntity (char msb, char lsb)
374                 {
375                         throw new NotSupportedException ();
376                 }
377
378                 public override void WriteCharEntity (char c)
379                 {
380                         throw new NotSupportedException ();
381                 }
382
383                 public override void WriteEntityRef (string entname)
384                 {
385                         if (state != WriteState.Attribute)
386                                 throw new InvalidOperationException ("Current state is not inside attribute. Cannot write attribute value.");
387                         attribute.AppendChild (attribute.OwnerDocument.CreateEntityReference (entname));
388                 }
389
390                 public override void WriteChars (char [] data, int start, int length)
391                 {
392                         WriteString (new string (data, start, length));
393                 }
394
395                 public override void WriteString (string text)
396                 {
397                         if (state != WriteState.Attribute)
398                                 throw new InvalidOperationException ("Current state is not inside attribute. Cannot write attribute value.");
399                         attribute.Value += text;
400                 }
401
402                 public override void WriteWhitespace (string text)
403                 {
404                         if (state != WriteState.Attribute)
405                                 throw new InvalidOperationException ("Current state is not inside attribute. Cannot write attribute value.");
406                         if (attribute.ChildNodes.Count == 0)
407                                 attribute.AppendChild (attribute.OwnerDocument.CreateWhitespace (text));
408                         else
409                                 attribute.Value += text;
410                 }
411
412                 public override void WriteEndAttribute ()
413                 {
414                         if (state != WriteState.Attribute)
415                                 throw new InvalidOperationException ("Current state is not inside attribute. Cannot close attribute.");
416                         element.SetAttributeNode (attribute);
417                         attribute = null;
418                         state = WriteState.Content;
419                 }
420         }
421
422         internal class XmlDocumentEditableNavigator : XPathNavigator, IHasXmlNode
423         {
424                 static readonly bool isXmlDocumentNavigatorImpl;
425                 
426                 static XmlDocumentEditableNavigator ()
427                 {
428                         isXmlDocumentNavigatorImpl =
429                                 (typeof (XmlDocumentEditableNavigator).Assembly 
430                                 == typeof (XmlDocument).Assembly);
431                 }
432
433                 XPathEditableDocument document;
434                 XPathNavigator navigator;
435
436                 public XmlDocumentEditableNavigator (XPathEditableDocument doc)
437                 {
438                         document = doc;
439                         if (isXmlDocumentNavigatorImpl)
440                                 navigator = new XmlDocumentNavigator (doc.Node);
441                         else
442                                 navigator = doc.CreateNavigator ();
443                 }
444
445                 public XmlDocumentEditableNavigator (XmlDocumentEditableNavigator nav)
446                 {
447                         document = nav.document;
448                         navigator = nav.navigator.Clone ();
449                 }
450
451                 public override string BaseURI {
452                         get { return navigator.BaseURI; }
453                 }
454
455                 public override bool CanEdit {
456                         get { return true; }
457                 }
458
459                 public override bool IsEmptyElement {
460                         get { return navigator.IsEmptyElement; }
461                 }
462
463                 public override string LocalName {
464                         get { return navigator.LocalName; }
465                 }
466
467                 public override XmlNameTable NameTable {
468                         get { return navigator.NameTable; }
469                 }
470
471                 public override string Name {
472                         get { return navigator.Name; }
473                 }
474
475                 public override string NamespaceURI {
476                         get { return navigator.NamespaceURI; }
477                 }
478
479                 public override XPathNodeType NodeType {
480                         get { return navigator.NodeType; }
481                 }
482
483                 public override string Prefix {
484                         get { return navigator.Prefix; }
485                 }
486
487                 public override IXmlSchemaInfo SchemaInfo {
488                         get { return navigator.SchemaInfo; }
489                 }
490
491                 public override object UnderlyingObject {
492                         get { return navigator.UnderlyingObject; }
493                 }
494
495                 public override string Value {
496                         get { return navigator.Value; }
497                 }
498
499                 public override XPathNavigator Clone ()
500                 {
501                         return new XmlDocumentEditableNavigator (this);
502                 }
503
504                 public override XPathNavigator CreateNavigator ()
505                 {
506                         return navigator.Clone ();
507                 }
508
509                 public XmlNode GetNode ()
510                 {
511                         return ((IHasXmlNode) navigator).GetNode ();
512                 }
513
514                 public override bool IsSamePosition (XPathNavigator other)
515                 {
516                         XmlDocumentEditableNavigator nav = other as XmlDocumentEditableNavigator;
517                         if (nav != null)
518                                 return navigator.IsSamePosition (nav.navigator);
519                         else
520                                 return navigator.IsSamePosition (nav);
521                 }
522
523                 public override bool MoveTo (XPathNavigator other)
524                 {
525                         XmlDocumentEditableNavigator nav = other as XmlDocumentEditableNavigator;
526                         if (nav != null)
527                                 return navigator.MoveTo (nav.navigator);
528                         else
529                                 return navigator.MoveTo (nav);
530                 }
531
532                 public override bool MoveToFirstAttribute ()
533                 {
534                         return navigator.MoveToFirstAttribute ();
535                 }
536
537                 public override bool MoveToFirstChild ()
538                 {
539                         return navigator.MoveToFirstChild ();
540                 }
541
542                 public override bool MoveToFirstNamespace (XPathNamespaceScope scope)
543                 {
544                         return navigator.MoveToFirstNamespace (scope);
545                 }
546
547                 public override bool MoveToId (string id)
548                 {
549                         return navigator.MoveToId (id);
550                 }
551
552                 public override bool MoveToNext ()
553                 {
554                         return navigator.MoveToNext ();
555                 }
556
557                 public override bool MoveToNextAttribute ()
558                 {
559                         return navigator.MoveToNextAttribute ();
560                 }
561
562                 public override bool MoveToNextNamespace (XPathNamespaceScope scope)
563                 {
564                         return navigator.MoveToNextNamespace (scope);
565                 }
566
567                 public override bool MoveToParent ()
568                 {
569                         return navigator.MoveToParent ();
570                 }
571
572                 public override bool MoveToPrevious ()
573                 {
574                         return navigator.MoveToPrevious ();
575                 }
576
577                 public override XmlWriter AppendChild ()
578                 {
579                         XmlNode n = ((IHasXmlNode) navigator).GetNode ();
580                         if (n == null)
581                                 throw new InvalidOperationException ("Should not happen.");
582                         return new XmlDocumentInsertionWriter (n, null);
583                 }
584
585                 public override void DeleteRange (XPathNavigator lastSiblingToDelete)
586                 {
587                         if (lastSiblingToDelete == null)
588                                 throw new ArgumentNullException ();
589
590                         XmlNode start = ((IHasXmlNode) navigator).GetNode ();
591                         XmlNode end = null;
592                         if (lastSiblingToDelete is IHasXmlNode)
593                                 end = ((IHasXmlNode) lastSiblingToDelete).GetNode ();
594                         // After removal, it moves to parent node.
595                         if (!navigator.MoveToParent ())
596                                 throw new InvalidOperationException ("There is no parent to remove current node.");
597
598                         if (end == null || start.ParentNode != end.ParentNode)
599                                 throw new InvalidOperationException ("Argument XPathNavigator has different parent node.");
600
601                         XmlNode parent = start.ParentNode;
602                         XmlNode next;
603                         bool loop = true;
604                         for (XmlNode n = start; loop; n = next) {
605                                 loop = n != end;
606                                 next = n.NextSibling;
607                                 parent.RemoveChild (n);
608                         }
609                 }
610
611                 public override XmlWriter ReplaceRange (XPathNavigator nav)
612                 {
613                         if (nav == null)
614                                 throw new ArgumentNullException ();
615
616                         XmlNode start = ((IHasXmlNode) navigator).GetNode ();
617                         XmlNode end = null;
618                         if (nav is IHasXmlNode)
619                                 end = ((IHasXmlNode) nav).GetNode ();
620                         if (end == null || start.ParentNode != end.ParentNode)
621                                 throw new InvalidOperationException ("Argument XPathNavigator has different parent node.");
622
623                         XmlDocumentInsertionWriter w =
624                                 (XmlDocumentInsertionWriter) InsertBefore ();
625
626                         // local variables to anonymous delegate
627                         XPathNavigator prev = Clone ();
628                         if (!prev.MoveToPrevious ())
629                                 prev = null;
630                         XPathNavigator parentNav = Clone ();
631                         parentNav.MoveToParent ();
632
633                         w.Closed += delegate (XmlWriter writer) {
634                                 XmlNode parent = start.ParentNode;
635                                 XmlNode next;
636                                 bool loop = true;
637                                 for (XmlNode n = start; loop; n = next) {
638                                         loop = n != end;
639                                         next = n.NextSibling;
640                                         parent.RemoveChild (n);
641                                 }
642                                 if (prev != null) {
643                                         MoveTo (prev);
644                                         MoveToNext ();
645                                 } else {
646                                         MoveTo (parentNav);
647                                         MoveToFirstChild ();
648                                 }
649                         };
650
651                         return w;
652                 }
653
654                 public override XmlWriter InsertBefore ()
655                 {
656                         XmlNode n = ((IHasXmlNode) navigator).GetNode ();
657                         return new XmlDocumentInsertionWriter (n.ParentNode, n);
658                 }
659
660                 public override XmlWriter CreateAttributes ()
661                 {
662                         XmlNode n = ((IHasXmlNode) navigator).GetNode ();
663                         return new XmlDocumentAttributeWriter (n);
664                 }
665
666                 public override void DeleteSelf ()
667                 {
668                         XmlNode n = ((IHasXmlNode) navigator).GetNode ();
669                         if (!navigator.MoveToNext ())
670                                 navigator.MoveToParent ();
671                         if (n.ParentNode == null)
672                                 throw new InvalidOperationException ("This node cannot be removed since it has no parent.");
673                         n.ParentNode.RemoveChild (n);
674                 }
675
676                 public override void ReplaceSelf (XmlReader reader)
677                 {
678                         XmlNode n = ((IHasXmlNode) navigator).GetNode ();
679                         XmlNode p = n.ParentNode;
680                         if (p == null)
681                                 throw new InvalidOperationException ("This node cannot be removed since it has no parent.");
682
683                         bool movenext = false;
684                         if (!MoveToPrevious ())
685                                 MoveToParent ();
686                         else
687                                 movenext = true;
688
689                         XmlDocument doc = p.NodeType == XmlNodeType.Document ?
690                                 p as XmlDocument : p.OwnerDocument;
691                         bool error = false;
692                         if (reader.ReadState == ReadState.Initial) {
693                                 reader.Read ();
694                                 if (reader.EOF)
695                                         error = true;
696                                 else
697                                         while (!reader.EOF)
698                                                 p.AppendChild (doc.ReadNode (reader));
699                         } else {
700                                 if (reader.EOF)
701                                         error = true;
702                                 else
703                                         p.AppendChild (doc.ReadNode (reader));
704                         }
705                         if (error)
706                                 throw new InvalidOperationException ("Content is required in argument XmlReader to replace current node.");
707
708                         p.RemoveChild (n);
709
710                         if (movenext)
711                                 MoveToNext ();
712                         else
713                                 MoveToFirstChild ();
714                 }
715
716                 public override void SetValue (string value)
717                 {
718                         XmlNode n = ((IHasXmlNode) navigator).GetNode ();
719                         while (n.FirstChild != null)
720                                 n.RemoveChild (n.FirstChild);
721                         n.InnerText = value;
722                 }
723         }
724 }
725
726 #endif