1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
16:
17: 18: 19: 20: 21: 22:
23: class Horde_Yaml_Loader
24: {
25: 26: 27: 28:
29: protected $_haveRefs = array();
30:
31: 32: 33: 34:
35: protected $_allNodes = array();
36:
37: 38: 39: 40:
41: protected $_allParent = array();
42:
43: 44: 45: 46:
47: protected $_lastIndent = 0;
48:
49: 50: 51: 52:
53: protected $_lastNode = null;
54:
55: 56: 57: 58:
59: protected $_inBlock = false;
60:
61: 62: 63:
64: protected $_isInline = false;
65:
66: 67: 68: 69:
70: protected $_nodeId = 1;
71:
72: 73: 74: 75:
76: protected $_lineNumber = 0;
77:
78: 79: 80:
81: public function __construct()
82: {
83: $base = new Horde_Yaml_Node($this->_nodeId++);
84: $base->indent = 0;
85: $this->_lastNode = $base->id;
86: }
87:
88: 89: 90: 91: 92:
93: public function toArray()
94: {
95:
96:
97: $this->_linkReferences();
98:
99:
100: return $this->_buildArray();
101: }
102:
103: 104: 105: 106: 107: 108:
109: public function parse($line)
110: {
111:
112:
113: ++$this->_lineNumber;
114:
115: $trimmed = trim($line);
116:
117:
118: if (preg_match('/^ *(\t) *[^\t ]/', $line)) {
119: $msg = "Line {$this->_lineNumber} indent contains a tab. "
120: . 'YAML only allows spaces for indentation.';
121: throw new Horde_Yaml_Exception($msg);
122: }
123:
124: if (!$this->_inBlock && empty($trimmed)) {
125: return;
126: } elseif ($this->_inBlock && empty($trimmed)) {
127: $last =& $this->_allNodes[$this->_lastNode];
128: $last->data[key($last->data)] .= "\n";
129: } elseif ($trimmed[0] != '#' && substr($trimmed, 0, 3) != '---') {
130:
131: $node = new Horde_Yaml_Node($this->_nodeId++);
132: $node->indent = $this->_getIndent($line);
133:
134:
135: if ($this->_lastIndent == $node->indent) {
136:
137: if ($this->_inBlock) {
138: $parent =& $this->_allNodes[$this->_lastNode];
139: $parent->data[key($parent->data)] .= trim($line) . $this->_blockEnd;
140: } else {
141:
142: if (isset($this->_allNodes[$this->_lastNode])) {
143: $node->parent = $this->_allNodes[$this->_lastNode]->parent;
144: }
145: }
146: } elseif ($this->_lastIndent < $node->indent) {
147: if ($this->_inBlock) {
148: $parent =& $this->_allNodes[$this->_lastNode];
149: $parent->data[key($parent->data)] .= trim($line) . $this->_blockEnd;
150: } elseif (!$this->_inBlock) {
151:
152: $node->parent = $this->_lastNode;
153:
154:
155:
156:
157: $parent =& $this->_allNodes[$node->parent];
158: $this->_allNodes[$node->parent]->children = true;
159: if (is_array($parent->data)) {
160: if (isset($parent->data[key($parent->data)])) {
161: $chk = $parent->data[key($parent->data)];
162: if ($chk === '>') {
163: $this->_inBlock = true;
164: $this->_blockEnd = '';
165: $parent->data[key($parent->data)] =
166: str_replace('>', '', $parent->data[key($parent->data)]);
167: $parent->data[key($parent->data)] .= trim($line) . ' ';
168: $this->_allNodes[$node->parent]->children = false;
169: $this->_lastIndent = $node->indent;
170: } elseif ($chk === '|') {
171: $this->_inBlock = true;
172: $this->_blockEnd = "\n";
173: $parent->data[key($parent->data)] =
174: str_replace('|', '', $parent->data[key($parent->data)]);
175: $parent->data[key($parent->data)] .= trim($line) . "\n";
176: $this->_allNodes[$node->parent]->children = false;
177: $this->_lastIndent = $node->indent;
178: }
179: }
180: }
181: }
182: } elseif ($this->_lastIndent > $node->indent) {
183:
184: if ($this->_inBlock) {
185: $this->_inBlock = false;
186: if ($this->_blockEnd == "\n") {
187: $last =& $this->_allNodes[$this->_lastNode];
188: $last->data[key($last->data)] =
189: trim($last->data[key($last->data)]);
190: }
191: }
192:
193:
194:
195: foreach ($this->_indentSort[$node->indent] as $n) {
196: if ($n->indent == $node->indent) {
197: $node->parent = $n->parent;
198: }
199: }
200: }
201:
202: if (!$this->_inBlock) {
203:
204:
205: $this->_lastIndent = $node->indent;
206:
207:
208: $this->_lastNode = $node->id;
209:
210:
211: $node->data = $this->_parseLine($line);
212:
213:
214: $this->_allNodes[$node->id] = $node;
215:
216:
217: $this->_allParent[intval($node->parent)][] = $node->id;
218:
219:
220: $this->_indentSort[$node->indent][] =& $this->_allNodes[$node->id];
221:
222:
223:
224: $is_array = is_array($node->data);
225: $key = key($node->data);
226: $isset = isset($node->data[$key]);
227: if ($isset) {
228: $nodeval = $node->data[$key];
229: }
230: if (($is_array && $isset && !is_array($nodeval) && !is_object($nodeval))
231: && (strlen($nodeval) && ($nodeval[0] == '&' || $nodeval[0] == '*') && $nodeval[1] != ' ')) {
232: $this->_haveRefs[] =& $this->_allNodes[$node->id];
233: } elseif ($is_array && $isset && is_array($nodeval)) {
234:
235:
236: foreach ($node->data[$key] as $d) {
237: if (!is_array($d) && strlen($d) && (($d[0] == '&' || $d[0] == '*') && $d[1] != ' ')) {
238: $this->_haveRefs[] =& $this->_allNodes[$node->id];
239: }
240: }
241: }
242: }
243: }
244: }
245:
246: 247: 248: 249: 250: 251:
252: protected function _getIndent($line)
253: {
254: if (preg_match('/^\s+/', $line, $match)) {
255: return strlen($match[0]);
256: } else {
257: return 0;
258: }
259: }
260:
261: 262: 263: 264: 265: 266:
267: protected function _parseLine($line)
268: {
269: $array = array();
270:
271: $line = trim($line);
272: if (preg_match('/^-(.*):$/', $line)) {
273:
274: $key = trim(substr(substr($line, 1), 0, -1));
275: $array[$key] = '';
276: } elseif ($line[0] == '-' && substr($line, 0, 3) != '---') {
277:
278: if (strlen($line) > 1) {
279:
280: $array[] = $this->_toType(trim(substr($line, 1)));
281: } else {
282: $array[] = array();
283: }
284: } elseif (preg_match('/^(.+):/', $line, $key)) {
285:
286:
287: if (preg_match('/^(["\'](.*)["\'](\s)*:)/', $line, $matches)) {
288: $value = trim(str_replace($matches[1], '', $line));
289: $key = $matches[2];
290: } else {
291:
292: $explode = explode(':', $line);
293: $key = trim(array_shift($explode));
294: $value = trim(implode(':', $explode));
295: }
296:
297:
298: $value = $this->_toType($value);
299: if (empty($key)) {
300: $array[] = $value;
301: } else {
302: $array[$key] = $value;
303: }
304: }
305:
306: return $array;
307: }
308:
309: 310: 311: 312: 313: 314:
315: protected function _toType($value)
316: {
317:
318: self::_unserialize($value);
319: if (!is_scalar($value)) {
320: return $value;
321: }
322:
323:
324: $lower_value = strtolower($value);
325:
326: if (preg_match('/^("(.*)"|\'(.*)\')/', $value, $matches)) {
327: $value = (string)str_replace(array('\'\'', '\\\''), "'", end($matches));
328: $value = str_replace('\\"', '"', $value);
329: } elseif (preg_match('/^\\[(\s*)\\]$/', $value)) {
330:
331: $value = array();
332: } elseif (preg_match('/^\\[(.+)\\]$/', $value, $matches)) {
333:
334:
335:
336: $explode = $this->_inlineEscape($matches[1]);
337:
338:
339: $value = array();
340: foreach ($explode as $v) {
341: $value[] = $this->_toType($v);
342: }
343: } elseif (preg_match('/^\\{(\s*)\\}$/', $value)) {
344:
345: $value = array();
346: } elseif (strpos($value, ': ') !== false && !preg_match('/^{(.+)/', $value)) {
347:
348: $array = explode(': ', $value);
349: $key = trim($array[0]);
350: array_shift($array);
351: $value = trim(implode(': ', $array));
352: $value = $this->_toType($value);
353: $value = array($key => $value);
354: } elseif (preg_match("/{(.+)}$/", $value, $matches)) {
355:
356:
357:
358: $explode = $this->_inlineEscape($matches[1]);
359:
360:
361: $array = array();
362: foreach ($explode as $v) {
363: $array = $array + $this->_toType($v);
364: }
365: $value = $array;
366: } elseif ($lower_value == 'null' || $value == '' || $value == '~') {
367: $value = null;
368: } elseif ($lower_value == '.nan') {
369: $value = NAN;
370: } elseif ($lower_value == '.inf') {
371: $value = INF;
372: } elseif ($lower_value == '-.inf') {
373: $value = -INF;
374: } elseif (ctype_digit($value)) {
375: $value = (int)$value;
376: } elseif (in_array($lower_value,
377: array('true', 'on', '+', 'yes', 'y'))) {
378: $value = true;
379: } elseif (in_array($lower_value,
380: array('false', 'off', '-', 'no', 'n'))) {
381: $value = false;
382: } elseif (is_numeric($value)) {
383: $value = (float)$value;
384: } else {
385:
386: if (($pos = strpos($value, '#')) !== false) {
387: $value = substr($value, 0, $pos);
388: }
389: $value = trim($value);
390: }
391:
392: return $value;
393: }
394:
395: 396: 397: 398: 399:
400: protected function _unserialize(&$data)
401: {
402: if (substr($data, 0, 5) != '!php/') {
403: return;
404: }
405:
406: $first_space = strpos($data, ' ');
407: $type = substr($data, 5, $first_space - 5);
408: $class = null;
409: if (strpos($type, '::') !== false) {
410: list($type, $class) = explode('::', $type);
411:
412: if (!in_array($class, Horde_Yaml::$allowedClasses)) {
413: throw new Horde_Yaml_Exception("$class is not in the list of allowed classes");
414: }
415: }
416:
417: switch ($type) {
418: case 'object':
419: if (!class_exists($class)) {
420: throw new Horde_Yaml_Exception("$class is not defined");
421: }
422:
423: $reflector = new ReflectionClass($class);
424: if (!$reflector->implementsInterface('Serializable')) {
425: throw new Horde_Yaml_Exception("$class does not implement Serializable");
426: }
427:
428: $class_data = substr($data, $first_space + 1);
429: $serialized = 'C:' . strlen($class) . ':"' . $class . '":' . strlen($class_data) . ':{' . $class_data . '}';
430: $data = unserialize($serialized);
431: break;
432:
433: case 'array':
434: case 'hash':
435: $array_data = substr($data, $first_space + 1);
436: $array_data = Horde_Yaml::load('a: ' . $array_data);
437:
438: if (is_null($class)) {
439: $data = $array_data['a'];
440: } else {
441: if (!class_exists($class)) {
442: throw new Horde_Yaml_Exception("$class is not defined");
443: }
444:
445: $array = new $class;
446: if (!$array instanceof ArrayAccess) {
447: throw new Horde_Yaml_Exception("$class does not implement ArrayAccess");
448: }
449:
450: foreach ($array_data['a'] as $key => $val) {
451: $array[$key] = $val;
452: }
453:
454: $data = $array;
455: }
456: break;
457: }
458: }
459:
460: 461: 462: 463: 464: 465: 466: 467: 468: 469: 470:
471: protected function _inlineEscape($inline)
472: {
473: $saved_strings = array();
474:
475:
476: $regex = '/(?:(")|(?:\'))((?(1)[^"]+|[^\']+))(?(1)"|\')/';
477: if (preg_match_all($regex, $inline, $strings)) {
478: $saved_strings = $strings[0];
479: $inline = preg_replace($regex, 'YAMLString', $inline);
480: }
481:
482:
483: if (preg_match_all('/\[(.+)\]/U', $inline, $seqs)) {
484: $inline = preg_replace('/\[(.+)\]/U', 'YAMLSeq', $inline);
485: $seqs = $seqs[0];
486: }
487:
488:
489: if (preg_match_all('/{(.+)}/U', $inline, $maps)) {
490: $inline = preg_replace('/{(.+)}/U', 'YAMLMap', $inline);
491: $maps = $maps[0];
492: }
493:
494: $explode = explode(', ', $inline);
495:
496:
497: if (!empty($seqs)) {
498: $i = 0;
499: foreach ($explode as $key => $value) {
500: if (strpos($value, 'YAMLSeq') !== false) {
501: $explode[$key] = str_replace('YAMLSeq', $seqs[$i], $value);
502: ++$i;
503: }
504: }
505: }
506:
507:
508: if (!empty($maps)) {
509: $i = 0;
510: foreach ($explode as $key => $value) {
511: if (strpos($value, 'YAMLMap') !== false) {
512: $explode[$key] = str_replace('YAMLMap', $maps[$i], $value);
513: ++$i;
514: }
515: }
516: }
517:
518:
519: if (!empty($saved_strings)) {
520: $i = 0;
521: foreach ($explode as $key => $value) {
522: while (strpos($value, 'YAMLString') !== false) {
523: $explode[$key] = preg_replace('/YAMLString/', $saved_strings[$i], $value, 1);
524: ++$i;
525: $value = $explode[$key];
526: }
527: }
528: }
529:
530: return $explode;
531: }
532:
533: 534: 535: 536: 537:
538: protected function _buildArray()
539: {
540: $trunk = array();
541: if (!isset($this->_indentSort[0])) {
542: return $trunk;
543: }
544:
545: foreach ($this->_indentSort[0] as $n) {
546: if (empty($n->parent)) {
547: $this->_nodeArrayizeData($n);
548:
549:
550: $this->_makeReferences($n);
551:
552:
553: $trunk = $this->_array_kmerge($trunk, $n->data);
554: }
555: }
556:
557: return $trunk;
558: }
559:
560: 561: 562: 563: 564:
565: protected function _linkReferences()
566: {
567: if (is_array($this->_haveRefs)) {
568: foreach ($this->_haveRefs as $node) {
569: if (!empty($node->data)) {
570: $key = key($node->data);
571:
572: if (is_array($node->data[$key])) {
573: foreach ($node->data[$key] as $k => $v) {
574: $this->_linkRef($node, $key, $k, $v);
575: }
576: } else {
577: $this->_linkRef($node, $key);
578: }
579: }
580: }
581: }
582:
583: return true;
584: }
585:
586: 587: 588: 589: 590: 591: 592: 593:
594: function _linkRef(&$n, $key, $k = null, $v = null)
595: {
596: if (empty($k) && empty($v)) {
597:
598: if (preg_match('/^&([^ ]+)/', $n->data[$key], $matches)) {
599:
600: $this->_allNodes[$n->id]->ref = substr($matches[0], 1);
601: $this->_allNodes[$n->id]->data[$key] =
602: substr($n->data[$key], strlen($matches[0]) + 1);
603:
604: } elseif (preg_match('/^\*([^ ]+)/', $n->data[$key], $matches)) {
605: $ref = substr($matches[0], 1);
606:
607: $this->_allNodes[$n->id]->refKey = $ref;
608: }
609: } elseif (!empty($k) && !empty($v)) {
610: if (preg_match('/^&([^ ]+)/', $v, $matches)) {
611:
612: $this->_allNodes[$n->id]->ref = substr($matches[0], 1);
613: $this->_allNodes[$n->id]->data[$key][$k] =
614: substr($v, strlen($matches[0]) + 1);
615:
616: } elseif (preg_match('/^\*([^ ]+)/', $v, $matches)) {
617: $ref = substr($matches[0], 1);
618:
619: $this->_allNodes[$n->id]->refKey = $ref;
620: }
621: }
622: }
623:
624: 625: 626: 627: 628: 629:
630: protected function _gatherChildren($nid)
631: {
632: $return = array();
633: $node =& $this->_allNodes[$nid];
634: if (is_array ($this->_allParent[$node->id])) {
635: foreach ($this->_allParent[$node->id] as $nodeZ) {
636: $z =& $this->_allNodes[$nodeZ];
637:
638: $this->_nodeArrayizeData($z);
639:
640:
641: $this->_makeReferences($z);
642:
643:
644:
645:
646: $return = $this->_array_kmerge($return, $z->data);
647: }
648: }
649: return $return;
650: }
651:
652: 653: 654: 655: 656: 657:
658: protected function _nodeArrayizeData(&$node)
659: {
660: if ($node->children == true) {
661: if (is_array($node->data)) {
662:
663: $children = $this->_gatherChildren($node->id);
664:
665:
666: $key = key($node->data);
667: $key = empty($key) ? 0 : $key;
668:
669: if (isset($node->data[$key])) {
670: if (is_array($node->data[$key])) {
671: $node->data[$key] = $this->_array_kmerge($node->data[$key], $children);
672: } else {
673: $node->data[$key] = $children;
674: }
675: } else {
676: $node->data[$key] = $children;
677: }
678: } else {
679:
680: $children = $this->_gatherChildren($node->id);
681: $node->data = array();
682: $node->data[] = $children;
683: }
684: } else {
685:
686: if (is_array($node->data)) {
687: $key = key($node->data);
688: $key = empty($key) ? 0 : $key;
689:
690: if (!isset($node->data[$key]) || is_array($node->data[$key]) || is_object($node->data[$key])) {
691: return true;
692: }
693:
694: self::_unserialize($node->data[$key]);
695: } elseif (is_string($node->data)) {
696: self::_unserialize($node->data);
697: }
698: }
699:
700:
701: return true;
702: }
703:
704: 705: 706: 707: 708: 709:
710: protected function _makeReferences(&$z)
711: {
712:
713: if (isset($z->ref)) {
714: $key = key($z->data);
715:
716: $this->ref[$z->ref] =& $z->data[$key];
717:
718: } elseif (isset($z->refKey)) {
719: if (isset($this->ref[$z->refKey])) {
720: $key = key($z->data);
721:
722: $z->data[$key] =& $this->ref[$z->refKey];
723: }
724: }
725:
726: return true;
727: }
728:
729: 730: 731: 732: 733: 734: 735: 736: 737:
738: protected function _array_kmerge($arr1, $arr2)
739: {
740: while (list($key, $val) = each($arr2)) {
741: if (isset($arr1[$key]) && is_int($key)) {
742: $arr1[] = $val;
743: } else {
744: $arr1[$key] = $val;
745: }
746: }
747:
748: return $arr1;
749: }
750:
751: }
752: