Updates referencesource to .NET 4.7
[mono-project.git] / mcs / class / referencesource / System.Xml / System / Xml / Cache / XPathDocumentBuilder.cs
blob7282d54555c41ab31eadb5f0c8c3fb54a187693e
1 //------------------------------------------------------------------------------
2 // <copyright file="XPathDocumentBuilder.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
7 using System;
8 using System.Globalization;
9 using System.IO;
10 using System.Threading;
11 using System.Xml;
12 using System.Xml.XPath;
13 using System.Xml.Schema;
14 using System.Diagnostics;
15 using System.Collections;
16 using System.Collections.Generic;
18 namespace MS.Internal.Xml.Cache {
20 /// <summary>
21 /// Although the XPath data model does not differentiate between text and whitespace, Managed Xml 1.0
22 /// does. Therefore, when building from an XmlReader, we must preserve these designations in order
23 /// to remain backwards-compatible.
24 /// </summary>
25 internal enum TextBlockType {
26 None = 0,
27 Text = XPathNodeType.Text,
28 SignificantWhitespace = XPathNodeType.SignificantWhitespace,
29 Whitespace = XPathNodeType.Whitespace,
33 /// <summary>
34 /// Implementation of XmlRawWriter that builds nodes in an XPathDocument.
35 /// </summary>
36 internal sealed class XPathDocumentBuilder : XmlRawWriter {
37 private NodePageFactory nodePageFact; // Creates non-namespace node pages
38 private NodePageFactory nmspPageFact; // Creates namespace node pages
39 private TextBlockBuilder textBldr; // Concatenates adjacent text blocks
41 private Stack<XPathNodeRef> stkNmsp; // In-scope namespaces
42 private XPathNodeInfoTable infoTable; // Atomization table for shared node information
43 private XPathDocument doc; // Currently building document
44 private IXmlLineInfo lineInfo; // Line information provider
45 private XmlNameTable nameTable; // Atomization table for all names in the document
46 private bool atomizeNames; // True if all names should be atomized (false if they are pre-atomized)
48 private XPathNode[] pageNmsp; // Page of last in-scope namespace node
49 private int idxNmsp; // Page index of last in-scope namespace node
50 private XPathNode[] pageParent; // Page of last parent-type node (Element or Root)
51 private int idxParent; // Page index of last parent-type node (Element or Root)
52 private XPathNode[] pageSibling; // Page of previous sibling node (may be null if no previous sibling)
53 private int idxSibling; // Page index of previous sibling node
54 private int lineNumBase; // Line number from which offsets are computed
55 private int linePosBase; // Line position from which offsets are computed
57 private XmlQualifiedName idAttrName; // Cached name of an ID attribute
58 private Hashtable elemIdMap; // Map from element name to ID attribute name
59 private XPathNodeRef[] elemNameIndex; // Elements with the same name are linked together so that they can be searched quickly
61 private const int ElementIndexSize = 64;
63 /// <summary>
64 /// Create a new XPathDocumentBuilder which creates nodes in "doc".
65 /// </summary>
66 public XPathDocumentBuilder(XPathDocument doc, IXmlLineInfo lineInfo, string baseUri, XPathDocument.LoadFlags flags) {
67 // Allocate the initial node (for non-namespaces) page, and the initial namespace page
68 this.nodePageFact.Init(256);
69 this.nmspPageFact.Init(16);
71 this.stkNmsp = new Stack<XPathNodeRef>();
73 Initialize(doc, lineInfo, baseUri, flags);
76 /// <summary>
77 /// Start construction of a new document. This must be called before any other methods are called.
78 /// It may also be called after Close(), in order to build further documents.
79 /// </summary>
80 public void Initialize(XPathDocument doc, IXmlLineInfo lineInfo, string baseUri, XPathDocument.LoadFlags flags) {
81 XPathNode[] page;
82 int idx;
84 this.doc = doc;
85 this.nameTable = doc.NameTable;
86 this.atomizeNames = (flags & XPathDocument.LoadFlags.AtomizeNames) != 0;
87 this.idxParent = this.idxSibling = 0;
88 this.elemNameIndex = new XPathNodeRef[ElementIndexSize];
90 // Prepare line number information
91 this.textBldr.Initialize(lineInfo);
92 this.lineInfo = lineInfo;
93 this.lineNumBase = 0;
94 this.linePosBase = 0;
96 // Allocate the atomization table
97 this.infoTable = new XPathNodeInfoTable();
99 // Allocate singleton collapsed text node
100 idx = NewNode(out page, XPathNodeType.Text, string.Empty, string.Empty, string.Empty, string.Empty);
101 this.doc.SetCollapsedTextNode(page, idx);
103 // Allocate xmlns:xml namespace node
104 this.idxNmsp = NewNamespaceNode(out this.pageNmsp, this.nameTable.Add("xml"), this.nameTable.Add(XmlReservedNs.NsXml), null, 0);
105 this.doc.SetXmlNamespaceNode(this.pageNmsp, this.idxNmsp);
107 if ((flags & XPathDocument.LoadFlags.Fragment) == 0) {
108 // This tree has a document root node
109 this.idxParent = NewNode(out this.pageParent, XPathNodeType.Root, string.Empty, string.Empty, string.Empty, baseUri);
110 this.doc.SetRootNode(this.pageParent, this.idxParent);
112 else {
113 // This tree is an XQuery fragment (no document root node), so root will be next node in the current page
114 this.doc.SetRootNode(this.nodePageFact.NextNodePage, this.nodePageFact.NextNodeIndex);
119 //-----------------------------------------------
120 // XmlWriter interface
121 //-----------------------------------------------
123 /// <summary>
124 /// XPathDocument ignores the DocType information.
125 /// </summary>
126 public override void WriteDocType(string name, string pubid, string sysid, string subset) {
129 /// <summary>
130 /// Shortcut for calling WriteStartElement with elemType == null.
131 /// </summary>
132 public override void WriteStartElement(string prefix, string localName, string ns) {
133 this.WriteStartElement(prefix, localName, ns, string.Empty);
136 /// <summary>
137 /// Build an element node and attach it to its parent, if one exists. Make the element the new parent node.
138 /// </summary>
139 public void WriteStartElement(string prefix, string localName, string ns, string baseUri) {
140 int hash;
141 Debug.Assert(prefix != null && localName != null && ns != null && localName.Length != 0 && baseUri != null);
143 if (this.atomizeNames) {
144 prefix = this.nameTable.Add(prefix);
145 localName = this.nameTable.Add(localName);
146 ns = this.nameTable.Add(ns);
149 AddSibling(XPathNodeType.Element, localName, ns, prefix, baseUri);
150 this.pageParent = this.pageSibling;
151 this.idxParent = this.idxSibling;
152 this.idxSibling = 0;
154 // Link elements with the same name together
155 hash = (this.pageParent[this.idxParent].LocalNameHashCode & (ElementIndexSize - 1));
156 this.elemNameIndex[hash] = LinkSimilarElements(this.elemNameIndex[hash].Page, this.elemNameIndex[hash].Index, this.pageParent, this.idxParent);
158 // If elements within this document might have IDs, then cache the name of the ID attribute, if one exists
159 if (this.elemIdMap != null)
160 this.idAttrName = (XmlQualifiedName) this.elemIdMap[new XmlQualifiedName(localName, prefix)];
163 /// <summary>
164 /// Must be called when an element node's children have been fully enumerated.
165 /// </summary>
166 public override void WriteEndElement() {
167 WriteEndElement(true);
170 /// <summary>
171 /// Must be called when an element node's children have been fully enumerated.
172 /// </summary>
173 public override void WriteFullEndElement() {
174 WriteEndElement(false);
177 /// <summary>
178 /// Must be called when an element node's children have been fully enumerated.
179 /// </summary>
180 internal override void WriteEndElement(string prefix, string localName, string namespaceName) {
181 WriteEndElement(true);
184 /// <summary>
185 /// Must be called when an element node's children have been fully enumerated.
186 /// </summary>
187 internal override void WriteFullEndElement(string prefix, string localName, string namespaceName) {
188 WriteEndElement(false);
191 /// <summary>
192 /// Must be called when an element node's children have been fully enumerated.
193 /// </summary>
194 public void WriteEndElement(bool allowShortcutTag) {
195 XPathNodeRef nodeRef;
196 Debug.Assert(this.pageParent[this.idxParent].NodeType == XPathNodeType.Element);
198 // If element has no content-typed children except for the one about to be added, then
199 // its value is the same as its only text child's.
200 if (!this.pageParent[this.idxParent].HasContentChild) {
201 switch (this.textBldr.TextType) {
202 case TextBlockType.Text:
203 // Collapsed text node can be created if text line number information can be encoded efficiently in parent node
204 if (this.lineInfo != null) {
205 // If collapsed text node is not on same line as parent, don't collapse text
206 if (this.textBldr.LineNumber != this.pageParent[this.idxParent].LineNumber)
207 goto case TextBlockType.Whitespace;
209 // If position is not within 256 of parent, don't collapse text
210 int posDiff = this.textBldr.LinePosition - this.pageParent[this.idxParent].LinePosition;
211 if (posDiff < 0 || posDiff > XPathNode.MaxCollapsedPositionOffset)
212 goto case TextBlockType.Whitespace;
214 // Set collapsed node line position offset
215 this.pageParent[this.idxParent].SetCollapsedLineInfoOffset(posDiff);
218 // Set collapsed node text
219 this.pageParent[this.idxParent].SetCollapsedValue(this.textBldr.ReadText());
220 break;
222 case TextBlockType.SignificantWhitespace:
223 case TextBlockType.Whitespace:
224 // Create separate whitespace node
225 CachedTextNode();
226 this.pageParent[this.idxParent].SetValue(this.pageSibling[this.idxSibling].Value);
227 break;
229 default:
230 // Empty value, so don't create collapsed text node
231 this.pageParent[this.idxParent].SetEmptyValue(allowShortcutTag);
232 break;
235 else {
236 if (this.textBldr.HasText) {
237 // Element's last child (one of several) is a text or whitespace node
238 CachedTextNode();
242 // If namespaces were added to this element,
243 if (this.pageParent[this.idxParent].HasNamespaceDecls) {
244 // Add it to the document's element --> namespace mapping
245 this.doc.AddNamespace(this.pageParent, this.idxParent, this.pageNmsp, this.idxNmsp);
247 // Restore the previous namespace chain
248 nodeRef = this.stkNmsp.Pop();
249 this.pageNmsp = nodeRef.Page;
250 this.idxNmsp = nodeRef.Index;
253 // Make parent of this element the current element
254 this.pageSibling = this.pageParent;
255 this.idxSibling = this.idxParent;
256 this.idxParent = this.pageParent[this.idxParent].GetParent(out this.pageParent);
259 /// <summary>
260 /// Shortcut for calling WriteStartAttribute with attrfType == null.
261 /// </summary>
262 public override void WriteStartAttribute(string prefix, string localName, string namespaceName) {
263 Debug.Assert(!prefix.Equals("xmlns"));
264 Debug.Assert(this.idxParent == 0 || this.pageParent[this.idxParent].NodeType == XPathNodeType.Element);
265 Debug.Assert(this.idxSibling == 0 || this.pageSibling[this.idxSibling].NodeType == XPathNodeType.Attribute);
267 if (this.atomizeNames) {
268 prefix = this.nameTable.Add(prefix);
269 localName = this.nameTable.Add(localName);
270 namespaceName = this.nameTable.Add(namespaceName);
273 AddSibling(XPathNodeType.Attribute, localName, namespaceName, prefix, string.Empty);
276 /// <summary>
277 /// Attach the attribute's text or typed value to the previously constructed attribute node.
278 /// </summary>
279 public override void WriteEndAttribute() {
280 Debug.Assert(this.pageSibling[this.idxSibling].NodeType == XPathNodeType.Attribute);
282 this.pageSibling[this.idxSibling].SetValue(this.textBldr.ReadText());
284 if (this.idAttrName != null) {
285 // If this is an ID attribute,
286 if (this.pageSibling[this.idxSibling].LocalName == this.idAttrName.Name &&
287 this.pageSibling[this.idxSibling].Prefix == this.idAttrName.Namespace) {
289 // Then add its value to the idValueMap map
290 Debug.Assert(this.idxParent != 0, "ID attribute must have an element parent");
291 this.doc.AddIdElement(this.pageSibling[this.idxSibling].Value, this.pageParent, this.idxParent);
296 /// <summary>
297 /// Map CData text into regular text.
298 /// </summary>
299 public override void WriteCData(string text) {
300 WriteString(text, TextBlockType.Text);
303 /// <summary>
304 /// Construct comment node.
305 /// </summary>
306 public override void WriteComment(string text) {
307 AddSibling(XPathNodeType.Comment, string.Empty, string.Empty, string.Empty, string.Empty);
308 this.pageSibling[this.idxSibling].SetValue(text);
311 /// <summary>
312 /// Shortcut for calling WriteProcessingInstruction with baseUri = string.Empty.
313 /// </summary>
314 public override void WriteProcessingInstruction(string name, string text) {
315 this.WriteProcessingInstruction(name, text, string.Empty);
318 /// <summary>
319 /// Construct pi node.
320 /// </summary>
321 public void WriteProcessingInstruction(string name, string text, string baseUri) {
322 if (this.atomizeNames)
323 name = this.nameTable.Add(name);
325 AddSibling(XPathNodeType.ProcessingInstruction, name, string.Empty, string.Empty, baseUri);
326 this.pageSibling[this.idxSibling].SetValue(text);
329 /// <summary>
330 /// Write a whitespace text block.
331 /// </summary>
332 public override void WriteWhitespace(string ws) {
333 WriteString(ws, TextBlockType.Whitespace);
336 /// <summary>
337 /// Write an attribute or element text block.
338 /// </summary>
339 public override void WriteString(string text) {
340 WriteString(text, TextBlockType.Text);
343 public override void WriteChars(char[] buffer, int index, int count) {
344 WriteString(new string(buffer, index, count), TextBlockType.Text);
347 /// <summary>
348 /// Map RawText to Text. This will lose entitization and won't roundtrip.
349 /// </summary>
350 public override void WriteRaw(string data) {
351 WriteString(data, TextBlockType.Text);
354 public override void WriteRaw(char[] buffer, int index, int count) {
355 WriteString(new string(buffer, index, count), TextBlockType.Text);
358 /// <summary>
359 /// Write an element text block with the specified text type (whitespace, significant whitespace, or text).
360 /// </summary>
361 public void WriteString(string text, TextBlockType textType) {
362 this.textBldr.WriteTextBlock(text, textType);
365 /// <summary>
366 /// Cache does not handle entity references.
367 /// </summary>
368 public override void WriteEntityRef(string name) {
369 throw new NotImplementedException();
372 /// <summary>
373 /// Don't entitize, since the cache cannot represent character entities.
374 /// </summary>
375 public override void WriteCharEntity(char ch) {
376 char[] chars = {ch};
377 WriteString(new string(chars), TextBlockType.Text);
380 /// <summary>
381 /// Don't entitize, since the cache cannot represent character entities.
382 /// </summary>
383 public override void WriteSurrogateCharEntity(char lowChar, char highChar) {
384 char[] chars = {highChar, lowChar};
385 WriteString(new string(chars), TextBlockType.Text);
388 /// <summary>
389 /// Signals the end of tree construction.
390 /// </summary>
391 public override void Close() {
392 XPathNode[] page;
393 int idx;
395 // If cached text exists, then create a text node at the top-level
396 if (this.textBldr.HasText)
397 CachedTextNode();
399 // If document does not yet contain nodes, then an empty text node must have been created
400 idx = this.doc.GetRootNode(out page);
401 if (idx == this.nodePageFact.NextNodeIndex && page == this.nodePageFact.NextNodePage) {
402 AddSibling(XPathNodeType.Text, string.Empty, string.Empty, string.Empty, string.Empty);
403 this.pageSibling[this.idxSibling].SetValue(string.Empty);
407 /// <summary>
408 /// Since output is not forwarded to another object, this does nothing.
409 /// </summary>
410 public override void Flush() {
414 //-----------------------------------------------
415 // XmlRawWriter interface
416 //-----------------------------------------------
418 /// <summary>
419 /// Write the xml declaration. This must be the first call after Open.
420 /// </summary>
421 internal override void WriteXmlDeclaration(XmlStandalone standalone) {
422 // Ignore the xml declaration when building the cache
425 internal override void WriteXmlDeclaration(string xmldecl) {
426 // Ignore the xml declaration when building the cache
429 /// <summary>
430 /// Called as element node's children are about to be enumerated.
431 /// </summary>
432 internal override void StartElementContent() {
433 Debug.Assert(this.pageParent[this.idxParent].NodeType == XPathNodeType.Element);
436 /// <summary>
437 /// Build a namespace declaration node. Attach it to an element parent, if one was previously constructed.
438 /// All namespace declarations are linked together in an in-scope namespace tree.
439 /// </summary>
440 internal override void WriteNamespaceDeclaration(string prefix, string namespaceName) {
441 XPathNode[] pageTemp, pageOverride, pageNew, pageOrig, pageCopy;
442 int idxTemp, idxOverride, idxNew, idxOrig, idxCopy;
443 Debug.Assert(this.idxSibling == 0 || this.pageSibling[this.idxSibling].NodeType == XPathNodeType.Attribute);
444 Debug.Assert(!prefix.Equals("xmlns") && !namespaceName.Equals(XmlReservedNs.NsXmlNs));
445 Debug.Assert(this.idxParent == 0 || this.idxNmsp != 0);
446 Debug.Assert(this.idxParent == 0 || this.pageParent[this.idxParent].NodeType == XPathNodeType.Element);
448 if (this.atomizeNames)
449 prefix = this.nameTable.Add(prefix);
451 namespaceName = this.nameTable.Add(namespaceName);
453 // Does the new namespace override a previous namespace node?
454 pageOverride = this.pageNmsp;
455 idxOverride = this.idxNmsp;
456 while (idxOverride != 0) {
457 if ((object) pageOverride[idxOverride].LocalName == (object) prefix) {
458 // Need to clone all namespaces up until the overridden node in order to bypass it
459 break;
461 idxOverride = pageOverride[idxOverride].GetSibling(out pageOverride);
464 // Create new namespace node and add it to front of namespace list
465 idxNew = NewNamespaceNode(out pageNew, prefix, namespaceName, this.pageParent, this.idxParent);
467 if (idxOverride != 0) {
468 // Bypass overriden node by cloning nodes in list leading to it
469 pageOrig = this.pageNmsp;
470 idxOrig = this.idxNmsp;
471 pageCopy = pageNew;
472 idxCopy = idxNew;
474 while (idxOrig != idxOverride || pageOrig != pageOverride) {
475 // Make a copy of the original namespace node
476 idxTemp = pageOrig[idxOrig].GetParent(out pageTemp);
477 idxTemp = NewNamespaceNode(out pageTemp, pageOrig[idxOrig].LocalName, pageOrig[idxOrig].Value, pageTemp, idxTemp);
479 // Attach copy to chain of copied nodes
480 pageCopy[idxCopy].SetSibling(this.infoTable, pageTemp, idxTemp);
482 // Position on the new copy
483 pageCopy = pageTemp;
484 idxCopy = idxTemp;
486 // Get next original sibling
487 idxOrig = pageOrig[idxOrig].GetSibling(out pageOrig);
490 // Link farther up in the original chain, just past the last overriden node
491 idxOverride = pageOverride[idxOverride].GetSibling(out pageOverride);
493 if (idxOverride != 0)
494 pageCopy[idxCopy].SetSibling(this.infoTable, pageOverride, idxOverride);
495 else
496 Debug.Assert(prefix.Equals("xml"), "xmlns:xml namespace declaration should always be present in the list.");
498 else if (this.idxParent != 0) {
499 // Link new node directly to last in-scope namespace. No overrides necessary.
500 pageNew[idxNew].SetSibling(this.infoTable, this.pageNmsp, this.idxNmsp);
502 else {
503 // Floating namespace, so make this the root of the tree
504 this.doc.SetRootNode(pageNew, idxNew);
507 if (this.idxParent != 0) {
508 // If this is the first namespace on the current element,
509 if (!this.pageParent[this.idxParent].HasNamespaceDecls) {
510 // Then save the last in-scope namespace on a stack so that EndElementNode can restore it.
511 this.stkNmsp.Push(new XPathNodeRef(this.pageNmsp, this.idxNmsp));
513 // Mark element parent as having namespace nodes declared on it
514 this.pageParent[this.idxParent].HasNamespaceDecls = true;
517 // New namespace is now last in-scope namespace
518 this.pageNmsp = pageNew;
519 this.idxNmsp = idxNew;
524 //-----------------------------------------------
525 // Custom Build Helper Methods
526 //-----------------------------------------------
528 /// <summary>
529 /// Build ID lookup tables from the XSD schema or DTD.
530 /// </summary>
531 public void CreateIdTables(IDtdInfo dtdInfo) {
532 // Extract the elements which has attribute defined as ID from the element declarations
533 foreach (IDtdAttributeListInfo attrList in dtdInfo.GetAttributeLists()) {
534 IDtdAttributeInfo idAttribute = attrList.LookupIdAttribute();
535 if (idAttribute != null) {
536 if (this.elemIdMap == null)
537 this.elemIdMap = new Hashtable();
539 // Id was defined in DTD and DTD doesn't have notion of namespace so we should
540 // use prefix instead of namespace here. Schema already does this for us.
541 this.elemIdMap.Add(new XmlQualifiedName(attrList.LocalName, attrList.Prefix),
542 new XmlQualifiedName(idAttribute.LocalName, idAttribute.Prefix));
547 /// <summary>
548 /// Link "prev" element with "next" element, which has a "similar" name. This increases the performance of searches by element name.
549 /// </summary>
550 private XPathNodeRef LinkSimilarElements(XPathNode[] pagePrev, int idxPrev, XPathNode[] pageNext, int idxNext) {
551 // Set link on previous element
552 if (pagePrev != null)
553 pagePrev[idxPrev].SetSimilarElement(this.infoTable, pageNext, idxNext);
555 // Add next element to index
556 return new XPathNodeRef(pageNext, idxNext);
559 /// <summary>
560 /// Helper method that constructs a new Namespace XPathNode.
561 /// </summary>
562 private int NewNamespaceNode(out XPathNode[] page, string prefix, string namespaceUri, XPathNode[] pageElem, int idxElem) {
563 XPathNode[] pageNode;
564 int idxNode, lineNumOffset, linePosOffset;
565 XPathNodeInfoAtom info;
566 Debug.Assert(pageElem == null || pageElem[idxElem].NodeType == XPathNodeType.Element);
568 // Allocate a page slot for the new XPathNode
569 this.nmspPageFact.AllocateSlot(out pageNode, out idxNode);
571 // Compute node's line number information
572 ComputeLineInfo(false, out lineNumOffset, out linePosOffset);
574 // Obtain a XPathNodeInfoAtom object for this node
575 info = this.infoTable.Create(prefix, string.Empty, string.Empty, string.Empty,
576 pageElem, pageNode, null,
577 this.doc, this.lineNumBase, this.linePosBase);
579 // Initialize the new node
580 pageNode[idxNode].Create(info, XPathNodeType.Namespace, idxElem);
581 pageNode[idxNode].SetValue(namespaceUri);
582 pageNode[idxNode].SetLineInfoOffsets(lineNumOffset, linePosOffset);
584 page = pageNode;
585 return idxNode;
588 /// <summary>
589 /// Helper method that constructs a new XPathNode.
590 /// </summary>
591 private int NewNode(out XPathNode[] page, XPathNodeType xptyp, string localName, string namespaceUri, string prefix, string baseUri) {
592 XPathNode[] pageNode;
593 int idxNode, lineNumOffset, linePosOffset;
594 XPathNodeInfoAtom info;
595 Debug.Assert(xptyp != XPathNodeType.Namespace);
597 // Allocate a page slot for the new XPathNode
598 this.nodePageFact.AllocateSlot(out pageNode, out idxNode);
600 // Compute node's line number information
601 ComputeLineInfo(XPathNavigator.IsText(xptyp), out lineNumOffset, out linePosOffset);
603 // Obtain a XPathNodeInfoAtom object for this node
604 info = this.infoTable.Create(localName, namespaceUri, prefix, baseUri,
605 this.pageParent, pageNode, pageNode,
606 this.doc, this.lineNumBase, this.linePosBase);
608 // Initialize the new node
609 pageNode[idxNode].Create(info, xptyp, this.idxParent);
610 pageNode[idxNode].SetLineInfoOffsets(lineNumOffset, linePosOffset);
612 page = pageNode;
613 return idxNode;
616 /// <summary>
617 /// Compute current node's line number information.
618 /// </summary>
619 private void ComputeLineInfo(bool isTextNode, out int lineNumOffset, out int linePosOffset) {
620 int lineNum, linePos;
622 if (this.lineInfo == null) {
623 lineNumOffset = 0;
624 linePosOffset = 0;
625 return;
628 // Get line number info from TextBlockBuilder if current node is a text node
629 if (isTextNode) {
630 lineNum = this.textBldr.LineNumber;
631 linePos = this.textBldr.LinePosition;
633 else {
634 Debug.Assert(this.lineInfo.HasLineInfo(), "HasLineInfo should have been checked before this.");
635 lineNum = this.lineInfo.LineNumber;
636 linePos = this.lineInfo.LinePosition;
639 lineNumOffset = lineNum - this.lineNumBase;
640 if (lineNumOffset < 0 || lineNumOffset > XPathNode.MaxLineNumberOffset) {
641 this.lineNumBase = lineNum;
642 lineNumOffset = 0;
645 linePosOffset = linePos - this.linePosBase;
646 if (linePosOffset < 0 || linePosOffset > XPathNode.MaxLinePositionOffset) {
647 this.linePosBase = linePos;
648 linePosOffset = 0;
652 /// <summary>
653 /// Add a sibling node. If no previous sibling exists, add the node as the first child of the parent.
654 /// If no parent exists, make this node the root of the document.
655 /// </summary>
656 private void AddSibling(XPathNodeType xptyp, string localName, string namespaceUri, string prefix, string baseUri) {
657 XPathNode[] pageNew;
658 int idxNew;
659 Debug.Assert(xptyp != XPathNodeType.Root && xptyp != XPathNodeType.Namespace);
661 if (this.textBldr.HasText)
662 CachedTextNode();
664 idxNew = NewNode(out pageNew, xptyp, localName, namespaceUri, prefix, baseUri);
666 // this.idxParent is only 0 for the top-most node
667 if (this.idxParent != 0) {
668 // Set properties on parent
669 this.pageParent[this.idxParent].SetParentProperties(xptyp);
671 if (this.idxSibling == 0) {
672 // This is the first child of the parent (so should be allocated immediately after parent)
673 Debug.Assert(this.idxParent + 1 == idxNew || idxNew == 1);
675 else {
676 // There is already a previous sibling
677 this.pageSibling[this.idxSibling].SetSibling(this.infoTable, pageNew, idxNew);
681 this.pageSibling = pageNew;
682 this.idxSibling = idxNew;
685 /// <summary>
686 /// Creates a text node from cached text parts.
687 /// </summary>
688 private void CachedTextNode() {
689 TextBlockType textType;
690 string text;
691 Debug.Assert(this.textBldr.HasText || (this.idxSibling == 0 && this.idxParent == 0), "Cannot create empty text node unless it's a top-level text node.");
692 Debug.Assert(this.idxSibling == 0 || !this.pageSibling[this.idxSibling].IsText, "Cannot create adjacent text nodes.");
694 // Create a text node
695 textType = this.textBldr.TextType;
696 text = this.textBldr.ReadText();
697 AddSibling((XPathNodeType) textType, string.Empty, string.Empty, string.Empty, string.Empty);
698 this.pageSibling[this.idxSibling].SetValue(text);
701 /// <summary>
702 /// Allocates pages of nodes for the XPathDocumentBuilder. The initial pages and arrays are
703 /// fairly small. As each page fills, a new page that is twice as big is allocated.
704 /// The max size of a page is 65536 nodes, since XPathNode indexes are 16-bits.
705 /// </summary>
706 private struct NodePageFactory {
707 private XPathNode[] page;
708 private XPathNodePageInfo pageInfo;
709 private int pageSize;
711 /// <summary>
712 /// Allocates and returns the initial node page.
713 /// </summary>
714 public void Init(int initialPageSize) {
715 // 0th slot: Index 0 is reserved to mean "null node". Only use 0th slot to store PageInfo.
716 this.pageSize = initialPageSize;
717 this.page = new XPathNode[this.pageSize];
718 this.pageInfo = new XPathNodePageInfo(null, 1);
719 this.page[0].Create(this.pageInfo);
722 /// <summary>
723 /// Return the page on which the next node will be allocated.
724 /// </summary>
725 public XPathNode[] NextNodePage {
726 get { return this.page; }
729 /// <summary>
730 /// Return the page index that the next node will be given.
731 /// </summary>
732 public int NextNodeIndex {
733 get { return this.pageInfo.NodeCount; }
736 /// <summary>
737 /// Allocate the next slot in the current node page. Return a reference to the page and the index
738 /// of the allocated slot.
739 /// </summary>
740 public void AllocateSlot(out XPathNode[] page, out int idx) {
741 page = this.page;
742 idx = this.pageInfo.NodeCount;
744 // Allocate new page if necessary
745 if (++this.pageInfo.NodeCount >= this.page.Length) {
746 if (this.pageSize < (1 << 16)) {
747 // New page shouldn't contain more slots than 16 bits can address
748 this.pageSize *= 2;
750 this.page = new XPathNode[this.pageSize];
751 this.pageInfo.NextPage = this.page;
752 this.pageInfo = new XPathNodePageInfo(page, this.pageInfo.PageNumber + 1);
753 this.page[0].Create(this.pageInfo);
758 /// <summary>
759 /// This class concatenates adjacent text blocks and tracks TextBlockType and line number information.
760 /// </summary>
761 private struct TextBlockBuilder {
762 private IXmlLineInfo lineInfo;
763 private TextBlockType textType;
764 private string text;
765 private int lineNum, linePos;
767 /// <summary>
768 /// Constructor.
769 /// </summary>
770 public void Initialize(IXmlLineInfo lineInfo) {
771 this.lineInfo = lineInfo;
772 this.textType = TextBlockType.None;
775 /// <summary>
776 /// Return the type of the cached text block.
777 /// </summary>
778 public TextBlockType TextType {
779 get { return this.textType; }
782 /// <summary>
783 /// Returns true if text has been cached.
784 /// </summary>
785 public bool HasText {
786 get { return this.textType != TextBlockType.None; }
789 /// <summary>
790 /// Returns the line number of the last text block to be cached.
791 /// </summary>
792 public int LineNumber {
793 get { return this.lineNum; }
796 /// <summary>
797 /// Returns the line position of the last text block to be cached.
798 /// </summary>
799 public int LinePosition {
800 get { return this.linePos; }
803 /// <summary>
804 /// Append a text block with the specified type.
805 /// </summary>
806 public void WriteTextBlock(string text, TextBlockType textType) {
807 Debug.Assert((int) XPathNodeType.Text < (int) XPathNodeType.SignificantWhitespace);
808 Debug.Assert((int) XPathNodeType.SignificantWhitespace < (int) XPathNodeType.Whitespace);
810 if (text.Length != 0) {
811 if (this.textType == TextBlockType.None) {
812 this.text = text;
813 this.textType = textType;
815 if (this.lineInfo != null) {
816 this.lineNum = this.lineInfo.LineNumber;
817 this.linePos = this.lineInfo.LinePosition;
820 else {
821 this.text = string.Concat(this.text, text);
823 // Determine whether text is Text, Whitespace, or SignificantWhitespace
824 if ((int) textType < (int) this.textType)
825 this.textType = textType;
830 /// <summary>
831 /// Read all cached text, or string.Empty if no text has been cached, and clear the text block type.
832 /// </summary>
833 public string ReadText() {
834 if (this.textType == TextBlockType.None)
835 return string.Empty;
837 this.textType = TextBlockType.None;
838 return this.text;