2004-01-27 Atsushi Enomoto <atsushi@ximian.com>
[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, 400)
23                 {
24                 }
25
26                 public DTMXPathDocumentBuilder (string url, XmlSpace space)
27                         : this (url, space, 400)
28                 {
29                 }
30
31                 public DTMXPathDocumentBuilder (string url, XmlSpace space, int defaultCapacity)
32                         : this (new XmlTextReader (url), space, defaultCapacity)
33                 {
34                 }
35
36                 public DTMXPathDocumentBuilder (XmlReader reader)
37                         : this (reader, XmlSpace.None, 400)
38                 {
39                 }
40
41                 public DTMXPathDocumentBuilder (XmlReader reader, XmlSpace space)
42                         : this (reader, space, 400)
43                 {
44                 }
45
46                 public DTMXPathDocumentBuilder (XmlReader reader, XmlSpace space, int defaultCapacity)
47                 {
48                         this.xmlReader = reader;
49                         this.validatingReader = reader as XmlValidatingReader;
50                         lineInfo = reader as IXmlLineInfo;
51                         this.xmlSpace = space;
52                         this.nameTable = reader.NameTable;
53                         nodeCapacity = nodeCapacity;
54                         attributeCapacity = nodeCapacity * 2;
55                         Compile ();
56                 }
57                 
58                 XmlReader xmlReader;
59                 XmlValidatingReader validatingReader;
60                 XmlSpace xmlSpace;
61                 XmlNameTable nameTable;
62                 IXmlLineInfo lineInfo;
63                 int nodeCapacity = 400;
64                 int attributeCapacity = 800;
65                 int nsCapacity = 10;
66
67                 // Linked Node
68                 DTMXPathLinkedNode [] nodes = new DTMXPathLinkedNode [0];
69
70                 // Attribute
71                 DTMXPathAttributeNode [] attributes = new DTMXPathAttributeNode [0];
72
73                 // NamespaceNode
74                 DTMXPathNamespaceNode [] namespaces = new DTMXPathNamespaceNode [0];
75
76                 // idTable [string value] -> int nodeId
77                 Hashtable idTable;
78
79                 int nodeIndex;
80                 int attributeIndex;
81                 int nsIndex;
82                 bool requireFirstChildFill;
83
84                 int prevSibling;
85                 int position;
86                 int lastNsInScope;
87                 bool skipRead = false;
88
89                 public DTMXPathDocument CreateDocument ()
90                 {
91                         return new DTMXPathDocument (nameTable,
92                                 nodes,
93                                 attributes,
94                                 namespaces,
95                                 idTable
96                         );
97                 }
98
99                 public void Compile ()
100                 {
101                         idTable = new Hashtable ();
102
103                         // index 0 is dummy. No node (including Root) is assigned to this index
104                         // So that we can easily compare index != 0 instead of index < 0.
105                         // (Difference between jnz or jbe in 80x86.)
106                         AddNode (0, 0, 0, 0, 0, 0, XPathNodeType.All, "", false, "", "", "", "", "", 0, 0, 0);
107                         nodeIndex++;
108                         AddAttribute (0, null, null, null, null, null, 0, 0);
109 //                      attributes [0].NextAttribute = 0;
110                         AddNsNode (0, null, null);
111                         nsIndex++;
112 //                      nextNsNode_ [0] = 0;
113                         AddNsNode (1, "xml", XmlNamespaces.XML);
114 //                      nextNsNode_ [1] = 0;
115
116                         // add root.
117                         AddNode (0, 0, 0, 0, -1, 0, XPathNodeType.Root, xmlReader.BaseURI, false, "", "", "", "", "", 1, 0, 0);
118
119                         this.nodeIndex = 1;
120                         this.lastNsInScope = 1;
121                         this.requireFirstChildFill = true;
122
123                         while (!xmlReader.EOF)
124                                 Read ();
125                         SetNodeArrayLength (nodeIndex + 1);
126                         SetAttributeArrayLength (attributeIndex + 1);
127                         SetNsArrayLength (nsIndex + 1);
128
129                         xmlReader = null;       // It is no more required.
130                 }
131
132                 public void Read ()
133                 {
134                         if (!skipRead)
135                                 if (!xmlReader.Read ())
136                                         return;
137                         skipRead = false;
138                         int parent = nodeIndex;
139
140                         if (nodes [nodeIndex].Depth >= xmlReader.Depth) {       // not ">=" ? But == worked when with ArrayList...
141                                 // if not, then current node is parent.
142                                 while (xmlReader.Depth <= nodes [parent].Depth)
143                                         parent = nodes [parent].Parent;
144                         }
145
146                         prevSibling = nodeIndex;
147                         position = 0;
148                         switch (xmlReader.NodeType) {
149                         case XmlNodeType.Element:
150                         case XmlNodeType.CDATA:
151                         case XmlNodeType.SignificantWhitespace:
152                         case XmlNodeType.Comment:
153                         case XmlNodeType.Text:
154                         case XmlNodeType.ProcessingInstruction:
155                                 if (requireFirstChildFill)
156                                         prevSibling = 0;
157                                 else
158                                         while (nodes [prevSibling].Depth != xmlReader.Depth)
159                                                 prevSibling = nodes [prevSibling].Parent;
160                                 if (prevSibling != 0)
161                                         position = nodes [prevSibling].Position + 1;
162
163                                 nodeIndex++;
164
165                                 if (prevSibling != 0)
166                                         nodes [prevSibling].NextSibling = nodeIndex;
167                                 if (requireFirstChildFill)
168                                         nodes [parent].FirstChild = nodeIndex;
169                                 break;
170                         case XmlNodeType.Whitespace:
171                                 if (xmlSpace == XmlSpace.Preserve)
172                                         goto case XmlNodeType.Text;
173                                 else
174                                         goto default;
175                         case XmlNodeType.EndElement:
176                                 requireFirstChildFill = false;
177                                 return;
178                         default:
179                                 // No operations. Doctype, EntityReference, 
180                                 return;
181                         }
182
183                         requireFirstChildFill = false;  // Might be changed in ProcessElement().
184
185                         string value = null;
186                         XPathNodeType nodeType = xmlReader.NodeType == XmlNodeType.Whitespace ?
187                                 XPathNodeType.Whitespace : XPathNodeType.Text;
188
189                         switch (xmlReader.NodeType) {
190                         case XmlNodeType.Element:
191                                 ProcessElement (parent, prevSibling, position);
192                                 break;
193                         case XmlNodeType.CDATA:
194                         case XmlNodeType.SignificantWhitespace:
195                         case XmlNodeType.Text:
196                         case XmlNodeType.Whitespace:
197                                 if (value == null)
198                                         skipRead = true;
199                                 AddNode (parent,
200                                         0,
201                                         attributeIndex,
202                                         prevSibling,
203                                         xmlReader.Depth,
204                                         position,
205                                         nodeType,
206                                         xmlReader.BaseURI,
207                                         xmlReader.IsEmptyElement,
208                                         xmlReader.LocalName,    // for PI
209                                         xmlReader.NamespaceURI, // for PI
210                                         xmlReader.Prefix,
211                                         value,
212                                         xmlReader.XmlLang,
213                                         nsIndex,
214                                         lineInfo != null ? lineInfo.LineNumber : 0,
215                                         lineInfo != null ? lineInfo.LinePosition : 0);
216                                 // this code is tricky, but after ReadString() invokation,
217                                 // xmlReader is moved to next node!!
218                                 if (value == null)
219                                         nodes [nodeIndex].Value = xmlReader.ReadString ();
220                                 break;
221                         case XmlNodeType.Comment:
222                                 value = xmlReader.Value;
223                                 nodeType = XPathNodeType.Comment;
224                                 goto case XmlNodeType.Text;
225                         case XmlNodeType.ProcessingInstruction:
226                                 value = xmlReader.Value;
227                                 nodeType = XPathNodeType.ProcessingInstruction;
228                                 goto case XmlNodeType.Text;
229                         }
230                 }
231
232                 private void ProcessElement (int parent, int previousSibling, int position)
233                 {
234                         int firstAttributeIndex = 0;
235                         int lastNsIndexInCurrent = 0;
236
237                         while (namespaces [lastNsInScope].DeclaredElement == previousSibling) {
238                                 lastNsInScope = namespaces [lastNsInScope].NextNamespace;
239                         }
240
241                         // process namespaces and attributes.
242                         if (xmlReader.MoveToFirstAttribute ()) {
243                                 do {
244                                         if (xmlReader.NamespaceURI == XmlNamespaces.XMLNS) {
245                                                 // add namespace node.
246                                                 nsIndex++;
247
248                                                 int nextTmp = lastNsIndexInCurrent == 0 ? nodes [parent].FirstNamespace : lastNsIndexInCurrent;
249
250                                                 this.AddNsNode (nodeIndex,
251                                                         (xmlReader.Prefix == null || xmlReader.Prefix == String.Empty) ?
252                                                                 "" : xmlReader.LocalName,
253                                                         xmlReader.Value);
254                                                 namespaces [nsIndex].NextNamespace = nextTmp;
255                                                 lastNsIndexInCurrent = nsIndex;
256                                         } else {
257                                                 // add attribute node.
258                                                 attributeIndex ++;
259                                                 this.AddAttribute (nodeIndex,
260                                                         xmlReader.LocalName,
261                                                         xmlReader.NamespaceURI, 
262                                                         xmlReader.Prefix != null ? xmlReader.Prefix : String.Empty, 
263                                                         xmlReader.Value,
264                                                         null, 
265                                                         lineInfo != null ? lineInfo.LineNumber : 0,
266                                                         lineInfo != null ? lineInfo.LinePosition : 0);
267                                                 if (firstAttributeIndex == 0)
268                                                         firstAttributeIndex = attributeIndex;
269                                                 else
270                                                         attributes [attributeIndex - 1].NextAttribute = attributeIndex;
271                                                 // dummy for "current" attribute.
272                                                 attributes [attributeIndex].NextAttribute = 0;
273
274                                                 // Identity infoset
275                                                 if (validatingReader != null) {
276                                                         XmlSchemaDatatype dt = validatingReader.SchemaType as XmlSchemaDatatype;
277                                                         if (dt == null) {
278                                                                 XmlSchemaType xsType = validatingReader.SchemaType as XmlSchemaType;
279                                                                 if (xsType != null)
280                                                                         dt = xsType.Datatype;
281                                                         }
282                                                         if (dt != null && dt.TokenizedType == XmlTokenizedType.ID)
283                                                                 idTable.Add (xmlReader.Value, nodeIndex);
284                                                 }
285                                         }
286                                 } while (xmlReader.MoveToNextAttribute ());
287                                 xmlReader.MoveToElement ();
288                         }
289
290                         if (lastNsIndexInCurrent > 0)
291                                 lastNsInScope = nsIndex;
292
293                         AddNode (parent,
294                                 firstAttributeIndex,
295                                 attributeIndex,
296                                 previousSibling,
297                                 xmlReader.Depth,
298                                 position,
299                                 XPathNodeType.Element,
300                                 xmlReader.BaseURI,
301                                 xmlReader.IsEmptyElement,
302                                 xmlReader.LocalName,
303                                 xmlReader.NamespaceURI,
304                                 xmlReader.Prefix,
305                                 "",     // Element has no internal value.
306                                 xmlReader.XmlLang,
307                                 lastNsInScope,
308                                 lineInfo != null ? lineInfo.LineNumber : 0,
309                                 lineInfo != null ? lineInfo.LinePosition : 0);
310                         if (!xmlReader.IsEmptyElement)
311                                 requireFirstChildFill = true;
312                 }
313
314                 private void SetNodeArrayLength (int size)
315                 {
316                         DTMXPathLinkedNode [] newArr = new DTMXPathLinkedNode [size];
317                         Array.Copy (nodes, newArr, System.Math.Min (size, nodes.Length));
318                         nodes = newArr;
319                 }
320
321                 private void SetAttributeArrayLength (int size)
322                 {
323                         DTMXPathAttributeNode [] newArr = 
324                                 new DTMXPathAttributeNode [size];
325                         Array.Copy (attributes, newArr, System.Math.Min (size, attributes.Length));
326                         attributes = newArr;
327                 }
328
329                 private void SetNsArrayLength (int size)
330                 {
331                         DTMXPathNamespaceNode [] newArr =
332                                 new DTMXPathNamespaceNode [size];
333                         Array.Copy (namespaces, newArr, System.Math.Min (size, namespaces.Length));
334                         namespaces = newArr;
335                 }
336
337                 // Here followings are skipped: firstChild, nextSibling, 
338                 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, int lineNumber, int linePosition)
339                 {
340                         if (nodes.Length < nodeIndex + 1) {
341                                 nodeCapacity *= 2;
342                                 SetNodeArrayLength (nodeCapacity);
343                         }
344
345                         DTMXPathLinkedNode curNode = nodes [nodeIndex];
346                         nodes [nodeIndex].FirstChild = 0;               // dummy
347                         nodes [nodeIndex].Parent = parent;
348                         nodes [nodeIndex].FirstAttribute = firstAttribute;
349                         nodes [nodeIndex].PreviousSibling = previousSibling;
350                         nodes [nodeIndex].NextSibling = 0;      // dummy
351                         nodes [nodeIndex].Depth = depth;
352                         nodes [nodeIndex].Position = position;
353                         nodes [nodeIndex].NodeType = nodeType;
354                         nodes [nodeIndex].BaseURI = baseUri;
355                         nodes [nodeIndex].IsEmptyElement = isEmptyElement;
356                         nodes [nodeIndex].LocalName = localName;
357                         nodes [nodeIndex].NamespaceURI = ns;
358                         nodes [nodeIndex].Prefix = prefix;
359                         nodes [nodeIndex].Value = value;
360                         nodes [nodeIndex].XmlLang = xmlLang;
361                         nodes [nodeIndex].FirstNamespace = namespaceNode;
362                         nodes [nodeIndex].LineNumber = lineNumber;
363                         nodes [nodeIndex].LinePosition = linePosition;
364                 }
365
366                 // Followings are skipped: nextAttribute,
367                 public void AddAttribute (int ownerElement, string localName, string ns, string prefix, string value, object schemaType, int lineNumber, int linePosition)
368                 {
369                         if (attributes.Length < attributeIndex + 1) {
370                                 attributeCapacity *= 2;
371                                 SetAttributeArrayLength (attributeCapacity);
372                         }
373
374                         DTMXPathAttributeNode attr = attributes [attributeIndex];
375                         attributes [attributeIndex].OwnerElement = ownerElement;
376                         attributes [attributeIndex].LocalName = localName;
377                         attributes [attributeIndex].NamespaceURI = ns;
378                         attributes [attributeIndex].Prefix = prefix;
379                         attributes [attributeIndex].Value = value;
380                         attributes [attributeIndex].SchemaType = schemaType;
381                         attributes [attributeIndex].LineNumber = lineNumber;
382                         attributes [attributeIndex].LinePosition = linePosition;
383                 }
384
385                 // Followings are skipped: nextNsNode (may be next attribute in the same element, or ancestors' nsNode)
386                 public void AddNsNode (int declaredElement, string name, string ns)
387                 {
388                         if (namespaces.Length < nsIndex + 1) {
389                                 nsCapacity *= 2;
390                                 SetNsArrayLength (nsCapacity);
391                         }
392
393                         DTMXPathNamespaceNode nsNode = namespaces [nsIndex];
394                         namespaces [nsIndex].DeclaredElement = declaredElement;
395                         namespaces [nsIndex].Name = name;
396                         namespaces [nsIndex].Namespace = ns;
397                 }
398         }
399 }
400