Mirror of Svelto.ECS because we're a fan of it
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

790 lines
24KB

  1. // Uncomment this to enable the following debugging aids:
  2. // LeftLeaningRedBlackTree.HtmlFragment
  3. // LeftLeaningRedBlackTree.Node.HtmlFragment
  4. // LeftLeaningRedBlackTree.AssertInvariants
  5. // #define DEBUGGING
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Diagnostics;
  9. /// <summary>
  10. /// Implements a left-leaning red-black tree.
  11. /// </summary>
  12. /// <remarks>
  13. /// Based on the research paper "Left-leaning Red-Black Trees"
  14. /// by Robert Sedgewick. More information available at:
  15. /// http://www.cs.princeton.edu/~rs/talks/LLRB/RedBlack.pdf
  16. /// http://www.cs.princeton.edu/~rs/talks/LLRB/08Penn.pdf
  17. /// </remarks>
  18. /// <typeparam name="TKey">Type of keys.</typeparam>
  19. /// <typeparam name="TValue">Type of values.</typeparam>
  20. public class LeftLeaningRedBlackTree<TKey, TValue>
  21. {
  22. /// <summary>
  23. /// Stores the key comparison function.
  24. /// </summary>
  25. private Comparison<TKey> _keyComparison;
  26. /// <summary>
  27. /// Stores the value comparison function.
  28. /// </summary>
  29. private Comparison<TValue> _valueComparison;
  30. /// <summary>
  31. /// Stores the root node of the tree.
  32. /// </summary>
  33. private Node _rootNode;
  34. /// <summary>
  35. /// Represents a node of the tree.
  36. /// </summary>
  37. /// <remarks>
  38. /// Using fields instead of properties drops execution time by about 40%.
  39. /// </remarks>
  40. [DebuggerDisplay("Key={Key}, Value={Value}, Siblings={Siblings}")]
  41. private class Node
  42. {
  43. /// <summary>
  44. /// Gets or sets the node's key.
  45. /// </summary>
  46. public TKey Key;
  47. /// <summary>
  48. /// Gets or sets the node's value.
  49. /// </summary>
  50. public TValue Value;
  51. /// <summary>
  52. /// Gets or sets the left node.
  53. /// </summary>
  54. public Node Left;
  55. /// <summary>
  56. /// Gets or sets the right node.
  57. /// </summary>
  58. public Node Right;
  59. /// <summary>
  60. /// Gets or sets the color of the node.
  61. /// </summary>
  62. public bool IsBlack;
  63. /// <summary>
  64. /// Gets or sets the number of "siblings" (nodes with the same key/value).
  65. /// </summary>
  66. public int Siblings;
  67. #if DEBUGGING
  68. /// <summary>
  69. /// Gets an HTML fragment representing the node and its children.
  70. /// </summary>
  71. public string HtmlFragment
  72. {
  73. get
  74. {
  75. return
  76. "<table border='1'>" +
  77. "<tr>" +
  78. "<td colspan='2' align='center' bgcolor='" + (IsBlack ? "gray" : "red") + "'>" + Key + ", " + Value + " [" + Siblings + "]</td>" +
  79. "</tr>" +
  80. "<tr>" +
  81. "<td valign='top'>" + (null != Left ? Left.HtmlFragment : "[null]") + "</td>" +
  82. "<td valign='top'>" + (null != Right ? Right.HtmlFragment : "[null]") + "</td>" +
  83. "</tr>" +
  84. "</table>";
  85. }
  86. }
  87. #endif
  88. }
  89. /// <summary>
  90. /// Initializes a new instance of the LeftLeaningRedBlackTree class implementing a normal dictionary.
  91. /// </summary>
  92. /// <param name="keyComparison">The key comparison function.</param>
  93. public LeftLeaningRedBlackTree(Comparison<TKey> keyComparison)
  94. {
  95. if (null == keyComparison)
  96. {
  97. throw new ArgumentNullException("keyComparison");
  98. }
  99. _keyComparison = keyComparison;
  100. }
  101. /// <summary>
  102. /// Initializes a new instance of the LeftLeaningRedBlackTree class implementing an ordered multi-dictionary.
  103. /// </summary>
  104. /// <param name="keyComparison">The key comparison function.</param>
  105. /// <param name="valueComparison">The value comparison function.</param>
  106. public LeftLeaningRedBlackTree(Comparison<TKey> keyComparison, Comparison<TValue> valueComparison)
  107. : this(keyComparison)
  108. {
  109. if (null == valueComparison)
  110. {
  111. throw new ArgumentNullException("valueComparison");
  112. }
  113. _valueComparison = valueComparison;
  114. }
  115. /// <summary>
  116. /// Gets a value indicating whether the tree is acting as an ordered multi-dictionary.
  117. /// </summary>
  118. private bool IsMultiDictionary
  119. {
  120. get { return null != _valueComparison; }
  121. }
  122. /// <summary>
  123. /// Adds a key/value pair to the tree.
  124. /// </summary>
  125. /// <param name="key">Key to add.</param>
  126. /// <param name="value">Value to add.</param>
  127. public void Add(TKey key, TValue value)
  128. {
  129. _rootNode = Add(_rootNode, key, value);
  130. _rootNode.IsBlack = true;
  131. #if DEBUGGING
  132. AssertInvariants();
  133. #endif
  134. }
  135. /// <summary>
  136. /// Removes a key (and its associated value) from a normal (non-multi) dictionary.
  137. /// </summary>
  138. /// <param name="key">Key to remove.</param>
  139. /// <returns>True if key present and removed.</returns>
  140. public bool Remove(TKey key)
  141. {
  142. if (IsMultiDictionary)
  143. {
  144. throw new InvalidOperationException("Remove is only supported when acting as a normal (non-multi) dictionary.");
  145. }
  146. return Remove(key, default(TValue));
  147. }
  148. /// <summary>
  149. /// Removes a key/value pair from the tree.
  150. /// </summary>
  151. /// <param name="key">Key to remove.</param>
  152. /// <param name="value">Value to remove.</param>
  153. /// <returns>True if key/value present and removed.</returns>
  154. public bool Remove(TKey key, TValue value)
  155. {
  156. int initialCount = Count;
  157. if (null != _rootNode)
  158. {
  159. _rootNode = Remove(_rootNode, key, value);
  160. if (null != _rootNode)
  161. {
  162. _rootNode.IsBlack = true;
  163. }
  164. }
  165. #if DEBUGGING
  166. AssertInvariants();
  167. #endif
  168. return initialCount != Count;
  169. }
  170. /// <summary>
  171. /// Removes all nodes in the tree.
  172. /// </summary>
  173. public void Clear()
  174. {
  175. _rootNode = null;
  176. Count = 0;
  177. #if DEBUGGING
  178. AssertInvariants();
  179. #endif
  180. }
  181. /// <summary>
  182. /// Gets a sorted list of keys in the tree.
  183. /// </summary>
  184. /// <returns>Sorted list of keys.</returns>
  185. public IEnumerable<TKey> GetKeys()
  186. {
  187. TKey lastKey = default(TKey);
  188. bool lastKeyValid = false;
  189. return Traverse(
  190. _rootNode,
  191. n => !lastKeyValid || !object.Equals(lastKey, n.Key),
  192. n =>
  193. {
  194. lastKey = n.Key;
  195. lastKeyValid = true;
  196. return lastKey;
  197. });
  198. }
  199. /// <summary>
  200. /// Gets the value associated with the specified key in a normal (non-multi) dictionary.
  201. /// </summary>
  202. /// <param name="key">Specified key.</param>
  203. /// <returns>Value associated with the specified key.</returns>
  204. public TValue GetValueForKey(TKey key)
  205. {
  206. if (IsMultiDictionary)
  207. {
  208. throw new InvalidOperationException("GetValueForKey is only supported when acting as a normal (non-multi) dictionary.");
  209. }
  210. Node node = GetNodeForKey(key);
  211. if (null != node)
  212. {
  213. return node.Value;
  214. }
  215. else
  216. {
  217. throw new KeyNotFoundException();
  218. }
  219. }
  220. /// <summary>
  221. /// Gets a sequence of the values associated with the specified key.
  222. /// </summary>
  223. /// <param name="key">Specified key.</param>
  224. /// <returns>Sequence of values.</returns>
  225. public IEnumerable<TValue> GetValuesForKey(TKey key)
  226. {
  227. return Traverse(GetNodeForKey(key), n => 0 == _keyComparison(n.Key, key), n => n.Value);
  228. }
  229. /// <summary>
  230. /// Gets a sequence of all the values in the tree.
  231. /// </summary>
  232. /// <returns>Sequence of all values.</returns>
  233. public IEnumerable<TValue> GetValuesForAllKeys()
  234. {
  235. return Traverse(_rootNode, n => true, n => n.Value);
  236. }
  237. /// <summary>
  238. /// Gets the count of key/value pairs in the tree.
  239. /// </summary>
  240. public int Count { get; private set; }
  241. /// <summary>
  242. /// Gets the minimum key in the tree.
  243. /// </summary>
  244. public TKey MinimumKey
  245. {
  246. get { return GetExtreme(_rootNode, n => n.Left, n => n.Key); }
  247. }
  248. /// <summary>
  249. /// Gets the maximum key in the tree.
  250. /// </summary>
  251. public TKey MaximumKey
  252. {
  253. get { return GetExtreme(_rootNode, n => n.Right, n => n.Key); }
  254. }
  255. /// <summary>
  256. /// Returns true if the specified node is red.
  257. /// </summary>
  258. /// <param name="node">Specified node.</param>
  259. /// <returns>True if specified node is red.</returns>
  260. private static bool IsRed(Node node)
  261. {
  262. if (null == node)
  263. {
  264. // "Virtual" leaf nodes are always black
  265. return false;
  266. }
  267. return !node.IsBlack;
  268. }
  269. /// <summary>
  270. /// Adds the specified key/value pair below the specified root node.
  271. /// </summary>
  272. /// <param name="node">Specified node.</param>
  273. /// <param name="key">Key to add.</param>
  274. /// <param name="value">Value to add.</param>
  275. /// <returns>New root node.</returns>
  276. private Node Add(Node node, TKey key, TValue value)
  277. {
  278. if (null == node)
  279. {
  280. // Insert new node
  281. Count++;
  282. return new Node { Key = key, Value = value };
  283. }
  284. if (IsRed(node.Left) && IsRed(node.Right))
  285. {
  286. // Split node with two red children
  287. FlipColor(node);
  288. }
  289. // Find right place for new node
  290. int comparisonResult = KeyAndValueComparison(key, value, node.Key, node.Value);
  291. if (comparisonResult < 0)
  292. {
  293. node.Left = Add(node.Left, key, value);
  294. }
  295. else if (0 < comparisonResult)
  296. {
  297. node.Right = Add(node.Right, key, value);
  298. }
  299. else
  300. {
  301. if (IsMultiDictionary)
  302. {
  303. // Store the presence of a "duplicate" node
  304. node.Siblings++;
  305. Count++;
  306. }
  307. else
  308. {
  309. // Replace the value of the existing node
  310. node.Value = value;
  311. }
  312. }
  313. if (IsRed(node.Right))
  314. {
  315. // Rotate to prevent red node on right
  316. node = RotateLeft(node);
  317. }
  318. if (IsRed(node.Left) && IsRed(node.Left.Left))
  319. {
  320. // Rotate to prevent consecutive red nodes
  321. node = RotateRight(node);
  322. }
  323. return node;
  324. }
  325. /// <summary>
  326. /// Removes the specified key/value pair from below the specified node.
  327. /// </summary>
  328. /// <param name="node">Specified node.</param>
  329. /// <param name="key">Key to remove.</param>
  330. /// <param name="value">Value to remove.</param>
  331. /// <returns>True if key/value present and removed.</returns>
  332. private Node Remove(Node node, TKey key, TValue value)
  333. {
  334. int comparisonResult = KeyAndValueComparison(key, value, node.Key, node.Value);
  335. if (comparisonResult < 0)
  336. {
  337. // * Continue search if left is present
  338. if (null != node.Left)
  339. {
  340. if (!IsRed(node.Left) && !IsRed(node.Left.Left))
  341. {
  342. // Move a red node over
  343. node = MoveRedLeft(node);
  344. }
  345. // Remove from left
  346. node.Left = Remove(node.Left, key, value);
  347. }
  348. }
  349. else
  350. {
  351. if (IsRed(node.Left))
  352. {
  353. // Flip a 3 node or unbalance a 4 node
  354. node = RotateRight(node);
  355. }
  356. if ((0 == KeyAndValueComparison(key, value, node.Key, node.Value)) && (null == node.Right))
  357. {
  358. // Remove leaf node
  359. Debug.Assert(null == node.Left, "About to remove an extra node.");
  360. Count--;
  361. if (0 < node.Siblings)
  362. {
  363. // Record the removal of the "duplicate" node
  364. Debug.Assert(IsMultiDictionary, "Should not have siblings if tree is not a multi-dictionary.");
  365. node.Siblings--;
  366. return node;
  367. }
  368. else
  369. {
  370. // Leaf node is gone
  371. return null;
  372. }
  373. }
  374. // * Continue search if right is present
  375. if (null != node.Right)
  376. {
  377. if (!IsRed(node.Right) && !IsRed(node.Right.Left))
  378. {
  379. // Move a red node over
  380. node = MoveRedRight(node);
  381. }
  382. if (0 == KeyAndValueComparison(key, value, node.Key, node.Value))
  383. {
  384. // Remove leaf node
  385. Count--;
  386. if (0 < node.Siblings)
  387. {
  388. // Record the removal of the "duplicate" node
  389. Debug.Assert(IsMultiDictionary, "Should not have siblings if tree is not a multi-dictionary.");
  390. node.Siblings--;
  391. }
  392. else
  393. {
  394. // Find the smallest node on the right, swap, and remove it
  395. Node m = GetExtreme(node.Right, n => n.Left, n => n);
  396. node.Key = m.Key;
  397. node.Value = m.Value;
  398. node.Siblings = m.Siblings;
  399. node.Right = DeleteMinimum(node.Right);
  400. }
  401. }
  402. else
  403. {
  404. // Remove from right
  405. node.Right = Remove(node.Right, key, value);
  406. }
  407. }
  408. }
  409. // Maintain invariants
  410. return FixUp(node);
  411. }
  412. /// <summary>
  413. /// Flip the colors of the specified node and its direct children.
  414. /// </summary>
  415. /// <param name="node">Specified node.</param>
  416. private static void FlipColor(Node node)
  417. {
  418. node.IsBlack = !node.IsBlack;
  419. node.Left.IsBlack = !node.Left.IsBlack;
  420. node.Right.IsBlack = !node.Right.IsBlack;
  421. }
  422. /// <summary>
  423. /// Rotate the specified node "left".
  424. /// </summary>
  425. /// <param name="node">Specified node.</param>
  426. /// <returns>New root node.</returns>
  427. private static Node RotateLeft(Node node)
  428. {
  429. Node x = node.Right;
  430. node.Right = x.Left;
  431. x.Left = node;
  432. x.IsBlack = node.IsBlack;
  433. node.IsBlack = false;
  434. return x;
  435. }
  436. /// <summary>
  437. /// Rotate the specified node "right".
  438. /// </summary>
  439. /// <param name="node">Specified node.</param>
  440. /// <returns>New root node.</returns>
  441. private static Node RotateRight(Node node)
  442. {
  443. Node x = node.Left;
  444. node.Left = x.Right;
  445. x.Right = node;
  446. x.IsBlack = node.IsBlack;
  447. node.IsBlack = false;
  448. return x;
  449. }
  450. /// <summary>
  451. /// Moves a red node from the right child to the left child.
  452. /// </summary>
  453. /// <param name="node">Parent node.</param>
  454. /// <returns>New root node.</returns>
  455. private static Node MoveRedLeft(Node node)
  456. {
  457. FlipColor(node);
  458. if (IsRed(node.Right.Left))
  459. {
  460. node.Right = RotateRight(node.Right);
  461. node = RotateLeft(node);
  462. FlipColor(node);
  463. // * Avoid creating right-leaning nodes
  464. if (IsRed(node.Right.Right))
  465. {
  466. node.Right = RotateLeft(node.Right);
  467. }
  468. }
  469. return node;
  470. }
  471. /// <summary>
  472. /// Moves a red node from the left child to the right child.
  473. /// </summary>
  474. /// <param name="node">Parent node.</param>
  475. /// <returns>New root node.</returns>
  476. private static Node MoveRedRight(Node node)
  477. {
  478. FlipColor(node);
  479. if (IsRed(node.Left.Left))
  480. {
  481. node = RotateRight(node);
  482. FlipColor(node);
  483. }
  484. return node;
  485. }
  486. /// <summary>
  487. /// Deletes the minimum node under the specified node.
  488. /// </summary>
  489. /// <param name="node">Specified node.</param>
  490. /// <returns>New root node.</returns>
  491. private Node DeleteMinimum(Node node)
  492. {
  493. if (null == node.Left)
  494. {
  495. // Nothing to do
  496. return null;
  497. }
  498. if (!IsRed(node.Left) && !IsRed(node.Left.Left))
  499. {
  500. // Move red node left
  501. node = MoveRedLeft(node);
  502. }
  503. // Recursively delete
  504. node.Left = DeleteMinimum(node.Left);
  505. // Maintain invariants
  506. return FixUp(node);
  507. }
  508. /// <summary>
  509. /// Maintains invariants by adjusting the specified nodes children.
  510. /// </summary>
  511. /// <param name="node">Specified node.</param>
  512. /// <returns>New root node.</returns>
  513. private static Node FixUp(Node node)
  514. {
  515. if (IsRed(node.Right))
  516. {
  517. // Avoid right-leaning node
  518. node = RotateLeft(node);
  519. }
  520. if (IsRed(node.Left) && IsRed(node.Left.Left))
  521. {
  522. // Balance 4-node
  523. node = RotateRight(node);
  524. }
  525. if (IsRed(node.Left) && IsRed(node.Right))
  526. {
  527. // Push red up
  528. FlipColor(node);
  529. }
  530. // * Avoid leaving behind right-leaning nodes
  531. if ((null != node.Left) && IsRed(node.Left.Right) && !IsRed(node.Left.Left))
  532. {
  533. node.Left = RotateLeft(node.Left);
  534. if (IsRed(node.Left))
  535. {
  536. // Balance 4-node
  537. node = RotateRight(node);
  538. }
  539. }
  540. return node;
  541. }
  542. /// <summary>
  543. /// Gets the (first) node corresponding to the specified key.
  544. /// </summary>
  545. /// <param name="key">Key to search for.</param>
  546. /// <returns>Corresponding node or null if none found.</returns>
  547. private Node GetNodeForKey(TKey key)
  548. {
  549. // Initialize
  550. Node node = _rootNode;
  551. while (null != node)
  552. {
  553. // Compare keys and go left/right
  554. int comparisonResult = _keyComparison(key, node.Key);
  555. if (comparisonResult < 0)
  556. {
  557. node = node.Left;
  558. }
  559. else if (0 < comparisonResult)
  560. {
  561. node = node.Right;
  562. }
  563. else
  564. {
  565. // Match; return node
  566. return node;
  567. }
  568. }
  569. // No match found
  570. return null;
  571. }
  572. /// <summary>
  573. /// Gets an extreme (ex: minimum/maximum) value.
  574. /// </summary>
  575. /// <typeparam name="T">Type of value.</typeparam>
  576. /// <param name="node">Node to start from.</param>
  577. /// <param name="successor">Successor function.</param>
  578. /// <param name="selector">Selector function.</param>
  579. /// <returns>Extreme value.</returns>
  580. private static T GetExtreme<T>(Node node, Func<Node, Node> successor, Func<Node, T> selector)
  581. {
  582. // Initialize
  583. T extreme = default(T);
  584. Node current = node;
  585. while (null != current)
  586. {
  587. // Go to extreme
  588. extreme = selector(current);
  589. current = successor(current);
  590. }
  591. return extreme;
  592. }
  593. /// <summary>
  594. /// Traverses a subset of the sequence of nodes in order and selects the specified nodes.
  595. /// </summary>
  596. /// <typeparam name="T">Type of elements.</typeparam>
  597. /// <param name="node">Starting node.</param>
  598. /// <param name="condition">Condition method.</param>
  599. /// <param name="selector">Selector method.</param>
  600. /// <returns>Sequence of selected nodes.</returns>
  601. private IEnumerable<T> Traverse<T>(Node node, Func<Node, bool> condition, Func<Node, T> selector)
  602. {
  603. // Create a stack to avoid recursion
  604. Stack<Node> stack = new Stack<Node>();
  605. Node current = node;
  606. while (null != current)
  607. {
  608. if (null != current.Left)
  609. {
  610. // Save current state and go left
  611. stack.Push(current);
  612. current = current.Left;
  613. }
  614. else
  615. {
  616. do
  617. {
  618. for (int i = 0; i <= current.Siblings; i++)
  619. {
  620. // Select current node if relevant
  621. if (condition(current))
  622. {
  623. yield return selector(current);
  624. }
  625. }
  626. // Go right - or up if nothing to the right
  627. current = current.Right;
  628. }
  629. while ((null == current) &&
  630. (0 < stack.Count) &&
  631. (null != (current = stack.Pop())));
  632. }
  633. }
  634. }
  635. /// <summary>
  636. /// Compares the specified keys (primary) and values (secondary).
  637. /// </summary>
  638. /// <param name="leftKey">The left key.</param>
  639. /// <param name="leftValue">The left value.</param>
  640. /// <param name="rightKey">The right key.</param>
  641. /// <param name="rightValue">The right value.</param>
  642. /// <returns>CompareTo-style results: -1 if left is less, 0 if equal, and 1 if greater than right.</returns>
  643. private int KeyAndValueComparison(TKey leftKey, TValue leftValue, TKey rightKey, TValue rightValue)
  644. {
  645. // Compare keys
  646. int comparisonResult = _keyComparison(leftKey, rightKey);
  647. if ((0 == comparisonResult) && (null != _valueComparison))
  648. {
  649. // Keys match; compare values
  650. comparisonResult = _valueComparison(leftValue, rightValue);
  651. }
  652. return comparisonResult;
  653. }
  654. #if DEBUGGING
  655. /// <summary>
  656. /// Asserts that tree invariants are not violated.
  657. /// </summary>
  658. private void AssertInvariants()
  659. {
  660. // Root is black
  661. Debug.Assert((null == _rootNode) || _rootNode.IsBlack, "Root is not black");
  662. // Every path contains the same number of black nodes
  663. Dictionary<Node, Node> parents = new Dictionary<LeftLeaningRedBlackTree<TKey, TValue>.Node, LeftLeaningRedBlackTree<TKey, TValue>.Node>();
  664. foreach (Node node in Traverse(_rootNode, n => true, n => n))
  665. {
  666. if (null != node.Left)
  667. {
  668. parents[node.Left] = node;
  669. }
  670. if (null != node.Right)
  671. {
  672. parents[node.Right] = node;
  673. }
  674. }
  675. if (null != _rootNode)
  676. {
  677. parents[_rootNode] = null;
  678. }
  679. int treeCount = -1;
  680. foreach (Node node in Traverse(_rootNode, n => (null == n.Left) || (null == n.Right), n => n))
  681. {
  682. int pathCount = 0;
  683. Node current = node;
  684. while (null != current)
  685. {
  686. if (current.IsBlack)
  687. {
  688. pathCount++;
  689. }
  690. current = parents[current];
  691. }
  692. Debug.Assert((-1 == treeCount) || (pathCount == treeCount), "Not all paths have the same number of black nodes.");
  693. treeCount = pathCount;
  694. }
  695. // Verify node properties...
  696. foreach (Node node in Traverse(_rootNode, n => true, n => n))
  697. {
  698. // Left node is less
  699. if (null != node.Left)
  700. {
  701. Debug.Assert(0 > KeyAndValueComparison(node.Left.Key, node.Left.Value, node.Key, node.Value), "Left node is greater than its parent.");
  702. }
  703. // Right node is greater
  704. if (null != node.Right)
  705. {
  706. Debug.Assert(0 < KeyAndValueComparison(node.Right.Key, node.Right.Value, node.Key, node.Value), "Right node is less than its parent.");
  707. }
  708. // Both children of a red node are black
  709. Debug.Assert(!IsRed(node) || (!IsRed(node.Left) && !IsRed(node.Right)), "Red node has a red child.");
  710. // Always left-leaning
  711. Debug.Assert(!IsRed(node.Right) || IsRed(node.Left), "Node is not left-leaning.");
  712. // No consecutive reds (subset of previous rule)
  713. //Debug.Assert(!(IsRed(node) && IsRed(node.Left)));
  714. }
  715. }
  716. /// <summary>
  717. /// Gets an HTML fragment representing the tree.
  718. /// </summary>
  719. public string HtmlDocument
  720. {
  721. get
  722. {
  723. return
  724. "<html>" +
  725. "<body>" +
  726. (null != _rootNode ? _rootNode.HtmlFragment : "[null]") +
  727. "</body>" +
  728. "</html>";
  729. }
  730. }
  731. #endif
  732. }