LineInfo on namespace nodes were missing.
[mono.git] / mcs / class / System.XML / Mono.Xml.XPath / DTMXPathDocumentBuilder2.cs
1 //
2 // Mono.Xml.XPath.DTMXPathDocumentBuilder2
3 //
4 // Author:
5 //      Atsushi Enomoto  <atsushi@ximian.com>
6 //
7 // (C)2004 Novell Inc.
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
40 #if OUTSIDE_SYSTEM_XML
41         public
42 #else
43         internal
44 #endif
45         class DTMXPathDocumentBuilder2
46         {
47                 public DTMXPathDocumentBuilder2 (string url)
48                         : this (url, XmlSpace.None, 200)
49                 {
50                 }
51
52                 public DTMXPathDocumentBuilder2 (string url, XmlSpace space)
53                         : this (url, space, 200)
54                 {
55                 }
56
57                 public DTMXPathDocumentBuilder2 (string url, XmlSpace space, int defaultCapacity)
58                 {
59                         XmlReader r = null;
60                         try {
61                                 r = new XmlTextReader (url);
62                                 Init (r, space, defaultCapacity);
63                         } finally {
64                                 if (r != null)
65                                         r.Close ();
66                         }
67                 }
68
69                 public DTMXPathDocumentBuilder2 (XmlReader reader)
70                         : this (reader, XmlSpace.None, 200)
71                 {
72                 }
73
74                 public DTMXPathDocumentBuilder2 (XmlReader reader, XmlSpace space)
75                         : this (reader, space, 200)
76                 {
77                 }
78
79                 public DTMXPathDocumentBuilder2 (XmlReader reader, XmlSpace space, int defaultCapacity)
80                 {
81                         Init (reader, space, defaultCapacity);
82                 }
83
84                 private void Init (XmlReader reader, XmlSpace space, int defaultCapacity)
85                 {
86                         xmlReader = reader;
87                         this.validatingReader = reader as XmlValidatingReader;
88                         lineInfo = reader as IXmlLineInfo;
89                         this.xmlSpace = space;
90                         this.nameTable = reader.NameTable;
91                         nodeCapacity = defaultCapacity;
92                         attributeCapacity = nodeCapacity;
93                         nsCapacity = 10;
94                         idTable = new Hashtable ();
95
96                         nodes = new DTMXPathLinkedNode2 [nodeCapacity];
97                         attributes = new DTMXPathAttributeNode2 [attributeCapacity];
98                         namespaces = new DTMXPathNamespaceNode2 [nsCapacity];
99                         atomicStringPool = new string [20];
100                         nonAtomicStringPool = new string [20];
101
102                         Compile ();
103                 }
104                 
105                 XmlReader xmlReader;
106                 XmlValidatingReader validatingReader;
107                 XmlSpace xmlSpace;
108                 XmlNameTable nameTable;
109                 IXmlLineInfo lineInfo;
110                 int nodeCapacity;
111                 int attributeCapacity;
112                 int nsCapacity;
113
114                 // Linked Node
115                 DTMXPathLinkedNode2 [] nodes;
116
117                 // Attribute
118                 DTMXPathAttributeNode2 [] attributes;
119
120                 // NamespaceNode
121                 DTMXPathNamespaceNode2 [] namespaces;
122
123                 // String pool
124                 string [] atomicStringPool;
125                 int atomicIndex;
126                 string [] nonAtomicStringPool;
127                 int nonAtomicIndex;
128
129                 // idTable [string value] -> int nodeId
130                 Hashtable idTable;
131
132                 int nodeIndex;
133                 int attributeIndex;
134                 int nsIndex;
135
136                 // for attribute processing; should be reset per each element.
137                 bool hasAttributes;
138                 bool hasLocalNs;
139                 int attrIndexAtStart;
140                 int nsIndexAtStart;
141
142                 int lastNsInScope;
143                 bool skipRead = false;
144
145                 int [] parentStack = new int [10];
146                 int parentStackIndex = 0;
147
148                 public DTMXPathDocument2 CreateDocument ()
149                 {
150                         return new DTMXPathDocument2 (nameTable,
151                                 nodes,
152                                 attributes,
153                                 namespaces,
154                                 atomicStringPool,
155                                 nonAtomicStringPool,
156                                 idTable
157                         );
158                 }
159
160                 public void Compile ()
161                 {
162                         // string pool index 0 to 3 are fixed.
163                         atomicStringPool [0] = nonAtomicStringPool [0] = "";
164                         atomicStringPool [1] = nonAtomicStringPool [1] = null;
165                         atomicStringPool [2] = nonAtomicStringPool [2] = XmlNamespaces.XML;
166                         atomicStringPool [3] = nonAtomicStringPool [3] = XmlNamespaces.XMLNS;
167                         atomicIndex = nonAtomicIndex = 4;
168
169                         // index 0 is dummy. No node (including Root) is assigned to this index
170                         // So that we can easily compare index != 0 instead of index < 0.
171                         // (Difference between jnz or jbe in 80x86.)
172                         AddNode (0, 0, 0, XPathNodeType.All, 0, false, 0, 0, 0, 0, 0, 0, 0, 0);
173                         nodeIndex++;
174                         AddAttribute (0, 0, 0, 0, 0, 0, 0);
175                         AddNsNode (0, 0, 0, 0);
176                         nsIndex++;
177                         AddNsNode (1, AtomicIndex ("xml"), AtomicIndex (XmlNamespaces.XML), 0);
178
179                         // add root.
180                         AddNode (0, 0, 0, XPathNodeType.Root, AtomicIndex (xmlReader.BaseURI), false, 0, 0, 0, 0, 0, 1, 0, 0);
181
182                         this.nodeIndex = 1;
183                         this.lastNsInScope = 1;
184                         parentStack [0] = nodeIndex;
185
186                         if (xmlReader.ReadState == ReadState.Initial)
187                                 xmlReader.Read ();
188                         int startDepth = xmlReader.Depth;
189                         do {
190                                 Read ();
191                         } while (skipRead || xmlReader.Read () && xmlReader.Depth >= startDepth);
192                         SetNodeArrayLength (nodeIndex + 1);
193                         SetAttributeArrayLength (attributeIndex + 1);
194                         SetNsArrayLength (nsIndex + 1);
195
196                         string [] newArr = new string [atomicIndex];
197                         Array.Copy (atomicStringPool, newArr, atomicIndex);
198                         atomicStringPool = newArr;
199
200                         newArr = new string [nonAtomicIndex];
201                         Array.Copy (nonAtomicStringPool, newArr, nonAtomicIndex);
202                         nonAtomicStringPool = newArr;
203
204                         xmlReader = null;       // It is no more required.
205                 }
206
207                 public void Read ()
208                 {
209                         skipRead = false;
210
211                         int parent = parentStack [parentStackIndex];
212                         int prevSibling = nodeIndex;
213
214                         switch (xmlReader.NodeType) {
215                         case XmlNodeType.Element:
216                         case XmlNodeType.CDATA:
217                         case XmlNodeType.Whitespace:
218                         case XmlNodeType.SignificantWhitespace:
219                         case XmlNodeType.Comment:
220                         case XmlNodeType.Text:
221                         case XmlNodeType.ProcessingInstruction:
222                                 break;
223                         case XmlNodeType.EndElement:
224                                 int endedNode = parentStack [parentStackIndex];
225                                 AdjustLastNsInScope (endedNode);
226                                 parentStackIndex--;
227                                 return;
228                         default:
229                                 // No operations. Doctype, EntityReference, 
230                                 return;
231                         }
232
233                         string value = null;
234                         XPathNodeType nodeType = XPathNodeType.Root; // dummy
235                         bool containsCDATA = false;
236
237                         switch (xmlReader.NodeType) {
238                         case XmlNodeType.Element:
239                                 nodeType = XPathNodeType.Element;
240                                 goto case XmlNodeType.None;
241                         case XmlNodeType.SignificantWhitespace:
242                         case XmlNodeType.Whitespace:
243                         case XmlNodeType.CDATA:
244                         case XmlNodeType.Text:
245                                 skipRead = true;
246                                 do {
247                                         switch (xmlReader.NodeType) {
248                                         case XmlNodeType.Whitespace:
249                                                 switch (nodeType) {
250                                                 case XPathNodeType.Root:
251                                                         nodeType = XPathNodeType.Whitespace;
252                                                         break;
253                                                 }
254                                                 // whitespaces after CDATA are ignored
255                                                 if (!containsCDATA)
256                                                         value += xmlReader.Value;
257                                                 continue;
258                                         case XmlNodeType.SignificantWhitespace:
259                                                 if (nodeType == XPathNodeType.Root ||
260                                                     nodeType == XPathNodeType.Whitespace)
261                                                         nodeType = XPathNodeType.SignificantWhitespace;
262                                                 value += xmlReader.Value;
263                                                 continue;
264                                         case XmlNodeType.CDATA:
265                                                 // whitespaces before CDATA are ignored
266                                                 if (nodeType != XPathNodeType.Text)
267                                                         value = String.Empty;
268                                                 containsCDATA = true;
269                                                 goto case XmlNodeType.Text;
270                                         case XmlNodeType.Text:
271                                                 nodeType = XPathNodeType.Text;
272                                                 value += xmlReader.Value;
273                                                 continue;
274                                         }
275                                         break;
276                                 } while (xmlReader.Read ());
277                                 goto case XmlNodeType.None;
278                         case XmlNodeType.None:
279                                 if (nodeType == XPathNodeType.Root ||
280                                     nodeType == XPathNodeType.Whitespace && xmlSpace != XmlSpace.Preserve)
281                                         return; // do not process as a node.
282
283                                 // prepare a slot for new node.
284                                 if (parent == nodeIndex)
285                                         prevSibling = 0;
286                                 else
287                                         while (nodes [prevSibling].Parent != parent)
288                                                 prevSibling = nodes [prevSibling].Parent;
289
290                                 nodeIndex++;
291
292                                 if (prevSibling != 0)
293                                         nodes [prevSibling].NextSibling = nodeIndex;
294                                 if (parentStack [parentStackIndex] == nodeIndex - 1)
295                                         nodes [parent].FirstChild = nodeIndex;
296
297                                 // append new node.
298                                 if (nodeType == XPathNodeType.Element) {
299                                         ProcessElement (parent, prevSibling);
300                                         break;
301                                 }
302                                 AddNode (parent,
303                                         0,
304                                         prevSibling,
305                                         nodeType,
306                                         AtomicIndex (xmlReader.BaseURI),
307                                         xmlReader.IsEmptyElement,
308                                         skipRead ? 0 : AtomicIndex (xmlReader.LocalName),       // for PI
309                                         skipRead ? 0 : AtomicIndex (xmlReader.NamespaceURI),    // for PI
310                                         AtomicIndex (xmlReader.Prefix),
311                                         value == null ? 0 : NonAtomicIndex (value),
312                                         AtomicIndex (xmlReader.XmlLang),
313                                         nsIndex,
314                                         lineInfo != null ? lineInfo.LineNumber : 0,
315                                         lineInfo != null ? lineInfo.LinePosition : 0);
316                                 break;
317                         case XmlNodeType.Comment:
318                                 value = xmlReader.Value;
319                                 nodeType = XPathNodeType.Comment;
320                                 goto case XmlNodeType.None;
321                         case XmlNodeType.ProcessingInstruction:
322                                 value = xmlReader.Value;
323                                 nodeType = XPathNodeType.ProcessingInstruction;
324                                 goto case XmlNodeType.None;
325                         }
326                 }
327
328                 private void ProcessElement (int parent, int previousSibling)
329                 {
330                         WriteStartElement (parent, previousSibling);
331
332                         // process namespaces and attributes.
333                         if (xmlReader.MoveToFirstAttribute ()) {
334                                 do {
335                                         string prefix = xmlReader.Prefix;
336                                         string ns = xmlReader.NamespaceURI;
337                                         if (ns == XmlNamespaces.XMLNS)
338                                                 ProcessNamespace ((prefix == null || prefix == String.Empty) ? "" : xmlReader.LocalName, xmlReader.Value);
339                                         else
340                                                 ProcessAttribute (prefix, xmlReader.LocalName, ns, xmlReader.Value);
341
342                                 } while (xmlReader.MoveToNextAttribute ());
343                                 xmlReader.MoveToElement ();
344                         }
345
346                         CloseStartElement ();
347                 }
348
349                 private void PrepareStartElement (int previousSibling)
350                 {
351                         hasAttributes = false;
352                         hasLocalNs = false;
353                         attrIndexAtStart = attributeIndex;
354                         nsIndexAtStart = nsIndex;
355                         AdjustLastNsInScope (previousSibling);
356                 }
357
358                 private void AdjustLastNsInScope (int target)
359                 {
360                         while (namespaces [lastNsInScope].DeclaredElement == target) {
361                                 lastNsInScope = namespaces [lastNsInScope].NextNamespace;
362                         }
363                 }
364
365                 private void WriteStartElement (int parent, int previousSibling)
366                 {
367                         PrepareStartElement (previousSibling);
368
369                         AddNode (parent,
370                                 0, // dummy:firstAttribute
371                                 previousSibling,
372                                 XPathNodeType.Element,
373                                 AtomicIndex (xmlReader.BaseURI),
374                                 xmlReader.IsEmptyElement,
375                                 AtomicIndex (xmlReader.LocalName),
376                                 AtomicIndex (xmlReader.NamespaceURI),
377                                 AtomicIndex (xmlReader.Prefix),
378                                 0,      // Element has no internal value.
379                                 AtomicIndex (xmlReader.XmlLang),
380                                 lastNsInScope,
381                                 lineInfo != null ? lineInfo.LineNumber : 0,
382                                 lineInfo != null ? lineInfo.LinePosition : 0);
383
384                 }
385
386                 private void CloseStartElement ()
387                 {
388                         if (attrIndexAtStart != attributeIndex)
389                                 nodes [nodeIndex].FirstAttribute = attrIndexAtStart + 1;
390                         if (nsIndexAtStart != nsIndex) {
391                                 nodes [nodeIndex].FirstNamespace = nsIndex;
392                                 if (!xmlReader.IsEmptyElement)
393                                         lastNsInScope = nsIndex;
394                         }
395
396                         if (!nodes [nodeIndex].IsEmptyElement) {
397                                 parentStackIndex++;
398                                 if (parentStack.Length == parentStackIndex) {
399                                         int [] tmp = new int [parentStackIndex * 2];
400                                         Array.Copy (parentStack, tmp, parentStackIndex);
401                                         parentStack = tmp;
402                                 }
403                                 parentStack [parentStackIndex] = nodeIndex;
404                         }
405                 }
406
407                 private void ProcessNamespace (string prefix, string ns)
408                 {
409                         int nextTmp = hasLocalNs ?
410                                 nsIndex : nodes [nodeIndex].FirstNamespace;
411
412                         nsIndex++;
413
414                         this.AddNsNode (nodeIndex,
415                                 AtomicIndex (prefix),
416                                 AtomicIndex (ns),
417                                 nextTmp);
418                         hasLocalNs = true;
419                 }
420
421                 private void ProcessAttribute (string prefix, string localName, string ns, string value)
422                 {
423                         attributeIndex ++;
424
425                         this.AddAttribute (nodeIndex,
426                                 AtomicIndex (localName),
427                                 AtomicIndex (ns), 
428                                 prefix != null ? AtomicIndex (prefix) : 0, 
429                                 NonAtomicIndex (value),
430                                 lineInfo != null ? lineInfo.LineNumber : 0,
431                                 lineInfo != null ? lineInfo.LinePosition : 0);
432                         if (hasAttributes)
433                                 attributes [attributeIndex - 1].NextAttribute = attributeIndex;
434                         else
435                                 hasAttributes = true;
436
437                         // Identity infoset
438                         if (validatingReader != null) {
439                                 XmlSchemaDatatype dt = validatingReader.SchemaType as XmlSchemaDatatype;
440                                 if (dt == null) {
441                                         XmlSchemaType xsType = validatingReader.SchemaType as XmlSchemaType;
442                                         if (xsType != null)
443                                                 dt = xsType.Datatype;
444                                 }
445                                 if (dt != null && dt.TokenizedType == XmlTokenizedType.ID)
446                                         idTable.Add (value, nodeIndex);
447                         }
448                 }
449
450                 private int AtomicIndex (string s)
451                 {
452                         if (s == "")
453                                 return 0;
454                         if (s == null)
455                                 return 1;
456                         int i = 2;
457                         for (; i < atomicIndex; i++)
458                                 if (Object.ReferenceEquals (s, atomicStringPool [i]))
459                                         return i;
460
461                         if (atomicIndex == atomicStringPool.Length) {
462                                 string [] newArr = new string [atomicIndex * 2];
463                                 Array.Copy (atomicStringPool, newArr, atomicIndex);
464                                 atomicStringPool = newArr;
465                         }
466                         atomicStringPool [atomicIndex] = s;
467                         return atomicIndex++;
468                 }
469
470                 private int NonAtomicIndex (string s)
471                 {
472                         if (s == "")
473                                 return 0;
474                         if (s == null)
475                                 return 1;
476                         int i = 2;
477
478                         // Here we don't compare all the entries (sometimes it
479                         // goes extremely slow).
480                         int max = nonAtomicIndex < 100 ? nonAtomicIndex : 100;
481                         for (; i < max; i++)
482                                 if (s == nonAtomicStringPool [i])
483                                         return i;
484
485                         if (nonAtomicIndex == nonAtomicStringPool.Length) {
486                                 string [] newArr = new string [nonAtomicIndex * 2];
487                                 Array.Copy (nonAtomicStringPool, newArr, nonAtomicIndex);
488                                 nonAtomicStringPool = newArr;
489                         }
490                         nonAtomicStringPool [nonAtomicIndex] = s;
491                         return nonAtomicIndex++;
492                 }
493
494                 private void SetNodeArrayLength (int size)
495                 {
496                         DTMXPathLinkedNode2 [] newArr = new DTMXPathLinkedNode2 [size];
497                         Array.Copy (nodes, newArr, System.Math.Min (size, nodes.Length));
498                         nodes = newArr;
499                 }
500
501                 private void SetAttributeArrayLength (int size)
502                 {
503                         DTMXPathAttributeNode2 [] newArr = 
504                                 new DTMXPathAttributeNode2 [size];
505                         Array.Copy (attributes, newArr, System.Math.Min (size, attributes.Length));
506                         attributes = newArr;
507                 }
508
509                 private void SetNsArrayLength (int size)
510                 {
511                         DTMXPathNamespaceNode2 [] newArr =
512                                 new DTMXPathNamespaceNode2 [size];
513                         Array.Copy (namespaces, newArr, System.Math.Min (size, namespaces.Length));
514                         namespaces = newArr;
515                 }
516
517                 // Here followings are skipped: firstChild, nextSibling, 
518                 public void AddNode (int parent, int firstAttribute, int previousSibling, XPathNodeType nodeType, int baseUri, bool isEmptyElement, int localName, int ns, int prefix, int value, int xmlLang, int namespaceNode, int lineNumber, int linePosition)
519                 {
520                         if (nodes.Length < nodeIndex + 1) {
521                                 nodeCapacity *= 4;
522                                 SetNodeArrayLength (nodeCapacity);
523                         }
524
525 #if DTM_CLASS
526                         nodes [nodeIndex] = new DTMXPathLinkedNode2 ();
527 #endif
528                         nodes [nodeIndex].FirstChild = 0;               // dummy
529                         nodes [nodeIndex].Parent = parent;
530                         nodes [nodeIndex].FirstAttribute = firstAttribute;
531                         nodes [nodeIndex].PreviousSibling = previousSibling;
532                         nodes [nodeIndex].NextSibling = 0;      // dummy
533                         nodes [nodeIndex].NodeType = nodeType;
534                         nodes [nodeIndex].BaseURI = baseUri;
535                         nodes [nodeIndex].IsEmptyElement = isEmptyElement;
536                         nodes [nodeIndex].LocalName = localName;
537                         nodes [nodeIndex].NamespaceURI = ns;
538                         nodes [nodeIndex].Prefix = prefix;
539                         nodes [nodeIndex].Value = value;
540                         nodes [nodeIndex].XmlLang = xmlLang;
541                         nodes [nodeIndex].FirstNamespace = namespaceNode;
542                         nodes [nodeIndex].LineNumber = lineNumber;
543                         nodes [nodeIndex].LinePosition = linePosition;
544                 }
545
546                 // Followings are skipped: nextAttribute,
547                 public void AddAttribute (int ownerElement, int localName, int ns, int prefix, int value, int lineNumber, int linePosition)
548                 {
549                         if (attributes.Length < attributeIndex + 1) {
550                                 attributeCapacity *= 4;
551                                 SetAttributeArrayLength (attributeCapacity);
552                         }
553
554 #if DTM_CLASS
555                         attributes [attributeIndex] = new DTMXPathAttributeNode2 ();
556 #endif
557                         attributes [attributeIndex].OwnerElement = ownerElement;
558                         attributes [attributeIndex].LocalName = localName;
559                         attributes [attributeIndex].NamespaceURI = ns;
560                         attributes [attributeIndex].Prefix = prefix;
561                         attributes [attributeIndex].Value = value;
562                         attributes [attributeIndex].LineNumber = lineNumber;
563                         attributes [attributeIndex].LinePosition = linePosition;
564                 }
565
566                 // Followings are skipped: nextNsNode (may be next attribute in the same element, or ancestors' nsNode)
567                 public void AddNsNode (int declaredElement, int name, int ns, int nextNs)
568                 {
569                         if (namespaces.Length < nsIndex + 1) {
570                                 nsCapacity *= 4;
571                                 SetNsArrayLength (nsCapacity);
572                         }
573
574 #if DTM_CLASS
575                         namespaces [nsIndex] = new DTMXPathNamespaceNode2 ();
576 #endif
577                         namespaces [nsIndex].DeclaredElement = declaredElement;
578                         namespaces [nsIndex].Name = name;
579                         namespaces [nsIndex].Namespace = ns;
580                         namespaces [nsIndex].NextNamespace = nextNs;
581                         if (lineInfo != null && lineInfo.HasLineInfo ()) {
582                                 namespaces [nsIndex].LineNumber = lineInfo.LineNumber;
583                                 namespaces [nsIndex].LinePosition = lineInfo.LinePosition;
584                         }
585                 }
586         }
587 }
588