2003-07-17 Peter Williams <peter@newton.cx>
[mono.git] / mcs / class / System.XML / Mono.Xml.XPath / DTMXPathDocumentBuilder.cs
1 //
2 // Mono.Xml.XPath.DTMXPathDocumentBuilder
3 //
4 // Author:
5 //      Atsushi Enomoto (ginga@kit.hi-ho.ne.jp)
6 //
7 // (C) 2003 Atsushi Enomoto
8 //
9 using System;
10 using System.Collections;
11 using System.IO;
12 using System.Xml;
13 using System.Xml.Schema;
14 using System.Xml.XPath;
15
16 namespace Mono.Xml.XPath
17 {
18
19         public class DTMXPathDocumentBuilder
20         {
21                 public DTMXPathDocumentBuilder (string url)
22                         : this (url, XmlSpace.None)
23                 {
24                 }
25
26                 public DTMXPathDocumentBuilder (string url, XmlSpace space)
27                         : this (new XmlTextReader (url), space)
28                 {
29                 }
30
31                 public DTMXPathDocumentBuilder (XmlReader reader)
32                         : this (reader, XmlSpace.None)
33                 {
34                 }
35
36                 public DTMXPathDocumentBuilder (XmlReader reader, XmlSpace space)
37                 {
38                         this.xmlReader = reader;
39                         this.xmlSpace = xmlSpace;
40                         this.nameTable = reader.NameTable;
41                         Compile ();
42                 }
43                 
44                 XmlReader xmlReader;
45                 XmlSpace xmlSpace;
46                 XmlNameTable nameTable;
47                 int defaultCapacity = 100;
48                 public int DefaultCapacity {
49                         get { return defaultCapacity; }
50                         set {
51                                 if (value < 0)
52                                         throw new ArgumentOutOfRangeException ();
53                                 defaultCapacity = value;
54                         }
55                 }
56
57 #region Tree node info collections.
58                 // Tree Node
59                 int [] firstChild_ = new int [0];
60                 int [] parent_ = new int [0];
61                 int [] firstAttribute_ = new int [0];
62                 int [] previousSibling_ = new int [0];
63                 int [] nextSibling_ = new int [0];
64                 int [] depth_ = new int [0];
65                 int [] position_ = new int [0];
66                 XPathNodeType [] nodeType_ = new XPathNodeType [0];
67                 string [] baseUri_ = new string [0];
68                 bool [] isEmptyElement_ = new bool [0];
69                 string [] localName_ = new string [0];
70                 string [] namespaceUri_ = new string [0];
71                 string [] prefix_ = new string [0];
72                 string [] value_ = new string [0];
73                 string [] xmlLang_ = new string [0];
74                 int [] namespaceNode_ = new int [0];
75                 object [] schemaType_ = new object [0];
76
77                 // Attribute
78                 int [] ownerElement_ = new int [0];
79                 int [] nextAttribute_ = new int [0];
80                 string [] attrLocalName_ = new string [0];
81                 string [] attrPrefix_ = new string [0];
82                 string [] attrNsUri_ = new string [0];
83                 string [] attrValue_ = new string [0];
84                 object [] attrSchemaType_ = new object [0];
85
86                 // NamespaceNode
87                 int [] nsDeclaredElement_ = new int [100];
88                 int [] nextNsNode_ = new int [100];
89                 string [] nsNodeName_ = new string [100];
90                 string [] nsNodeUri_ = new string [100];
91 #endregion
92
93                 int nodeIndex;
94                 int attributeIndex;
95                 int nsIndex;
96                 bool requireFirstChildFill;
97
98                 public DTMXPathDocument CreateDocument ()
99                 {
100                         return new DTMXPathDocument (nameTable,
101                                 firstChild_,
102                                 parent_,
103                                 firstAttribute_,
104                                 previousSibling_,
105                                 nextSibling_,
106                                 depth_,
107                                 position_,
108                                 nodeType_,
109                                 baseUri_,
110                                 isEmptyElement_,
111                                 localName_,
112                                 namespaceUri_,
113                                 prefix_,
114                                 value_,
115                                 xmlLang_,
116                                 namespaceNode_,
117                                 schemaType_,
118
119                                 // Attribute
120                                 ownerElement_,
121                                 nextAttribute_,
122                                 attrLocalName_,
123                                 attrPrefix_,
124                                 attrNsUri_,
125                                 attrValue_,
126                                 attrSchemaType_,
127
128                                 // NamespaceNode
129                                 nsDeclaredElement_,
130                                 nextNsNode_,
131                                 nsNodeName_,
132                                 nsNodeUri_
133                         );
134                 }
135
136                 public void Compile ()
137                 {
138                         // index 0 is dummy. No node (including Root) is assigned to this index
139                         // So that we can easily compare index != 0 instead of index < 0.
140                         // (Difference between jnz or jbe in 80x86.)
141                         AddNode (0, 0, 0, 0, 0, 0, XPathNodeType.All, "", false, "", "", "", "", "", 0, null);
142                         nodeIndex++;
143                         AddAttribute (0, null, null, null, null, null);
144                         nextAttribute_ [0] = 0;
145                         AddNsNode (0, null, null);
146                         nsIndex++;
147                         nextNsNode_ [0] = 0;
148                         AddNsNode (1, "xml", XmlNamespaces.XML);
149                         nextNsNode_ [1] = 0;
150
151                         // add root.
152                         AddNode (0, 0, 0, 0, -1, 0, XPathNodeType.Root, xmlReader.BaseURI, false, "", "", "", "", "", 1, null);
153
154                         this.nodeIndex = 1;
155                         this.requireFirstChildFill = true;
156
157                         while (!xmlReader.EOF)
158                                 Read ();
159                         SetNodeArraysLength (nodeIndex + 1);
160                         SetAttributeArraysLength (attributeIndex + 1);
161                         SetNsArraysLength (nsIndex + 1);
162
163                         xmlReader = null;       // It is no more required.
164                 }
165
166                 int prevSibling;
167                 int position;
168                 int parent;
169                 bool skipRead = false;
170                 public void Read ()
171                 {
172                         if (!skipRead)
173                                 if (!xmlReader.Read ())
174                                         return;
175                         skipRead = false;
176
177                         parent = nodeIndex;
178                         if (depth_ [nodeIndex] >= xmlReader.Depth) {    // not ">=" ? But == worked when with ArrayList...
179                                 // if not, then current node is parent.
180                                 while (xmlReader.Depth <= depth_ [parent])
181                                         parent = parent_ [parent];
182                         }
183
184                         prevSibling = nodeIndex;
185                         position = 0;
186                         switch (xmlReader.NodeType) {
187                         case XmlNodeType.Element:
188                         case XmlNodeType.CDATA:
189                         case XmlNodeType.SignificantWhitespace:
190                         case XmlNodeType.Comment:
191                         case XmlNodeType.Text:
192                         case XmlNodeType.ProcessingInstruction:
193                                 if (requireFirstChildFill)
194                                         prevSibling = 0;
195                                 else
196                                         while (depth_ [prevSibling] != xmlReader.Depth)
197                                                 prevSibling = parent_ [prevSibling];
198                                 if (prevSibling != 0)
199                                         position = position_ [prevSibling] + 1;
200
201                                 nodeIndex++;
202
203                                 if (prevSibling != 0)
204                                         nextSibling_ [prevSibling] = nodeIndex;
205                                 if (requireFirstChildFill)
206                                         firstChild_ [nodeIndex-1] = nodeIndex;
207                                 break;
208                         case XmlNodeType.Whitespace:
209                                 if (xmlSpace == XmlSpace.Preserve)
210                                         goto case XmlNodeType.Text;
211                                 else
212                                         goto default;
213                         default:
214                                 // No operations. Doctype, EntityReference, 
215                                 return;
216                         }
217
218                         requireFirstChildFill = false;  // Might be changed in ProcessElement().
219
220                         string value = null;
221                         XPathNodeType nodeType = XPathNodeType.Text;
222
223                         switch (xmlReader.NodeType) {
224                         case XmlNodeType.Element:
225                                 ProcessElement (parent, prevSibling, position);
226                                 break;
227                         case XmlNodeType.EndElement:
228                                 break;
229                         case XmlNodeType.CDATA:
230                         case XmlNodeType.SignificantWhitespace:
231                         case XmlNodeType.Text:
232                                 if (value == null)
233                                         skipRead = true;
234                                 AddNode (parent,
235                                         0,
236                                         attributeIndex,
237                                         prevSibling,
238                                         xmlReader.Depth,
239                                         position,
240                                         nodeType,
241                                         xmlReader.BaseURI,
242                                         xmlReader.IsEmptyElement,
243                                         xmlReader.LocalName,    // for PI
244                                         xmlReader.NamespaceURI, // for PI
245                                         xmlReader.Prefix,
246                                         value,
247                                         xmlReader.XmlLang,
248                                         nsIndex,
249                                         null);  // schemaType
250                                 // this code is tricky, but after ReadString() invokation,
251                                 // xmlReader is moved to next node!!
252                                 if (value == null)
253                                         value_ [nodeIndex] = xmlReader.ReadString ();
254                                 break;
255                         case XmlNodeType.Comment:
256                                 value = xmlReader.Value;
257                                 nodeType = XPathNodeType.Comment;
258                                 goto case XmlNodeType.Text;
259                         case XmlNodeType.Whitespace:
260                                 goto case XmlNodeType.Text;
261                         case XmlNodeType.ProcessingInstruction:
262                                 value = xmlReader.Value;
263                                 nodeType = XPathNodeType.ProcessingInstruction;
264                                 goto case XmlNodeType.Text;
265                         }
266                 }
267
268                 private void ProcessElement (int parent, int previousSibling, int position)
269                 {
270                         int firstAttributeIndex = 0;
271                         int lastNsIndexInCurrent = 0;
272
273
274                         // process namespaces and attributes.
275                         if (xmlReader.MoveToFirstAttribute ()) {
276                                 do {
277                                         if (xmlReader.NamespaceURI == XmlNamespaces.XMLNS) {
278                                                 // add namespace node.
279                                                 nsIndex++;
280
281                                                 nextNsNode_ [nsIndex] = lastNsIndexInCurrent == 0 ? namespaceNode_ [parent] : lastNsIndexInCurrent;
282
283                                                 if (lastNsIndexInCurrent == 0)
284                                                         namespaceNode_ [nodeIndex] = nsIndex;
285                                                 this.AddNsNode (nodeIndex,
286                                                         xmlReader.Prefix == "" ?
287                                                                 "" : xmlReader.LocalName,
288                                                         xmlReader.Value);
289                                                 lastNsIndexInCurrent = nsIndex;
290                                         } else {
291                                                 // add attribute node.
292                                                 attributeIndex ++;
293                                                 this.AddAttribute (nodeIndex, xmlReader.LocalName, xmlReader.NamespaceURI, xmlReader.Prefix, xmlReader.Value, null);
294                                                 if (firstAttributeIndex == 0)
295                                                         firstAttributeIndex = attributeIndex;
296                                                 else
297                                                         nextAttribute_ [attributeIndex - 1] = attributeIndex;
298                                                 // dummy for "current" attribute.
299                                                 nextAttribute_ [attributeIndex] = 0;
300                                         }
301                                 } while (xmlReader.MoveToNextAttribute ());
302                                 xmlReader.MoveToElement ();
303                         }
304
305                         AddNode (parent,
306                                 firstAttributeIndex,
307                                 attributeIndex,
308                                 previousSibling,
309                                 xmlReader.Depth,
310                                 position,
311                                 XPathNodeType.Element,
312                                 xmlReader.BaseURI,
313                                 xmlReader.IsEmptyElement,
314                                 xmlReader.LocalName,
315                                 xmlReader.NamespaceURI,
316                                 xmlReader.Prefix,
317                                 "",     // Element has no internal value.
318                                 xmlReader.XmlLang,
319                                 nsIndex,
320                                 null);  // schemaType
321                         if (!xmlReader.IsEmptyElement)
322                                 requireFirstChildFill = true;
323                 }
324
325                 private void SetObjectArrayLength (ref object [] a, int length)
326                 {
327                         object [] arr = new object [length];
328                         Array.Copy (a, arr, System.Math.Min (a.Length, length));
329                         a = arr;
330                 }
331
332                 private void SetBoolArrayLength (ref bool [] a, int length)
333                 {
334                         bool [] bArr = new bool [length];
335                         Array.Copy (a, bArr, System.Math.Min (a.Length, length));
336                         a = bArr;
337                 }
338
339                 private void SetXPathNodeTypeArrayLength (ref XPathNodeType [] a, int length)
340                 {
341                         XPathNodeType [] arr = new XPathNodeType [length];
342                         Array.Copy (a, arr, System.Math.Min (a.Length, length));
343                         a = arr;
344                 }
345
346                 private void SetIntArrayLength (ref int [] a, int length)
347                 {
348                         int [] intArr = new int [length];
349                         Array.Copy (a, intArr, System.Math.Min (a.Length, length));
350                         a = intArr;
351                 }
352
353                 private void SetStringArrayLength (ref string [] a, int length)
354                 {
355                         string [] strArr = new string [length];
356                         Array.Copy (a, strArr, System.Math.Min (a.Length, length));
357                         a = strArr;
358                 }
359
360                 private void SetNodeArraysLength (int size)
361                 {
362                         SetIntArrayLength (ref firstChild_, size);
363                         SetIntArrayLength (ref parent_, size);
364                         SetIntArrayLength (ref firstAttribute_, size);
365                         SetIntArrayLength (ref previousSibling_, size);
366                         SetIntArrayLength (ref nextSibling_, size);
367                         SetIntArrayLength (ref depth_, size);
368                         SetIntArrayLength (ref position_, size);
369                         SetXPathNodeTypeArrayLength (ref nodeType_, size);
370                         SetStringArrayLength (ref baseUri_, size);
371                         SetBoolArrayLength (ref isEmptyElement_, size);
372                         SetStringArrayLength (ref localName_, size);
373                         SetStringArrayLength (ref namespaceUri_, size);
374                         SetStringArrayLength (ref prefix_, size);
375                         SetStringArrayLength (ref value_, size);
376                         SetStringArrayLength (ref xmlLang_, size);
377                         SetIntArrayLength (ref namespaceNode_, size);
378                         SetObjectArrayLength (ref schemaType_, size);
379                 }
380
381                 private void SetAttributeArraysLength (int size)
382                 {
383                         SetIntArrayLength (ref ownerElement_, size);
384                         SetIntArrayLength (ref nextAttribute_, size);
385                         SetStringArrayLength (ref attrLocalName_, size);
386                         SetStringArrayLength (ref attrPrefix_, size);
387                         SetStringArrayLength (ref attrNsUri_, size);
388                         SetStringArrayLength (ref attrValue_, size);
389                         SetObjectArrayLength (ref attrSchemaType_, size);
390                 }
391
392                 private void SetNsArraysLength (int size)
393                 {
394                         SetIntArrayLength (ref nsDeclaredElement_, size);
395                         SetIntArrayLength (ref nextNsNode_, size);
396                         SetStringArrayLength (ref nsNodeName_, size);
397                         SetStringArrayLength (ref nsNodeUri_, size);
398                 }
399
400                 // Here followings are skipped: firstChild, nextSibling, 
401                 public void AddNode (int parent, int firstAttribute, int attributeEnd, int previousSibling, int depth, int position, XPathNodeType nodeType, string baseUri, bool isEmptyElement, string localName, string ns, string prefix, string value, string xmlLang, int namespaceNode, object schemaType)
402                 {
403                         if (firstChild_.Length < nodeIndex + 1) {
404                                 if (firstChild_.Length >= defaultCapacity)
405                                         defaultCapacity *= 2;
406                                 SetNodeArraysLength (defaultCapacity);
407                         }
408
409                         firstChild_ [nodeIndex] = 0;            // dummy
410                         parent_ [nodeIndex] = parent;
411                         firstAttribute_ [nodeIndex] = firstAttribute;
412                         previousSibling_ [nodeIndex] = previousSibling;
413                         nextSibling_ [nodeIndex] = 0;   // dummy
414                         depth_ [nodeIndex] = depth;
415                         position_ [nodeIndex] = position;
416                         nodeType_ [nodeIndex] = nodeType;
417                         baseUri_ [nodeIndex] = baseUri;
418                         isEmptyElement_ [nodeIndex] = isEmptyElement;
419                         localName_ [nodeIndex] = localName;
420                         namespaceUri_ [nodeIndex] = ns;
421                         prefix_ [nodeIndex] = prefix;
422                         value_ [nodeIndex] = value;
423                         xmlLang_ [nodeIndex] = xmlLang;
424                         namespaceNode_ [nodeIndex] = namespaceNode;
425                         schemaType_ [nodeIndex] = schemaType;
426                 }
427
428                 // Followings are skipped: nextAttribute,
429                 public void AddAttribute (int ownerElement, string localName, string ns, string prefix, string value, object schemaType)
430                 {
431                         if (ownerElement_.Length < attributeIndex + 1) {
432                                 if (ownerElement_.Length >= defaultCapacity)
433                                         defaultCapacity *= 2;
434                                 SetAttributeArraysLength (defaultCapacity);
435                         }
436
437                         ownerElement_ [attributeIndex] = ownerElement;
438                         attrLocalName_ [attributeIndex] = localName;
439                         attrNsUri_ [attributeIndex] = ns;
440                         attrPrefix_ [attributeIndex] = prefix;
441                         attrValue_ [attributeIndex] = value;
442                         attrSchemaType_ [attributeIndex] = schemaType;
443                 }
444
445                 // Followings are skipped: nextNsNode (may be next attribute in the same element, or ancestors' nsNode)
446                 public void AddNsNode (int declaredElement, string name, string ns)
447                 {
448                         if (nsDeclaredElement_.Length < nsIndex + 1) {
449                                 if (nsDeclaredElement_.Length >= defaultCapacity)
450                                         defaultCapacity *= 2;
451                                 SetNsArraysLength (defaultCapacity);
452                         }
453
454                         nsDeclaredElement_ [nsIndex] = declaredElement;
455                         nsNodeName_ [nsIndex] = name;
456                         nsNodeUri_ [nsIndex] = ns;
457                 }
458         }
459 }
460