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

/include/spyc/spyc.php5

https://github.com/radicaldesigns/amp
PHP | 867 lines | 478 code | 69 blank | 320 comment | 96 complexity | def42567a10ad22826e6db4b991296d1 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. public $parent;
  22. public $id;
  23. /**#@+*/
  24. /**
  25. * @access public
  26. * @var mixed
  27. */
  28. public $data;
  29. /**
  30. * @access public
  31. * @var int
  32. */
  33. public $indent;
  34. /**
  35. * @access public
  36. * @var bool
  37. */
  38. public $children = false;
  39. /**
  40. * The constructor assigns the node a unique ID.
  41. * @access public
  42. * @return void
  43. */
  44. public 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. public static 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. public static 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. public 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. // if (isset ($parent->data[key($parent->data)]))
  182. $chk = $parent->data[key($parent->data)];
  183. if ($chk === '>') {
  184. $this->_inBlock = true;
  185. $this->_blockEnd = ' ';
  186. $parent->data[key($parent->data)] =
  187. str_replace('>','',$parent->data[key($parent->data)]);
  188. $parent->data[key($parent->data)] .= trim($line).' ';
  189. $this->_allNodes[$node->parent]->children = false;
  190. $this->_lastIndent = $node->indent;
  191. } elseif ($chk === '|') {
  192. $this->_inBlock = true;
  193. $this->_blockEnd = "\n";
  194. $parent->data[key($parent->data)] =
  195. str_replace('|','',$parent->data[key($parent->data)]);
  196. $parent->data[key($parent->data)] .= trim($line)."\n";
  197. $this->_allNodes[$node->parent]->children = false;
  198. $this->_lastIndent = $node->indent;
  199. }
  200. }
  201. }
  202. } elseif ($this->_lastIndent > $node->indent) {
  203. // Any block we had going is dead now
  204. if ($this->_inBlock === true) {
  205. $this->_inBlock = false;
  206. if ($this->_blockEnd = "\n") {
  207. $last = $this->_allNodes[$this->_lastNode];
  208. $last->data[key($last->data)] =
  209. trim($last->data[key($last->data)]);
  210. }
  211. }
  212. // We don't know the parent of the node so we have to find it
  213. // foreach ($this->_allNodes as $n) {
  214. foreach ($this->_indentSort[$node->indent] as $n) {
  215. if ($n->indent == $node->indent) {
  216. $node->parent = $n->parent;
  217. }
  218. }
  219. }
  220. if ($this->_inBlock === false) {
  221. // Set these properties with information from our current node
  222. $this->_lastIndent = $node->indent;
  223. // Set the last node
  224. $this->_lastNode = $node->id;
  225. // Parse the YAML line and return its data
  226. $node->data = $this->_parseLine($line);
  227. // Add the node to the master list
  228. $this->_allNodes[$node->id] = $node;
  229. // Add a reference to the parent list
  230. $this->_allParent[intval($node->parent)][] = $node->id;
  231. // Add a reference to the node in an indent array
  232. $this->_indentSort[$node->indent][] = $this->_allNodes[$node->id];
  233. // Add a reference to the node in a References array if this node
  234. // has a YAML reference in it.
  235. if (
  236. ( (is_array($node->data)) &&
  237. isset($node->data[key($node->data)]) &&
  238. (!is_array($node->data[key($node->data)])) )
  239. &&
  240. ( (preg_match('/^&([^ ]+)/',$node->data[key($node->data)]))
  241. ||
  242. (preg_match('/^\*([^ ]+)/',$node->data[key($node->data)])) )
  243. ) {
  244. $this->_haveRefs[] = $this->_allNodes[$node->id];
  245. } elseif (
  246. ( (is_array($node->data)) &&
  247. isset($node->data[key($node->data)]) &&
  248. (is_array($node->data[key($node->data)])) )
  249. ) {
  250. // Incomplete reference making code. Ugly, needs cleaned up.
  251. foreach ($node->data[key($node->data)] as $d) {
  252. if ( !is_array($d) &&
  253. ( (preg_match('/^&([^ ]+)/',$d))
  254. ||
  255. (preg_match('/^\*([^ ]+)/',$d)) )
  256. ) {
  257. $this->_haveRefs[] = $this->_allNodes[$node->id];
  258. }
  259. }
  260. }
  261. }
  262. }
  263. }
  264. unset($node);
  265. // Here we travel through node-space and pick out references (& and *)
  266. $this->_linkReferences();
  267. // Build the PHP array out of node-space
  268. $trunk = $this->_buildArray();
  269. return $trunk;
  270. }
  271. /**
  272. * Dump PHP array to YAML
  273. *
  274. * The dump method, when supplied with an array, will do its best
  275. * to convert the array into friendly YAML. Pretty simple. Feel free to
  276. * save the returned string as tasteful.yaml and pass it around.
  277. *
  278. * Oh, and you can decide how big the indent is and what the wordwrap
  279. * for folding is. Pretty cool -- just pass in 'false' for either if
  280. * you want to use the default.
  281. *
  282. * Indent's default is 2 spaces, wordwrap's default is 40 characters. And
  283. * you can turn off wordwrap by passing in 0.
  284. *
  285. * @access public
  286. * @return string
  287. * @param array $array PHP array
  288. * @param int $indent Pass in false to use the default, which is 2
  289. * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40)
  290. */
  291. public function dump($array,$indent = false,$wordwrap = false) {
  292. // Dumps to some very clean YAML. We'll have to add some more features
  293. // and options soon. And better support for folding.
  294. // New features and options.
  295. if ($indent === false or !is_numeric($indent)) {
  296. $this->_dumpIndent = 2;
  297. } else {
  298. $this->_dumpIndent = $indent;
  299. }
  300. if ($wordwrap === false or !is_numeric($wordwrap)) {
  301. $this->_dumpWordWrap = 40;
  302. } else {
  303. $this->_dumpWordWrap = $wordwrap;
  304. }
  305. // New YAML document
  306. $string = "---\n";
  307. // Start at the base of the array and move through it.
  308. foreach ($array as $key => $value) {
  309. $string .= $this->_yamlize($key,$value,0);
  310. }
  311. return $string;
  312. }
  313. /**** Private Properties ****/
  314. /**#@+
  315. * @access private
  316. * @var mixed
  317. */
  318. private $_haveRefs;
  319. private $_allNodes;
  320. private $_allParent;
  321. private $_lastIndent;
  322. private $_lastNode;
  323. private $_inBlock;
  324. private $_isInline;
  325. private $_dumpIndent;
  326. private $_dumpWordWrap;
  327. /**#@+*/
  328. /**** Public Properties ****/
  329. /**#@+
  330. * @access public
  331. * @var mixed
  332. */
  333. public $_nodeId;
  334. /**#@+*/
  335. /**** Private Methods ****/
  336. /**
  337. * Attempts to convert a key / value array item to YAML
  338. * @access private
  339. * @return string
  340. * @param $key The name of the key
  341. * @param $value The value of the item
  342. * @param $indent The indent of the current node
  343. */
  344. private function _yamlize($key,$value,$indent) {
  345. if (is_array($value)) {
  346. // It has children. What to do?
  347. // Make it the right kind of item
  348. $string = $this->_dumpNode($key,NULL,$indent);
  349. // Add the indent
  350. $indent += $this->_dumpIndent;
  351. // Yamlize the array
  352. $string .= $this->_yamlizeArray($value,$indent);
  353. } elseif (!is_array($value)) {
  354. // It doesn't have children. Yip.
  355. $string = $this->_dumpNode($key,$value,$indent);
  356. }
  357. return $string;
  358. }
  359. /**
  360. * Attempts to convert an array to YAML
  361. * @access private
  362. * @return string
  363. * @param $array The array you want to convert
  364. * @param $indent The indent of the current level
  365. */
  366. private function _yamlizeArray($array,$indent) {
  367. if (is_array($array)) {
  368. $string = '';
  369. foreach ($array as $key => $value) {
  370. $string .= $this->_yamlize($key,$value,$indent);
  371. }
  372. return $string;
  373. } else {
  374. return false;
  375. }
  376. }
  377. /**
  378. * Returns YAML from a key and a value
  379. * @access private
  380. * @return string
  381. * @param $key The name of the key
  382. * @param $value The value of the item
  383. * @param $indent The indent of the current node
  384. */
  385. private function _dumpNode($key,$value,$indent) {
  386. // do some folding here, for blocks
  387. if (strpos($value,"\n") !== false || strpos($value,": ") !== false || strpos($value,"- ") !== false) {
  388. $value = $this->_doLiteralBlock($value,$indent);
  389. } else {
  390. $value = $this->_doFolding($value,$indent);
  391. }
  392. if (is_bool($value)) {
  393. $value = ($value) ? "true" : "false";
  394. }
  395. $spaces = str_repeat(' ',$indent);
  396. if (is_int($key)) {
  397. // It's a sequence
  398. $string = $spaces.'- '.$value."\n";
  399. } else {
  400. // It's mapped
  401. $string = $spaces.$key.': '.$value."\n";
  402. }
  403. return $string;
  404. }
  405. /**
  406. * Creates a literal block for dumping
  407. * @access private
  408. * @return string
  409. * @param $value
  410. * @param $indent int The value of the indent
  411. */
  412. private function _doLiteralBlock($value,$indent) {
  413. $exploded = explode("\n",$value);
  414. $newValue = '|';
  415. $indent += $this->_dumpIndent;
  416. $spaces = str_repeat(' ',$indent);
  417. foreach ($exploded as $line) {
  418. $newValue .= "\n" . $spaces . trim($line);
  419. }
  420. return $newValue;
  421. }
  422. /**
  423. * Folds a string of text, if necessary
  424. * @access private
  425. * @return string
  426. * @param $value The string you wish to fold
  427. */
  428. private function _doFolding($value,$indent) {
  429. // Don't do anything if wordwrap is set to 0
  430. if ($this->_dumpWordWrap === 0) {
  431. return $value;
  432. }
  433. if (strlen($value) > $this->_dumpWordWrap) {
  434. $indent += $this->_dumpIndent;
  435. $indent = str_repeat(' ',$indent);
  436. $wrapped = wordwrap($value,$this->_dumpWordWrap,"\n$indent");
  437. $value = ">\n".$indent.$wrapped;
  438. }
  439. return $value;
  440. }
  441. /* Methods used in loading */
  442. /**
  443. * Finds and returns the indentation of a YAML line
  444. * @access private
  445. * @return int
  446. * @param string $line A line from the YAML file
  447. */
  448. private function _getIndent($line) {
  449. preg_match('/^\s{1,}/',$line,$match);
  450. if (!empty($match[0])) {
  451. $indent = substr_count($match[0],' ');
  452. } else {
  453. $indent = 0;
  454. }
  455. return $indent;
  456. }
  457. /**
  458. * Parses YAML code and returns an array for a node
  459. * @access private
  460. * @return array
  461. * @param string $line A line from the YAML file
  462. */
  463. private function _parseLine($line) {
  464. $line = trim($line);
  465. $array = array();
  466. if (preg_match('/^-(.*):$/',$line)) {
  467. // It's a mapped sequence
  468. $key = trim(substr(substr($line,1),0,-1));
  469. $array[$key] = '';
  470. } elseif ($line[0] == '-' && substr($line,0,3) != '---') {
  471. // It's a list item but not a new stream
  472. if (strlen($line) > 1) {
  473. $value = trim(substr($line,1));
  474. // Set the type of the value. Int, string, etc
  475. $value = $this->_toType($value);
  476. $array[] = $value;
  477. } else {
  478. $array[] = array();
  479. }
  480. } elseif (preg_match('/^(.+):/',$line,$key)) {
  481. // It's a key/value pair most likely
  482. // If the key is in double quotes pull it out
  483. if (preg_match('/^(["\'](.*)["\'](\s)*:)/',$line,$matches)) {
  484. $value = trim(str_replace($matches[1],'',$line));
  485. $key = $matches[2];
  486. } else {
  487. // Do some guesswork as to the key and the value
  488. $explode = explode(':',$line);
  489. $key = trim($explode[0]);
  490. array_shift($explode);
  491. $value = trim(implode(':',$explode));
  492. }
  493. // Set the type of the value. Int, string, etc
  494. $value = $this->_toType($value);
  495. if (empty($key)) {
  496. $array[] = $value;
  497. } else {
  498. $array[$key] = $value;
  499. }
  500. }
  501. return $array;
  502. }
  503. /**
  504. * Finds the type of the passed value, returns the value as the new type.
  505. * @access private
  506. * @param string $value
  507. * @return mixed
  508. */
  509. private function _toType($value) {
  510. if (preg_match('/^("(.*)"|\'(.*)\')/',$value,$matches)) {
  511. $value = (string)preg_replace('/(\'\'|\\\\\')/',"'",end($matches));
  512. $value = preg_replace('/\\\\"/','"',$value);
  513. } elseif (preg_match('/^\\[(.+)\\]$/',$value,$matches)) {
  514. // Inline Sequence
  515. // Take out strings sequences and mappings
  516. $explode = $this->_inlineEscape($matches[1]);
  517. // Propogate value array
  518. $value = array();
  519. foreach ($explode as $v) {
  520. $value[] = $this->_toType($v);
  521. }
  522. } elseif (strpos($value,': ')!==false && !preg_match('/^{(.+)/',$value)) {
  523. // It's a map
  524. $array = explode(': ',$value);
  525. $key = trim($array[0]);
  526. array_shift($array);
  527. $value = trim(implode(': ',$array));
  528. $value = $this->_toType($value);
  529. $value = array($key => $value);
  530. } elseif (preg_match("/{(.+)}$/",$value,$matches)) {
  531. // Inline Mapping
  532. // Take out strings sequences and mappings
  533. $explode = $this->_inlineEscape($matches[1]);
  534. // Propogate value array
  535. $array = array();
  536. foreach ($explode as $v) {
  537. $array = $array + $this->_toType($v);
  538. }
  539. $value = $array;
  540. } elseif (strtolower($value) == 'null' or $value == '' or $value == '~') {
  541. $value = NULL;
  542. } elseif (preg_match ('/^[0-9]+$/', $value)) {
  543. $value = (int)$value;
  544. } elseif (in_array(strtolower($value),
  545. array('true', 'on', '+', 'yes', 'y'))) {
  546. $value = TRUE;
  547. } elseif (in_array(strtolower($value),
  548. array('false', 'off', '-', 'no', 'n'))) {
  549. $value = FALSE;
  550. } elseif (is_numeric($value)) {
  551. $value = (float)$value;
  552. } else {
  553. // Just a normal string, right?
  554. $value = trim(preg_replace('/#(.+)$/','',$value));
  555. }
  556. return $value;
  557. }
  558. /**
  559. * Used in inlines to check for more inlines or quoted strings
  560. * @access private
  561. * @return array
  562. */
  563. private function _inlineEscape($inline) {
  564. // There's gotta be a cleaner way to do this...
  565. // While pure sequences seem to be nesting just fine,
  566. // pure mappings and mappings with sequences inside can't go very
  567. // deep. This needs to be fixed.
  568. $saved_strings = array();
  569. // Check for strings
  570. $regex = '/(?:(")|(?:\'))((?(1)[^"]+|[^\']+))(?(1)"|\')/';
  571. if (preg_match_all($regex,$inline,$strings)) {
  572. $saved_strings = $strings[0];
  573. $inline = preg_replace($regex,'YAMLString',$inline);
  574. }
  575. unset($regex);
  576. // Check for sequences
  577. if (preg_match_all('/\[(.+)\]/U',$inline,$seqs)) {
  578. $inline = preg_replace('/\[(.+)\]/U','YAMLSeq',$inline);
  579. $seqs = $seqs[0];
  580. }
  581. // Check for mappings
  582. if (preg_match_all('/{(.+)}/U',$inline,$maps)) {
  583. $inline = preg_replace('/{(.+)}/U','YAMLMap',$inline);
  584. $maps = $maps[0];
  585. }
  586. $explode = explode(', ',$inline);
  587. // Re-add the sequences
  588. if (!empty($seqs)) {
  589. $i = 0;
  590. foreach ($explode as $key => $value) {
  591. if (strpos($value,'YAMLSeq') !== false) {
  592. $explode[$key] = str_replace('YAMLSeq',$seqs[$i],$value);
  593. ++$i;
  594. }
  595. }
  596. }
  597. // Re-add the mappings
  598. if (!empty($maps)) {
  599. $i = 0;
  600. foreach ($explode as $key => $value) {
  601. if (strpos($value,'YAMLMap') !== false) {
  602. $explode[$key] = str_replace('YAMLMap',$maps[$i],$value);
  603. ++$i;
  604. }
  605. }
  606. }
  607. // Re-add the strings
  608. if (!empty($saved_strings)) {
  609. $i = 0;
  610. foreach ($explode as $key => $value) {
  611. while (strpos($value,'YAMLString') !== false) {
  612. $explode[$key] = preg_replace('/YAMLString/',$saved_strings[$i],$value, 1);
  613. ++$i;
  614. $value = $explode[$key];
  615. }
  616. }
  617. }
  618. return $explode;
  619. }
  620. /**
  621. * Builds the PHP array from all the YAML nodes we've gathered
  622. * @access private
  623. * @return array
  624. */
  625. private function _buildArray() {
  626. $trunk = array();
  627. if (!isset($this->_indentSort[0])) {
  628. return $trunk;
  629. }
  630. foreach ($this->_indentSort[0] as $n) {
  631. if (empty($n->parent)) {
  632. $this->_nodeArrayizeData($n);
  633. // Check for references and copy the needed data to complete them.
  634. $this->_makeReferences($n);
  635. // Merge our data with the big array we're building
  636. $trunk = $this->_array_kmerge($trunk,$n->data);
  637. }
  638. }
  639. return $trunk;
  640. }
  641. /**
  642. * Traverses node-space and sets references (& and *) accordingly
  643. * @access private
  644. * @return bool
  645. */
  646. private function _linkReferences() {
  647. if (is_array($this->_haveRefs)) {
  648. foreach ($this->_haveRefs as $node) {
  649. if (!empty($node->data)) {
  650. $key = key($node->data);
  651. // If it's an array, don't check.
  652. if (is_array($node->data[$key])) {
  653. foreach ($node->data[$key] as $k => $v) {
  654. $this->_linkRef($node,$key,$k,$v);
  655. }
  656. } else {
  657. $this->_linkRef($node,$key);
  658. }
  659. }
  660. }
  661. }
  662. return true;
  663. }
  664. function _linkRef(&$n,$key,$k = NULL,$v = NULL) {
  665. if (empty($k) && empty($v)) {
  666. // Look for &refs
  667. if (preg_match('/^&([^ ]+)/',$n->data[$key],$matches)) {
  668. // Flag the node so we know it's a reference
  669. $this->_allNodes[$n->id]->ref = substr($matches[0],1);
  670. $this->_allNodes[$n->id]->data[$key] =
  671. substr($n->data[$key],strlen($matches[0])+1);
  672. // Look for *refs
  673. } elseif (preg_match('/^\*([^ ]+)/',$n->data[$key],$matches)) {
  674. $ref = substr($matches[0],1);
  675. // Flag the node as having a reference
  676. $this->_allNodes[$n->id]->refKey = $ref;
  677. }
  678. } elseif (!empty($k) && !empty($v)) {
  679. if (preg_match('/^&([^ ]+)/',$v,$matches)) {
  680. // Flag the node so we know it's a reference
  681. $this->_allNodes[$n->id]->ref = substr($matches[0],1);
  682. $this->_allNodes[$n->id]->data[$key][$k] =
  683. substr($v,strlen($matches[0])+1);
  684. // Look for *refs
  685. } elseif (preg_match('/^\*([^ ]+)/',$v,$matches)) {
  686. $ref = substr($matches[0],1);
  687. // Flag the node as having a reference
  688. $this->_allNodes[$n->id]->refKey = $ref;
  689. }
  690. }
  691. }
  692. /**
  693. * Finds the children of a node and aids in the building of the PHP array
  694. * @access private
  695. * @param int $nid The id of the node whose children we're gathering
  696. * @return array
  697. */
  698. private function _gatherChildren($nid) {
  699. $return = array();
  700. $node = $this->_allNodes[$nid];
  701. if (is_array ($this->_allParent[$node->id])) {
  702. foreach ($this->_allParent[$node->id] as $nodeZ) {
  703. $z = $this->_allNodes[$nodeZ];
  704. // We found a child
  705. $this->_nodeArrayizeData($z);
  706. // Check for references
  707. $this->_makeReferences($z);
  708. // Merge with the big array we're returning
  709. // The big array being all the data of the children of our parent node
  710. $return = $this->_array_kmerge($return,$z->data);
  711. }
  712. }
  713. return $return;
  714. }
  715. /**
  716. * Turns a node's data and its children's data into a PHP array
  717. *
  718. * @access private
  719. * @param array $node The node which you want to arrayize
  720. * @return boolean
  721. */
  722. private function _nodeArrayizeData(&$node) {
  723. if (is_array($node->data) && $node->children == true) {
  724. // This node has children, so we need to find them
  725. $childs = $this->_gatherChildren($node->id);
  726. // We've gathered all our children's data and are ready to use it
  727. $key = key($node->data);
  728. $key = empty($key) ? 0 : $key;
  729. // If it's an array, add to it of course
  730. if (isset ($node->data[$key])) {
  731. if (is_array($node->data[$key])) {
  732. $node->data[$key] = $this->_array_kmerge($node->data[$key],$childs);
  733. } else {
  734. $node->data[$key] = $childs;
  735. }
  736. } else {
  737. $node->data[$key] = $childs;
  738. }
  739. } elseif (!is_array($node->data) && $node->children == true) {
  740. // Same as above, find the children of this node
  741. $childs = $this->_gatherChildren($node->id);
  742. $node->data = array();
  743. $node->data[] = $childs;
  744. }
  745. // We edited $node by reference, so just return true
  746. return true;
  747. }
  748. /**
  749. * Traverses node-space and copies references to / from this object.
  750. * @access private
  751. * @param object $z A node whose references we wish to make real
  752. * @return bool
  753. */
  754. private function _makeReferences(&$z) {
  755. // It is a reference
  756. if (isset($z->ref)) {
  757. $key = key($z->data);
  758. // Copy the data to this object for easy retrieval later
  759. $this->ref[$z->ref] = $z->data[$key];
  760. // It has a reference
  761. } elseif (isset($z->refKey)) {
  762. if (isset($this->ref[$z->refKey])) {
  763. $key = key($z->data);
  764. // Copy the data from this object to make the node a real reference
  765. $z->data[$key] = $this->ref[$z->refKey];
  766. }
  767. }
  768. return true;
  769. }
  770. /**
  771. * Merges arrays and maintains numeric keys.
  772. *
  773. * An ever-so-slightly modified version of the array_kmerge() function posted
  774. * to php.net by mail at nospam dot iaindooley dot com on 2004-04-08.
  775. *
  776. * http://us3.php.net/manual/en/function.array-merge.php#41394
  777. *
  778. * @access private
  779. * @param array $arr1
  780. * @param array $arr2
  781. * @return array
  782. */
  783. private function _array_kmerge($arr1,$arr2) {
  784. if(!is_array($arr1)) $arr1 = array();
  785. if(!is_array($arr2)) $arr2 = array();
  786. $keys = array_merge(array_keys($arr1),array_keys($arr2));
  787. $vals = array_merge(array_values($arr1),array_values($arr2));
  788. $ret = array();
  789. foreach($keys as $key) {
  790. list($unused,$val) = each($vals);
  791. if (isset($ret[$key]) and is_int($key)) $ret[] = $val; else $ret[$key] = $val;
  792. }
  793. return $ret;
  794. }
  795. }
  796. ?>