PageRenderTime 49ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/src/arc/tree.php

https://github.com/poef/arc
PHP | 244 lines | 139 code | 16 blank | 89 comment | 23 complexity | 7583a5754f48bac4cfe4e87585693989 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /*
  3. * This file is part of the Ariadne Component Library.
  4. *
  5. * (c) Muze <info@muze.nl>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace arc;
  11. /**
  12. * Utility methods to handle common path related tasks, cleaning, changing relative to absolute, etc.
  13. */
  14. class tree {
  15. /**
  16. * Create a simple array from a tree. Each non-null nodeValue will be added with the path to its node as the key
  17. * @param \arc\tree\Node $node
  18. * @param string $root
  19. * @param string $nodeName
  20. * @internal param \arc\tree\Node $tree
  21. * @return array [ $path => $data, ... ]
  22. */
  23. static public function collapse( $node, $root = '', $nodeName = 'nodeName' ) {
  24. return \arc\tree::map(
  25. $node,
  26. function( $child ) {
  27. return $child->nodeValue;
  28. },
  29. $root,
  30. $nodeName
  31. );
  32. }
  33. /**
  34. * Creates a NamedNode tree from an array with path => nodeValue entries.
  35. * @param array $tree The collapsed tree: [ $path => $data, ... ]
  36. * @return \arc\tree\NamedNode an object tree with parent/children relations
  37. */
  38. static public function expand( $tree = null ) {
  39. if ( is_object( $tree ) && isset( $tree->childNodes ) ) {
  40. return $tree; //FIXME: should we clone the tree to avoid shared state?
  41. }
  42. $root = new \arc\tree\NamedNode();
  43. if ( !is_array($tree) ) {
  44. return $root; // empty tree
  45. }
  46. //ksort($tree); // sort by path, so parents are always earlier in the array than children
  47. $previousPath = '/';
  48. $previousParent = $root;
  49. foreach( $tree as $path => $data ) {
  50. $previousPath = $previousParent->getPath();
  51. $subPath = \arc\path::diff( $previousPath, $path );
  52. if ( $subPath ) {
  53. // create missing parent nodes, input tree may be sparsely filled
  54. $node = \arc\path::reduce(
  55. $subPath,
  56. function( $previous, $name ) {
  57. if ( $name == '..' ) {
  58. return $previous->parentNode;
  59. }
  60. return $previous->appendChild( $name );
  61. },
  62. $previousParent
  63. );
  64. } else {
  65. // means the previousParent is equal to the current path, e.g. the root
  66. $node = $previousParent;
  67. }
  68. $node->nodeValue = $data;
  69. $previousParent = $node;
  70. }
  71. return $root;
  72. }
  73. /**
  74. * Calls the first callback method on each successive parent until a non-null value is returned. Then
  75. * calls all the parents from that point back to this node with the second callback in reverse order.
  76. * The first callback (dive) must accept one parameter, the node.
  77. * The second callback (rise) must accept two parameters, the nde and the result up to that point.
  78. * @param \arc\tree\Node $node A tree node, must have traversable childNodes property and a parentNode property
  79. * @param callable $diveCallback The callback for the dive phase.
  80. * @param callable $riseCallback The callback for the rise phase.
  81. * @return mixed
  82. */
  83. static public function dive( $node, $diveCallback = null, $riseCallback = null ) {
  84. $result = null;
  85. if ( is_callable( $diveCallback ) ) {
  86. $result = call_user_func( $diveCallback, $node );
  87. }
  88. if ( !isset( $result ) && $node->parentNode ) {
  89. $result = \arc\tree::dive( $node->parentNode, $diveCallback, $riseCallback );
  90. }
  91. if ( is_callable( $riseCallback ) ) {
  92. return call_user_func( $riseCallback, $node, $result );
  93. } else {
  94. return $result;
  95. }
  96. }
  97. /**
  98. * Calls the callback method on each parent of the given node, starting at the root.
  99. * @param \arc\tree\Node $node A tree node, must have traversable childNodes property and a parentNode property
  100. * @param callable $callback The callback function applied to each parent.
  101. * @return mixed
  102. */
  103. static public function parents( $node, $callback = null ) {
  104. if ( !isset( $callback ) ) {
  105. $callback = function( $node, $result ) {
  106. return ( (array) $result ) + array( $node );
  107. };
  108. }
  109. return self::dive( $node, null, $callback );
  110. }
  111. /**
  112. * Calls the callback method on each of the direct child nodes of the given node.
  113. * @param \arc\tree\Node $node
  114. * @param callable $callback The callback function applied to each child node
  115. * @param mixed $nodeName The name of the 'name' property or a function that returns the name of a node.
  116. * @return array
  117. */
  118. static public function ls( $node, $callback, $nodeName = 'nodeName' ) {
  119. $result = [];
  120. foreach( $node->childNodes as $child ) {
  121. $name = self::getNodeName( $child, $nodeName );
  122. $result[ $name ] = call_user_func( $callback, $child );
  123. }
  124. return $result;
  125. }
  126. /**
  127. * Calls the callback method on each child of the current node, including the node itself, until a non-null
  128. * result is returned. Returns that result. The tree is searched depth first.
  129. * @param \arc\tree\Node $node
  130. * @param callable $callback The callback function applied to each child node
  131. * @return mixed
  132. */
  133. static public function search( $node, $callback ) {
  134. $result = call_user_func( $callback, $node );
  135. if ( isset( $result ) ) {
  136. return $result;
  137. }
  138. foreach( $node->childNodes as $child ) {
  139. $result = self::search( $child, $callback );
  140. if ( isset( $result ) ) {
  141. return $result;
  142. }
  143. }
  144. return null;
  145. }
  146. /**
  147. * Calls the callback method on each child of the current node, including the node itself. Any non-null result
  148. * is added to the result array, with the path to the node as the key.
  149. * @param \arc\tree\Node $node
  150. * @param callable $callback The callback function applied to each child node
  151. * @param string $root
  152. * @param mixed $nodeName The name of the 'name' property or a function that returns the name of a node.
  153. * @return array
  154. */
  155. static public function map( $node, $callback, $root = '', $nodeName = 'nodeName' ) {
  156. $result = [];
  157. $name = self::getNodeName( $node, $nodeName );
  158. $path = $root . $name . '/';
  159. $callbackResult = call_user_func( $callback, $node );
  160. if ( isset($callbackResult) ) {
  161. $result[ $path ] = $callbackResult;
  162. }
  163. foreach ( $node->childNodes as $child ) {
  164. $result += self::map( $child, $callback, $path, $nodeName );
  165. }
  166. return $result;
  167. }
  168. /**
  169. * Calls the callback method on all child nodes of the given node, including the node itself. The result of each
  170. * call is passed on as the first argument to each succesive call.
  171. * @param \arc\tree\Node $node
  172. * @param callable $callback
  173. * @param mixed $initial The value to pass to the first callback call.
  174. * @return mixed
  175. */
  176. static public function reduce( $node, $callback, $initial = null ) {
  177. $result = call_user_func( $callback, $initial, $node );
  178. foreach ( $node->childNodes as $child ) {
  179. $result = self::reduce( $child, $callback, $result );
  180. }
  181. return $result;
  182. }
  183. /**
  184. * Filters the tree using a callback method. If the callback method returns true, the node's value is included
  185. * in the result, otherwise it is skipped. Filter returns a collapsed tree: [ path => nodeValue ]
  186. * The callback method must take one argument: the current node.
  187. * @param \arc\tree\Node $node
  188. * @param callable $callback
  189. * @return array
  190. */
  191. static public function filter( $node, $callback, $root = '', $nodeName = 'nodeName' ) {
  192. return self::map( $node, function( $node ) use ( $callback ) {
  193. if ( call_user_func( $callback, $node ) ) {
  194. return $node->nodeValue;
  195. }
  196. }, $root, $nodeName );
  197. }
  198. /**
  199. * Sorts the childNodes list of the node, recursively.
  200. * @param \arc\tree\Node $node
  201. * @param callable $callback
  202. * @param mixed $nodeName
  203. * @throws ExceptionDefault
  204. */
  205. static public function sort( $node, $callback, $nodeName = 'nodeName' ) {
  206. if ( is_array($node->childNodes) ) {
  207. $sort = function( $node ) {
  208. uasort( $node->childNodes, $callback );
  209. };
  210. } else if ( $node->childNodes instanceof \ArrayObject ) {
  211. $sort = function( $node ) {
  212. $node->childNodes->uasort( $callback );
  213. };
  214. } else {
  215. throw new \arc\ExceptionDefault( 'Cannot sort this tree - no suitable sort method found',
  216. \arc\exceptions::OBJECT_NOT_FOUND);
  217. }
  218. self::map( $node, $sort, '', $nodeName );
  219. }
  220. static private function getNodeName( $node, $nodeName ) {
  221. if ( is_callable($nodeName) ) {
  222. $name = call_user_func( $nodeName, $node );
  223. } else {
  224. $name = $node->{$nodeName};
  225. }
  226. return $name;
  227. }
  228. }