2004-11-30 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / System.XML / 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, 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 \r
180                         state = WriteState.Content;\r
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, 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].LineNumber = lineNumber;
255                         attributes [attributeIndex].LinePosition = linePosition;
256                 }
257
258                 // Followings are skipped: nextNsNode (may be next attribute in the same element, or ancestors' nsNode)
259                 public void AddNsNode (int declaredElement, string name, string ns, int nextNs)
260                 {
261                         if (namespaces.Length < nsIndex + 1) {
262                                 nsCapacity *= 4;
263                                 SetNsArrayLength (nsCapacity);
264                         }
265
266 #if DTM_CLASS
267                         namespaces [nsIndex] = new DTMXPathNamespaceNode ();
268 #endif
269                         namespaces [nsIndex].DeclaredElement = declaredElement;
270                         namespaces [nsIndex].Name = name;
271                         namespaces [nsIndex].Namespace = ns;
272                         namespaces [nsIndex].NextNamespace = nextNs;
273                 }
274                 #endregion
275
276                 #region XmlWriter methods
277                 // They are not supported
278                 public override string XmlLang { get { return null; } }
279                 public override XmlSpace XmlSpace { get { return XmlSpace.None; } }
280
281                 public override WriteState WriteState { get { return state; } }
282
283                 public override void Close ()
284                 {
285                         // Fixup arrays
286                         SetNodeArrayLength (nodeIndex + 1);
287                         SetAttributeArrayLength (attributeIndex + 1);
288                         SetNsArrayLength (nsIndex + 1);
289                 }
290
291                 public override void Flush ()
292                 {
293                         // do nothing
294                 }
295
296                 public override string LookupPrefix (string ns)
297                 {
298                         int tmp = nsIndex;
299                         while (tmp != 0) {
300                                 if (namespaces [tmp].Namespace == ns)
301                                         return namespaces [tmp].Name;
302                                 tmp = namespaces [tmp].NextNamespace;
303                         }
304                         return null;
305                 }
306 \r
307                 public override void WriteCData (string data)\r
308                 {\r
309                         AddTextNode (data);\r
310                 }\r
311 \r
312                 private void AddTextNode (string data)\r
313                 {\r
314                         switch (state) {\r
315                         case WriteState.Element:\r
316                                 CloseStartElement ();\r
317                                 break;\r
318                         case WriteState.Content:\r
319                                 break;\r
320                         default:\r
321                                 throw new InvalidOperationException ("Invalid document state for CDATA section: " + state);\r
322                         }\r
323 \r
324                         // When text after text, just add the value, and return.\r
325                         if (nodes [nodeIndex].Depth == writerDepth) {\r
326                                 switch (nodes [nodeIndex].NodeType) {\r
327                                 case XPathNodeType.Text:\r
328                                 case XPathNodeType.SignificantWhitespace:\r
329                                         nodes [nodeIndex].Value += data;\r
330                                         if (IsWhitespace (data))\r
331                                                 nodes [nodeIndex].NodeType = XPathNodeType.SignificantWhitespace;\r
332                                         else\r
333                                                 nodes [nodeIndex].NodeType = XPathNodeType.Text;\r
334                                         return;\r
335                                 }\r
336                         }\r
337 \r
338                         int parent = GetParentIndex ();\r
339                         UpdateTreeForAddition ();\r
340
341                         AddNode (parent,
342                                 0, // attribute
343                                 prevSibling,
344                                 writerDepth,
345                                 XPathNodeType.Text,
346                                 null,
347                                 false,
348                                 null,
349                                 String.Empty,
350                                 String.Empty,
351                                 data,
352                                 null,
353                                 0, // nsIndex
354                                 0, // line info
355                                 0);
356                 }\r
357 \r
358                 private void CheckTopLevelNode ()\r
359                 {\r
360                         switch (state) {\r
361                         case WriteState.Element:\r
362                                 CloseStartElement ();\r
363                                 break;\r
364                         case WriteState.Content:\r
365                         case WriteState.Prolog:\r
366                         case WriteState.Start:\r
367                                 break;\r
368                         default:\r
369                                 throw new InvalidOperationException ("Invalid document state for CDATA section: " + state);\r
370                         }\r
371                 }\r
372 \r
373                 public override void WriteComment (string data)\r
374                 {\r
375                         CheckTopLevelNode ();\r
376 \r
377                         int parent = GetParentIndex ();\r
378                         UpdateTreeForAddition ();\r
379
380                         AddNode (parent,
381                                 0, // attribute
382                                 prevSibling,
383                                 writerDepth,
384                                 XPathNodeType.Comment,
385                                 null,
386                                 false,
387                                 null,
388                                 String.Empty,
389                                 String.Empty,
390                                 data,
391                                 null,
392                                 0, // nsIndex
393                                 0, // line info
394                                 0);
395                 }\r
396 \r
397                 public override void WriteProcessingInstruction (string name, string data)\r
398                 {\r
399                         CheckTopLevelNode ();\r
400 \r
401                         int parent = GetParentIndex ();\r
402                         UpdateTreeForAddition ();\r
403
404                         AddNode (parent,
405                                 0, // attribute
406                                 prevSibling,
407                                 writerDepth,
408                                 XPathNodeType.ProcessingInstruction,
409                                 null,
410                                 false,
411                                 name,
412                                 String.Empty,
413                                 String.Empty,
414                                 data,
415                                 null,
416                                 0, // nsIndex
417                                 0, // line info
418                                 0);
419                 }\r
420 \r
421                 public override void WriteWhitespace (string data)\r
422                 {\r
423                         CheckTopLevelNode ();\r
424 \r
425                         int parent = GetParentIndex ();\r
426                         UpdateTreeForAddition ();\r
427
428                         AddNode (parent,
429                                 0, // attribute
430                                 prevSibling,
431                                 writerDepth,
432                                 XPathNodeType.Whitespace,
433                                 null,
434                                 false,
435                                 null,
436                                 String.Empty,
437                                 String.Empty,
438                                 data,
439                                 null,
440                                 0, // nsIndex
441                                 0, // line info
442                                 0);
443                 }\r
444 \r
445                 public override void WriteStartDocument ()\r
446                 {\r
447                         // do nothing\r
448                 }\r
449 \r
450                 public override void WriteStartDocument (bool standalone)\r
451                 {\r
452                         // do nothing\r
453                 }\r
454 \r
455                 public override void WriteEndDocument ()\r
456                 {\r
457                         // do nothing\r
458                 }\r
459 \r
460                 public override void WriteStartElement (string prefix, string localName, string ns)\r
461                 {\r
462                         switch (state) {\r
463                         case WriteState.Element:\r
464                                 CloseStartElement ();\r
465                                 break;\r
466                         case WriteState.Start:\r
467                         case WriteState.Prolog:\r
468                         case WriteState.Content:\r
469                                 break;\r
470                         default:\r
471                                 throw new InvalidOperationException ("Invalid document state for writing element: " + state);\r
472                         }\r
473 \r
474                         int parent = GetParentIndex ();\r
475                         UpdateTreeForAddition ();\r
476
477                         WriteStartElement (parent, prevSibling, prefix, localName, ns);
478                         state = WriteState.Element;
479                 }\r
480
481                 private void WriteStartElement (int parent, int previousSibling, string prefix, string localName, string ns)
482                 {
483                         PrepareStartElement (previousSibling);
484
485                         AddNode (parent,
486                                 0, // dummy:firstAttribute
487                                 previousSibling,
488                                 writerDepth,
489                                 XPathNodeType.Element,
490                                 null,
491                                 false,
492                                 localName,
493                                 ns,
494                                 prefix,
495                                 "",     // Element has no internal value.
496                                 null,
497                                 lastNsInScope,
498                                 0,
499                                 0);
500                 }
501
502                 private void PrepareStartElement (int previousSibling)
503                 {
504                         firstAttributeIndex = 0;
505                         lastNsIndexInCurrent = 0;
506                         attrIndexAtStart = attributeIndex;
507                         nsIndexAtStart = nsIndex;
508
509                         while (namespaces [lastNsInScope].DeclaredElement == previousSibling) {
510                                 lastNsInScope = namespaces [lastNsInScope].NextNamespace;
511                         }
512                 }
513 \r
514                 public override void WriteEndElement ()\r
515                 {\r
516                         WriteEndElement (false);\r
517                 }\r
518 \r
519                 public override void WriteFullEndElement ()\r
520                 {\r
521                         WriteEndElement (true);\r
522                 }\r
523 \r
524                 private void WriteEndElement (bool full)\r
525                 {\r
526                         switch (state) {\r
527                         case WriteState.Element:\r
528                                 CloseStartElement ();\r
529                                 break;\r
530                         case WriteState.Content:\r
531                                 break;\r
532                         default:\r
533                                 throw new InvalidOperationException ("Invalid state for writing EndElement: " + state);\r
534                         }\r
535                         parentForFirstChild = -1;\r
536                         if (nodes [nodeIndex].NodeType == XPathNodeType.Element) {\r
537                                 if (!full)\r
538                                         nodes [nodeIndex].IsEmptyElement = true;\r
539                         }\r
540 \r
541                         writerDepth--;\r
542                 }\r
543 \r
544                 public override void WriteStartAttribute (string prefix, string localName, string ns)\r
545                 {\r
546                         if (state != WriteState.Element)\r
547                                 throw new InvalidOperationException ("Invalid document state for attribute: " + state);\r
548 \r
549                         state = WriteState.Attribute;\r
550                         if (ns == XmlNamespaces.XMLNS)
551                                 ProcessNamespace ((prefix == null || prefix == String.Empty) ? "" : localName, String.Empty); // dummy: Value should be completed
552                         else
553                                 ProcessAttribute (prefix, localName, ns, String.Empty); // dummy: Value should be completed
554                 }\r
555 \r
556                 private void ProcessNamespace (string prefix, string ns)
557                 {
558                         nsIndex++;
559
560                         int nextTmp = lastNsIndexInCurrent == 0 ? nodes [nodeIndex].FirstNamespace : lastNsIndexInCurrent;
561
562                         this.AddNsNode (nodeIndex,
563                                 prefix,
564                                 ns,
565                                 nextTmp);
566                         lastNsIndexInCurrent = nsIndex;
567                         openNamespace = true;
568                 }
569
570                 private void ProcessAttribute (string prefix, string localName, string ns, string value)
571                 {
572                         attributeIndex ++;
573
574                         this.AddAttribute (nodeIndex,
575                                 localName,
576                                 ns, 
577                                 prefix != null ? prefix : String.Empty, 
578                                 value,
579                                 0,
580                                 0);
581                         if (firstAttributeIndex == 0)
582                                 firstAttributeIndex = attributeIndex;
583                         else
584                                 attributes [attributeIndex - 1].NextAttribute = attributeIndex;
585                 }
586
587                 public override void WriteEndAttribute ()\r
588                 {\r
589                         if (state != WriteState.Attribute)\r
590                                 throw new InvalidOperationException ();\r
591 \r
592                         openNamespace = false;\r
593                         state = WriteState.Element;\r
594                 }\r
595 \r
596                 public override void WriteString (string text)\r
597                 {\r
598                         if (WriteState == WriteState.Attribute) {\r
599                                 if (openNamespace)\r
600                                         namespaces [nsIndex].Namespace += text;\r
601                                 else\r
602                                         attributes [attributeIndex].Value += text;\r
603                         }\r
604                         else\r
605                                 AddTextNode (text);\r
606                 }\r
607 \r
608                 // Well, they cannot be supported, but actually used to\r
609                 // disable-output-escaping = "true"\r
610                 public override void WriteRaw (string data)\r
611                 {\r
612                         WriteString (data);\r
613                 }\r
614
615                 public override void WriteRaw (char [] data, int start, int len)\r
616                 {\r
617                         WriteString (new string (data, start, len));\r
618                 }\r
619 \r
620                 public override void WriteName (string name)\r
621                 {\r
622                         WriteString (name);\r
623                 }\r
624 \r
625                 public override void WriteNmToken (string name)\r
626                 {\r
627                         WriteString (name);\r
628                 }\r
629 \r
630                 public override void WriteBase64 (byte [] buffer, int index, int count)\r
631                 {\r
632                         throw new NotSupportedException ();\r
633                 }
634
635                 public override void WriteBinHex (byte [] buffer, int index, int count)\r
636                 {\r
637                         throw new NotSupportedException ();\r
638                 }\r
639 \r
640                 public override void WriteChars (char [] buffer, int index, int count)\r
641                 {\r
642                         throw new NotSupportedException ();\r
643                 }\r
644 \r
645                 public override void WriteCharEntity (char c)\r
646                 {\r
647                         throw new NotSupportedException ();\r
648                 }\r
649 \r
650                 public override void WriteDocType (string name, string pub, string sys, string intSubset)\r
651                 {\r
652                         throw new NotSupportedException ();\r
653                 }\r
654
655                 public override void WriteEntityRef (string name)
656                 {
657                         throw new NotSupportedException ();\r
658                 }
659
660                 public override void WriteQualifiedName (string localName, string ns)\r
661                 {\r
662                         throw new NotSupportedException ();\r
663                 }\r
664 \r
665                 public override void WriteSurrogateCharEntity (char high, char low)\r
666                 {\r
667                         throw new NotSupportedException ();\r
668                 }
669
670                 private bool IsWhitespace (string data)
671                 {
672                         for (int i = 0; i < data.Length; i++) {
673                                 switch (data [i]) {
674                                 case ' ':
675                                 case '\r':
676                                 case '\n':
677                                 case '\t':
678                                         continue;
679                                 default:
680                                         return false;
681                                 }
682                         }
683                         return true;
684                 }\r
685                 #endregion
686         }
687 }