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