PageRenderTime 60ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/pear/php/Tree/Memory.php

https://github.com/wrobel/horde-glue
PHP | 1521 lines | 507 code | 125 blank | 889 comment | 90 complexity | ab63c01ec7434cf4cca4c174401ad4a8 MD5 | raw file
Possible License(s): BSD-2-Clause, LGPL-2.1
  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4: */
  3. // +----------------------------------------------------------------------+
  4. // | PHP Version 4 |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2003 The PHP Group |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 2.02 of the PHP license, |
  9. // | that is bundled with this package in the file LICENSE, and is |
  10. // | available at through the world-wide-web at |
  11. // | http://www.php.net/license/2_02.txt. |
  12. // | If you did not receive a copy of the PHP license and are unable to |
  13. // | obtain it through the world-wide-web, please send a note to |
  14. // | license@php.net so we can mail you a copy immediately. |
  15. // +----------------------------------------------------------------------+
  16. // | Authors: Wolfram Kriesing <wolfram@kriesing.de> |
  17. // +----------------------------------------------------------------------+
  18. //
  19. // $Id: Memory.php,v 1.34.2.3 2009/03/12 17:19:52 dufuz Exp $
  20. require_once 'Tree/Common.php';
  21. /**
  22. * this class can be used to step through a tree using ['parent'],['child']
  23. * the tree is saved as flat data in a db, where at least the parent
  24. * needs to be given if a previous member is given too then the order
  25. * on a level can be determined too
  26. * actually this class was used for a navigation tree
  27. * now it is extended to serve any kind of tree
  28. * you can unambigiously refer to any element by using the following
  29. * syntax
  30. * tree->data[currentId][<where>]...[<where>]
  31. * <where> can be either "parent", "child", "next" or "previous", this way
  32. * you can "walk" from any point to any other point in the tree
  33. * by using <where> in any order you want
  34. * example (in parentheses the id):
  35. * root
  36. * +---level 1_1 (1)
  37. * | +----level 2_1 (2)
  38. * | +----level 2_2 (3)
  39. * | +-----level 3_1 (4)
  40. * +---level 1_2 (5)
  41. *
  42. * the database table to this structure (without defined order)
  43. * id parentId name
  44. * 1 0 level 1_1
  45. * 2 1 level 2_1
  46. * 3 1 level 2_1
  47. * 4 3 level 3_1
  48. * 5 0 level 1_2
  49. *
  50. * now you can refer to elements for example like this (all examples assume you
  51. * know the structure):
  52. * go from "level 3_1" to "level 1_1": $tree->data[4]['parent']['parent']
  53. * go from "level 3_1" to "level 1_2":
  54. * $tree->data[4]['parent']['parent']['next']
  55. * go from "level 2_1" to "level 3_1": $tree->data[2]['next']['child']
  56. * go from "level 2_2" to "level 2_1": $tree->data[3]['previous']
  57. * go from "level 1_2" to "level 3_1":
  58. * $tree->data[5]['previous']['child']['next']['child']
  59. *
  60. * on a pentium 1.9 GHz 512 MB RAM, Linux 2.4, Apache 1.3.19, PHP 4.0.6
  61. * performance statistics for version 1.26, using examples/Tree/Tree.php
  62. * - reading from DB and preparing took: 0.14958894252777
  63. * - building took: 0.074488043785095
  64. * - buildStructure took: 0.05151903629303
  65. * - setting up the tree time: 0.29579293727875
  66. * - number of elements: 1564
  67. * - deepest level: 17
  68. * so you can use it for tiny-big trees too :-)
  69. * but watch the db traffic, which might be considerable, depending
  70. * on your setup.
  71. *
  72. * FIXXXME there is one really bad thing about the entire class, at some points
  73. * there are references to $this->data returned, or the programmer can even
  74. * access this->data, which means he can change the structure, since this->data
  75. * can not be set to read-only, therefore this->data has to be handled
  76. * with great care!!! never do something like this:
  77. * <code>
  78. * $x = &$tree->data[<some-id>]; $x = $y;
  79. * </code>
  80. * this overwrites the element in the structure !!!
  81. *
  82. *
  83. * @access public
  84. * @author Wolfram Kriesing <wolfram@kriesing.de>
  85. * @version 2001/06/27
  86. * @package Tree
  87. */
  88. class Tree_Memory extends Tree_Common
  89. {
  90. /**
  91. * this array contains the pure data from the DB
  92. * which are always kept, since all other structures will
  93. * only make references on any element
  94. * and those data are extended by the elements 'parent' 'children' etc...
  95. * @var array $data
  96. */
  97. var $data = array();
  98. /**
  99. * this array contains references to this->data but it
  100. * additionally represents the directory structure
  101. * that means the array has as many dimensions as the
  102. * tree structure has levels
  103. * but this array is only used internally from outside you can do
  104. * everything using the node-id's
  105. *
  106. * @var array $structure
  107. * @access private
  108. */
  109. var $structure = array();
  110. /**
  111. * it contains all the parents and their children, where the parentId is the
  112. * key and all the children are the values, this is for speeding up
  113. * the tree-building process
  114. *
  115. * @var array $children
  116. */
  117. var $children = array();
  118. /**
  119. * @access private
  120. * @var boolean saves if tree nodes shall be removed recursively
  121. * @see setRemoveRecursively()
  122. */
  123. var $removeRecursively = false;
  124. /**
  125. * @access public
  126. * @var integer the debug mode, if > 0 then debug info are shown,
  127. * actually those messages only show performance
  128. * times
  129. */
  130. var $debug = 0;
  131. /**
  132. * @see &getNode()
  133. * @see &_getNode()
  134. * @access private
  135. * @var integer variable only used in the method getNode and _getNode
  136. */
  137. var $_getNodeMaxLevel;
  138. /**
  139. * @see &getNode()
  140. * @see &_getNode()
  141. * @access private
  142. * @var integer variable only used in the method getNode and
  143. * _getNode
  144. */
  145. var $_getNodeCurParent;
  146. /**
  147. * the maximum depth of the tree
  148. * @access private
  149. * @var int the maximum depth of the tree
  150. */
  151. var $_treeDepth = 0;
  152. // {{{ Tree_Memory()
  153. /**
  154. * set up this object
  155. *
  156. * @version 2001/06/27
  157. * @access public
  158. * @author Wolfram Kriesing <wolfram@kriesing.de>
  159. * @param mixed this is a DSN for the PEAR::DB, can be
  160. * either an object/string
  161. * @param array additional options you can set
  162. */
  163. function Tree_Memory($type, $dsn = '', $options = array())
  164. {
  165. // set the options for $this
  166. $this->Tree_Options($options);
  167. include_once "Tree/Memory/$type.php";
  168. $className = 'Tree_Memory_'.$type;
  169. $this->dataSourceClass =& new $className($dsn, $options);
  170. // copy the options to be able to get them via getOption(s)
  171. // FIXXME this is not really cool, maybe overwrite
  172. // the *Option* methods!!!
  173. if (isset($this->dataSourceClass->options)) {
  174. $this->options = $this->dataSourceClass->options;
  175. }
  176. }
  177. // }}}
  178. // {{{ switchDataSource()
  179. /**
  180. * use this to switch data sources on the run
  181. * i.e. if you are reading the data from a db-tree and want to save it
  182. * as xml data (which will work one day too)
  183. * or reading the data from an xml file and writing it in the db
  184. * which should already work
  185. *
  186. * @version 2002/01/17
  187. * @access public
  188. * @author Wolfram Kriesing <wolfram@kriesing.de>
  189. * @param string this is a DSN of the for that PEAR::DB uses it
  190. * only that additionally you can add parameters
  191. * like ...?table=test_table to define the table.
  192. * @param array additional options you can set
  193. * @return boolean true on success
  194. */
  195. function switchDataSource($type, $dsn = '', $options = array())
  196. {
  197. $data = $this->getNode();
  198. //$this->Tree($dsn, $options);
  199. $this->Tree_Memory($type, $GLOBALS['dummy'], $options);
  200. // this method prepares data retreived using getNode to be used
  201. // in this type of tree
  202. $this->dataSourceClass->setData($data);
  203. $this->setup();
  204. }
  205. // }}}
  206. // {{{ setupByRawData()
  207. /**
  208. *
  209. *
  210. * @version 2002/01/19
  211. * @access public
  212. * @author Wolfram Kriesing <wolfram@kriesing.de>
  213. * @return boolean true if the setup succeeded
  214. * @
  215. */
  216. function setupByRawData($string)
  217. {
  218. // expects
  219. // for XML an XML-String,
  220. // for DB-a result set, may be or an array, dont know here
  221. // not implemented yet
  222. $res = $this->dataSourceClass->setupByRawData($string);
  223. return $this->_setup($res);
  224. }
  225. // }}}
  226. // {{{ setup()
  227. /**
  228. * @version 2002/01/19
  229. * @access public
  230. * @author Wolfram Kriesing <wolfram@kriesing.de>
  231. * @param array the result of a query which retreives (all)
  232. * the tree data from a source
  233. * @return true or Tree_Error
  234. */
  235. function setup($data = null)
  236. {
  237. if ($this->debug) {
  238. $startTime = split(' ',microtime());
  239. $startTime = $startTime[1]+$startTime[0];
  240. }
  241. if (PEAR::isError($res = $this->dataSourceClass->setup($data))) {
  242. return $res;
  243. }
  244. if ($this->debug) {
  245. $endTime = split(' ',microtime());
  246. $endTime = $endTime[1]+$endTime[0];
  247. echo ' reading and preparing tree data took: '.
  248. ($endTime - $startTime) . '<br>';
  249. }
  250. return $this->_setup($res);
  251. }
  252. // }}}
  253. // {{{ _setup()
  254. /**
  255. * retreive all the navigation data from the db and build the
  256. * tree in the array data and structure
  257. *
  258. * @version 2001/11/20
  259. * @access private
  260. * @author Wolfram Kriesing <wolfram@kriesing.de>
  261. * @return boolean true on success
  262. */
  263. function _setup($setupData)
  264. {
  265. // TODO sort by prevId (parentId,prevId $addQuery) too if it exists
  266. // in the table, or the root might be wrong TODO since the prevId
  267. // of the root should be 0
  268. if (!$setupData) {
  269. return false;
  270. }
  271. //FIXXXXXME validate the structure.
  272. // i.e. a problem occurs, if you give one node, which has a parentId=1,
  273. // it screws up everything!!!
  274. //empty the data structures, since we are reading the data
  275. // from the db (again)
  276. $this->structure = array();
  277. $this->data = array();
  278. $this->children = array();
  279. // build an array where all the parents have their children as a member
  280. // this i do to speed up the buildStructure
  281. $columnNameMappings = $this->getOption('columnNameMaps');
  282. foreach ($setupData as $values) {
  283. if (is_array($values)) {
  284. $this->data[$values['id']] = $values;
  285. $this->children[$values['parentId']][] = $values['id'];
  286. }
  287. }
  288. // walk through all the children on each level and set the
  289. // next/previous relations of those children, since all children
  290. // for "children[$id]" are on the same level we can do
  291. // this here :-)
  292. foreach ($this->children as $children) {
  293. $lastPrevId = 0;
  294. if (count($children)) {
  295. foreach ($children as $key) {
  296. if ($lastPrevId) {
  297. // remember the nextId too, so the build process can
  298. // be speed up
  299. $this->data[$lastPrevId]['nextId'] = $key;
  300. $this->data[$lastPrevId]['next'] = &$this->data[$key];
  301. $this->data[$key]['prevId'] = $lastPrevId;
  302. $this->data[$key]['previous'] = &$this->data[ $lastPrevId ];
  303. }
  304. $lastPrevId = $key;
  305. }
  306. }
  307. }
  308. if ($this->debug) {
  309. $startTime = split(' ',microtime());
  310. $startTime = $startTime[1] + $startTime[0];
  311. }
  312. // when NO prevId is given, sort the entries in each level by the given
  313. // sort order (to be defined) and set the prevId so the build can work
  314. // properly does a prevId exist?
  315. if (!isset($setupData[0]['prevId'])) {
  316. $lastPrevId = 0;
  317. $lastParentId = 0;
  318. $level = 0;
  319. // build the entire recursive relations, so you have 'parentId',
  320. // 'childId', 'nextId', 'prevId' and the references 'child',
  321. // 'parent', 'next', 'previous' set in the property 'data'
  322. foreach($this->data as $key => $value) {
  323. // most if checks in this foreach are for the following reason,
  324. // if not stated otherwise:
  325. // dont make an data[''] or data[0] since this was not read
  326. // from the DB, because id is autoincrement and starts at 1
  327. // and also in an xml tree there can not be an element </>
  328. // i hope :-)
  329. if ($value['parentId']) {
  330. $this->data[$key]['parent'] = &$this->data[$value['parentId']];
  331. // the parent has an extra array which contains a reference
  332. // to all it's children, set it here
  333. $this->data[ $value['parentId']]['children'][] =
  334. &$this->data[$key];
  335. }
  336. // was a child saved (in the above 'if')
  337. // see comment above
  338. if (isset($this->children[$key]) &&
  339. count($this->children[$key])
  340. ) {
  341. // refer to the first child in the [child]
  342. // and [childId] keys
  343. $this->data[$key]['childId'] = $this->children[$key][0];
  344. $this->data[$key]['child'] =
  345. &$this->data[$this->children[$key][0]];
  346. }
  347. $lastParentId = $value['parentId'];
  348. }
  349. }
  350. if ($this->debug) {
  351. $endTime = split(' ',microtime());
  352. $endTime = $endTime[1]+$endTime[0];
  353. echo ' building took: ' . ($endTime - $startTime) . ' <br>';
  354. }
  355. // build the property 'structure'
  356. // empty it, just to be sure everything
  357. //will be set properly
  358. $this->structure = array();
  359. if ($this->debug) {
  360. $startTime = split(' ',microtime());
  361. $startTime = $startTime[1] + $startTime[0];
  362. }
  363. // build all the children that are on the root level, if we wouldnt
  364. // do that. We would have to create a root element with an id 0,
  365. // but since this is not read from the db we dont add another element.
  366. // The user wants to get what he had saved
  367. if (isset($this->children[0])) {
  368. foreach ($this->children[0] as $rootElement) {
  369. $this->buildStructure($rootElement, $this->structure);
  370. }
  371. }
  372. if ($this->debug) {
  373. $endTime = split(' ',microtime());
  374. $endTime = $endTime[1] + $endTime[0];
  375. echo ' buildStructure took: ' . ($endTime - $startTime).' <br>';
  376. }
  377. return true;
  378. }
  379. // }}}
  380. // {{{ add()
  381. /**
  382. * adds _one_ new element in the tree under the given parent
  383. * the values' keys given have to match the db-columns, because the
  384. * value gets inserted in the db directly
  385. * to add an entire node containing children and so on see 'addNode()'
  386. * @see addNode()
  387. * @version 2001/10/09
  388. * @access public
  389. * @author Wolfram Kriesing <wolfram@kriesing.de>
  390. * @param array this array contains the values that shall be inserted
  391. * in the db-table
  392. * @param int the parent id
  393. * @param int the prevId
  394. * @return mixed either boolean false on failure or the id
  395. * of the inserted row
  396. */
  397. function add($newValues, $parentId = 0, $prevId = 0)
  398. {
  399. // see comments in 'move' and 'remove'
  400. if (method_exists($this->dataSourceClass, 'add')) {
  401. return $this->dataSourceClass->add($newValues, $parentId, $prevId);
  402. } else {
  403. return $this->_throwError('method not implemented yet.' ,
  404. __LINE__);
  405. }
  406. }
  407. // }}}
  408. // {{{ remove()
  409. /**
  410. * removes the given node and all children if removeRecursively is on
  411. *
  412. * @version 2002/01/24
  413. * @access public
  414. * @author Wolfram Kriesing <wolfram@kriesing.de>
  415. * @param mixed $id the id of the node to be removed
  416. * @return boolean true on success
  417. */
  418. function remove($id)
  419. {
  420. // if removing recursively is not allowed, which means every child
  421. // should be removed
  422. // then check if this element has a child and return
  423. // "sorry baby cant remove :-) "
  424. if ($this->removeRecursively != true) {
  425. if (isset($this->data[$id]['child'])) {
  426. // TODO raise PEAR warning
  427. return $this->_throwError("Element with id=$id has children ".
  428. "that cant be removed. Set ".
  429. "'setRemoveRecursively' to true to ".
  430. "allow this.",
  431. __LINE__
  432. );
  433. }
  434. }
  435. // see comment in 'move'
  436. // if the prevId is in use we need to update the prevId of the element
  437. // after the one that is removed too, to have the prevId of the one
  438. // that is removed!!!
  439. if (method_exists($this->dataSourceClass, 'remove')) {
  440. return $this->dataSourceClass->remove($id);
  441. } else {
  442. return $this->_throwError('method not implemented yet.', __LINE__);
  443. }
  444. }
  445. // }}}
  446. // {{{ _remove()
  447. /**
  448. * collects the ID's of the elements to be removed
  449. *
  450. * @version 2001/10/09
  451. * @access public
  452. * @author Wolfram Kriesing <wolfram@kriesing.de>
  453. * @param mixed $id the id of the node to be removed
  454. * @return boolean true on success
  455. */
  456. function _remove($element)
  457. {
  458. return $element['id'];
  459. }
  460. // }}}
  461. // {{{ move()
  462. /**
  463. * move an entry under a given parent or behind a given entry.
  464. * !!! the 'move behind another element' is only implemented for nested
  465. * trees now!!!.
  466. * If a newPrevId is given the newParentId is dismissed!
  467. * call it either like this:
  468. * $tree->move(x, y)
  469. * to move the element (or entire tree) with the id x
  470. * under the element with the id y
  471. * or
  472. * <code>
  473. * // ommit the second parameter by setting it to 0
  474. * $tree->move(x, 0, y);
  475. * </code>
  476. * to move the element (or entire tree) with the id x
  477. * behind the element with the id y
  478. * or
  479. * <code>
  480. * $tree->move(array(x1,x2,x3), ...
  481. * </code>
  482. * the first parameter can also be an array of elements that shall
  483. * be moved
  484. * the second and third para can be as described above
  485. *
  486. * @version 2002/06/08
  487. * @access public
  488. * @author Wolfram Kriesing <wolfram@kriesing.de>
  489. * @param integer the id(s) of the element(s) that shall be moved
  490. * @param integer the id of the element which will be the new parent
  491. * @param integer if prevId is given the element with the id idToMove
  492. * shall be moved _behind_ the element
  493. * with id=prevId if it is 0 it will be put at
  494. * the beginning
  495. * @return boolean true for success
  496. */
  497. function move($idsToMove, $newParentId, $newPrevId = 0)
  498. {
  499. settype($idsToMove,'array');
  500. $errors = array();
  501. foreach ($idsToMove as $idToMove) {
  502. $ret = $this->_move($idToMove, $newParentId, $newPrevId);
  503. if (PEAR::isError($ret)) {
  504. $errors[] = $ret;
  505. }
  506. }
  507. // FIXXME return a Tree_Error, not an array !!!!!
  508. if (count($errors)) {
  509. return $errors;
  510. }
  511. return true;
  512. }
  513. // }}}
  514. // {{{ _move()
  515. /**
  516. * this method moves one tree element
  517. *
  518. * @see move()
  519. * @version 2001/10/10
  520. * @access public
  521. * @author Wolfram Kriesing <wolfram@kriesing.de>
  522. * @param integer the id of the element that shall be moved
  523. * @param integer the id of the element which will be
  524. * the new parent
  525. * @param integer if prevId is given the element with the id idToMove
  526. * shall be moved _behind_ the element with id=prevId
  527. * if it is 0 it will be put at the beginning
  528. * @return mixed true for success, Tree_Error on failure
  529. */
  530. function _move($idToMove, $newParentId, $prevId = 0)
  531. {
  532. // itself can not be a parent of itself
  533. if ($idToMove == $newParentId) {
  534. // TODO PEAR-ize error
  535. return TREE_ERROR_INVALID_PARENT;
  536. }
  537. // check if $newParentId is a child (or a child-child ...) of $idToMove
  538. // if so prevent moving, because that is not possible
  539. // does this element have children?
  540. if ($this->hasChildren($idToMove)) {
  541. $allChildren = $this->getChildren($idToMove);
  542. // FIXXME what happens here we are changing $allChildren,
  543. // doesnt this change the property data too??? since getChildren
  544. // (might, not yet) return a reference use while since foreach
  545. // only works on a copy of the data to loop through, but we are
  546. // changing $allChildren in the loop
  547. while (list(, $aChild) = each ($allChildren)) {
  548. // remove the first element because if array_merge is called
  549. // the array pointer seems to be
  550. array_shift($allChildren);
  551. // set to the beginning and this way the beginning is always
  552. // the current element, simply work off and truncate in front
  553. if (@$aChild['children']) {
  554. $allChildren = array_merge($allChildren,
  555. $aChild['children']
  556. );
  557. }
  558. if ($newParentId == $aChild['id']) {
  559. // TODO PEAR-ize error
  560. return TREE_ERROR_INVALID_PARENT;
  561. }
  562. }
  563. }
  564. // what happens if i am using the prevId too, then the db class also
  565. // needs to know where the element should be moved to
  566. // and it has to change the prevId of the element that will be after it
  567. // so we may be simply call some method like 'update' too?
  568. if (method_exists($this->dataSourceClass, 'move')) {
  569. return $this->dataSourceClass->move($idToMove,
  570. $newParentId,
  571. $prevId
  572. );
  573. } else {
  574. return $this->_throwError('method not implemented yet.', __LINE__);
  575. }
  576. }
  577. // }}}
  578. // {{{ update()
  579. /**
  580. * update data in a node
  581. *
  582. * @version 2002/01/29
  583. * @access public
  584. * @author Wolfram Kriesing <wolfram@kriesing.de>
  585. * @param integer the ID of the element that shall be updated
  586. * @param array the data to update
  587. * @return mixed either boolean or
  588. * an error object if the method is not implemented
  589. */
  590. function update($id, $data)
  591. {
  592. if (method_exists($this->dataSourceClass, 'update')) {
  593. return $this->dataSourceClass->update($id,$data);
  594. } else {
  595. return $this->_throwError(
  596. 'method not implemented yet.', __LINE__
  597. );
  598. }
  599. }
  600. // }}}
  601. //
  602. //
  603. // from here all methods are not interacting on the 'dataSourceClass'
  604. //
  605. //
  606. // {{{ buildStructure()
  607. /**
  608. * builds the structure in the parameter $insertIn
  609. * this function works recursively down into depth of the folder structure
  610. * it builds an array which goes as deep as the structure goes
  611. *
  612. * @access public
  613. * @version 2001/05/02
  614. * @author Wolfram Kriesing <wolfram@kriesing.de>
  615. * @param integer the parent for which it's structure shall
  616. * be built
  617. * @param integer the array where to build the structure in
  618. * given as a reference to be sure the substructure is built
  619. * in the same array as passed to the function
  620. * @return boolean returns always true
  621. *
  622. */
  623. function buildStructure($parentId, &$insertIn)
  624. {
  625. // create the element, so it exists in the property "structure"
  626. // also if there are no children below
  627. $insertIn[$parentId] = array();
  628. // set the level, since we are walking through the structure here.
  629. // Anyway we can do this here, instead of up in the setup method :-)
  630. // always set the level to one higher than the parent's level, easy ha?
  631. // this applies only to the root element(s)
  632. if (isset($this->data[$parentId]['parent']['level'])) {
  633. $this->data[$parentId]['level'] =
  634. $this->data[$parentId]['parent']['level']+1;
  635. if ($this->data[$parentId]['level']>$this->_treeDepth) {
  636. $this->_treeDepth = $this->data[$parentId]['level'];
  637. }
  638. } else {
  639. // set first level number to 0
  640. $this->data[$parentId]['level'] = 0;
  641. }
  642. if (isset($this->children[$parentId])
  643. && count($this->children[$parentId])) {
  644. // go thru all the folders
  645. foreach ($this->children[$parentId] as $child) {
  646. // build the structure under this folder,
  647. // use the current folder as the new parent and call
  648. // build recursively to build all the children by calling build
  649. // with $insertIn[someindex] the array is filled
  650. // since the array was empty before
  651. $this->buildStructure($child, $insertIn[$parentId]);
  652. }
  653. }
  654. return true;
  655. }
  656. // }}}
  657. // {{{ walk()
  658. /**
  659. * this method only serves to call the _walk method and
  660. * reset $this->walkReturn that will be returned by all the walk-steps
  661. *
  662. * @version 2001/11/25
  663. * @access public
  664. * @author Wolfram Kriesing <wolfram@kriesing.de>
  665. * @param mixed the name of the function to call for each walk step,
  666. * or an array for a method, where [0] is the method name
  667. * and [1] the object
  668. * @param array the id to start walking through the tree, everything
  669. * below is walked through
  670. * @param string the return of all the walk data will be of the given
  671. * type (values: string, array)
  672. * @return mixed this is all the return data collected from all
  673. * the walk-steps
  674. */
  675. function walk($walkFunction, $id = 0, $returnType = 'string')
  676. {
  677. // by default all of structure is used
  678. $useNode = $this->structure;
  679. if ($id == 0) {
  680. $keys = array_keys($this->structure);
  681. $id = $keys[0];
  682. } else {
  683. // get the path, to be able to go to the element in this->structure
  684. $path = $this->getPath($id);
  685. // pop off the last element, since it is the one requested
  686. array_pop($path);
  687. // start at the root of structure
  688. $curNode = $this->structure;
  689. foreach ($path as $node) {
  690. // go as deep into structure as path defines
  691. $curNode = $curNode[$node['id']];
  692. }
  693. // empty it first, so we dont have the other stuff in there
  694. // from before
  695. $useNode = array();
  696. // copy only the branch of the tree that the parameter
  697. // $id requested
  698. $useNode[$id] = $curNode[$id];
  699. }
  700. // a new walk starts, unset the return value
  701. unset($this->walkReturn);
  702. return $this->_walk($walkFunction, $useNode, $returnType);
  703. }
  704. // }}}
  705. // {{{ _walk()
  706. /**
  707. * walks through the entire tree and returns the current element and the level
  708. * so a user can use this to build a treemap or whatever
  709. *
  710. * @version 2001/06/xx
  711. * @access private
  712. * @author Wolfram Kriesing <wolfram@kriesing.de>
  713. * @param mixed the name of the function to call for each walk step,
  714. * or an array for a method, where [0] is the method name
  715. * and [1] the object
  716. *
  717. * @param array the reference in the this->structure, to walk
  718. * everything below
  719. * @param string the return of all the walk data will be
  720. * of the given type (values: string, array, ifArray)
  721. * @return mixed this is all the return data collected from all
  722. * the walk-steps
  723. */
  724. function _walk($walkFunction, &$curLevel, $returnType)
  725. {
  726. if (count($curLevel)) {
  727. foreach ($curLevel as $key => $value) {
  728. $ret = call_user_func($walkFunction, $this->data[$key]);
  729. switch ($returnType) {
  730. case 'array':
  731. $this->walkReturn[] = $ret;
  732. break;
  733. // this only adds the element if the $ret is an array
  734. // and contains data
  735. case 'ifArray':
  736. if (is_array($ret)) {
  737. $this->walkReturn[] = $ret;
  738. }
  739. break;
  740. default:
  741. $this->walkReturn.= $ret;
  742. break;
  743. }
  744. $this->_walk($walkFunction, $value, $returnType);
  745. }
  746. }
  747. return $this->walkReturn;
  748. }
  749. // }}}
  750. // {{{ addNode()
  751. /**
  752. * Adds multiple elements. You have to pass those elements
  753. * in a multidimensional array which represents the tree structure
  754. * as it shall be added (this array can of course also simply contain
  755. * one element).
  756. * The following array $x passed as the parameter
  757. * $x[0] = array('name' => 'bla',
  758. * 'parentId' => '30',
  759. * array('name' => 'bla1',
  760. * 'comment' => 'foo',
  761. * array('name' => 'bla2'),
  762. * array('name' => 'bla2_1')
  763. * ),
  764. * array('name'=>'bla1_1'),
  765. * );
  766. * $x[1] = array('name' => 'fooBla',
  767. * 'parentId' => '30');
  768. *
  769. * would add the following tree (or subtree/node) under the parent
  770. * with the id 30 (since 'parentId'=30 in $x[0] and in $x[1]):
  771. * +--bla
  772. * | +--bla1
  773. * | | +--bla2
  774. * | | +--bla2_1
  775. * | +--bla1_1
  776. * +--fooBla
  777. *
  778. * @see add()
  779. * @version 2001/12/19
  780. * @access public
  781. * @author Wolfram Kriesing <wolfram@kriesing.de>
  782. * @param array the tree to be inserted, represents the tree structure,
  783. * see add() for the exact member of each node
  784. * @return mixed either boolean false on failure
  785. * or the id of the inserted row
  786. */
  787. function addNode($node)
  788. {
  789. if (count($node)) {
  790. foreach ($node as $aNode) {
  791. $newNode = array();
  792. // this should always have data, if not the passed
  793. // structure has an error
  794. foreach ($aNode as $name => $value) {
  795. // collect the data that need to go in the DB
  796. if (!is_array($value)) {
  797. $newEntry[$name] = $value;
  798. } else {
  799. // collect the children
  800. $newNode[] = $value;
  801. }
  802. }
  803. // add the element and get the id, that it got, to have
  804. // the parentId for the children
  805. $insertedId = $this->add($newEntry);
  806. // if inserting suceeded, we have received the id
  807. // under which we can insert the children
  808. if ($insertedId!= false) {
  809. // if there are children, set their parentId.
  810. // So they kknow where they belong in the tree
  811. if (count($newNode)) {
  812. foreach($newNode as $key => $aNewNode) {
  813. $newNode[$key]['parentId'] = $insertedId;
  814. }
  815. }
  816. // call yourself recursively to insert the children
  817. // and its children
  818. $this->addNode($newNode);
  819. }
  820. }
  821. }
  822. }
  823. // }}}
  824. // {{{ getPath()
  825. /**
  826. * gets the path to the element given by its id
  827. * !!! ATTENTION watch out that you never change any of the data returned,
  828. * since they are references to the internal property $data
  829. *
  830. * @access public
  831. * @version 2001/10/10
  832. * @access public
  833. * @author Wolfram Kriesing <wolfram@kriesing.de>
  834. * @param mixed the id of the node to get the path for
  835. * @return array this array contains all elements from the root
  836. * to the element given by the id
  837. *
  838. */
  839. function getPath($id)
  840. {
  841. // empty the path, to be clean
  842. $path = array();
  843. // FIXXME may its better to use a for(level) to count down,
  844. // since a while is always a little risky
  845. // until there are no more parents
  846. while (@$this->data[$id]['parent']) {
  847. // curElement is already a reference, so save it in path
  848. $path[] = &$this->data[$id];
  849. // get the next parent id, for the while to retreive the parent's parent
  850. $id = $this->data[$id]['parent']['id'];
  851. }
  852. // dont forget the last one
  853. $path[] = &$this->data[$id];
  854. return array_reverse($path);
  855. }
  856. // }}}
  857. // {{{ setRemoveRecursively()
  858. /**
  859. * sets the remove-recursively mode, either true or false
  860. *
  861. * @version 2001/10/09
  862. * @access public
  863. * @author Wolfram Kriesing <wolfram@kriesing.de>
  864. * @param boolean set to true if removing a tree level
  865. * shall remove all it's children and theit children
  866. *
  867. */
  868. function setRemoveRecursively($case=true)
  869. {
  870. $this->removeRecursively = $case;
  871. }
  872. // }}}
  873. // {{{ _getElement()
  874. /**
  875. *
  876. *
  877. * @version 2002/01/21
  878. * @access private
  879. * @author Wolfram Kriesing <wolfram@kriesing.de>
  880. * @param int the element ID
  881. *
  882. */
  883. function &_getElement($id, $what = '')
  884. {
  885. // We should not return false, since that might be a value of the
  886. // element that is requested.
  887. $element = null;
  888. if ($what == '') {
  889. $element = $this->data[$id];
  890. }
  891. $elementId = $this->_getElementId($id, $what);
  892. if ($elementId !== null) {
  893. $element = $this->data[$elementId];
  894. }
  895. // we should not return false, since that might be a value
  896. // of the element that is requested
  897. return $element;
  898. }
  899. // }}}
  900. // {{{ _getElementId()
  901. /**
  902. *
  903. *
  904. * @version 2002/01/21
  905. * @access private
  906. * @author Wolfram Kriesing <wolfram@kriesing.de>
  907. * @param int the element ID
  908. *
  909. */
  910. function _getElementId($id, $what)
  911. {
  912. if (isset($this->data[$id][$what]) && $this->data[$id][$what]) {
  913. return $this->data[$id][$what]['id'];
  914. }
  915. return null;
  916. }
  917. // }}}
  918. // {{{ getElement()
  919. /**
  920. * gets an element as a reference
  921. *
  922. * @version 2002/01/21
  923. * @access private
  924. * @author Wolfram Kriesing <wolfram@kriesing.de>
  925. * @param int the element ID
  926. *
  927. */
  928. function &getElement($id)
  929. {
  930. return $element = &$this->_getElement($id);
  931. }
  932. // }}}
  933. // {{{ getElementContent()
  934. /**
  935. *
  936. *
  937. * @version 2002/02/06
  938. * @access private
  939. * @author Wolfram Kriesing <wolfram@kriesing.de>
  940. * @param mixed either the id of an element
  941. * or the path to the element
  942. *
  943. */
  944. function getElementContent($idOrPath, $fieldName)
  945. {
  946. if (is_string($idOrPath)) {
  947. $idOrPath = $this->getIdByPath($idOrPath);
  948. }
  949. return $this->data[$idOrPath][$fieldName];
  950. }
  951. // }}}
  952. // {{{ getElementsContent()
  953. /**
  954. *
  955. *
  956. * @version 2002/02/06
  957. * @access private
  958. * @author Wolfram Kriesing <wolfram@kriesing.de>
  959. * @param int the element ID
  960. *
  961. */
  962. function getElementsContent($ids, $fieldName) {
  963. // i dont know if this method is not just overloading the file.
  964. // Since it only serves my lazyness
  965. // is this effective here? i can also loop in the calling code!?
  966. $fields = array();
  967. if (is_array($ids) && count($ids)) {
  968. foreach ($ids as $aId) {
  969. $fields[] = $this->getElementContent($aId, $fieldName);
  970. }
  971. }
  972. return $fields;
  973. }
  974. // }}}
  975. // {{{ getElementByPath()
  976. /**
  977. * gets an element given by it's path as a reference
  978. *
  979. * @version 2002/01/21
  980. * @access public
  981. * @author Wolfram Kriesing <wolfram@kriesing.de>
  982. * @param string the path to search for
  983. * @param integer the id where to search for the path
  984. * @param string the name of the key that contains the node name
  985. * @param string the path separator
  986. * @return integer the id of the searched element
  987. *
  988. */
  989. function &getElementByPath($path, $startId = 0, $nodeName = 'name', $seperator = '/')
  990. {
  991. $element = null;
  992. $id = $this->getIdByPath($path,$startId);
  993. if ($id) {
  994. $element = &$this->getElement($id);
  995. }
  996. // return null since false might be interpreted as id 0
  997. return $element;
  998. }
  999. // }}}
  1000. // {{{ getLevel()
  1001. /**
  1002. * get the level, which is how far below the root are we?
  1003. *
  1004. * @version 2001/11/25
  1005. * @access public
  1006. * @author Wolfram Kriesing <wolfram@kriesing.de>
  1007. * @param mixed $id the id of the node to get the level for
  1008. *
  1009. */
  1010. function getLevel($id)
  1011. {
  1012. return $this->data[$id]['level'];
  1013. }
  1014. // }}}
  1015. // {{{ getChild()
  1016. /**
  1017. * returns the child if the node given has one
  1018. * !!! ATTENTION watch out that you never change any of the data returned,
  1019. * since they are references to the internal property $data
  1020. *
  1021. * @version 2001/11/27
  1022. * @access public
  1023. * @author Wolfram Kriesing <wolfram@kriesing.de>
  1024. * @param mixed the id of the node to get the child for
  1025. *
  1026. */
  1027. function &getChild($id)
  1028. {
  1029. return $element = &$this->_getElement($id, 'child');
  1030. }
  1031. // }}}
  1032. // {{{ getParent()
  1033. /**
  1034. * returns the child if the node given has one
  1035. * !!! ATTENTION watch out that you never change any of the data returned,
  1036. * since they are references to the internal property $data
  1037. *
  1038. * @version 2001/11/27
  1039. * @access public
  1040. * @author Wolfram Kriesing <wolfram@kriesing.de>
  1041. * @param mixed $id the id of the node to get the child for
  1042. *
  1043. */
  1044. function &getParent($id)
  1045. {
  1046. return $element = &$this->_getElement($id, 'parent');
  1047. }
  1048. // }}}
  1049. // {{{ getNext()
  1050. /**
  1051. * returns the next element if the node given has one
  1052. * !!! ATTENTION watch out that you never change any of the data returned,
  1053. * since they are references to the internal property $data
  1054. *
  1055. * @version 2002/01/17
  1056. * @access public
  1057. * @author Wolfram Kriesing <wolfram@kriesing.de>
  1058. * @param mixed the id of the node to get the child for
  1059. * @return mixed reference to the next element or false if there is none
  1060. */
  1061. function &getNext($id)
  1062. {
  1063. return $element = &$this->_getElement($id, 'next');
  1064. }
  1065. // }}}
  1066. // {{{ getPrevious()
  1067. /**
  1068. * returns the previous element if the node given has one
  1069. * !!! ATTENTION watch out that you never change any of the data returned,
  1070. * since they are references to the internal property $data
  1071. *
  1072. * @version 2002/02/05
  1073. * @access public
  1074. * @author Wolfram Kriesing <wolfram@kriesing.de>
  1075. * @param mixed the id of the node to get the child for
  1076. * @return mixed reference to the next element or false if there is none
  1077. */
  1078. function &getPrevious($id)
  1079. {
  1080. return $element = &$this->_getElement($id, 'previous');
  1081. }
  1082. // }}}
  1083. // {{{ getNode()
  1084. /**
  1085. * returns the node for the given id
  1086. * !!! ATTENTION watch out that you never change any of the data returned,
  1087. * since they are references to the internal property $data
  1088. *
  1089. * @version 2001/11/28
  1090. * @access public
  1091. * @author Wolfram Kriesing <wolfram@kriesing.de>
  1092. * @param mixed $id the id of the node to get
  1093. *
  1094. */
  1095. /*
  1096. function &getNode($id)
  1097. {
  1098. //return $element = &$this->_getElement($id);
  1099. }
  1100. */
  1101. // }}}
  1102. // {{{ getIdByPath()
  1103. /**
  1104. * return the id of the element which is referenced by $path
  1105. * this is useful for xml-structures, like: getIdByPath('/root/sub1/sub2')
  1106. * this requires the structure to use each name uniquely
  1107. * if this is not given it will return the first proper path found
  1108. * i.e. there should only be one path /x/y/z
  1109. *
  1110. * @version 2001/11/28
  1111. * @access public
  1112. * @author Wolfram Kriesing <wolfram@kriesing.de>
  1113. * @param string $path the path to search for
  1114. * @param integer $startId the id where to search for the path
  1115. * @param string $nodeName the name of the key that contains the node name
  1116. * @param string $seperator the path seperator
  1117. * @return integer the id of the searched element
  1118. *
  1119. */
  1120. function getIdByPath($path, $startId = 0, $nodeName = 'name', $seperator = '/')
  1121. // should this method be called getElementIdByPath ????
  1122. {
  1123. // if no start ID is given get the root
  1124. if ($startId == 0) {
  1125. $startId = $this->getFirstRootId();
  1126. } else { // if a start id is given, get its first child to start searching there
  1127. $startId = $this->getChildId($startId);
  1128. if ($startId==false) { // is there a child to this element?
  1129. return false;
  1130. }
  1131. }
  1132. if (strpos($path,$seperator) === 0) { // if a seperator is at the beginning strip it off
  1133. $path = substr($path,strlen($seperator));
  1134. }
  1135. $nodes = explode($seperator, $path);
  1136. $curId = $startId;
  1137. foreach ($nodes as $key => $aNodeName) {
  1138. $nodeFound = false;
  1139. do {
  1140. if ($this->data[$curId][$nodeName] == $aNodeName) {
  1141. $nodeFound = true;
  1142. // do only save the child if we are not already at the end of path
  1143. // because then we need curId to return it
  1144. if ($key < (count($nodes) - 1)) {
  1145. $curId = $this->getChildId($curId);
  1146. }
  1147. break;
  1148. }
  1149. $curId = $this->getNextId($curId);
  1150. } while($curId);
  1151. if ($nodeFound == false) {
  1152. return false;
  1153. }
  1154. }
  1155. return $curId;
  1156. // FIXXME to be implemented
  1157. }
  1158. // }}}
  1159. // {{{ getFirstRoot()
  1160. /**
  1161. * this gets the first element that is in the root node
  1162. * i think that there can't be a "getRoot" method since there might
  1163. * be multiple number of elements in the root node, at least the
  1164. * way it works now
  1165. *
  1166. * @access public
  1167. * @version 2001/12/10
  1168. * @author Wolfram Kriesing <wolfram@kriesing.de>
  1169. * @return returns the first root element
  1170. */
  1171. function &getFirstRoot()
  1172. {
  1173. // could also be reset($this->data) i think since php keeps the order
  1174. // ... but i didnt try
  1175. reset($this->structure);
  1176. return $this->data[key($this->structure)];
  1177. }
  1178. // }}}
  1179. // {{{ getRoot()
  1180. /**
  1181. * since in a nested tree there can only be one root
  1182. * which i think (now) is correct, we also need an alias for this method
  1183. * this also makes all the methods in Tree_Common, which access the
  1184. * root element work properly!
  1185. *
  1186. * @access public
  1187. * @version 2002/07/26
  1188. * @author Wolfram Kriesing <wolfram@kriesing.de>
  1189. * @return returns the first root element
  1190. */
  1191. function &getRoot()
  1192. {
  1193. return $this->getFirstRoot();
  1194. }
  1195. // }}}
  1196. // {{{ getRoot()
  1197. /**
  1198. * gets the tree under the given element in one array, sorted
  1199. * so you can go through the elements from begin to end and list them
  1200. * as they are in the tree, where every child (until the deepest) is retreived
  1201. *
  1202. * @see &_getNode()
  1203. * @access public
  1204. * @version 2001/12/17
  1205. * @author Wolfram Kriesing <wolfram@kriesing.de>
  1206. * @param integer the id where to start walking
  1207. * @param integer this number says how deep into
  1208. * the structure the elements shall be
  1209. * retreived
  1210. * @return array sorted as listed in the tree
  1211. */
  1212. function &getNode($startId=0, $depth=0)
  1213. {
  1214. if ($startId == 0) {
  1215. $level = 0;
  1216. } else {
  1217. $level = $this->getLevel($startId);
  1218. }
  1219. $this->_getNodeMaxLevel = $depth ? ($depth + $level) : 0 ;
  1220. //!!! $this->_getNodeCurParent = $this->data['parent']['id'];
  1221. // if the tree is empty dont walk through it
  1222. if (!count($this->data)) {
  1223. return;
  1224. }
  1225. $ret = $this->walk(array(&$this,'_getNode'), $startId, 'ifArray');
  1226. return $ret;
  1227. }
  1228. // }}}
  1229. // {{{ _getNode()
  1230. /**
  1231. * this is used for walking through the tree structure
  1232. * until a given level, this method should only be used by getNode
  1233. *
  1234. * @see &getNode()
  1235. * @see walk()
  1236. * @see _walk()
  1237. * @access private
  1238. * @version 2001/12/17
  1239. * @author Wolfram Kriesing <wolfram@kriesing.de>
  1240. * @param array the node passed by _walk
  1241. * @return mixed either returns the node, or nothing
  1242. * if the level _getNodeMaxLevel is reached
  1243. */
  1244. function &_getNode(&$node)
  1245. {
  1246. if ($this->_getNodeMaxLevel) {
  1247. if ($this->getLevel($node['id']) < $this->_getNodeMaxLevel) {
  1248. return $node;
  1249. }
  1250. return;
  1251. }
  1252. return $node;
  1253. }
  1254. // }}}
  1255. // {{{ getChildren()
  1256. /**
  1257. * returns the children of the given ids
  1258. *
  1259. * @version 2001/12/17
  1260. * @access public
  1261. * @author Wolfram Kriesing <wolfram@kriesing.de>
  1262. * @param integer $id the id of the node to check for children
  1263. * @param integer the children of how many levels shall be returned
  1264. * @return boolean true if the node has children
  1265. */
  1266. function getChildren($ids, $levels = 1)
  1267. {
  1268. //FIXXME $levels to be implemented
  1269. $ret = array();
  1270. if (is_array($ids)) {
  1271. foreach ($ids as $aId) {
  1272. if ($this->hasChildren($aId)) {
  1273. $ret[$aId] = $this->data[$aId]['children'];
  1274. }
  1275. }
  1276. } else {
  1277. if ($this->hasChildren($ids)) {
  1278. $ret = $this->data[$ids]['children'];
  1279. }
  1280. }
  1281. return $ret;
  1282. }
  1283. // }}}
  1284. // {{{ isNode()
  1285. /**
  1286. * returns if the given element is a valid node
  1287. *
  1288. * @version 2001/12/21
  1289. * @access public
  1290. * @author Wolfram Kriesing <wolfram@kriesing.de>
  1291. * @param integer $id the id of the node to check for children
  1292. * @return boolean true if the node has children
  1293. */
  1294. function isNode($id = 0)
  1295. {
  1296. return isset($this->data[$id]);
  1297. }
  1298. // }}}
  1299. // {{{ varDump()
  1300. /**
  1301. * this is for debugging, dumps the entire data-array
  1302. * an extra method is needed, since this array contains recursive
  1303. * elements which make a normal print_f or var_dump not show all the data
  1304. *
  1305. * @version 2002/01/21
  1306. * @access public
  1307. * @author Wolfram Kriesing <wolfram@kriesing.de>
  1308. * @params mixed either the id of the node to dump, this will dump
  1309. * everything below the given node or an array of nodes
  1310. * to dump. This only dumps the elements passed
  1311. * as an array. 0 or no parameter if the entire tree shall
  1312. * be dumped if you want to dump only a single element
  1313. * pass it as an array using array($element).
  1314. */
  1315. function varDump($node = 0)
  1316. {
  1317. $dontDump = array('parent', 'child', 'children', 'next', 'previous');
  1318. // if $node is an array, we assume it is a collection of elements
  1319. if (!is_array($node)) {
  1320. $nodes = $this->getNode($node);
  1321. } else {
  1322. $nodes = $node;
  1323. }
  1324. // if $node==0 then the entire tree is retreived
  1325. if (count($node)) {
  1326. echo '<table border="1"><tr><th>name</th>';
  1327. $keys = array();
  1328. foreach ($this->getRoot() as $key => $x) {
  1329. if (!is_array($x)) {
  1330. echo "<th>$key</th>";
  1331. $keys[] = $key;
  1332. }
  1333. }
  1334. echo '</tr>';
  1335. foreach ($nodes as $aNode) {
  1336. echo '<tr><td nowrap="nowrap">';
  1337. $prefix = '';
  1338. for ($i = 0; $i < $aNode['level']; $i++) $prefix .= '- ';
  1339. echo "$prefix {$aNode['name']}</td>";
  1340. foreach ($keys as $aKey) {
  1341. if (!is_array($key)) {
  1342. $val = isset($aNode[$aKey]) ? $aNode[$aKey] : '&nbsp;';
  1343. echo "<td>$val</td>";
  1344. }
  1345. }
  1346. echo '</tr>';
  1347. }
  1348. echo '</table>';
  1349. }
  1350. }
  1351. // }}}
  1352. //### TODO's ###
  1353. // {{{ copy()
  1354. /**
  1355. * NOT IMPLEMENTED YET
  1356. * copies a part of the tree under a given parent
  1357. *
  1358. * @version 2001/12/19
  1359. * @access public
  1360. * @author Wolfram Kriesing <wolfram@kriesing.de>
  1361. * @param the id of the element which is copied, all its children are copied too
  1362. * @param the id that shall be the new parent
  1363. * @return boolean true on success
  1364. */
  1365. function copy($srcId, $destId)
  1366. {
  1367. if (method_exists($this->dataSourceClass, 'copy')) {
  1368. return $this->dataSourceClass->copy($srcId, $destId);
  1369. } else {
  1370. return $this->_throwError('method not implemented yet.', __LINE__);
  1371. }
  1372. /*
  1373. remove all array elements after 'parent' since those had been created
  1374. and remove id and set parentId and that should be it, build the tree and pass it to addNode
  1375. those are the fields in one data-entry
  1376. id=>41
  1377. parentId=>39
  1378. name=>Java
  1379. parent=>Array
  1380. prevId=>58
  1381. previous=>Array
  1382. childId=>77
  1383. child=>Array
  1384. nextId=>104
  1385. next=>Array
  1386. children=>Array
  1387. level=>2
  1388. $this->getNode
  1389. foreach($this->data[$srcId] as $key=>$value)
  1390. echo "$key=>$value<br>";
  1391. */
  1392. }
  1393. // }}}
  1394. }