XPathNode.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. //------------------------------------------------------------------------------
  2. // <copyright file="XPathNode.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.Diagnostics;
  9. using System.Text;
  10. using System.Xml;
  11. using System.Xml.XPath;
  12. using System.Xml.Schema;
  13. namespace MS.Internal.Xml.Cache {
  14. /// <summary>
  15. /// Implementation of a Node in the XPath/XQuery data model.
  16. /// 1. All nodes are stored in variable-size pages (max 65536 nodes/page) of XPathNode structures.
  17. /// 2. Pages are sequentially numbered. Nodes are allocated in strict document order.
  18. /// 3. Node references take the form of a (page, index) pair.
  19. /// 4. Each node explicitly stores a parent and a sibling reference.
  20. /// 5. If a node has one or more attributes and/or non-collapsed content children, then its first
  21. /// child is stored in the next slot. If the node is in the last slot of a page, then its first
  22. /// child is stored in the first slot of the next page.
  23. /// 6. Attributes are linked together at the start of the child list.
  24. /// 7. Namespaces are allocated in totally separate pages. Elements are associated with
  25. /// declared namespaces via a hashtable map in the document.
  26. /// 8. Name parts are always non-null (string.Empty for nodes without names)
  27. /// 9. XPathNodeInfoAtom contains all information that is common to many nodes in a
  28. /// document, and therefore is atomized to save space. This includes the document, the name,
  29. /// the child, sibling, parent, and value pages, and the schema type.
  30. /// 10. The node structure is 20 bytes in length. Out-of-line overhead is typically 2-4 bytes per node.
  31. /// </summary>
  32. internal struct XPathNode {
  33. private XPathNodeInfoAtom info; // Atomized node information
  34. private ushort idxSibling; // Page index of sibling node
  35. private ushort idxParent; // Page index of parent node
  36. private ushort idxSimilar; // Page index of next node in document order that has local name with same hashcode
  37. private ushort posOffset; // Line position offset of node (added to LinePositionBase)
  38. private uint props; // Node properties (broken down into bits below)
  39. private string value; // String value of node
  40. private const uint NodeTypeMask = 0xF;
  41. private const uint HasAttributeBit = 0x10;
  42. private const uint HasContentChildBit = 0x20;
  43. private const uint HasElementChildBit = 0x40;
  44. private const uint HasCollapsedTextBit = 0x80;
  45. private const uint AllowShortcutTagBit = 0x100; // True if this is an element that allows shortcut tag syntax
  46. private const uint HasNmspDeclsBit = 0x200; // True if this is an element with namespace declarations declared on it
  47. private const uint LineNumberMask = 0x00FFFC00; // 14 bits for line number offset (0 - 16K)
  48. private const int LineNumberShift = 10;
  49. private const int CollapsedPositionShift = 24; // 8 bits for collapsed text position offset (0 - 256)
  50. #if DEBUG
  51. public const int MaxLineNumberOffset = 0x20;
  52. public const int MaxLinePositionOffset = 0x20;
  53. public const int MaxCollapsedPositionOffset = 0x10;
  54. #else
  55. public const int MaxLineNumberOffset = 0x3FFF;
  56. public const int MaxLinePositionOffset = 0xFFFF;
  57. public const int MaxCollapsedPositionOffset = 0xFF;
  58. #endif
  59. /// <summary>
  60. /// Returns the type of this node
  61. /// </summary>
  62. public XPathNodeType NodeType {
  63. get { return (XPathNodeType) (this.props & NodeTypeMask); }
  64. }
  65. /// <summary>
  66. /// Returns the namespace prefix of this node. If this node has no prefix, then the empty string
  67. /// will be returned (never null).
  68. /// </summary>
  69. public string Prefix {
  70. get { return this.info.Prefix; }
  71. }
  72. /// <summary>
  73. /// Returns the local name of this node. If this node has no name, then the empty string
  74. /// will be returned (never null).
  75. /// </summary>
  76. public string LocalName {
  77. get { return this.info.LocalName; }
  78. }
  79. /// <summary>
  80. /// Returns the name of this node. If this node has no name, then the empty string
  81. /// will be returned (never null).
  82. /// </summary>
  83. public string Name {
  84. get {
  85. if (Prefix.Length == 0) {
  86. return LocalName;
  87. }
  88. else {
  89. return string.Concat(Prefix, ":", LocalName);
  90. }
  91. }
  92. }
  93. /// <summary>
  94. /// Returns the namespace part of this node's name. If this node has no name, then the empty string
  95. /// will be returned (never null).
  96. /// </summary>
  97. public string NamespaceUri {
  98. get { return this.info.NamespaceUri; }
  99. }
  100. /// <summary>
  101. /// Returns this node's document.
  102. /// </summary>
  103. public XPathDocument Document {
  104. get { return this.info.Document; }
  105. }
  106. /// <summary>
  107. /// Returns this node's base Uri. This is string.Empty for all node kinds except Element, Root, and PI.
  108. /// </summary>
  109. public string BaseUri {
  110. get { return this.info.BaseUri; }
  111. }
  112. /// <summary>
  113. /// Returns this node's source line number.
  114. /// </summary>
  115. public int LineNumber {
  116. get { return this.info.LineNumberBase + (int) ((this.props & LineNumberMask) >> LineNumberShift); }
  117. }
  118. /// <summary>
  119. /// Return this node's source line position.
  120. /// </summary>
  121. public int LinePosition {
  122. get { return this.info.LinePositionBase + (int) this.posOffset; }
  123. }
  124. /// <summary>
  125. /// If this node is an element with collapsed text, then return the source line position of the node (the
  126. /// source line number is the same as LineNumber).
  127. /// </summary>
  128. public int CollapsedLinePosition {
  129. get {
  130. Debug.Assert(HasCollapsedText, "Do not call CollapsedLinePosition unless HasCollapsedText is true.");
  131. return LinePosition + (int) (this.props >> CollapsedPositionShift);
  132. }
  133. }
  134. /// <summary>
  135. /// Returns information about the node page. Only the 0th node on each page has this property defined.
  136. /// </summary>
  137. public XPathNodePageInfo PageInfo {
  138. get { return this.info.PageInfo; }
  139. }
  140. /// <summary>
  141. /// Returns the root node of the current document. This always succeeds.
  142. /// </summary>
  143. public int GetRoot(out XPathNode[] pageNode) {
  144. return this.info.Document.GetRootNode(out pageNode);
  145. }
  146. /// <summary>
  147. /// Returns the parent of this node. If this node has no parent, then 0 is returned.
  148. /// </summary>
  149. public int GetParent(out XPathNode[] pageNode) {
  150. pageNode = this.info.ParentPage;
  151. return this.idxParent;
  152. }
  153. /// <summary>
  154. /// Returns the next sibling of this node. If this node has no next sibling, then 0 is returned.
  155. /// </summary>
  156. public int GetSibling(out XPathNode[] pageNode) {
  157. pageNode = this.info.SiblingPage;
  158. return this.idxSibling;
  159. }
  160. /// <summary>
  161. /// Returns the next element in document order that has the same local name hashcode as this element.
  162. /// If there are no similar elements, then 0 is returned.
  163. /// </summary>
  164. public int GetSimilarElement(out XPathNode[] pageNode) {
  165. pageNode = this.info.SimilarElementPage;
  166. return this.idxSimilar;
  167. }
  168. /// <summary>
  169. /// Returns true if this node's name matches the specified localName and namespaceName. Assume
  170. /// that localName has been atomized, but namespaceName has not.
  171. /// </summary>
  172. public bool NameMatch(string localName, string namespaceName) {
  173. Debug.Assert(localName == null || (object) Document.NameTable.Get(localName) == (object) localName, "localName must be atomized.");
  174. return (object) this.info.LocalName == (object) localName &&
  175. this.info.NamespaceUri == namespaceName;
  176. }
  177. /// <summary>
  178. /// Returns true if this is an Element node with a name that matches the specified localName and
  179. /// namespaceName. Assume that localName has been atomized, but namespaceName has not.
  180. /// </summary>
  181. public bool ElementMatch(string localName, string namespaceName) {
  182. Debug.Assert(localName == null || (object) Document.NameTable.Get(localName) == (object) localName, "localName must be atomized.");
  183. return NodeType == XPathNodeType.Element &&
  184. (object) this.info.LocalName == (object) localName &&
  185. this.info.NamespaceUri == namespaceName;
  186. }
  187. /// <summary>
  188. /// Return true if this node is an xmlns:xml node.
  189. /// </summary>
  190. public bool IsXmlNamespaceNode {
  191. get {
  192. string localName = this.info.LocalName;
  193. return NodeType == XPathNodeType.Namespace && localName.Length == 3 && localName == "xml";
  194. }
  195. }
  196. /// <summary>
  197. /// Returns true if this node has a sibling.
  198. /// </summary>
  199. public bool HasSibling {
  200. get { return this.idxSibling != 0; }
  201. }
  202. /// <summary>
  203. /// Returns true if this node has a collapsed text node as its only content-typed child.
  204. /// </summary>
  205. public bool HasCollapsedText {
  206. get { return (this.props & HasCollapsedTextBit) != 0; }
  207. }
  208. /// <summary>
  209. /// Returns true if this node has at least one attribute.
  210. /// </summary>
  211. public bool HasAttribute {
  212. get { return (this.props & HasAttributeBit) != 0; }
  213. }
  214. /// <summary>
  215. /// Returns true if this node has at least one content-typed child (attributes and namespaces
  216. /// don't count).
  217. /// </summary>
  218. public bool HasContentChild {
  219. get { return (this.props & HasContentChildBit) != 0; }
  220. }
  221. /// <summary>
  222. /// Returns true if this node has at least one element child.
  223. /// </summary>
  224. public bool HasElementChild {
  225. get { return (this.props & HasElementChildBit) != 0; }
  226. }
  227. /// <summary>
  228. /// Returns true if this is an attribute or namespace node.
  229. /// </summary>
  230. public bool IsAttrNmsp {
  231. get {
  232. XPathNodeType xptyp = NodeType;
  233. return xptyp == XPathNodeType.Attribute || xptyp == XPathNodeType.Namespace;
  234. }
  235. }
  236. /// <summary>
  237. /// Returns true if this is a text or whitespace node.
  238. /// </summary>
  239. public bool IsText {
  240. get { return XPathNavigator.IsText(NodeType); }
  241. }
  242. /// <summary>
  243. /// Returns true if this node has local namespace declarations associated with it. Since all
  244. /// namespace declarations are stored out-of-line in the owner Document, this property
  245. /// can be consulted in order to avoid a lookup in the common case where this node has no
  246. /// local namespace declarations.
  247. /// </summary>
  248. public bool HasNamespaceDecls {
  249. get { return (this.props & HasNmspDeclsBit) != 0; }
  250. set {
  251. if (value) this.props |= HasNmspDeclsBit;
  252. else unchecked { this.props &= (byte) ~((uint) HasNmspDeclsBit); }
  253. }
  254. }
  255. /// <summary>
  256. /// Returns true if this node is an empty element that allows shortcut tag syntax.
  257. /// </summary>
  258. public bool AllowShortcutTag {
  259. get { return (this.props & AllowShortcutTagBit) != 0; }
  260. }
  261. /// <summary>
  262. /// Cached hashcode computed over the local name of this element.
  263. /// </summary>
  264. public int LocalNameHashCode {
  265. get { return this.info.LocalNameHashCode; }
  266. }
  267. /// <summary>
  268. /// Return the precomputed String value of this node (null if no value exists, i.e. document node, element node with complex content, etc).
  269. /// </summary>
  270. public string Value {
  271. get { return this.value; }
  272. }
  273. //-----------------------------------------------
  274. // Node construction
  275. //-----------------------------------------------
  276. /// <summary>
  277. /// Constructs the 0th XPathNode in each page, which contains only page information.
  278. /// </summary>
  279. public void Create(XPathNodePageInfo pageInfo) {
  280. this.info = new XPathNodeInfoAtom(pageInfo);
  281. }
  282. /// <summary>
  283. /// Constructs a XPathNode. Later, the idxSibling and value fields may be fixed up.
  284. /// </summary>
  285. public void Create(XPathNodeInfoAtom info, XPathNodeType xptyp, int idxParent) {
  286. Debug.Assert(info != null && idxParent <= UInt16.MaxValue);
  287. this.info = info;
  288. this.props = (uint) xptyp;
  289. this.idxParent = (ushort) idxParent;
  290. }
  291. /// <summary>
  292. /// Set this node's line number information.
  293. /// </summary>
  294. public void SetLineInfoOffsets(int lineNumOffset, int linePosOffset) {
  295. Debug.Assert(lineNumOffset >= 0 && lineNumOffset <= MaxLineNumberOffset, "Line number offset too large or small: " + lineNumOffset);
  296. Debug.Assert(linePosOffset >= 0 && linePosOffset <= MaxLinePositionOffset, "Line position offset too large or small: " + linePosOffset);
  297. this.props |= ((uint) lineNumOffset << LineNumberShift);
  298. this.posOffset = (ushort) linePosOffset;
  299. }
  300. /// <summary>
  301. /// Set the position offset of this element's collapsed text.
  302. /// </summary>
  303. public void SetCollapsedLineInfoOffset(int posOffset) {
  304. Debug.Assert(posOffset >= 0 && posOffset <= MaxCollapsedPositionOffset, "Collapsed text line position offset too large or small: " + posOffset);
  305. this.props |= ((uint) posOffset << CollapsedPositionShift);
  306. }
  307. /// <summary>
  308. /// Set this node's value.
  309. /// </summary>
  310. public void SetValue(string value) {
  311. this.value = value;
  312. }
  313. /// <summary>
  314. /// Create an empty element value.
  315. /// </summary>
  316. public void SetEmptyValue(bool allowShortcutTag) {
  317. Debug.Assert(NodeType == XPathNodeType.Element);
  318. this.value = string.Empty;
  319. if (allowShortcutTag)
  320. this.props |= AllowShortcutTagBit;
  321. }
  322. /// <summary>
  323. /// Create a collapsed text node on this element having the specified value.
  324. /// </summary>
  325. public void SetCollapsedValue(string value) {
  326. Debug.Assert(NodeType == XPathNodeType.Element);
  327. this.value = value;
  328. this.props |= HasContentChildBit | HasCollapsedTextBit;
  329. }
  330. /// <summary>
  331. /// This method is called when a new child is appended to this node's list of attributes and children.
  332. /// The type of the new child is used to determine how various parent properties should be set.
  333. /// </summary>
  334. public void SetParentProperties(XPathNodeType xptyp) {
  335. if (xptyp == XPathNodeType.Attribute) {
  336. this.props |= HasAttributeBit;
  337. }
  338. else {
  339. this.props |= HasContentChildBit;
  340. if (xptyp == XPathNodeType.Element)
  341. this.props |= HasElementChildBit;
  342. }
  343. }
  344. /// <summary>
  345. /// Link this node to its next sibling. If "pageSibling" is different than the one stored in the InfoAtom, re-atomize.
  346. /// </summary>
  347. public void SetSibling(XPathNodeInfoTable infoTable, XPathNode[] pageSibling, int idxSibling) {
  348. Debug.Assert(pageSibling != null && idxSibling != 0 && idxSibling <= UInt16.MaxValue, "Bad argument");
  349. Debug.Assert(this.idxSibling == 0, "SetSibling should not be called more than once.");
  350. this.idxSibling = (ushort) idxSibling;
  351. if (pageSibling != this.info.SiblingPage) {
  352. // Re-atomize the InfoAtom
  353. this.info = infoTable.Create(this.info.LocalName, this.info.NamespaceUri, this.info.Prefix, this.info.BaseUri,
  354. this.info.ParentPage, pageSibling, this.info.SimilarElementPage,
  355. this.info.Document, this.info.LineNumberBase, this.info.LinePositionBase);
  356. }
  357. }
  358. /// <summary>
  359. /// Link this element to the next element in document order that shares a local name having the same hash code.
  360. /// If "pageSimilar" is different than the one stored in the InfoAtom, re-atomize.
  361. /// </summary>
  362. public void SetSimilarElement(XPathNodeInfoTable infoTable, XPathNode[] pageSimilar, int idxSimilar) {
  363. Debug.Assert(pageSimilar != null && idxSimilar != 0 && idxSimilar <= UInt16.MaxValue, "Bad argument");
  364. Debug.Assert(this.idxSimilar == 0, "SetSimilarElement should not be called more than once.");
  365. this.idxSimilar = (ushort) idxSimilar;
  366. if (pageSimilar != this.info.SimilarElementPage) {
  367. // Re-atomize the InfoAtom
  368. this.info = infoTable.Create(this.info.LocalName, this.info.NamespaceUri, this.info.Prefix, this.info.BaseUri,
  369. this.info.ParentPage, this.info.SiblingPage, pageSimilar,
  370. this.info.Document, this.info.LineNumberBase, this.info.LinePositionBase);
  371. }
  372. }
  373. }
  374. /// <summary>
  375. /// A reference to a XPathNode is composed of two values: the page on which the node is located, and the node's
  376. /// index in the page.
  377. /// </summary>
  378. internal struct XPathNodeRef {
  379. private XPathNode[] page;
  380. private int idx;
  381. public static XPathNodeRef Null {
  382. get { return new XPathNodeRef(); }
  383. }
  384. public XPathNodeRef(XPathNode[] page, int idx) {
  385. this.page = page;
  386. this.idx = idx;
  387. }
  388. public bool IsNull {
  389. get { return this.page == null; }
  390. }
  391. public XPathNode[] Page {
  392. get { return this.page; }
  393. }
  394. public int Index {
  395. get { return this.idx; }
  396. }
  397. public override int GetHashCode() {
  398. return XPathNodeHelper.GetLocation(this.page, this.idx);
  399. }
  400. }
  401. }