XPathDocumentBuilder.cs 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842
  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;
  17. namespace MS.Internal.Xml.Cache {
  18. /// <summary>
  19. /// Although the XPath data model does not differentiate between text and whitespace, Managed Xml 1.0
  20. /// does. Therefore, when building from an XmlReader, we must preserve these designations in order
  21. /// to remain backwards-compatible.
  22. /// </summary>
  23. internal enum TextBlockType {
  24. None = 0,
  25. Text = XPathNodeType.Text,
  26. SignificantWhitespace = XPathNodeType.SignificantWhitespace,
  27. Whitespace = XPathNodeType.Whitespace,
  28. };
  29. /// <summary>
  30. /// Implementation of XmlRawWriter that builds nodes in an XPathDocument.
  31. /// </summary>
  32. internal sealed class XPathDocumentBuilder : XmlRawWriter {
  33. private NodePageFactory nodePageFact; // Creates non-namespace node pages
  34. private NodePageFactory nmspPageFact; // Creates namespace node pages
  35. private TextBlockBuilder textBldr; // Concatenates adjacent text blocks
  36. private Stack<XPathNodeRef> stkNmsp; // In-scope namespaces
  37. private XPathNodeInfoTable infoTable; // Atomization table for shared node information
  38. private XPathDocument doc; // Currently building document
  39. private IXmlLineInfo lineInfo; // Line information provider
  40. private XmlNameTable nameTable; // Atomization table for all names in the document
  41. private bool atomizeNames; // True if all names should be atomized (false if they are pre-atomized)
  42. private XPathNode[] pageNmsp; // Page of last in-scope namespace node
  43. private int idxNmsp; // Page index of last in-scope namespace node
  44. private XPathNode[] pageParent; // Page of last parent-type node (Element or Root)
  45. private int idxParent; // Page index of last parent-type node (Element or Root)
  46. private XPathNode[] pageSibling; // Page of previous sibling node (may be null if no previous sibling)
  47. private int idxSibling; // Page index of previous sibling node
  48. private int lineNumBase; // Line number from which offsets are computed
  49. private int linePosBase; // Line position from which offsets are computed
  50. private XmlQualifiedName idAttrName; // Cached name of an ID attribute
  51. private Hashtable elemIdMap; // Map from element name to ID attribute name
  52. private XPathNodeRef[] elemNameIndex; // Elements with the same name are linked together so that they can be searched quickly
  53. private const int ElementIndexSize = 64;
  54. /// <summary>
  55. /// Create a new XPathDocumentBuilder which creates nodes in "doc".
  56. /// </summary>
  57. public XPathDocumentBuilder(XPathDocument doc, IXmlLineInfo lineInfo, string baseUri, XPathDocument.LoadFlags flags) {
  58. // Allocate the initial node (for non-namespaces) page, and the initial namespace page
  59. this.nodePageFact.Init(256);
  60. this.nmspPageFact.Init(16);
  61. this.stkNmsp = new Stack<XPathNodeRef>();
  62. Initialize(doc, lineInfo, baseUri, flags);
  63. }
  64. /// <summary>
  65. /// Start construction of a new document. This must be called before any other methods are called.
  66. /// It may also be called after Close(), in order to build further documents.
  67. /// </summary>
  68. public void Initialize(XPathDocument doc, IXmlLineInfo lineInfo, string baseUri, XPathDocument.LoadFlags flags) {
  69. XPathNode[] page;
  70. int idx;
  71. this.doc = doc;
  72. this.nameTable = doc.NameTable;
  73. this.atomizeNames = (flags & XPathDocument.LoadFlags.AtomizeNames) != 0;
  74. this.idxParent = this.idxSibling = 0;
  75. this.elemNameIndex = new XPathNodeRef[ElementIndexSize];
  76. // Prepare line number information
  77. this.textBldr.Initialize(lineInfo);
  78. this.lineInfo = lineInfo;
  79. this.lineNumBase = 0;
  80. this.linePosBase = 0;
  81. // Allocate the atomization table
  82. this.infoTable = new XPathNodeInfoTable();
  83. // Allocate singleton collapsed text node
  84. idx = NewNode(out page, XPathNodeType.Text, string.Empty, string.Empty, string.Empty, string.Empty);
  85. this.doc.SetCollapsedTextNode(page, idx);
  86. // Allocate xmlns:xml namespace node
  87. this.idxNmsp = NewNamespaceNode(out this.pageNmsp, this.nameTable.Add("xml"), this.nameTable.Add(XmlReservedNs.NsXml), null, 0);
  88. this.doc.SetXmlNamespaceNode(this.pageNmsp, this.idxNmsp);
  89. if ((flags & XPathDocument.LoadFlags.Fragment) == 0) {
  90. // This tree has a document root node
  91. this.idxParent = NewNode(out this.pageParent, XPathNodeType.Root, string.Empty, string.Empty, string.Empty, baseUri);
  92. this.doc.SetRootNode(this.pageParent, this.idxParent);
  93. }
  94. else {
  95. // This tree is an XQuery fragment (no document root node), so root will be next node in the current page
  96. this.doc.SetRootNode(this.nodePageFact.NextNodePage, this.nodePageFact.NextNodeIndex);
  97. }
  98. }
  99. //-----------------------------------------------
  100. // XmlWriter interface
  101. //-----------------------------------------------
  102. /// <summary>
  103. /// XPathDocument ignores the DocType information.
  104. /// </summary>
  105. public override void WriteDocType(string name, string pubid, string sysid, string subset) {
  106. }
  107. /// <summary>
  108. /// Shortcut for calling WriteStartElement with elemType == null.
  109. /// </summary>
  110. public override void WriteStartElement(string prefix, string localName, string ns) {
  111. this.WriteStartElement(prefix, localName, ns, string.Empty);
  112. }
  113. /// <summary>
  114. /// Build an element node and attach it to its parent, if one exists. Make the element the new parent node.
  115. /// </summary>
  116. public void WriteStartElement(string prefix, string localName, string ns, string baseUri) {
  117. int hash;
  118. Debug.Assert(prefix != null && localName != null && ns != null && localName.Length != 0 && baseUri != null);
  119. if (this.atomizeNames) {
  120. prefix = this.nameTable.Add(prefix);
  121. localName = this.nameTable.Add(localName);
  122. ns = this.nameTable.Add(ns);
  123. }
  124. AddSibling(XPathNodeType.Element, localName, ns, prefix, baseUri);
  125. this.pageParent = this.pageSibling;
  126. this.idxParent = this.idxSibling;
  127. this.idxSibling = 0;
  128. // Link elements with the same name together
  129. hash = (this.pageParent[this.idxParent].LocalNameHashCode & (ElementIndexSize - 1));
  130. this.elemNameIndex[hash] = LinkSimilarElements(this.elemNameIndex[hash].Page, this.elemNameIndex[hash].Index, this.pageParent, this.idxParent);
  131. // If elements within this document might have IDs, then cache the name of the ID attribute, if one exists
  132. if (this.elemIdMap != null)
  133. this.idAttrName = (XmlQualifiedName) this.elemIdMap[new XmlQualifiedName(localName, prefix)];
  134. }
  135. /// <summary>
  136. /// Must be called when an element node's children have been fully enumerated.
  137. /// </summary>
  138. public override void WriteEndElement() {
  139. WriteEndElement(true);
  140. }
  141. /// <summary>
  142. /// Must be called when an element node's children have been fully enumerated.
  143. /// </summary>
  144. public override void WriteFullEndElement() {
  145. WriteEndElement(false);
  146. }
  147. /// <summary>
  148. /// Must be called when an element node's children have been fully enumerated.
  149. /// </summary>
  150. internal override void WriteEndElement(string prefix, string localName, string namespaceName) {
  151. WriteEndElement(true);
  152. }
  153. /// <summary>
  154. /// Must be called when an element node's children have been fully enumerated.
  155. /// </summary>
  156. internal override void WriteFullEndElement(string prefix, string localName, string namespaceName) {
  157. WriteEndElement(false);
  158. }
  159. /// <summary>
  160. /// Must be called when an element node's children have been fully enumerated.
  161. /// </summary>
  162. public void WriteEndElement(bool allowShortcutTag) {
  163. XPathNodeRef nodeRef;
  164. Debug.Assert(this.pageParent[this.idxParent].NodeType == XPathNodeType.Element);
  165. // If element has no content-typed children except for the one about to be added, then
  166. // its value is the same as its only text child's.
  167. if (!this.pageParent[this.idxParent].HasContentChild) {
  168. switch (this.textBldr.TextType) {
  169. case TextBlockType.Text:
  170. // Collapsed text node can be created if text line number information can be encoded efficiently in parent node
  171. if (this.lineInfo != null) {
  172. // If collapsed text node is not on same line as parent, don't collapse text
  173. if (this.textBldr.LineNumber != this.pageParent[this.idxParent].LineNumber)
  174. goto case TextBlockType.Whitespace;
  175. // If position is not within 256 of parent, don't collapse text
  176. int posDiff = this.textBldr.LinePosition - this.pageParent[this.idxParent].LinePosition;
  177. if (posDiff < 0 || posDiff > XPathNode.MaxCollapsedPositionOffset)
  178. goto case TextBlockType.Whitespace;
  179. // Set collapsed node line position offset
  180. this.pageParent[this.idxParent].SetCollapsedLineInfoOffset(posDiff);
  181. }
  182. // Set collapsed node text
  183. this.pageParent[this.idxParent].SetCollapsedValue(this.textBldr.ReadText());
  184. break;
  185. case TextBlockType.SignificantWhitespace:
  186. case TextBlockType.Whitespace:
  187. // Create separate whitespace node
  188. CachedTextNode();
  189. this.pageParent[this.idxParent].SetValue(this.pageSibling[this.idxSibling].Value);
  190. break;
  191. default:
  192. // Empty value, so don't create collapsed text node
  193. this.pageParent[this.idxParent].SetEmptyValue(allowShortcutTag);
  194. break;
  195. }
  196. }
  197. else {
  198. if (this.textBldr.HasText) {
  199. // Element's last child (one of several) is a text or whitespace node
  200. CachedTextNode();
  201. }
  202. }
  203. // If namespaces were added to this element,
  204. if (this.pageParent[this.idxParent].HasNamespaceDecls) {
  205. // Add it to the document's element --> namespace mapping
  206. this.doc.AddNamespace(this.pageParent, this.idxParent, this.pageNmsp, this.idxNmsp);
  207. // Restore the previous namespace chain
  208. nodeRef = this.stkNmsp.Pop();
  209. this.pageNmsp = nodeRef.Page;
  210. this.idxNmsp = nodeRef.Index;
  211. }
  212. // Make parent of this element the current element
  213. this.pageSibling = this.pageParent;
  214. this.idxSibling = this.idxParent;
  215. this.idxParent = this.pageParent[this.idxParent].GetParent(out this.pageParent);
  216. }
  217. /// <summary>
  218. /// Shortcut for calling WriteStartAttribute with attrfType == null.
  219. /// </summary>
  220. public override void WriteStartAttribute(string prefix, string localName, string namespaceName) {
  221. Debug.Assert(!prefix.Equals("xmlns"));
  222. Debug.Assert(this.idxParent == 0 || this.pageParent[this.idxParent].NodeType == XPathNodeType.Element);
  223. Debug.Assert(this.idxSibling == 0 || this.pageSibling[this.idxSibling].NodeType == XPathNodeType.Attribute);
  224. if (this.atomizeNames) {
  225. prefix = this.nameTable.Add(prefix);
  226. localName = this.nameTable.Add(localName);
  227. namespaceName = this.nameTable.Add(namespaceName);
  228. }
  229. AddSibling(XPathNodeType.Attribute, localName, namespaceName, prefix, string.Empty);
  230. }
  231. /// <summary>
  232. /// Attach the attribute's text or typed value to the previously constructed attribute node.
  233. /// </summary>
  234. public override void WriteEndAttribute() {
  235. Debug.Assert(this.pageSibling[this.idxSibling].NodeType == XPathNodeType.Attribute);
  236. this.pageSibling[this.idxSibling].SetValue(this.textBldr.ReadText());
  237. if (this.idAttrName != null) {
  238. // If this is an ID attribute,
  239. if (this.pageSibling[this.idxSibling].LocalName == this.idAttrName.Name &&
  240. this.pageSibling[this.idxSibling].Prefix == this.idAttrName.Namespace) {
  241. // Then add its value to the idValueMap map
  242. Debug.Assert(this.idxParent != 0, "ID attribute must have an element parent");
  243. this.doc.AddIdElement(this.pageSibling[this.idxSibling].Value, this.pageParent, this.idxParent);
  244. }
  245. }
  246. }
  247. /// <summary>
  248. /// Map CData text into regular text.
  249. /// </summary>
  250. public override void WriteCData(string text) {
  251. WriteString(text, TextBlockType.Text);
  252. }
  253. /// <summary>
  254. /// Construct comment node.
  255. /// </summary>
  256. public override void WriteComment(string text) {
  257. AddSibling(XPathNodeType.Comment, string.Empty, string.Empty, string.Empty, string.Empty);
  258. this.pageSibling[this.idxSibling].SetValue(text);
  259. }
  260. /// <summary>
  261. /// Shortcut for calling WriteProcessingInstruction with baseUri = string.Empty.
  262. /// </summary>
  263. public override void WriteProcessingInstruction(string name, string text) {
  264. this.WriteProcessingInstruction(name, text, string.Empty);
  265. }
  266. /// <summary>
  267. /// Construct pi node.
  268. /// </summary>
  269. public void WriteProcessingInstruction(string name, string text, string baseUri) {
  270. if (this.atomizeNames)
  271. name = this.nameTable.Add(name);
  272. AddSibling(XPathNodeType.ProcessingInstruction, name, string.Empty, string.Empty, baseUri);
  273. this.pageSibling[this.idxSibling].SetValue(text);
  274. }
  275. /// <summary>
  276. /// Write a whitespace text block.
  277. /// </summary>
  278. public override void WriteWhitespace(string ws) {
  279. WriteString(ws, TextBlockType.Whitespace);
  280. }
  281. /// <summary>
  282. /// Write an attribute or element text block.
  283. /// </summary>
  284. public override void WriteString(string text) {
  285. WriteString(text, TextBlockType.Text);
  286. }
  287. public override void WriteChars(char[] buffer, int index, int count) {
  288. WriteString(new string(buffer, index, count), TextBlockType.Text);
  289. }
  290. /// <summary>
  291. /// Map RawText to Text. This will lose entitization and won't roundtrip.
  292. /// </summary>
  293. public override void WriteRaw(string data) {
  294. WriteString(data, TextBlockType.Text);
  295. }
  296. public override void WriteRaw(char[] buffer, int index, int count) {
  297. WriteString(new string(buffer, index, count), TextBlockType.Text);
  298. }
  299. /// <summary>
  300. /// Write an element text block with the specified text type (whitespace, significant whitespace, or text).
  301. /// </summary>
  302. public void WriteString(string text, TextBlockType textType) {
  303. this.textBldr.WriteTextBlock(text, textType);
  304. }
  305. /// <summary>
  306. /// Cache does not handle entity references.
  307. /// </summary>
  308. public override void WriteEntityRef(string name) {
  309. throw new NotImplementedException();
  310. }
  311. /// <summary>
  312. /// Don't entitize, since the cache cannot represent character entities.
  313. /// </summary>
  314. public override void WriteCharEntity(char ch) {
  315. char[] chars = {ch};
  316. WriteString(new string(chars), TextBlockType.Text);
  317. }
  318. /// <summary>
  319. /// Don't entitize, since the cache cannot represent character entities.
  320. /// </summary>
  321. public override void WriteSurrogateCharEntity(char lowChar, char highChar) {
  322. char[] chars = {highChar, lowChar};
  323. WriteString(new string(chars), TextBlockType.Text);
  324. }
  325. /// <summary>
  326. /// Signals the end of tree construction.
  327. /// </summary>
  328. public override void Close() {
  329. XPathNode[] page;
  330. int idx;
  331. // If cached text exists, then create a text node at the top-level
  332. if (this.textBldr.HasText)
  333. CachedTextNode();
  334. // If document does not yet contain nodes, then an empty text node must have been created
  335. idx = this.doc.GetRootNode(out page);
  336. if (idx == this.nodePageFact.NextNodeIndex && page == this.nodePageFact.NextNodePage) {
  337. AddSibling(XPathNodeType.Text, string.Empty, string.Empty, string.Empty, string.Empty);
  338. this.pageSibling[this.idxSibling].SetValue(string.Empty);
  339. }
  340. }
  341. /// <summary>
  342. /// Since output is not forwarded to another object, this does nothing.
  343. /// </summary>
  344. public override void Flush() {
  345. }
  346. //-----------------------------------------------
  347. // XmlRawWriter interface
  348. //-----------------------------------------------
  349. /// <summary>
  350. /// Write the xml declaration. This must be the first call after Open.
  351. /// </summary>
  352. internal override void WriteXmlDeclaration(XmlStandalone standalone) {
  353. // Ignore the xml declaration when building the cache
  354. }
  355. internal override void WriteXmlDeclaration(string xmldecl) {
  356. // Ignore the xml declaration when building the cache
  357. }
  358. /// <summary>
  359. /// Called as element node's children are about to be enumerated.
  360. /// </summary>
  361. internal override void StartElementContent() {
  362. Debug.Assert(this.pageParent[this.idxParent].NodeType == XPathNodeType.Element);
  363. }
  364. /// <summary>
  365. /// Build a namespace declaration node. Attach it to an element parent, if one was previously constructed.
  366. /// All namespace declarations are linked together in an in-scope namespace tree.
  367. /// </summary>
  368. internal override void WriteNamespaceDeclaration(string prefix, string namespaceName) {
  369. XPathNode[] pageTemp, pageOverride, pageNew, pageOrig, pageCopy;
  370. int idxTemp, idxOverride, idxNew, idxOrig, idxCopy;
  371. Debug.Assert(this.idxSibling == 0 || this.pageSibling[this.idxSibling].NodeType == XPathNodeType.Attribute);
  372. Debug.Assert(!prefix.Equals("xmlns") && !namespaceName.Equals(XmlReservedNs.NsXmlNs));
  373. Debug.Assert(this.idxParent == 0 || this.idxNmsp != 0);
  374. Debug.Assert(this.idxParent == 0 || this.pageParent[this.idxParent].NodeType == XPathNodeType.Element);
  375. if (this.atomizeNames)
  376. prefix = this.nameTable.Add(prefix);
  377. namespaceName = this.nameTable.Add(namespaceName);
  378. // Does the new namespace override a previous namespace node?
  379. pageOverride = this.pageNmsp;
  380. idxOverride = this.idxNmsp;
  381. while (idxOverride != 0) {
  382. if ((object) pageOverride[idxOverride].LocalName == (object) prefix) {
  383. // Need to clone all namespaces up until the overridden node in order to bypass it
  384. break;
  385. }
  386. idxOverride = pageOverride[idxOverride].GetSibling(out pageOverride);
  387. }
  388. // Create new namespace node and add it to front of namespace list
  389. idxNew = NewNamespaceNode(out pageNew, prefix, namespaceName, this.pageParent, this.idxParent);
  390. if (idxOverride != 0) {
  391. // Bypass overriden node by cloning nodes in list leading to it
  392. pageOrig = this.pageNmsp;
  393. idxOrig = this.idxNmsp;
  394. pageCopy = pageNew;
  395. idxCopy = idxNew;
  396. while (idxOrig != idxOverride || pageOrig != pageOverride) {
  397. // Make a copy of the original namespace node
  398. idxTemp = pageOrig[idxOrig].GetParent(out pageTemp);
  399. idxTemp = NewNamespaceNode(out pageTemp, pageOrig[idxOrig].LocalName, pageOrig[idxOrig].Value, pageTemp, idxTemp);
  400. // Attach copy to chain of copied nodes
  401. pageCopy[idxCopy].SetSibling(this.infoTable, pageTemp, idxTemp);
  402. // Position on the new copy
  403. pageCopy = pageTemp;
  404. idxCopy = idxTemp;
  405. // Get next original sibling
  406. idxOrig = pageOrig[idxOrig].GetSibling(out pageOrig);
  407. }
  408. // Link farther up in the original chain, just past the last overriden node
  409. idxOverride = pageOverride[idxOverride].GetSibling(out pageOverride);
  410. if (idxOverride != 0)
  411. pageCopy[idxCopy].SetSibling(this.infoTable, pageOverride, idxOverride);
  412. else
  413. Debug.Assert(prefix.Equals("xml"), "xmlns:xml namespace declaration should always be present in the list.");
  414. }
  415. else if (this.idxParent != 0) {
  416. // Link new node directly to last in-scope namespace. No overrides necessary.
  417. pageNew[idxNew].SetSibling(this.infoTable, this.pageNmsp, this.idxNmsp);
  418. }
  419. else {
  420. // Floating namespace, so make this the root of the tree
  421. this.doc.SetRootNode(pageNew, idxNew);
  422. }
  423. if (this.idxParent != 0) {
  424. // If this is the first namespace on the current element,
  425. if (!this.pageParent[this.idxParent].HasNamespaceDecls) {
  426. // Then save the last in-scope namespace on a stack so that EndElementNode can restore it.
  427. this.stkNmsp.Push(new XPathNodeRef(this.pageNmsp, this.idxNmsp));
  428. // Mark element parent as having namespace nodes declared on it
  429. this.pageParent[this.idxParent].HasNamespaceDecls = true;
  430. }
  431. // New namespace is now last in-scope namespace
  432. this.pageNmsp = pageNew;
  433. this.idxNmsp = idxNew;
  434. }
  435. }
  436. //-----------------------------------------------
  437. // Custom Build Helper Methods
  438. //-----------------------------------------------
  439. /// <summary>
  440. /// Build ID lookup tables from the XSD schema or DTD.
  441. /// </summary>
  442. public void CreateIdTables(IDtdInfo dtdInfo) {
  443. // Extract the elements which has attribute defined as ID from the element declarations
  444. foreach (IDtdAttributeListInfo attrList in dtdInfo.GetAttributeLists()) {
  445. IDtdAttributeInfo idAttribute = attrList.LookupIdAttribute();
  446. if (idAttribute != null) {
  447. if (this.elemIdMap == null)
  448. this.elemIdMap = new Hashtable();
  449. // Id was defined in DTD and DTD doesn't have notion of namespace so we should
  450. // use prefix instead of namespace here. Schema already does this for us.
  451. this.elemIdMap.Add(new XmlQualifiedName(attrList.LocalName, attrList.Prefix),
  452. new XmlQualifiedName(idAttribute.LocalName, idAttribute.Prefix));
  453. }
  454. }
  455. }
  456. /// <summary>
  457. /// Link "prev" element with "next" element, which has a "similar" name. This increases the performance of searches by element name.
  458. /// </summary>
  459. private XPathNodeRef LinkSimilarElements(XPathNode[] pagePrev, int idxPrev, XPathNode[] pageNext, int idxNext) {
  460. // Set link on previous element
  461. if (pagePrev != null)
  462. pagePrev[idxPrev].SetSimilarElement(this.infoTable, pageNext, idxNext);
  463. // Add next element to index
  464. return new XPathNodeRef(pageNext, idxNext);
  465. }
  466. /// <summary>
  467. /// Helper method that constructs a new Namespace XPathNode.
  468. /// </summary>
  469. private int NewNamespaceNode(out XPathNode[] page, string prefix, string namespaceUri, XPathNode[] pageElem, int idxElem) {
  470. XPathNode[] pageNode;
  471. int idxNode, lineNumOffset, linePosOffset;
  472. XPathNodeInfoAtom info;
  473. Debug.Assert(pageElem == null || pageElem[idxElem].NodeType == XPathNodeType.Element);
  474. // Allocate a page slot for the new XPathNode
  475. this.nmspPageFact.AllocateSlot(out pageNode, out idxNode);
  476. // Compute node's line number information
  477. ComputeLineInfo(false, out lineNumOffset, out linePosOffset);
  478. // Obtain a XPathNodeInfoAtom object for this node
  479. info = this.infoTable.Create(prefix, string.Empty, string.Empty, string.Empty,
  480. pageElem, pageNode, null,
  481. this.doc, this.lineNumBase, this.linePosBase);
  482. // Initialize the new node
  483. pageNode[idxNode].Create(info, XPathNodeType.Namespace, idxElem);
  484. pageNode[idxNode].SetValue(namespaceUri);
  485. pageNode[idxNode].SetLineInfoOffsets(lineNumOffset, linePosOffset);
  486. page = pageNode;
  487. return idxNode;
  488. }
  489. /// <summary>
  490. /// Helper method that constructs a new XPathNode.
  491. /// </summary>
  492. private int NewNode(out XPathNode[] page, XPathNodeType xptyp, string localName, string namespaceUri, string prefix, string baseUri) {
  493. XPathNode[] pageNode;
  494. int idxNode, lineNumOffset, linePosOffset;
  495. XPathNodeInfoAtom info;
  496. Debug.Assert(xptyp != XPathNodeType.Namespace);
  497. // Allocate a page slot for the new XPathNode
  498. this.nodePageFact.AllocateSlot(out pageNode, out idxNode);
  499. // Compute node's line number information
  500. ComputeLineInfo(XPathNavigator.IsText(xptyp), out lineNumOffset, out linePosOffset);
  501. // Obtain a XPathNodeInfoAtom object for this node
  502. info = this.infoTable.Create(localName, namespaceUri, prefix, baseUri,
  503. this.pageParent, pageNode, pageNode,
  504. this.doc, this.lineNumBase, this.linePosBase);
  505. // Initialize the new node
  506. pageNode[idxNode].Create(info, xptyp, this.idxParent);
  507. pageNode[idxNode].SetLineInfoOffsets(lineNumOffset, linePosOffset);
  508. page = pageNode;
  509. return idxNode;
  510. }
  511. /// <summary>
  512. /// Compute current node's line number information.
  513. /// </summary>
  514. private void ComputeLineInfo(bool isTextNode, out int lineNumOffset, out int linePosOffset) {
  515. int lineNum, linePos;
  516. if (this.lineInfo == null) {
  517. lineNumOffset = 0;
  518. linePosOffset = 0;
  519. return;
  520. }
  521. // Get line number info from TextBlockBuilder if current node is a text node
  522. if (isTextNode) {
  523. lineNum = this.textBldr.LineNumber;
  524. linePos = this.textBldr.LinePosition;
  525. }
  526. else {
  527. Debug.Assert(this.lineInfo.HasLineInfo(), "HasLineInfo should have been checked before this.");
  528. lineNum = this.lineInfo.LineNumber;
  529. linePos = this.lineInfo.LinePosition;
  530. }
  531. lineNumOffset = lineNum - this.lineNumBase;
  532. if (lineNumOffset < 0 || lineNumOffset > XPathNode.MaxLineNumberOffset) {
  533. this.lineNumBase = lineNum;
  534. lineNumOffset = 0;
  535. }
  536. linePosOffset = linePos - this.linePosBase;
  537. if (linePosOffset < 0 || linePosOffset > XPathNode.MaxLinePositionOffset) {
  538. this.linePosBase = linePos;
  539. linePosOffset = 0;
  540. }
  541. }
  542. /// <summary>
  543. /// Add a sibling node. If no previous sibling exists, add the node as the first child of the parent.
  544. /// If no parent exists, make this node the root of the document.
  545. /// </summary>
  546. private void AddSibling(XPathNodeType xptyp, string localName, string namespaceUri, string prefix, string baseUri) {
  547. XPathNode[] pageNew;
  548. int idxNew;
  549. Debug.Assert(xptyp != XPathNodeType.Root && xptyp != XPathNodeType.Namespace);
  550. if (this.textBldr.HasText)
  551. CachedTextNode();
  552. idxNew = NewNode(out pageNew, xptyp, localName, namespaceUri, prefix, baseUri);
  553. // this.idxParent is only 0 for the top-most node
  554. if (this.idxParent != 0) {
  555. // Set properties on parent
  556. this.pageParent[this.idxParent].SetParentProperties(xptyp);
  557. if (this.idxSibling == 0) {
  558. // This is the first child of the parent (so should be allocated immediately after parent)
  559. Debug.Assert(this.idxParent + 1 == idxNew || idxNew == 1);
  560. }
  561. else {
  562. // There is already a previous sibling
  563. this.pageSibling[this.idxSibling].SetSibling(this.infoTable, pageNew, idxNew);
  564. }
  565. }
  566. this.pageSibling = pageNew;
  567. this.idxSibling = idxNew;
  568. }
  569. /// <summary>
  570. /// Creates a text node from cached text parts.
  571. /// </summary>
  572. private void CachedTextNode() {
  573. TextBlockType textType;
  574. string text;
  575. Debug.Assert(this.textBldr.HasText || (this.idxSibling == 0 && this.idxParent == 0), "Cannot create empty text node unless it's a top-level text node.");
  576. Debug.Assert(this.idxSibling == 0 || !this.pageSibling[this.idxSibling].IsText, "Cannot create adjacent text nodes.");
  577. // Create a text node
  578. textType = this.textBldr.TextType;
  579. text = this.textBldr.ReadText();
  580. AddSibling((XPathNodeType) textType, string.Empty, string.Empty, string.Empty, string.Empty);
  581. this.pageSibling[this.idxSibling].SetValue(text);
  582. }
  583. /// <summary>
  584. /// Allocates pages of nodes for the XPathDocumentBuilder. The initial pages and arrays are
  585. /// fairly small. As each page fills, a new page that is twice as big is allocated.
  586. /// The max size of a page is 65536 nodes, since XPathNode indexes are 16-bits.
  587. /// </summary>
  588. private struct NodePageFactory {
  589. private XPathNode[] page;
  590. private XPathNodePageInfo pageInfo;
  591. private int pageSize;
  592. /// <summary>
  593. /// Allocates and returns the initial node page.
  594. /// </summary>
  595. public void Init(int initialPageSize) {
  596. // 0th slot: Index 0 is reserved to mean "null node". Only use 0th slot to store PageInfo.
  597. this.pageSize = initialPageSize;
  598. this.page = new XPathNode[this.pageSize];
  599. this.pageInfo = new XPathNodePageInfo(null, 1);
  600. this.page[0].Create(this.pageInfo);
  601. }
  602. /// <summary>
  603. /// Return the page on which the next node will be allocated.
  604. /// </summary>
  605. public XPathNode[] NextNodePage {
  606. get { return this.page; }
  607. }
  608. /// <summary>
  609. /// Return the page index that the next node will be given.
  610. /// </summary>
  611. public int NextNodeIndex {
  612. get { return this.pageInfo.NodeCount; }
  613. }
  614. /// <summary>
  615. /// Allocate the next slot in the current node page. Return a reference to the page and the index
  616. /// of the allocated slot.
  617. /// </summary>
  618. public void AllocateSlot(out XPathNode[] page, out int idx) {
  619. page = this.page;
  620. idx = this.pageInfo.NodeCount;
  621. // Allocate new page if necessary
  622. if (++this.pageInfo.NodeCount >= this.page.Length) {
  623. if (this.pageSize < (1 << 16)) {
  624. // New page shouldn't contain more slots than 16 bits can address
  625. this.pageSize *= 2;
  626. }
  627. this.page = new XPathNode[this.pageSize];
  628. this.pageInfo.NextPage = this.page;
  629. this.pageInfo = new XPathNodePageInfo(page, this.pageInfo.PageNumber + 1);
  630. this.page[0].Create(this.pageInfo);
  631. }
  632. }
  633. }
  634. /// <summary>
  635. /// This class concatenates adjacent text blocks and tracks TextBlockType and line number information.
  636. /// </summary>
  637. private struct TextBlockBuilder {
  638. private IXmlLineInfo lineInfo;
  639. private TextBlockType textType;
  640. private string text;
  641. private int lineNum, linePos;
  642. /// <summary>
  643. /// Constructor.
  644. /// </summary>
  645. public void Initialize(IXmlLineInfo lineInfo) {
  646. this.lineInfo = lineInfo;
  647. this.textType = TextBlockType.None;
  648. }
  649. /// <summary>
  650. /// Return the type of the cached text block.
  651. /// </summary>
  652. public TextBlockType TextType {
  653. get { return this.textType; }
  654. }
  655. /// <summary>
  656. /// Returns true if text has been cached.
  657. /// </summary>
  658. public bool HasText {
  659. get { return this.textType != TextBlockType.None; }
  660. }
  661. /// <summary>
  662. /// Returns the line number of the last text block to be cached.
  663. /// </summary>
  664. public int LineNumber {
  665. get { return this.lineNum; }
  666. }
  667. /// <summary>
  668. /// Returns the line position of the last text block to be cached.
  669. /// </summary>
  670. public int LinePosition {
  671. get { return this.linePos; }
  672. }
  673. /// <summary>
  674. /// Append a text block with the specified type.
  675. /// </summary>
  676. public void WriteTextBlock(string text, TextBlockType textType) {
  677. Debug.Assert((int) XPathNodeType.Text < (int) XPathNodeType.SignificantWhitespace);
  678. Debug.Assert((int) XPathNodeType.SignificantWhitespace < (int) XPathNodeType.Whitespace);
  679. if (text.Length != 0) {
  680. if (this.textType == TextBlockType.None) {
  681. this.text = text;
  682. this.textType = textType;
  683. if (this.lineInfo != null) {
  684. this.lineNum = this.lineInfo.LineNumber;
  685. this.linePos = this.lineInfo.LinePosition;
  686. }
  687. }
  688. else {
  689. this.text = string.Concat(this.text, text);
  690. // Determine whether text is Text, Whitespace, or SignificantWhitespace
  691. if ((int) textType < (int) this.textType)
  692. this.textType = textType;
  693. }
  694. }
  695. }
  696. /// <summary>
  697. /// Read all cached text, or string.Empty if no text has been cached, and clear the text block type.
  698. /// </summary>
  699. public string ReadText() {
  700. if (this.textType == TextBlockType.None)
  701. return string.Empty;
  702. this.textType = TextBlockType.None;
  703. return this.text;
  704. }
  705. }
  706. }
  707. }