1 //------------------------------------------------------------------------------
2 // <copyright file="XPathDocumentBuilder.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
8 using System
.Globalization
;
10 using System
.Threading
;
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
{
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.
25 internal enum TextBlockType
{
27 Text
= XPathNodeType
.Text
,
28 SignificantWhitespace
= XPathNodeType
.SignificantWhitespace
,
29 Whitespace
= XPathNodeType
.Whitespace
,
34 /// Implementation of XmlRawWriter that builds nodes in an XPathDocument.
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;
64 /// Create a new XPathDocumentBuilder which creates nodes in "doc".
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
);
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.
80 public void Initialize(XPathDocument doc
, IXmlLineInfo lineInfo
, string baseUri
, XPathDocument
.LoadFlags flags
) {
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
;
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
);
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 //-----------------------------------------------
124 /// XPathDocument ignores the DocType information.
126 public override void WriteDocType(string name
, string pubid
, string sysid
, string subset
) {
130 /// Shortcut for calling WriteStartElement with elemType == null.
132 public override void WriteStartElement(string prefix
, string localName
, string ns
) {
133 this.WriteStartElement(prefix
, localName
, ns
, string.Empty
);
137 /// Build an element node and attach it to its parent, if one exists. Make the element the new parent node.
139 public void WriteStartElement(string prefix
, string localName
, string ns
, string baseUri
) {
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
;
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
)];
164 /// Must be called when an element node's children have been fully enumerated.
166 public override void WriteEndElement() {
167 WriteEndElement(true);
171 /// Must be called when an element node's children have been fully enumerated.
173 public override void WriteFullEndElement() {
174 WriteEndElement(false);
178 /// Must be called when an element node's children have been fully enumerated.
180 internal override void WriteEndElement(string prefix
, string localName
, string namespaceName
) {
181 WriteEndElement(true);
185 /// Must be called when an element node's children have been fully enumerated.
187 internal override void WriteFullEndElement(string prefix
, string localName
, string namespaceName
) {
188 WriteEndElement(false);
192 /// Must be called when an element node's children have been fully enumerated.
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());
222 case TextBlockType
.SignificantWhitespace
:
223 case TextBlockType
.Whitespace
:
224 // Create separate whitespace node
226 this.pageParent
[this.idxParent
].SetValue(this.pageSibling
[this.idxSibling
].Value
);
230 // Empty value, so don't create collapsed text node
231 this.pageParent
[this.idxParent
].SetEmptyValue(allowShortcutTag
);
236 if (this.textBldr
.HasText
) {
237 // Element's last child (one of several) is a text or whitespace node
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
);
260 /// Shortcut for calling WriteStartAttribute with attrfType == null.
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
);
277 /// Attach the attribute's text or typed value to the previously constructed attribute node.
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
);
297 /// Map CData text into regular text.
299 public override void WriteCData(string text
) {
300 WriteString(text
, TextBlockType
.Text
);
304 /// Construct comment node.
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
);
312 /// Shortcut for calling WriteProcessingInstruction with baseUri = string.Empty.
314 public override void WriteProcessingInstruction(string name
, string text
) {
315 this.WriteProcessingInstruction(name
, text
, string.Empty
);
319 /// Construct pi node.
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
);
330 /// Write a whitespace text block.
332 public override void WriteWhitespace(string ws
) {
333 WriteString(ws
, TextBlockType
.Whitespace
);
337 /// Write an attribute or element text block.
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
);
348 /// Map RawText to Text. This will lose entitization and won't roundtrip.
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
);
359 /// Write an element text block with the specified text type (whitespace, significant whitespace, or text).
361 public void WriteString(string text
, TextBlockType textType
) {
362 this.textBldr
.WriteTextBlock(text
, textType
);
366 /// Cache does not handle entity references.
368 public override void WriteEntityRef(string name
) {
369 throw new NotImplementedException();
373 /// Don't entitize, since the cache cannot represent character entities.
375 public override void WriteCharEntity(char ch
) {
377 WriteString(new string(chars
), TextBlockType
.Text
);
381 /// Don't entitize, since the cache cannot represent character entities.
383 public override void WriteSurrogateCharEntity(char lowChar
, char highChar
) {
384 char[] chars
= {highChar, lowChar}
;
385 WriteString(new string(chars
), TextBlockType
.Text
);
389 /// Signals the end of tree construction.
391 public override void Close() {
395 // If cached text exists, then create a text node at the top-level
396 if (this.textBldr
.HasText
)
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
);
408 /// Since output is not forwarded to another object, this does nothing.
410 public override void Flush() {
414 //-----------------------------------------------
415 // XmlRawWriter interface
416 //-----------------------------------------------
419 /// Write the xml declaration. This must be the first call after Open.
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
430 /// Called as element node's children are about to be enumerated.
432 internal override void StartElementContent() {
433 Debug
.Assert(this.pageParent
[this.idxParent
].NodeType
== XPathNodeType
.Element
);
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.
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
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
;
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
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
);
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
);
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 //-----------------------------------------------
529 /// Build ID lookup tables from the XSD schema or DTD.
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
));
548 /// Link "prev" element with "next" element, which has a "similar" name. This increases the performance of searches by element name.
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
);
560 /// Helper method that constructs a new Namespace XPathNode.
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
);
589 /// Helper method that constructs a new XPathNode.
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
);
617 /// Compute current node's line number information.
619 private void ComputeLineInfo(bool isTextNode
, out int lineNumOffset
, out int linePosOffset
) {
620 int lineNum
, linePos
;
622 if (this.lineInfo
== null) {
628 // Get line number info from TextBlockBuilder if current node is a text node
630 lineNum
= this.textBldr
.LineNumber
;
631 linePos
= this.textBldr
.LinePosition
;
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
;
645 linePosOffset
= linePos
- this.linePosBase
;
646 if (linePosOffset
< 0 || linePosOffset
> XPathNode
.MaxLinePositionOffset
) {
647 this.linePosBase
= linePos
;
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.
656 private void AddSibling(XPathNodeType xptyp
, string localName
, string namespaceUri
, string prefix
, string baseUri
) {
659 Debug
.Assert(xptyp
!= XPathNodeType
.Root
&& xptyp
!= XPathNodeType
.Namespace
);
661 if (this.textBldr
.HasText
)
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);
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
;
686 /// Creates a text node from cached text parts.
688 private void CachedTextNode() {
689 TextBlockType textType
;
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
);
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.
706 private struct NodePageFactory
{
707 private XPathNode
[] page
;
708 private XPathNodePageInfo pageInfo
;
709 private int pageSize
;
712 /// Allocates and returns the initial node page.
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
);
723 /// Return the page on which the next node will be allocated.
725 public XPathNode
[] NextNodePage
{
726 get { return this.page; }
730 /// Return the page index that the next node will be given.
732 public int NextNodeIndex
{
733 get { return this.pageInfo.NodeCount; }
737 /// Allocate the next slot in the current node page. Return a reference to the page and the index
738 /// of the allocated slot.
740 public void AllocateSlot(out XPathNode
[] page
, out int idx
) {
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
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
);
759 /// This class concatenates adjacent text blocks and tracks TextBlockType and line number information.
761 private struct TextBlockBuilder
{
762 private IXmlLineInfo lineInfo
;
763 private TextBlockType textType
;
765 private int lineNum
, linePos
;
770 public void Initialize(IXmlLineInfo lineInfo
) {
771 this.lineInfo
= lineInfo
;
772 this.textType
= TextBlockType
.None
;
776 /// Return the type of the cached text block.
778 public TextBlockType TextType
{
779 get { return this.textType; }
783 /// Returns true if text has been cached.
785 public bool HasText
{
786 get { return this.textType != TextBlockType.None; }
790 /// Returns the line number of the last text block to be cached.
792 public int LineNumber
{
793 get { return this.lineNum; }
797 /// Returns the line position of the last text block to be cached.
799 public int LinePosition
{
800 get { return this.linePos; }
804 /// Append a text block with the specified type.
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
) {
813 this.textType
= textType
;
815 if (this.lineInfo
!= null) {
816 this.lineNum
= this.lineInfo
.LineNumber
;
817 this.linePos
= this.lineInfo
.LinePosition
;
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
;
831 /// Read all cached text, or string.Empty if no text has been cached, and clear the text block type.
833 public string ReadText() {
834 if (this.textType
== TextBlockType
.None
)
837 this.textType
= TextBlockType
.None
;