Pelzini

This is the code documentation for the Pelzini project

source of /viewer/tree.php

A simple tree system
  1. <?php
  2. /*
  3. Copyright 2008 Josh Heidenreich
  4.  
  5. This file is part of Pelzini.
  6.  
  7. Pelzini is free software: you can redistribute it and/or modify
  8. it under the terms of the GNU General Public License as published by
  9. the Free Software Foundation, either version 3 of the License, or
  10. (at your option) any later version.
  11.  
  12. Pelzini is distributed in the hope that it will be useful,
  13. but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. GNU General Public License for more details.
  16.  
  17. You should have received a copy of the GNU General Public License
  18. along with Pelzini. If not, see <http://www.gnu.org/licenses/>.
  19. */
  20.  
  21. /**
  22.  * A simple tree system
  23.  *
  24.  * @author Josh
  25.  * @package Viewer
  26.  * @since 0.2
  27.  **/
  28.  
  29.  
  30. /**
  31.  * A node in a tree
  32.  **/
  33. class TreeNode implements ArrayAccess {
  34. private $data = array();
  35. private $children = array();
  36. private $parent = null;
  37.  
  38.  
  39. /**
  40.   * Adds a child node to this node
  41.   **/
  42. public function addChild(TreeNode $child)
  43. {
  44. $this->children[] = $child;
  45. $child->parent = $this;
  46. }
  47.  
  48.  
  49. /**
  50.   * Returns a list of all the child nodes of this node
  51.   **/
  52. public function getChildren()
  53. {
  54. return $this->children;
  55. }
  56.  
  57.  
  58. /**
  59.   * Returns all of the data of this node
  60.   **/
  61. public function getData()
  62. {
  63. return $this->data;
  64. }
  65.  
  66.  
  67. /**
  68.   * Returns true if a specific data field exists, and false otherwise
  69.   **/
  70. public function offsetExists($index)
  71. {
  72. return isset ($this->data[$index]);
  73. }
  74.  
  75.  
  76. /**
  77.   * Returns the value of a specific data field
  78.   **/
  79. public function offsetGet($index)
  80. {
  81. return $this->data[$index];
  82. }
  83.  
  84.  
  85. /**
  86.   * Sets the value of a specific data field
  87.   **/
  88. public function offsetSet($index, $value)
  89. {
  90. $this->data[$index] = $value;
  91. }
  92.  
  93.  
  94. /**
  95.   * Removes a specific data field
  96.   **/
  97. public function offsetUnset($index)
  98. {
  99. unset ($this->data[$index]);
  100. }
  101.  
  102.  
  103. /**
  104.   * Finds a node in the database
  105.   *
  106.   * @param TreeNodeMatcher $matcher The class which is used to determine if a node should be found or not
  107.   **/
  108. public function findNode(TreeNodeMatcher $matcher)
  109. {
  110. $res = $matcher->match ($this);
  111. if ($res) return $this;
  112.  
  113. foreach ($this->children as $node) {
  114. $res = $node->findNode ($matcher);
  115. if ($res) return $res;
  116. }
  117.  
  118. return null;
  119. }
  120.  
  121.  
  122. /**
  123.   * Returns an array of all the the ancestors of this node
  124.   **/
  125. public function findAncestors()
  126. {
  127. $ancestors = array();
  128.  
  129. $node = $this;
  130. while (! $node instanceof RootTreeNode) {
  131. $ancestors[] = $node;
  132. $node = $node->parent;
  133. }
  134.  
  135. return $ancestors;
  136. }
  137.  
  138.  
  139. /**
  140.   * Used for debugging only
  141.   **/
  142. public function dump()
  143. {
  144. echo '<div style="border: 1px black solid; padding: 0.5em; margin: 0.5em;">';
  145. foreach ($this->data as $key => $val) {
  146. echo "<p>'{$key}' = '{$val}'</p>";
  147. }
  148. foreach ($this->children as $node) {
  149. $node->dump();
  150. }
  151. echo '</div>';
  152. }
  153.  
  154.  
  155. }
  156.  
  157.  
  158. /**
  159.  * The root node in a tree
  160.  **/
  161. class RootTreeNode extends TreeNode {
  162.  
  163. /**
  164.   * Creates a tree based on a set of nodes in the database
  165.   *
  166.   * @param resource $res A database resource created with db_query
  167.   * @param string $id_col The name of the identification column in the database query
  168.   * @param string $parent_col The name of the column in the database that references the id column of another record
  169.   * @returns The loaded tree
  170.   **/
  171. static function createFromDatabase($res, $id_col, $parent_col)
  172. {
  173. // load query results into tree
  174. $root = new RootTreeNode;
  175. $known_nodes = array();
  176. $remnant_nodes = array();
  177.  
  178. while ($row = db_fetch_assoc ($res)) {
  179. $id = $row[$id_col];
  180. $parent = $row[$parent_col];
  181.  
  182. $node = new TreeNode ();
  183. foreach ($row as $key => $val) {
  184. $node[$key] = $val;
  185. }
  186.  
  187. if ($parent == null or $parent == '') {
  188. $root->addChild ($node);
  189. $known_nodes[$id] = $node;
  190. } else {
  191. $remnant_nodes[] = array ('id' => $id, 'node' => $node, 'parent' => $parent);
  192. }
  193. }
  194. $nodes_added_this_pass = count($root->getChildren ());
  195.  
  196. while (count($remnant_nodes) > 0 and $nodes_added_this_pass > 0) {
  197. $nodes_added_this_pass = 0;
  198.  
  199. foreach ($remnant_nodes as $remnant_id => $remnant) {
  200. foreach ($known_nodes as $known_id => $known_node) {
  201. if ($known_id == $remnant['parent']) {
  202. $known_node->addChild ($remnant['node']);
  203. $known_nodes[$remnant['id']] = $remnant['node'];
  204.  
  205. unset ($remnant_nodes[$remnant_id]);
  206. $nodes_added_this_pass++;
  207. break;
  208. }
  209. }
  210. }
  211.  
  212. // Looks for reminant nodes that have parents that are nodes that do not exist
  213. // If any of these are found, the parent is created
  214. if ($nodes_added_this_pass == 0) {
  215. $remnant = array_shift($remnant_nodes);
  216. $remnant_nodes[] = $remnant;
  217.  
  218. $found = false;
  219. foreach ($known_nodes as $known_id => $known_node) {
  220. if ($known_id == $remnant['parent']) {
  221. $found = true;
  222. }
  223. }
  224.  
  225. if (! $found) {
  226. $node = new TreeNode ();
  227. $node[$id_col] = $remnant['parent'];
  228. $root->addChild ($node);
  229.  
  230. $known_nodes[$remnant['parent']] = $node;
  231.  
  232. $nodes_added_this_pass++;
  233. }
  234. }
  235. }
  236.  
  237. return $root;
  238. }
  239.  
  240.  
  241. }
  242.  
  243.  
  244. /**
  245.  * The generic interface for classes which look for nodes that match specific conditions
  246.  **/
  247. interface TreeNodeMatcher {
  248. /**
  249.   * Will return true if the node matches the specified condition, or false otherwise
  250.   *
  251.   * @param TreeNode $node The node to check
  252.   * @return boolean True if the node matches, false otherwise
  253.   **/
  254. public function match($node);
  255. }
  256.  
  257.  
  258. /**
  259.  * Finds nodes in the tree which have a specified field which matches a specified value
  260.  **/
  261. class FieldTreeNodeMatcher implements TreeNodeMatcher {
  262. private $field;
  263. private $value;
  264.  
  265. public function __construct($field, $value)
  266. {
  267. $this->field = $field;
  268. $this->value = $value;
  269. }
  270.  
  271.  
  272. /**
  273.   * Will return true if the node matches the specified condition, or false otherwise
  274.   *
  275.   * @param TreeNode $node The node to check
  276.   * @return boolean True if the node matches, false otherwise
  277.   **/
  278. public function match($node)
  279. {
  280. $data = $node->getData();
  281. foreach ($data as $field => $value) {
  282. if ($this->field == $field and $this->value == $value) return true;
  283. }
  284.  
  285. return false;
  286. }
  287.  
  288.  
  289. }
  290.  
  291.  
  292. /**
  293.  * Loads a tree of the classes, based on 'extends'
  294.  **/
  295. function create_classes_tree()
  296. {
  297. global $project;
  298.  
  299. $q = "SELECT id, name, extends FROM classes WHERE projectid = {$project['id']}";
  300. $res = db_query ($q);
  301.  
  302. $root = RootTreeNode::createFromDatabase ($res, 'name', 'extends');
  303.  
  304. return $root;
  305. }
  306.  
  307.  
  308. ?>
  309.