New test.
[mono.git] / mcs / class / Mono.Xml.Ext / Mono.Xml.XPath / DTMXPathDocumentWriter.cs
1 //
2 // Mono.Xml.XPath.DTMXPathDocumentWriter
3 //
4 // Author:
5 //      Atsushi Enomoto (ginga@kit.hi-ho.ne.jp)
6 //
7 // (C) 2003 Atsushi Enomoto
8 //
9
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30 using System;
31 using System.Collections;
32 using System.IO;
33 using System.Xml;
34 using System.Xml.Schema;
35 using System.Xml.XPath;
36
37 namespace Mono.Xml.XPath
38 {
39 #if OUTSIDE_SYSTEM_XML
40         public
41 #else
42         internal
43 #endif
44                 class DTMXPathDocumentWriter : XmlWriter
45         {
46                 public DTMXPathDocumentWriter (XmlNameTable nt, int defaultCapacity)
47                 {
48                         nameTable = nt == null ? new NameTable () : nt;
49                         nodeCapacity = defaultCapacity;
50                         attributeCapacity = nodeCapacity;
51                         idTable = new Hashtable ();
52
53                         nodes = new DTMXPathLinkedNode [nodeCapacity];
54                         attributes = new DTMXPathAttributeNode [attributeCapacity];
55                         namespaces = new DTMXPathNamespaceNode [0];
56
57                         Init ();
58                 }
59                 
60                 XmlNameTable nameTable;
61                 int nodeCapacity = 200;
62                 int attributeCapacity = 200;
63                 int nsCapacity = 10;
64
65                 // Linked Node
66                 DTMXPathLinkedNode [] nodes;
67
68                 // Attribute
69                 DTMXPathAttributeNode [] attributes;
70
71                 // NamespaceNode
72                 DTMXPathNamespaceNode [] namespaces;
73
74                 // idTable [string value] -> int nodeId
75                 Hashtable idTable;
76
77                 int nodeIndex;
78                 int attributeIndex;
79                 int nsIndex;
80                 int parentForFirstChild;
81
82                 // for attribute processing; should be reset per each element.
83                 int firstAttributeIndex;
84                 int lastNsIndexInCurrent;
85                 int attrIndexAtStart;
86                 int nsIndexAtStart;
87
88                 int prevSibling;
89                 int lastNsInScope;
90
91                 // They are only used in Writer
92                 int writerDepth;
93                 WriteState state;
94                 bool openNamespace;
95
96                 public DTMXPathDocument CreateDocument ()
97                 {
98                         return new DTMXPathDocument (nameTable,
99                                 nodes,
100                                 attributes,
101                                 namespaces,
102                                 idTable
103                         );
104                 }
105
106                 public void Init ()
107                 {
108                         // index 0 is dummy. No node (including Root) is assigned to this index
109                         // So that we can easily compare index != 0 instead of index < 0.
110                         // (Difference between jnz or jbe in 80x86.)
111                         AddNode (0, 0, 0, 0, XPathNodeType.All, "", false, "", "", "", "", "", 0, 0, 0);
112                         nodeIndex++;
113                         AddAttribute (0, null, null, null, null, null, 0, 0);
114                         AddNsNode (0, null, null, 0);
115                         nsIndex++;
116                         AddNsNode (1, "xml", XmlNamespaces.XML, 0);
117
118                         // add root.
119                         AddNode (0, 0, 0, -1, XPathNodeType.Root, null, false, "", "", "", "", "", 1, 0, 0);
120
121                         this.nodeIndex = 1;
122                         this.lastNsInScope = 1;
123                         this.parentForFirstChild = nodeIndex;
124
125                         state = WriteState.Content;
126                 }
127
128                 private int GetParentIndex ()
129                 {
130                         if (parentForFirstChild >= 0)
131                                 return parentForFirstChild;
132
133                         int parent = nodeIndex;
134                         if (nodes [nodeIndex].Depth >= writerDepth) {
135                                 // if not, then current node is parent.
136                                 while (writerDepth <= nodes [parent].Depth)
137                                         parent = nodes [parent].Parent;
138                         }
139                         return parent;
140                 }
141
142                 private int GetPreviousSiblingIndex ()
143                 {
144                         int prevSibling = nodeIndex;
145                         if (parentForFirstChild >= 0)
146                                 prevSibling = 0;
147                         else
148                                 while (nodes [prevSibling].Depth != writerDepth)
149                                         prevSibling = nodes [prevSibling].Parent;
150                         return prevSibling;
151                 }
152
153                 private void UpdateTreeForAddition ()
154                 {
155                         int parent = GetParentIndex ();
156                         prevSibling = GetPreviousSiblingIndex ();
157
158                         nodeIndex++;
159
160                         if (prevSibling != 0)
161                                 nodes [prevSibling].NextSibling = nodeIndex;
162                         if (parentForFirstChild >= 0)
163                                 nodes [parent].FirstChild = nodeIndex;
164
165                         parentForFirstChild = -1;
166                 }
167
168                 private void CloseStartElement ()
169                 {
170                         if (attrIndexAtStart != attributeIndex)
171                                 nodes [nodeIndex].FirstAttribute = attrIndexAtStart + 1;
172                         if (nsIndexAtStart != nsIndex) {
173                                 nodes [nodeIndex].FirstNamespace = nsIndex;
174                                 lastNsInScope = nsIndex;
175                         }
176
177                         if (!nodes [nodeIndex].IsEmptyElement)
178                                 parentForFirstChild = nodeIndex;
179
180                         state = WriteState.Content;
181                         writerDepth++;
182                 }
183
184                 #region Adding Nodes
185                 private void SetNodeArrayLength (int size)
186                 {
187                         DTMXPathLinkedNode [] newArr = new DTMXPathLinkedNode [size];
188                         Array.Copy (nodes, newArr, System.Math.Min (size, nodes.Length));
189                         nodes = newArr;
190                 }
191
192                 private void SetAttributeArrayLength (int size)
193                 {
194                         DTMXPathAttributeNode [] newArr = 
195                                 new DTMXPathAttributeNode [size];
196                         Array.Copy (attributes, newArr, System.Math.Min (size, attributes.Length));
197                         attributes = newArr;
198                 }
199
200                 private void SetNsArrayLength (int size)
201                 {
202                         DTMXPathNamespaceNode [] newArr =
203                                 new DTMXPathNamespaceNode [size];
204                         Array.Copy (namespaces, newArr, System.Math.Min (size, namespaces.Length));
205                         namespaces = newArr;
206                 }
207
208                 // Here followings are skipped: firstChild, nextSibling, 
209                 public void AddNode (int parent, int firstAttribute, int previousSibling, int depth, XPathNodeType nodeType, string baseUri, bool isEmptyElement, string localName, string ns, string prefix, string value, string xmlLang, int namespaceNode, int lineNumber, int linePosition)
210                 {
211                         if (nodes.Length < nodeIndex + 1) {
212                                 nodeCapacity *= 4;
213                                 SetNodeArrayLength (nodeCapacity);
214                         }
215
216 #if DTM_CLASS
217                         nodes [nodeIndex] = new DTMXPathLinkedNode ();
218 #endif
219                         nodes [nodeIndex].FirstChild = 0;               // dummy
220                         nodes [nodeIndex].Parent = parent;
221                         nodes [nodeIndex].FirstAttribute = firstAttribute;
222                         nodes [nodeIndex].PreviousSibling = previousSibling;
223                         nodes [nodeIndex].NextSibling = 0;      // dummy
224                         nodes [nodeIndex].Depth = depth;
225                         nodes [nodeIndex].NodeType = nodeType;
226                         nodes [nodeIndex].BaseURI = baseUri;
227                         nodes [nodeIndex].IsEmptyElement = isEmptyElement;
228                         nodes [nodeIndex].LocalName = localName;
229                         nodes [nodeIndex].NamespaceURI = ns;
230                         nodes [nodeIndex].Prefix = prefix;
231                         nodes [nodeIndex].Value = value;
232                         nodes [nodeIndex].XmlLang = xmlLang;
233                         nodes [nodeIndex].FirstNamespace = namespaceNode;
234                         nodes [nodeIndex].LineNumber = lineNumber;
235                         nodes [nodeIndex].LinePosition = linePosition;
236                 }
237
238                 // Followings are skipped: nextAttribute,
239                 public void AddAttribute (int ownerElement, string localName, string ns, string prefix, string value, object schemaType, int lineNumber, int linePosition)
240                 {
241                         if (attributes.Length < attributeIndex + 1) {
242                                 attributeCapacity *= 4;
243                                 SetAttributeArrayLength (attributeCapacity);
244                         }
245
246 #if DTM_CLASS
247                         attributes [attributeIndex] = new DTMXPathAttributeNode ();
248 #endif
249                         attributes [attributeIndex].OwnerElement = ownerElement;
250                         attributes [attributeIndex].LocalName = localName;
251                         attributes [attributeIndex].NamespaceURI = ns;
252                         attributes [attributeIndex].Prefix = prefix;
253                         attributes [attributeIndex].Value = value;
254                         attributes [attributeIndex].SchemaType = schemaType;
255                         attributes [attributeIndex].LineNumber = lineNumber;
256                         attributes [attributeIndex].LinePosition = linePosition;
257                 }
258
259                 // Followings are skipped: nextNsNode (may be next attribute in the same element, or ancestors' nsNode)
260                 public void AddNsNode (int declaredElement, string name, string ns, int nextNs)
261                 {
262                         if (namespaces.Length < nsIndex + 1) {
263                                 nsCapacity *= 4;
264                                 SetNsArrayLength (nsCapacity);
265                         }
266
267 #if DTM_CLASS
268                         namespaces [nsIndex] = new DTMXPathNamespaceNode ();
269 #endif
270                         namespaces [nsIndex].DeclaredElement = declaredElement;
271                         namespaces [nsIndex].Name = name;
272                         namespaces [nsIndex].Namespace = ns;
273                         namespaces [nsIndex].NextNamespace = nextNs;
274                 }
275                 #endregion
276
277                 #region XmlWriter methods
278                 // They are not supported
279                 public override string XmlLang { get { return null; } }
280                 public override XmlSpace XmlSpace { get { return XmlSpace.None; } }
281
282                 public override WriteState WriteState { get { return state; } }
283
284                 public override void Close ()
285                 {
286                         // Fixup arrays
287                         SetNodeArrayLength (nodeIndex + 1);
288                         SetAttributeArrayLength (attributeIndex + 1);
289                         SetNsArrayLength (nsIndex + 1);
290                 }
291
292                 public override void Flush ()
293                 {
294                         // do nothing
295                 }
296
297                 public override string LookupPrefix (string ns)
298                 {
299                         int tmp = nsIndex;
300                         while (tmp != 0) {
301                                 if (namespaces [tmp].Namespace == ns)
302                                         return namespaces [tmp].Name;
303                                 tmp = namespaces [tmp].NextNamespace;
304                         }
305                         return null;
306                 }
307
308                 public override void WriteCData (string data)
309                 {
310                         AddTextNode (data);
311                 }
312
313                 private void AddTextNode (string data)
314                 {
315                         switch (state) {
316                         case WriteState.Element:
317                                 CloseStartElement ();
318                                 break;
319                         case WriteState.Content:
320                                 break;
321                         default:
322                                 throw new InvalidOperationException ("Invalid document state for CDATA section: " + state);
323                         }
324
325                         // When text after text, just add the value, and return.
326                         if (nodes [nodeIndex].Depth == writerDepth) {
327                                 switch (nodes [nodeIndex].NodeType) {
328                                 case XPathNodeType.Text:
329                                 case XPathNodeType.SignificantWhitespace:
330                                         nodes [nodeIndex].Value += data;
331                                         if (IsWhitespace (data))
332                                                 nodes [nodeIndex].NodeType = XPathNodeType.SignificantWhitespace;
333                                         else
334                                                 nodes [nodeIndex].NodeType = XPathNodeType.Text;
335                                         return;
336                                 }
337                         }
338
339                         int parent = GetParentIndex ();
340                         UpdateTreeForAddition ();
341
342                         AddNode (parent,
343                                 0, // attribute
344                                 prevSibling,
345                                 writerDepth,
346                                 XPathNodeType.Text,
347                                 null,
348                                 false,
349                                 null,
350                                 String.Empty,
351                                 String.Empty,
352                                 data,
353                                 null,
354                                 0, // nsIndex
355                                 0, // line info
356                                 0);
357                 }
358
359                 private void CheckTopLevelNode ()
360                 {
361                         switch (state) {
362                         case WriteState.Element:
363                                 CloseStartElement ();
364                                 break;
365                         case WriteState.Content:
366                         case WriteState.Prolog:
367                         case WriteState.Start:
368                                 break;
369                         default:
370                                 throw new InvalidOperationException ("Invalid document state for CDATA section: " + state);
371                         }
372                 }
373
374                 public override void WriteComment (string data)
375                 {
376                         CheckTopLevelNode ();
377
378                         int parent = GetParentIndex ();
379                         UpdateTreeForAddition ();
380
381                         AddNode (parent,
382                                 0, // attribute
383                                 prevSibling,
384                                 writerDepth,
385                                 XPathNodeType.Comment,
386                                 null,
387                                 false,
388                                 null,
389                                 String.Empty,
390                                 String.Empty,
391                                 data,
392                                 null,
393                                 0, // nsIndex
394                                 0, // line info
395                                 0);
396                 }
397
398                 public override void WriteProcessingInstruction (string name, string data)
399                 {
400                         CheckTopLevelNode ();
401
402                         int parent = GetParentIndex ();
403                         UpdateTreeForAddition ();
404
405                         AddNode (parent,
406                                 0, // attribute
407                                 prevSibling,
408                                 writerDepth,
409                                 XPathNodeType.ProcessingInstruction,
410                                 null,
411                                 false,
412                                 name,
413                                 String.Empty,
414                                 String.Empty,
415                                 data,
416                                 null,
417                                 0, // nsIndex
418                                 0, // line info
419                                 0);
420                 }
421
422                 public override void WriteWhitespace (string data)
423                 {
424                         CheckTopLevelNode ();
425
426                         int parent = GetParentIndex ();
427                         UpdateTreeForAddition ();
428
429                         AddNode (parent,
430                                 0, // attribute
431                                 prevSibling,
432                                 writerDepth,
433                                 XPathNodeType.Whitespace,
434                                 null,
435                                 false,
436                                 null,
437                                 String.Empty,
438                                 String.Empty,
439                                 data,
440                                 null,
441                                 0, // nsIndex
442                                 0, // line info
443                                 0);
444                 }
445
446                 public override void WriteStartDocument ()
447                 {
448                         // do nothing
449                 }
450
451                 public override void WriteStartDocument (bool standalone)
452                 {
453                         // do nothing
454                 }
455
456                 public override void WriteEndDocument ()
457                 {
458                         // do nothing
459                 }
460
461                 public override void WriteStartElement (string prefix, string localName, string ns)
462                 {
463                         switch (state) {
464                         case WriteState.Element:
465                                 CloseStartElement ();
466                                 break;
467                         case WriteState.Start:
468                         case WriteState.Prolog:
469                         case WriteState.Content:
470                                 break;
471                         default:
472                                 throw new InvalidOperationException ("Invalid document state for writing element: " + state);
473                         }
474
475                         int parent = GetParentIndex ();
476                         UpdateTreeForAddition ();
477
478                         WriteStartElement (parent, prevSibling, prefix, localName, ns);
479                         state = WriteState.Element;
480                 }
481
482                 private void WriteStartElement (int parent, int previousSibling, string prefix, string localName, string ns)
483                 {
484                         PrepareStartElement (previousSibling);
485
486                         AddNode (parent,
487                                 0, // dummy:firstAttribute
488                                 previousSibling,
489                                 writerDepth,
490                                 XPathNodeType.Element,
491                                 null,
492                                 false,
493                                 localName,
494                                 ns,
495                                 prefix,
496                                 "",     // Element has no internal value.
497                                 null,
498                                 lastNsInScope,
499                                 0,
500                                 0);
501                 }
502
503                 private void PrepareStartElement (int previousSibling)
504                 {
505                         firstAttributeIndex = 0;
506                         lastNsIndexInCurrent = 0;
507                         attrIndexAtStart = attributeIndex;
508                         nsIndexAtStart = nsIndex;
509
510                         while (namespaces [lastNsInScope].DeclaredElement == previousSibling) {
511                                 lastNsInScope = namespaces [lastNsInScope].NextNamespace;
512                         }
513                 }
514
515                 public override void WriteEndElement ()
516                 {
517                         WriteEndElement (false);
518                 }
519
520                 public override void WriteFullEndElement ()
521                 {
522                         WriteEndElement (true);
523                 }
524
525                 private void WriteEndElement (bool full)
526                 {
527                         switch (state) {
528                         case WriteState.Element:
529                                 CloseStartElement ();
530                                 break;
531                         case WriteState.Content:
532                                 break;
533                         default:
534                                 throw new InvalidOperationException ("Invalid state for writing EndElement: " + state);
535                         }
536                         parentForFirstChild = -1;
537                         if (nodes [nodeIndex].NodeType == XPathNodeType.Element) {
538                                 if (!full)
539                                         nodes [nodeIndex].IsEmptyElement = true;
540                         }
541
542                         writerDepth--;
543                 }
544
545                 public override void WriteStartAttribute (string prefix, string localName, string ns)
546                 {
547                         if (state != WriteState.Element)
548                                 throw new InvalidOperationException ("Invalid document state for attribute: " + state);
549
550                         state = WriteState.Attribute;
551                         if (ns == XmlNamespaces.XMLNS)
552                                 ProcessNamespace ((prefix == null || prefix == String.Empty) ? "" : localName, String.Empty); // dummy: Value should be completed
553                         else
554                                 ProcessAttribute (prefix, localName, ns, String.Empty); // dummy: Value should be completed
555                 }
556
557                 private void ProcessNamespace (string prefix, string ns)
558                 {
559                         nsIndex++;
560
561                         int nextTmp = lastNsIndexInCurrent == 0 ? nodes [nodeIndex].FirstNamespace : lastNsIndexInCurrent;
562
563                         this.AddNsNode (nodeIndex,
564                                 prefix,
565                                 ns,
566                                 nextTmp);
567                         lastNsIndexInCurrent = nsIndex;
568                         openNamespace = true;
569                 }
570
571                 private void ProcessAttribute (string prefix, string localName, string ns, string value)
572                 {
573                         attributeIndex ++;
574
575                         this.AddAttribute (nodeIndex,
576                                 localName,
577                                 ns, 
578                                 prefix != null ? prefix : String.Empty, 
579                                 value,
580                                 null,
581                                 0,
582                                 0);
583                         if (firstAttributeIndex == 0)
584                                 firstAttributeIndex = attributeIndex;
585                         else
586                                 attributes [attributeIndex - 1].NextAttribute = attributeIndex;
587                 }
588
589                 public override void WriteEndAttribute ()
590                 {
591                         if (state != WriteState.Attribute)
592                                 throw new InvalidOperationException ();
593
594                         openNamespace = false;
595                         state = WriteState.Element;
596                 }
597
598                 public override void WriteString (string text)
599                 {
600                         if (WriteState == WriteState.Attribute) {
601                                 if (openNamespace)
602                                         namespaces [nsIndex].Namespace += text;
603                                 else
604                                         attributes [attributeIndex].Value += text;
605                         }
606                         else
607                                 AddTextNode (text);
608                 }
609
610                 // Well, they cannot be supported, but actually used to
611                 // disable-output-escaping = "true"
612                 public override void WriteRaw (string data)
613                 {
614                         WriteString (data);
615                 }
616
617                 public override void WriteRaw (char [] data, int start, int len)
618                 {
619                         WriteString (new string (data, start, len));
620                 }
621
622                 public override void WriteName (string name)
623                 {
624                         WriteString (name);
625                 }
626
627                 public override void WriteNmToken (string name)
628                 {
629                         WriteString (name);
630                 }
631
632                 public override void WriteBase64 (byte [] buffer, int index, int count)
633                 {
634                         throw new NotSupportedException ();
635                 }
636
637                 public override void WriteBinHex (byte [] buffer, int index, int count)
638                 {
639                         throw new NotSupportedException ();
640                 }
641
642                 public override void WriteChars (char [] buffer, int index, int count)
643                 {
644                         throw new NotSupportedException ();
645                 }
646
647                 public override void WriteCharEntity (char c)
648                 {
649                         throw new NotSupportedException ();
650                 }
651
652                 public override void WriteDocType (string name, string pub, string sys, string intSubset)
653                 {
654                         throw new NotSupportedException ();
655                 }
656
657                 public override void WriteEntityRef (string name)
658                 {
659                         throw new NotSupportedException ();
660                 }
661
662                 public override void WriteQualifiedName (string localName, string ns)
663                 {
664                         throw new NotSupportedException ();
665                 }
666
667                 public override void WriteSurrogateCharEntity (char high, char low)
668                 {
669                         throw new NotSupportedException ();
670                 }
671
672                 private bool IsWhitespace (string data)
673                 {
674                         for (int i = 0; i < data.Length; i++) {
675                                 switch (data [i]) {
676                                 case ' ':
677                                 case '\r':
678                                 case '\n':
679                                 case '\t':
680                                         continue;
681                                 default:
682                                         return false;
683                                 }
684                         }
685                         return true;
686                 }
687                 #endregion
688         }
689 }