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