New test.
[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 IsEmptyElement {
456                         get { return navigator.IsEmptyElement; }
457                 }
458
459                 public override string LocalName {
460                         get { return navigator.LocalName; }
461                 }
462
463                 public override XmlNameTable NameTable {
464                         get { return navigator.NameTable; }
465                 }
466
467                 public override string Name {
468                         get { return navigator.Name; }
469                 }
470
471                 public override string NamespaceURI {
472                         get { return navigator.NamespaceURI; }
473                 }
474
475                 public override XPathNodeType NodeType {
476                         get { return navigator.NodeType; }
477                 }
478
479                 public override string Prefix {
480                         get { return navigator.Prefix; }
481                 }
482
483                 public override IXmlSchemaInfo SchemaInfo {
484                         get { return navigator.SchemaInfo; }
485                 }
486
487                 public override object UnderlyingObject {
488                         get { return navigator.UnderlyingObject; }
489                 }
490
491                 public override string Value {
492                         get { return navigator.Value; }
493                 }
494
495                 public override XPathNavigator Clone ()
496                 {
497                         return new XmlDocumentEditableNavigator (this);
498                 }
499
500                 public override XPathNavigator CreateNavigator ()
501                 {
502                         return navigator.Clone ();
503                 }
504
505                 public XmlNode GetNode ()
506                 {
507                         return ((IHasXmlNode) navigator).GetNode ();
508                 }
509
510                 public override bool IsSamePosition (XPathNavigator other)
511                 {
512                         XmlDocumentEditableNavigator nav = other as XmlDocumentEditableNavigator;
513                         if (nav != null)
514                                 return navigator.IsSamePosition (nav.navigator);
515                         else
516                                 return navigator.IsSamePosition (nav);
517                 }
518
519                 public override bool MoveTo (XPathNavigator other)
520                 {
521                         XmlDocumentEditableNavigator nav = other as XmlDocumentEditableNavigator;
522                         if (nav != null)
523                                 return navigator.MoveTo (nav.navigator);
524                         else
525                                 return navigator.MoveTo (nav);
526                 }
527
528                 public override bool MoveToFirstAttribute ()
529                 {
530                         return navigator.MoveToFirstAttribute ();
531                 }
532
533                 public override bool MoveToFirstChild ()
534                 {
535                         return navigator.MoveToFirstChild ();
536                 }
537
538                 public override bool MoveToFirstNamespace (XPathNamespaceScope scope)
539                 {
540                         return navigator.MoveToFirstNamespace (scope);
541                 }
542
543                 public override bool MoveToId (string id)
544                 {
545                         return navigator.MoveToId (id);
546                 }
547
548                 public override bool MoveToNext ()
549                 {
550                         return navigator.MoveToNext ();
551                 }
552
553                 public override bool MoveToNextAttribute ()
554                 {
555                         return navigator.MoveToNextAttribute ();
556                 }
557
558                 public override bool MoveToNextNamespace (XPathNamespaceScope scope)
559                 {
560                         return navigator.MoveToNextNamespace (scope);
561                 }
562
563                 public override bool MoveToParent ()
564                 {
565                         return navigator.MoveToParent ();
566                 }
567
568                 public override bool MoveToPrevious ()
569                 {
570                         return navigator.MoveToPrevious ();
571                 }
572
573                 public override XmlWriter AppendChild ()
574                 {
575                         XmlNode n = ((IHasXmlNode) navigator).GetNode ();
576                         if (n == null)
577                                 throw new InvalidOperationException ("Should not happen.");
578                         return new XmlDocumentInsertionWriter (n, null);
579                 }
580
581                 public override void DeleteRange (XPathNavigator lastSiblingToDelete)
582                 {
583                         if (lastSiblingToDelete == null)
584                                 throw new ArgumentNullException ();
585
586                         XmlNode start = ((IHasXmlNode) navigator).GetNode ();
587                         XmlNode end = null;
588                         if (lastSiblingToDelete is IHasXmlNode)
589                                 end = ((IHasXmlNode) lastSiblingToDelete).GetNode ();
590                         // After removal, it moves to parent node.
591                         if (!navigator.MoveToParent ())
592                                 throw new InvalidOperationException ("There is no parent to remove current node.");
593
594                         if (end == null || start.ParentNode != end.ParentNode)
595                                 throw new InvalidOperationException ("Argument XPathNavigator has different parent node.");
596
597                         XmlNode parent = start.ParentNode;
598                         XmlNode next;
599                         bool loop = true;
600                         for (XmlNode n = start; loop; n = next) {
601                                 loop = n != end;
602                                 next = n.NextSibling;
603                                 parent.RemoveChild (n);
604                         }
605                 }
606
607                 public override XmlWriter ReplaceRange (XPathNavigator nav)
608                 {
609                         if (nav == null)
610                                 throw new ArgumentNullException ();
611
612                         XmlNode start = ((IHasXmlNode) navigator).GetNode ();
613                         XmlNode end = null;
614                         if (nav is IHasXmlNode)
615                                 end = ((IHasXmlNode) nav).GetNode ();
616                         if (end == null || start.ParentNode != end.ParentNode)
617                                 throw new InvalidOperationException ("Argument XPathNavigator has different parent node.");
618
619                         XmlDocumentInsertionWriter w =
620                                 (XmlDocumentInsertionWriter) InsertBefore ();
621
622                         // local variables to anonymous delegate
623                         XPathNavigator prev = Clone ();
624                         if (!prev.MoveToPrevious ())
625                                 prev = null;
626                         XPathNavigator parentNav = Clone ();
627                         parentNav.MoveToParent ();
628
629                         w.Closed += delegate (XmlWriter writer) {
630                                 XmlNode parent = start.ParentNode;
631                                 XmlNode next;
632                                 bool loop = true;
633                                 for (XmlNode n = start; loop; n = next) {
634                                         loop = n != end;
635                                         next = n.NextSibling;
636                                         parent.RemoveChild (n);
637                                 }
638                                 if (prev != null) {
639                                         MoveTo (prev);
640                                         MoveToNext ();
641                                 } else {
642                                         MoveTo (parentNav);
643                                         MoveToFirstChild ();
644                                 }
645                         };
646
647                         return w;
648                 }
649
650                 public override XmlWriter InsertBefore ()
651                 {
652                         XmlNode n = ((IHasXmlNode) navigator).GetNode ();
653                         return new XmlDocumentInsertionWriter (n.ParentNode, n);
654                 }
655
656                 public override XmlWriter CreateAttributes ()
657                 {
658                         XmlNode n = ((IHasXmlNode) navigator).GetNode ();
659                         return new XmlDocumentAttributeWriter (n);
660                 }
661
662                 public override void DeleteSelf ()
663                 {
664                         XmlNode n = ((IHasXmlNode) navigator).GetNode ();
665                         if (!navigator.MoveToNext ())
666                                 navigator.MoveToParent ();
667                         if (n.ParentNode == null)
668                                 throw new InvalidOperationException ("This node cannot be removed since it has no parent.");
669                         n.ParentNode.RemoveChild (n);
670                 }
671
672                 public override void ReplaceSelf (XmlReader reader)
673                 {
674                         XmlNode n = ((IHasXmlNode) navigator).GetNode ();
675                         XmlNode p = n.ParentNode;
676                         if (p == null)
677                                 throw new InvalidOperationException ("This node cannot be removed since it has no parent.");
678
679                         bool movenext = false;
680                         if (!MoveToPrevious ())
681                                 MoveToParent ();
682                         else
683                                 movenext = true;
684
685                         XmlDocument doc = p.NodeType == XmlNodeType.Document ?
686                                 p as XmlDocument : p.OwnerDocument;
687                         bool error = false;
688                         if (reader.ReadState == ReadState.Initial) {
689                                 reader.Read ();
690                                 if (reader.EOF)
691                                         error = true;
692                                 else
693                                         while (!reader.EOF)
694                                                 p.AppendChild (doc.ReadNode (reader));
695                         } else {
696                                 if (reader.EOF)
697                                         error = true;
698                                 else
699                                         p.AppendChild (doc.ReadNode (reader));
700                         }
701                         if (error)
702                                 throw new InvalidOperationException ("Content is required in argument XmlReader to replace current node.");
703
704                         p.RemoveChild (n);
705
706                         if (movenext)
707                                 MoveToNext ();
708                         else
709                                 MoveToFirstChild ();
710                 }
711
712                 public override void SetValue (string value)
713                 {
714                         XmlNode n = ((IHasXmlNode) navigator).GetNode ();
715                         while (n.FirstChild != null)
716                                 n.RemoveChild (n.FirstChild);
717                         n.InnerText = value;
718                 }
719         }
720 }
721
722 #endif