PageRenderTime 61ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

/include/spyc/spyc.php

https://github.com/radicaldesigns/amp
PHP | 869 lines | 480 code | 69 blank | 320 comment | 97 complexity | a588b8d68007aafbee71f2adf90cbe64 MD5 | raw file
Possible License(s): LGPL-2.1, GPL-2.0, BSD-3-Clause, LGPL-2.0, CC-BY-SA-3.0, AGPL-1.0
  1. <?php
  2. /**
  3. * Spyc -- A Simple PHP YAML Class
  4. * @version 0.2.(5) -- 2006-12-31
  5. * @author Chris Wanstrath <chris@ozmm.org>
  6. * @author Vlad Andersen <vlad@oneiros.ru>
  7. * @link http://spyc.sourceforge.net/
  8. * @copyright Copyright 2005-2006 Chris Wanstrath
  9. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  10. * @package Spyc
  11. */
  12. /**
  13. * A node, used by Spyc for parsing YAML.
  14. * @package Spyc
  15. */
  16. class YAMLNode {
  17. /**#@+
  18. * @access public
  19. * @var string
  20. */
  21. var $parent;
  22. var $id;
  23. /**#@+*/
  24. /**
  25. * @access public
  26. * @var mixed
  27. */
  28. var $data;
  29. /**
  30. * @access public
  31. * @var int
  32. */
  33. var $indent;
  34. /**
  35. * @access public
  36. * @var bool
  37. */
  38. var $children = false;
  39. /**
  40. * The constructor assigns the node a unique ID.
  41. * @access public
  42. * @return void
  43. */
  44. function YAMLNode($nodeId) {
  45. $this->id = $nodeId;
  46. }
  47. }
  48. /**
  49. * The Simple PHP YAML Class.
  50. *
  51. * This class can be used to read a YAML file and convert its contents
  52. * into a PHP array. It currently supports a very limited subsection of
  53. * the YAML spec.
  54. *
  55. * Usage:
  56. * <code>
  57. * $parser = new Spyc;
  58. * $array = $parser->load($file);
  59. * </code>
  60. * @package Spyc
  61. */
  62. class Spyc {
  63. /**
  64. * Load YAML into a PHP array statically
  65. *
  66. * The load method, when supplied with a YAML stream (string or file),
  67. * will do its best to convert YAML in a file into a PHP array. Pretty
  68. * simple.
  69. * Usage:
  70. * <code>
  71. * $array = Spyc::YAMLLoad('lucky.yaml');
  72. * print_r($array);
  73. * </code>
  74. * @access public
  75. * @return array
  76. * @param string $input Path of YAML file or string containing YAML
  77. */
  78. function YAMLLoad($input) {
  79. $spyc = new Spyc;
  80. return $spyc->load($input);
  81. }
  82. /**
  83. * Dump YAML from PHP array statically
  84. *
  85. * The dump method, when supplied with an array, will do its best
  86. * to convert the array into friendly YAML. Pretty simple. Feel free to
  87. * save the returned string as nothing.yaml and pass it around.
  88. *
  89. * Oh, and you can decide how big the indent is and what the wordwrap
  90. * for folding is. Pretty cool -- just pass in 'false' for either if
  91. * you want to use the default.
  92. *
  93. * Indent's default is 2 spaces, wordwrap's default is 40 characters. And
  94. * you can turn off wordwrap by passing in 0.
  95. *
  96. * @access public
  97. * @return string
  98. * @param array $array PHP array
  99. * @param int $indent Pass in false to use the default, which is 2
  100. * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40)
  101. */
  102. function YAMLDump($array,$indent = false,$wordwrap = false) {
  103. $spyc = new Spyc;
  104. return $spyc->dump($array,$indent,$wordwrap);
  105. }
  106. /**
  107. * Load YAML into a PHP array from an instantiated object
  108. *
  109. * The load method, when supplied with a YAML stream (string or file path),
  110. * will do its best to convert the YAML into a PHP array. Pretty simple.
  111. * Usage:
  112. * <code>
  113. * $parser = new Spyc;
  114. * $array = $parser->load('lucky.yaml');
  115. * print_r($array);
  116. * </code>
  117. * @access public
  118. * @return array
  119. * @param string $input Path of YAML file or string containing YAML
  120. */
  121. function load($input) {
  122. // See what type of input we're talking about
  123. // If it's not a file, assume it's a string
  124. if (!empty($input) && (strpos($input, "\n") === false)
  125. && file_exists($input)) {
  126. $yaml = file($input);
  127. } else {
  128. $yaml = explode("\n",$input);
  129. }
  130. // Initiate some objects and values
  131. $base = new YAMLNode (1);
  132. $base->indent = 0;
  133. $this->_lastIndent = 0;
  134. $this->_lastNode = $base->id;
  135. $this->_inBlock = false;
  136. $this->_isInline = false;
  137. $this->_nodeId = 2;
  138. foreach ($yaml as $linenum => $line) {
  139. $ifchk = trim($line);
  140. // If the line starts with a tab (instead of a space), throw a fit.
  141. if (preg_match('/^(\t)+(\w+)/', $line)) {
  142. $err = 'ERROR: Line '. ($linenum + 1) .' in your input YAML begins'.
  143. ' with a tab. YAML only recognizes spaces. Please reformat.';
  144. die($err);
  145. }
  146. if ($this->_inBlock === false && empty($ifchk)) {
  147. continue;
  148. } elseif ($this->_inBlock == true && empty($ifchk)) {
  149. $last = $this->_allNodes[$this->_lastNode];
  150. $last->data[key($last->data)] .= "\n";
  151. } elseif ($ifchk{0} != '#' && substr($ifchk,0,3) != '---') {
  152. // Create a new node and get its indent
  153. $node = new YAMLNode ($this->_nodeId);
  154. $this->_nodeId++;
  155. $node->indent = $this->_getIndent($line);
  156. // Check where the node lies in the hierarchy
  157. if ($this->_lastIndent == $node->indent) {
  158. // If we're in a block, add the text to the parent's data
  159. if ($this->_inBlock === true) {
  160. $parent = $this->_allNodes[$this->_lastNode];
  161. $parent->data[key($parent->data)] .= trim($line).$this->_blockEnd;
  162. } else {
  163. // The current node's parent is the same as the previous node's
  164. if (isset($this->_allNodes[$this->_lastNode])) {
  165. $node->parent = $this->_allNodes[$this->_lastNode]->parent;
  166. }
  167. }
  168. } elseif ($this->_lastIndent < $node->indent) {
  169. if ($this->_inBlock === true) {
  170. $parent = $this->_allNodes[$this->_lastNode];
  171. $parent->data[key($parent->data)] .= trim($line).$this->_blockEnd;
  172. } elseif ($this->_inBlock === false) {
  173. // The current node's parent is the previous node
  174. $node->parent = $this->_lastNode;
  175. // If the value of the last node's data was > or | we need to
  176. // start blocking i.e. taking in all lines as a text value until
  177. // we drop our indent.
  178. $parent = $this->_allNodes[$node->parent];
  179. $this->_allNodes[$node->parent]->children = true;
  180. if (is_array($parent->data)) {
  181. $chk = '';
  182. if (isset ($parent->data[key($parent->data)]))
  183. $chk = $parent->data[key($parent->data)];
  184. if ($chk === '>') {
  185. $this->_inBlock = true;
  186. $this->_blockEnd = ' ';
  187. $parent->data[key($parent->data)] =
  188. str_replace('>','',$parent->data[key($parent->data)]);
  189. $parent->data[key($parent->data)] .= trim($line).' ';
  190. $this->_allNodes[$node->parent]->children = false;
  191. $this->_lastIndent = $node->indent;
  192. } elseif ($chk === '|') {
  193. $this->_inBlock = true;
  194. $this->_blockEnd = "\n";
  195. $parent->data[key($parent->data)] =
  196. str_replace('|','',$parent->data[key($parent->data)]);
  197. $parent->data[key($parent->data)] .= trim($line)."\n";
  198. $this->_allNodes[$node->parent]->children = false;
  199. $this->_lastIndent = $node->indent;
  200. }
  201. }
  202. }
  203. } elseif ($this->_lastIndent > $node->indent) {
  204. // Any block we had going is dead now
  205. if ($this->_inBlock === true) {
  206. $this->_inBlock = false;
  207. if ($this->_blockEnd = "\n") {
  208. $last = $this->_allNodes[$this->_lastNode];
  209. $last->data[key($last->data)] =
  210. trim($last->data[key($last->data)]);
  211. }
  212. }
  213. // We don't know the parent of the node so we have to find it
  214. // foreach ($this->_allNodes as $n) {
  215. foreach ($this->_indentSort[$node->indent] as $n) {
  216. if ($n->indent == $node->indent) {
  217. $node->parent = $n->parent;
  218. }
  219. }
  220. }
  221. if ($this->_inBlock === false) {
  222. // Set these properties with information from our current node
  223. $this->_lastIndent = $node->indent;
  224. // Set the last node
  225. $this->_lastNode = $node->id;
  226. // Parse the YAML line and return its data
  227. $node->data = $this->_parseLine($line);
  228. // Add the node to the master list
  229. $this->_allNodes[$node->id] = $node;
  230. // Add a reference to the parent list
  231. $this->_allParent[intval($node->parent)][] = $node->id;
  232. // Add a reference to the node in an indent array
  233. $this->_indentSort[$node->indent][] = $this->_allNodes[$node->id];
  234. // Add a reference to the node in a References array if this node
  235. // has a YAML reference in it.
  236. if (
  237. ( (is_array($node->data)) &&
  238. isset($node->data[key($node->data)]) &&
  239. (!is_array($node->data[key($node->data)])) )
  240. &&
  241. ( (preg_match('/^&([^ ]+)/',$node->data[key($node->data)]))
  242. ||
  243. (preg_match('/^\*([^ ]+)/',$node->data[key($node->data)])) )
  244. ) {
  245. $this->_haveRefs[] = $this->_allNodes[$node->id];
  246. } elseif (
  247. ( (is_array($node->data)) &&
  248. isset($node->data[key($node->data)]) &&
  249. (is_array($node->data[key($node->data)])) )
  250. ) {
  251. // Incomplete reference making code. Ugly, needs cleaned up.
  252. foreach ($node->data[key($node->data)] as $d) {
  253. if ( !is_array($d) &&
  254. ( (preg_match('/^&([^ ]+)/',$d))
  255. ||
  256. (preg_match('/^\*([^ ]+)/',$d)) )
  257. ) {
  258. $this->_haveRefs[] = $this->_allNodes[$node->id];
  259. }
  260. }
  261. }
  262. }
  263. }
  264. }
  265. unset($node);
  266. // Here we travel through node-space and pick out references (& and *)
  267. $this->_linkReferences();
  268. // Build the PHP array out of node-space
  269. $trunk = $this->_buildArray();
  270. return $trunk;
  271. }
  272. /**
  273. * Dump PHP array to YAML
  274. *
  275. * The dump method, when supplied with an array, will do its best
  276. * to convert the array into friendly YAML. Pretty simple. Feel free to
  277. * save the returned string as tasteful.yaml and pass it around.
  278. *
  279. * Oh, and you can decide how big the indent is and what the wordwrap
  280. * for folding is. Pretty cool -- just pass in 'false' for either if
  281. * you want to use the default.
  282. *
  283. * Indent's default is 2 spaces, wordwrap's default is 40 characters. And
  284. * you can turn off wordwrap by passing in 0.
  285. *
  286. * @access public
  287. * @return string
  288. * @param array $array PHP array
  289. * @param int $indent Pass in false to use the default, which is 2
  290. * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40)
  291. */
  292. function dump($array,$indent = false,$wordwrap = false) {
  293. // Dumps to some very clean YAML. We'll have to add some more features
  294. // and options soon. And better support for folding.
  295. // New features and options.
  296. if ($indent === false or !is_numeric($indent)) {
  297. $this->_dumpIndent = 2;
  298. } else {
  299. $this->_dumpIndent = $indent;
  300. }
  301. if ($wordwrap === false or !is_numeric($wordwrap)) {
  302. $this->_dumpWordWrap = 40;
  303. } else {
  304. $this->_dumpWordWrap = $wordwrap;
  305. }
  306. // New YAML document
  307. $string = "---\n";
  308. // Start at the base of the array and move through it.
  309. foreach ($array as $key => $value) {
  310. $string .= $this->_yamlize($key,$value,0);
  311. }
  312. return $string;
  313. }
  314. /**** Private Properties ****/
  315. /**#@+
  316. * @access private
  317. * @var mixed
  318. */
  319. var $_haveRefs;
  320. var $_allNodes;
  321. var $_allParent;
  322. var $_lastIndent;
  323. var $_lastNode;
  324. var $_inBlock;
  325. var $_isInline;
  326. var $_dumpIndent;
  327. var $_dumpWordWrap;
  328. /**#@+*/
  329. /**** Public Properties ****/
  330. /**#@+
  331. * @access public
  332. * @var mixed
  333. */
  334. var $_nodeId;
  335. /**#@+*/
  336. /**** Private Methods ****/
  337. /**
  338. * Attempts to convert a key / value array item to YAML
  339. * @access private
  340. * @return string
  341. * @param $key The name of the key
  342. * @param $value The value of the item
  343. * @param $indent The indent of the current node
  344. */
  345. function _yamlize($key,$value,$indent) {
  346. if (is_array($value)) {
  347. // It has children. What to do?
  348. // Make it the right kind of item
  349. $string = $this->_dumpNode($key,NULL,$indent);
  350. // Add the indent
  351. $indent += $this->_dumpIndent;
  352. // Yamlize the array
  353. $string .= $this->_yamlizeArray($value,$indent);
  354. } elseif (!is_array($value)) {
  355. // It doesn't have children. Yip.
  356. $string = $this->_dumpNode($key,$value,$indent);
  357. }
  358. return $string;
  359. }
  360. /**
  361. * Attempts to convert an array to YAML
  362. * @access private
  363. * @return string
  364. * @param $array The array you want to convert
  365. * @param $indent The indent of the current level
  366. */
  367. function _yamlizeArray($array,$indent) {
  368. if (is_array($array)) {
  369. $string = '';
  370. foreach ($array as $key => $value) {
  371. $string .= $this->_yamlize($key,$value,$indent);
  372. }
  373. return $string;
  374. } else {
  375. return false;
  376. }
  377. }
  378. /**
  379. * Returns YAML from a key and a value
  380. * @access private
  381. * @return string
  382. * @param $key The name of the key
  383. * @param $value The value of the item
  384. * @param $indent The indent of the current node
  385. */
  386. function _dumpNode($key,$value,$indent) {
  387. // do some folding here, for blocks
  388. if (strpos($value,"\n") !== false || strpos($value,": ") !== false || strpos($value,"- ") !== false) {
  389. $value = $this->_doLiteralBlock($value,$indent);
  390. } else {
  391. $value = $this->_doFolding($value,$indent);
  392. }
  393. if (is_bool($value)) {
  394. $value = ($value) ? "true" : "false";
  395. }
  396. $spaces = str_repeat(' ',$indent);
  397. if (is_int($key)) {
  398. // It's a sequence
  399. $string = $spaces.'- '.$value."\n";
  400. } else {
  401. // It's mapped
  402. $string = $spaces.$key.': '.$value."\n";
  403. }
  404. return $string;
  405. }
  406. /**
  407. * Creates a literal block for dumping
  408. * @access private
  409. * @return string
  410. * @param $value
  411. * @param $indent int The value of the indent
  412. */
  413. function _doLiteralBlock($value,$indent) {
  414. $exploded = explode("\n",$value);
  415. $newValue = '|';
  416. $indent += $this->_dumpIndent;
  417. $spaces = str_repeat(' ',$indent);
  418. foreach ($exploded as $line) {
  419. $newValue .= "\n" . $spaces . trim($line);
  420. }
  421. return $newValue;
  422. }
  423. /**
  424. * Folds a string of text, if necessary
  425. * @access private
  426. * @return string
  427. * @param $value The string you wish to fold
  428. */
  429. function _doFolding($value,$indent) {
  430. // Don't do anything if wordwrap is set to 0
  431. if ($this->_dumpWordWrap === 0) {
  432. return $value;
  433. }
  434. if (strlen($value) > $this->_dumpWordWrap) {
  435. $indent += $this->_dumpIndent;
  436. $indent = str_repeat(' ',$indent);
  437. $wrapped = wordwrap($value,$this->_dumpWordWrap,"\n$indent");
  438. $value = ">\n".$indent.$wrapped;
  439. }
  440. return $value;
  441. }
  442. /* Methods used in loading */
  443. /**
  444. * Finds and returns the indentation of a YAML line
  445. * @access private
  446. * @return int
  447. * @param string $line A line from the YAML file
  448. */
  449. function _getIndent($line) {
  450. preg_match('/^\s{1,}/',$line,$match);
  451. if (!empty($match[0])) {
  452. $indent = substr_count($match[0],' ');
  453. } else {
  454. $indent = 0;
  455. }
  456. return $indent;
  457. }
  458. /**
  459. * Parses YAML code and returns an array for a node
  460. * @access private
  461. * @return array
  462. * @param string $line A line from the YAML file
  463. */
  464. function _parseLine($line) {
  465. $line = trim($line);
  466. $array = array();
  467. if (preg_match('/^-(.*):$/',$line)) {
  468. // It's a mapped sequence
  469. $key = trim(substr(substr($line,1),0,-1));
  470. $array[$key] = '';
  471. } elseif ($line[0] == '-' && substr($line,0,3) != '---') {
  472. // It's a list item but not a new stream
  473. if (strlen($line) > 1) {
  474. $value = trim(substr($line,1));
  475. // Set the type of the value. Int, string, etc
  476. $value = $this->_toType($value);
  477. $array[] = $value;
  478. } else {
  479. $array[] = array();
  480. }
  481. } elseif (preg_match('/^(.+):/',$line,$key)) {
  482. // It's a key/value pair most likely
  483. // If the key is in double quotes pull it out
  484. if (preg_match('/^(["\'](.*)["\'](\s)*:)/',$line,$matches)) {
  485. $value = trim(str_replace($matches[1],'',$line));
  486. $key = $matches[2];
  487. } else {
  488. // Do some guesswork as to the key and the value
  489. $explode = explode(':',$line);
  490. $key = trim($explode[0]);
  491. array_shift($explode);
  492. $value = trim(implode(':',$explode));
  493. }
  494. // Set the type of the value. Int, string, etc
  495. $value = $this->_toType($value);
  496. if (empty($key)) {
  497. $array[] = $value;
  498. } else {
  499. $array[$key] = $value;
  500. }
  501. }
  502. return $array;
  503. }
  504. /**
  505. * Finds the type of the passed value, returns the value as the new type.
  506. * @access private
  507. * @param string $value
  508. * @return mixed
  509. */
  510. function _toType($value) {
  511. if (preg_match('/^("(.*)"|\'(.*)\')/',$value,$matches)) {
  512. $value = (string)preg_replace('/(\'\'|\\\\\')/',"'",end($matches));
  513. $value = preg_replace('/\\\\"/','"',$value);
  514. } elseif (preg_match('/^\\[(.+)\\]$/',$value,$matches)) {
  515. // Inline Sequence
  516. // Take out strings sequences and mappings
  517. $explode = $this->_inlineEscape($matches[1]);
  518. // Propogate value array
  519. $value = array();
  520. foreach ($explode as $v) {
  521. $value[] = $this->_toType($v);
  522. }
  523. } elseif (strpos($value,': ')!==false && !preg_match('/^{(.+)/',$value)) {
  524. // It's a map
  525. $array = explode(': ',$value);
  526. $key = trim($array[0]);
  527. array_shift($array);
  528. $value = trim(implode(': ',$array));
  529. $value = $this->_toType($value);
  530. $value = array($key => $value);
  531. } elseif (preg_match("/{(.+)}$/",$value,$matches)) {
  532. // Inline Mapping
  533. // Take out strings sequences and mappings
  534. $explode = $this->_inlineEscape($matches[1]);
  535. // Propogate value array
  536. $array = array();
  537. foreach ($explode as $v) {
  538. $array = $array + $this->_toType($v);
  539. }
  540. $value = $array;
  541. } elseif (strtolower($value) == 'null' or $value == '' or $value == '~') {
  542. $value = NULL;
  543. } elseif (preg_match ('/^[0-9]+$/', $value)) {
  544. // Cheeky change for compartibility with PHP < 4.2.0
  545. $value = (int)$value;
  546. } elseif (in_array(strtolower($value),
  547. array('true', 'on', '+', 'yes', 'y'))) {
  548. $value = true;
  549. } elseif (in_array(strtolower($value),
  550. array('false', 'off', '-', 'no', 'n'))) {
  551. $value = false;
  552. } elseif (is_numeric($value)) {
  553. $value = (float)$value;
  554. } else {
  555. // Just a normal string, right?
  556. $value = trim(preg_replace('/#(.+)$/','',$value));
  557. }
  558. return $value;
  559. }
  560. /**
  561. * Used in inlines to check for more inlines or quoted strings
  562. * @access private
  563. * @return array
  564. */
  565. function _inlineEscape($inline) {
  566. // There's gotta be a cleaner way to do this...
  567. // While pure sequences seem to be nesting just fine,
  568. // pure mappings and mappings with sequences inside can't go very
  569. // deep. This needs to be fixed.
  570. $saved_strings = array();
  571. // Check for strings
  572. $regex = '/(?:(")|(?:\'))((?(1)[^"]+|[^\']+))(?(1)"|\')/';
  573. if (preg_match_all($regex,$inline,$strings)) {
  574. $saved_strings = $strings[0];
  575. $inline = preg_replace($regex,'YAMLString',$inline);
  576. }
  577. unset($regex);
  578. // Check for sequences
  579. if (preg_match_all('/\[(.+)\]/U',$inline,$seqs)) {
  580. $inline = preg_replace('/\[(.+)\]/U','YAMLSeq',$inline);
  581. $seqs = $seqs[0];
  582. }
  583. // Check for mappings
  584. if (preg_match_all('/{(.+)}/U',$inline,$maps)) {
  585. $inline = preg_replace('/{(.+)}/U','YAMLMap',$inline);
  586. $maps = $maps[0];
  587. }
  588. $explode = explode(', ',$inline);
  589. // Re-add the sequences
  590. if (!empty($seqs)) {
  591. $i = 0;
  592. foreach ($explode as $key => $value) {
  593. if (strpos($value,'YAMLSeq') !== false) {
  594. $explode[$key] = str_replace('YAMLSeq',$seqs[$i],$value);
  595. ++$i;
  596. }
  597. }
  598. }
  599. // Re-add the mappings
  600. if (!empty($maps)) {
  601. $i = 0;
  602. foreach ($explode as $key => $value) {
  603. if (strpos($value,'YAMLMap') !== false) {
  604. $explode[$key] = str_replace('YAMLMap',$maps[$i],$value);
  605. ++$i;
  606. }
  607. }
  608. }
  609. // Re-add the strings
  610. if (!empty($saved_strings)) {
  611. $i = 0;
  612. foreach ($explode as $key => $value) {
  613. while (strpos($value,'YAMLString') !== false) {
  614. $explode[$key] = preg_replace('/YAMLString/',$saved_strings[$i],$value, 1);
  615. ++$i;
  616. $value = $explode[$key];
  617. }
  618. }
  619. }
  620. return $explode;
  621. }
  622. /**
  623. * Builds the PHP array from all the YAML nodes we've gathered
  624. * @access private
  625. * @return array
  626. */
  627. function _buildArray() {
  628. $trunk = array();
  629. if (!isset($this->_indentSort[0])) {
  630. return $trunk;
  631. }
  632. foreach ($this->_indentSort[0] as $n) {
  633. if (empty($n->parent)) {
  634. $this->_nodeArrayizeData($n);
  635. // Check for references and copy the needed data to complete them.
  636. $this->_makeReferences($n);
  637. // Merge our data with the big array we're building
  638. $trunk = $this->_array_kmerge($trunk,$n->data);
  639. }
  640. }
  641. return $trunk;
  642. }
  643. /**
  644. * Traverses node-space and sets references (& and *) accordingly
  645. * @access private
  646. * @return bool
  647. */
  648. function _linkReferences() {
  649. if (is_array($this->_haveRefs)) {
  650. foreach ($this->_haveRefs as $node) {
  651. if (!empty($node->data)) {
  652. $key = key($node->data);
  653. // If it's an array, don't check.
  654. if (is_array($node->data[$key])) {
  655. foreach ($node->data[$key] as $k => $v) {
  656. $this->_linkRef($node,$key,$k,$v);
  657. }
  658. } else {
  659. $this->_linkRef($node,$key);
  660. }
  661. }
  662. }
  663. }
  664. return true;
  665. }
  666. function _linkRef(&$n,$key,$k = NULL,$v = NULL) {
  667. if (empty($k) && empty($v)) {
  668. // Look for &refs
  669. if (preg_match('/^&([^ ]+)/',$n->data[$key],$matches)) {
  670. // Flag the node so we know it's a reference
  671. $this->_allNodes[$n->id]->ref = substr($matches[0],1);
  672. $this->_allNodes[$n->id]->data[$key] =
  673. substr($n->data[$key],strlen($matches[0])+1);
  674. // Look for *refs
  675. } elseif (preg_match('/^\*([^ ]+)/',$n->data[$key],$matches)) {
  676. $ref = substr($matches[0],1);
  677. // Flag the node as having a reference
  678. $this->_allNodes[$n->id]->refKey = $ref;
  679. }
  680. } elseif (!empty($k) && !empty($v)) {
  681. if (preg_match('/^&([^ ]+)/',$v,$matches)) {
  682. // Flag the node so we know it's a reference
  683. $this->_allNodes[$n->id]->ref = substr($matches[0],1);
  684. $this->_allNodes[$n->id]->data[$key][$k] =
  685. substr($v,strlen($matches[0])+1);
  686. // Look for *refs
  687. } elseif (preg_match('/^\*([^ ]+)/',$v,$matches)) {
  688. $ref = substr($matches[0],1);
  689. // Flag the node as having a reference
  690. $this->_allNodes[$n->id]->refKey = $ref;
  691. }
  692. }
  693. }
  694. /**
  695. * Finds the children of a node and aids in the building of the PHP array
  696. * @access private
  697. * @param int $nid The id of the node whose children we're gathering
  698. * @return array
  699. */
  700. function _gatherChildren($nid) {
  701. $return = array();
  702. $node = $this->_allNodes[$nid];
  703. if (is_array ($this->_allParent[$node->id])) {
  704. foreach ($this->_allParent[$node->id] as $nodeZ) {
  705. $z = $this->_allNodes[$nodeZ];
  706. // We found a child
  707. $this->_nodeArrayizeData($z);
  708. // Check for references
  709. $this->_makeReferences($z);
  710. // Merge with the big array we're returning
  711. // The big array being all the data of the children of our parent node
  712. $return = $this->_array_kmerge($return,$z->data);
  713. }
  714. }
  715. return $return;
  716. }
  717. /**
  718. * Turns a node's data and its children's data into a PHP array
  719. *
  720. * @access private
  721. * @param array $node The node which you want to arrayize
  722. * @return boolean
  723. */
  724. function _nodeArrayizeData(&$node) {
  725. if (is_array($node->data) && $node->children == true) {
  726. // This node has children, so we need to find them
  727. $childs = $this->_gatherChildren($node->id);
  728. // We've gathered all our children's data and are ready to use it
  729. $key = key($node->data);
  730. $key = empty($key) ? 0 : $key;
  731. // If it's an array, add to it of course
  732. if (isset ($node->data[$key])) {
  733. if (is_array($node->data[$key])) {
  734. $node->data[$key] = $this->_array_kmerge($node->data[$key],$childs);
  735. } else {
  736. $node->data[$key] = $childs;
  737. }
  738. } else {
  739. $node->data[$key] = $childs;
  740. }
  741. } elseif (!is_array($node->data) && $node->children == true) {
  742. // Same as above, find the children of this node
  743. $childs = $this->_gatherChildren($node->id);
  744. $node->data = array();
  745. $node->data[] = $childs;
  746. }
  747. // We edited $node by reference, so just return true
  748. return true;
  749. }
  750. /**
  751. * Traverses node-space and copies references to / from this object.
  752. * @access private
  753. * @param object $z A node whose references we wish to make real
  754. * @return bool
  755. */
  756. function _makeReferences(&$z) {
  757. // It is a reference
  758. if (isset($z->ref)) {
  759. $key = key($z->data);
  760. // Copy the data to this object for easy retrieval later
  761. $this->ref[$z->ref] = $z->data[$key];
  762. // It has a reference
  763. } elseif (isset($z->refKey)) {
  764. if (isset($this->ref[$z->refKey])) {
  765. $key = key($z->data);
  766. // Copy the data from this object to make the node a real reference
  767. $z->data[$key] = $this->ref[$z->refKey];
  768. }
  769. }
  770. return true;
  771. }
  772. /**
  773. * Merges arrays and maintains numeric keys.
  774. *
  775. * An ever-so-slightly modified version of the array_kmerge() function posted
  776. * to php.net by mail at nospam dot iaindooley dot com on 2004-04-08.
  777. *
  778. * http://us3.php.net/manual/en/function.array-merge.php#41394
  779. *
  780. * @access private
  781. * @param array $arr1
  782. * @param array $arr2
  783. * @return array
  784. */
  785. function _array_kmerge($arr1,$arr2) {
  786. if(!is_array($arr1)) $arr1 = array();
  787. if(!is_array($arr2)) $arr2 = array();
  788. $keys = array_merge(array_keys($arr1),array_keys($arr2));
  789. $vals = array_merge(array_values($arr1),array_values($arr2));
  790. $ret = array();
  791. foreach($keys as $key) {
  792. list($unused,$val) = each($vals);
  793. if (isset($ret[$key]) and is_int($key)) $ret[] = $val; else $ret[$key] = $val;
  794. }
  795. return $ret;
  796. }
  797. }
  798. ?>