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