1 <?php
2 /**
3    * Spyc -- A Simple PHP YAML Class
4    * @version 0.5
5    * @author Vlad Andersen <vlad.andersen@gmail.com>
6    * @author Chris Wanstrath <chris@ozmm.org>
7    * @link http://code.google.com/p/spyc/
8    * @copyright Copyright 2005-2006 Chris Wanstrath, 2006-2011 Vlad Andersen
9    * @license http://www.opensource.org/licenses/mit-license.php MIT License
10    * @package Spyc
11    */
12 
13 if (!function_exists('spyc_load')) {
14   /**
15    * Parses YAML to array.
16    * @param string $string YAML string.
17    * @return array
18    */
19   function spyc_load ($string) {
20     return Spyc::YAMLLoadString($string);
21   }
22 }
23 
24 if (!function_exists('spyc_load_file')) {
25   /**
26    * Parses YAML to array.
27    * @param string $file Path to YAML file.
28    * @return array
29    */
30   function spyc_load_file ($file) {
31     return Spyc::YAMLLoad($file);
32   }
33 }
34 
35 /**
36    * The Simple PHP YAML Class.
37    *
38    * This class can be used to read a YAML file and convert its contents
39    * into a PHP array.  It currently supports a very limited subsection of
40    * the YAML spec.
41    *
42    * Usage:
43    * <code>
44    *   $Spyc  = new Spyc;
45    *   $array = $Spyc->load($file);
46    * </code>
47    * or:
48    * <code>
49    *   $array = Spyc::YAMLLoad($file);
50    * </code>
51    * or:
52    * <code>
53    *   $array = spyc_load_file($file);
54    * </code>
55    * @package Spyc
56    */
57 class Spyc {
58 
59   // SETTINGS
60 
61   const REMPTY = "\0\0\0\0\0";
62 
63   /**
64    * Setting this to true will force YAMLDump to enclose any string value in
65    * quotes.  False by default.
66    *
67    * @var bool
68    */
69   public $setting_dump_force_quotes = false;
70 
71   /**
72    * Setting this to true will forse YAMLLoad to use syck_load function when
73    * possible. False by default.
74    * @var bool
75    */
76   public $setting_use_syck_is_possible = false;
77 
78 
79 
80   /**#@+
81   * @access private
82   * @var mixed
83   */
84   private $_dumpIndent;
85   private $_dumpWordWrap;
86   private $_containsGroupAnchor = false;
87   private $_containsGroupAlias = false;
88   private $path;
89   private $result;
90   private $LiteralPlaceHolder = '___YAML_Literal_Block___';
91   private $SavedGroups = array();
92   private $indent;
93   /**
94    * Path modifier that should be applied after adding current element.
95    * @var array
96    */
97   private $delayedPath = array();
98 
99   /**#@+
100   * @access public
101   * @var mixed
102   */
103   public $_nodeId;
104 
105 /**
106  * Load a valid YAML string to Spyc.
107  * @param string $input
108  * @return array
109  */
110   public function load ($input) {
111     return $this->__loadString($input);
112   }
113 
114  /**
115  * Load a valid YAML file to Spyc.
116  * @param string $file
117  * @return array
118  */
119   public function loadFile ($file) {
120     return $this->__load($file);
121   }
122 
123   /**
124      * Load YAML into a PHP array statically
125      *
126      * The load method, when supplied with a YAML stream (string or file),
127      * will do its best to convert YAML in a file into a PHP array.  Pretty
128      * simple.
129      *  Usage:
130      *  <code>
131      *   $array = Spyc::YAMLLoad('lucky.yaml');
132      *   print_r($array);
133      *  </code>
134      * @access public
135      * @return array
136      * @param string $input Path of YAML file or string containing YAML
137      */
138   public static function YAMLLoad($input) {
139     $Spyc = new Spyc;
140     return $Spyc->__load($input);
141   }
142 
143   /**
144      * Load a string of YAML into a PHP array statically
145      *
146      * The load method, when supplied with a YAML string, will do its best
147      * to convert YAML in a string into a PHP array.  Pretty simple.
148      *
149      * Note: use this function if you don't want files from the file system
150      * loaded and processed as YAML.  This is of interest to people concerned
151      * about security whose input is from a string.
152      *
153      *  Usage:
154      *  <code>
155      *   $array = Spyc::YAMLLoadString("---\n0: hello world\n");
156      *   print_r($array);
157      *  </code>
158      * @access public
159      * @return array
160      * @param string $input String containing YAML
161      */
162   public static function YAMLLoadString($input) {
163     $Spyc = new Spyc;
164     return $Spyc->__loadString($input);
165   }
166 
167   /**
168      * Dump YAML from PHP array statically
169      *
170      * The dump method, when supplied with an array, will do its best
171      * to convert the array into friendly YAML.  Pretty simple.  Feel free to
172      * save the returned string as nothing.yaml and pass it around.
173      *
174      * Oh, and you can decide how big the indent is and what the wordwrap
175      * for folding is.  Pretty cool -- just pass in 'false' for either if
176      * you want to use the default.
177      *
178      * Indent's default is 2 spaces, wordwrap's default is 40 characters.  And
179      * you can turn off wordwrap by passing in 0.
180      *
181      * @access public
182      * @return string
183      * @param array $array PHP array
184      * @param int $indent Pass in false to use the default, which is 2
185      * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40)
186      */
187   public static function YAMLDump($array,$indent = false,$wordwrap = false) {
188     $spyc = new Spyc;
189     return $spyc->dump($array,$indent,$wordwrap);
190   }
191 
192 
193   /**
194      * Dump PHP array to YAML
195      *
196      * The dump method, when supplied with an array, will do its best
197      * to convert the array into friendly YAML.  Pretty simple.  Feel free to
198      * save the returned string as tasteful.yaml and pass it around.
199      *
200      * Oh, and you can decide how big the indent is and what the wordwrap
201      * for folding is.  Pretty cool -- just pass in 'false' for either if
202      * you want to use the default.
203      *
204      * Indent's default is 2 spaces, wordwrap's default is 40 characters.  And
205      * you can turn off wordwrap by passing in 0.
206      *
207      * @access public
208      * @return string
209      * @param array $array PHP array
210      * @param int $indent Pass in false to use the default, which is 2
211      * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40)
212      */
213   public function dump($array,$indent = false,$wordwrap = false) {
214     // Dumps to some very clean YAML.  We'll have to add some more features
215     // and options soon.  And better support for folding.
216 
217     // New features and options.
218     if ($indent === false or !is_numeric($indent)) {
219       $this->_dumpIndent = 2;
220     } else {
221       $this->_dumpIndent = $indent;
222     }
223 
224     if ($wordwrap === false or !is_numeric($wordwrap)) {
225       $this->_dumpWordWrap = 40;
226     } else {
227       $this->_dumpWordWrap = $wordwrap;
228     }
229 
230     // New YAML document
231     $string = "---\n";
232 
233     // Start at the base of the array and move through it.
234     if ($array) {
235       $array = (array)$array;
236       $previous_key = -1;
237       foreach ($array as $key => $value) {
238         if (!isset($first_key)) $first_key = $key;
239         $string .= $this->_yamlize($key,$value,0,$previous_key, $first_key, $array);
240         $previous_key = $key;
241       }
242     }
243     return $string;
244   }
245 
246   /**
247      * Attempts to convert a key / value array item to YAML
248      * @access private
249      * @return string
250      * @param $key The name of the key
251      * @param $value The value of the item
252      * @param $indent The indent of the current node
253      */
254   private function _yamlize($key,$value,$indent, $previous_key = -1, $first_key = 0, $source_array = null) {
255     if (is_array($value)) {
256       if (empty ($value))
257         return $this->_dumpNode($key, array(), $indent, $previous_key, $first_key, $source_array);
258       // It has children.  What to do?
259       // Make it the right kind of item
260       $string = $this->_dumpNode($key, self::REMPTY, $indent, $previous_key, $first_key, $source_array);
261       // Add the indent
262       $indent += $this->_dumpIndent;
263       // Yamlize the array
264       $string .= $this->_yamlizeArray($value,$indent);
265     } elseif (!is_array($value)) {
266       // It doesn't have children.  Yip.
267       $string = $this->_dumpNode($key, $value, $indent, $previous_key, $first_key, $source_array);
268     }
269     return $string;
270   }
271 
272   /**
273      * Attempts to convert an array to YAML
274      * @access private
275      * @return string
276      * @param $array The array you want to convert
277      * @param $indent The indent of the current level
278      */
279   private function _yamlizeArray($array,$indent) {
280     if (is_array($array)) {
281       $string = '';
282       $previous_key = -1;
283       foreach ($array as $key => $value) {
284         if (!isset($first_key)) $first_key = $key;
285         $string .= $this->_yamlize($key, $value, $indent, $previous_key, $first_key, $array);
286         $previous_key = $key;
287       }
288       return $string;
289     } else {
290       return false;
291     }
292   }
293 
294   /**
295      * Returns YAML from a key and a value
296      * @access private
297      * @return string
298      * @param $key The name of the key
299      * @param $value The value of the item
300      * @param $indent The indent of the current node
301      */
302   private function _dumpNode($key, $value, $indent, $previous_key = -1, $first_key = 0, $source_array = null) {
303     // do some folding here, for blocks
304     if (is_string ($value) && ((strpos($value,"\n") !== false || strpos($value,": ") !== false || strpos($value,"- ") !== false ||
305       strpos($value,"*") !== false || strpos($value,"#") !== false || strpos($value,"<") !== false || strpos($value,">") !== false || strpos ($value, '  ') !== false ||
306       strpos($value,"[") !== false || strpos($value,"]") !== false || strpos($value,"{") !== false || strpos($value,"}") !== false) || strpos($value,"&") !== false || strpos($value, "'") !== false || strpos($value, "!") === 0 ||
307       substr ($value, -1, 1) == ':')
308     ) {
309       $value = $this->_doLiteralBlock($value,$indent);
310     } else {
311       $value  = $this->_doFolding($value,$indent);
312     }
313 
314     if ($value === array()) $value = '[ ]';
315     if (in_array ($value, array ('true', 'TRUE', 'false', 'FALSE', 'y', 'Y', 'n', 'N', 'null', 'NULL'), true)) {
316        $value = $this->_doLiteralBlock($value,$indent);
317     }
318     if (trim ($value) != $value)
319        $value = $this->_doLiteralBlock($value,$indent);
320 
321     if (is_bool($value)) {
322        $value = ($value) ? "true" : "false";
323     }
324 
325     if ($value === null) $value = 'null';
326     if ($value === "'" . self::REMPTY . "'") $value = null;
327 
328     $spaces = str_repeat(' ',$indent);
329 
330     //if (is_int($key) && $key - 1 == $previous_key && $first_key===0) {
331     if (is_array ($source_array) && array_keys($source_array) === range(0, count($source_array) - 1)) {
332       // It's a sequence
333       $string = $spaces.'- '.$value."\n";
334     } else {
335       // if ($first_key===0)  throw new Exception('Keys are all screwy.  The first one was zero, now it\'s "'. $key .'"');
336       // It's mapped
337       if (strpos($key, ":") !== false || strpos($key, "#") !== false) { $key = '"' . $key . '"'; }
338       $string = rtrim ($spaces.$key.': '.$value)."\n";
339     }
340     return $string;
341   }
342 
343   /**
344      * Creates a literal block for dumping
345      * @access private
346      * @return string
347      * @param $value
348      * @param $indent int The value of the indent
349      */
350   private function _doLiteralBlock($value,$indent) {
351     if ($value === "\n") return '\n';
352     if (strpos($value, "\n") === false && strpos($value, "'") === false) {
353       return sprintf ("'%s'", $value);
354     }
355     if (strpos($value, "\n") === false && strpos($value, '"') === false) {
356       return sprintf ('"%s"', $value);
357     }
358     $exploded = explode("\n",$value);
359     $newValue = '|';
360     $indent  += $this->_dumpIndent;
361     $spaces   = str_repeat(' ',$indent);
362     foreach ($exploded as $line) {
363       $newValue .= "\n" . $spaces . ($line);
364     }
365     return $newValue;
366   }
367 
368   /**
369      * Folds a string of text, if necessary
370      * @access private
371      * @return string
372      * @param $value The string you wish to fold
373      */
374   private function _doFolding($value,$indent) {
375     // Don't do anything if wordwrap is set to 0
376 
377     if ($this->_dumpWordWrap !== 0 && is_string ($value) && strlen($value) > $this->_dumpWordWrap) {
378       $indent += $this->_dumpIndent;
379       $indent = str_repeat(' ',$indent);
380       $wrapped = wordwrap($value,$this->_dumpWordWrap,"\n$indent");
381       $value   = ">\n".$indent.$wrapped;
382     } else {
383       if ($this->setting_dump_force_quotes && is_string ($value) && $value !== self::REMPTY)
384         $value = '"' . $value . '"';
385     }
386 
387 
388     return $value;
389   }
390 
391 // LOADING FUNCTIONS
392 
393   private function __load($input) {
394     $Source = $this->loadFromSource($input);
395     return $this->loadWithSource($Source);
396   }
397 
398   private function __loadString($input) {
399     $Source = $this->loadFromString($input);
400     return $this->loadWithSource($Source);
401   }
402 
403   private function loadWithSource($Source) {
404     if (empty ($Source)) return array();
405     if ($this->setting_use_syck_is_possible && function_exists ('syck_load')) {
406       $array = syck_load (implode ('', $Source));
407       return is_array($array) ? $array : array();
408     }
409 
410     $this->path = array();
411     $this->result = array();
412 
413     $cnt = count($Source);
414     for ($i = 0; $i < $cnt; $i++) {
415       $line = $Source[$i];
416 
417       $this->indent = strlen($line) - strlen(ltrim($line));
418       $tempPath = $this->getParentPathByIndent($this->indent);
419       $line = self::stripIndent($line, $this->indent);
420       if (self::isComment($line)) continue;
421       if (self::isEmpty($line)) continue;
422       $this->path = $tempPath;
423 
424       $literalBlockStyle = self::startsLiteralBlock($line);
425       if ($literalBlockStyle) {
426         $line = rtrim ($line, $literalBlockStyle . " \n");
427         $literalBlock = '';
428         $line .= $this->LiteralPlaceHolder;
429         $literal_block_indent = strlen($Source[$i+1]) - strlen(ltrim($Source[$i+1]));
430         while (++$i < $cnt && $this->literalBlockContinues($Source[$i], $this->indent)) {
431           $literalBlock = $this->addLiteralLine($literalBlock, $Source[$i], $literalBlockStyle, $literal_block_indent);
432         }
433         $i--;
434       }
435 
436       while (++$i < $cnt && self::greedilyNeedNextLine($line)) {
437         $line = rtrim ($line, " \n\t\r") . ' ' . ltrim ($Source[$i], " \t");
438       }
439       $i--;
440 
441 
442 
443       if (strpos ($line, '#')) {
444         if (strpos ($line, '"') === false && strpos ($line, "'") === false)
445           $line = preg_replace('/\s+#(.+)$/','',$line);
446       }
447 
448       $lineArray = $this->_parseLine($line);
449 
450       if ($literalBlockStyle)
451         $lineArray = $this->revertLiteralPlaceHolder ($lineArray, $literalBlock);
452 
453       $this->addArray($lineArray, $this->indent);
454 
455       foreach ($this->delayedPath as $indent => $delayedPath)
456         $this->path[$indent] = $delayedPath;
457 
458       $this->delayedPath = array();
459 
460     }
461     return $this->result;
462   }
463 
464   private function loadFromSource ($input) {
465     if (!empty($input) && strpos($input, "\n") === false && file_exists($input))
466     return file($input);
467 
468     return $this->loadFromString($input);
469   }
470 
471   private function loadFromString ($input) {
472     $lines = explode("\n",$input);
473     foreach ($lines as $k => $_) {
474       $lines[$k] = rtrim ($_, "\r");
475     }
476     return $lines;
477   }
478 
479   /**
480      * Parses YAML code and returns an array for a node
481      * @access private
482      * @return array
483      * @param string $line A line from the YAML file
484      */
485   private function _parseLine($line) {
486     if (!$line) return array();
487     $line = trim($line);
488     if (!$line) return array();
489 
490     $array = array();
491 
492     $group = $this->nodeContainsGroup($line);
493     if ($group) {
494       $this->addGroup($line, $group);
495       $line = $this->stripGroup ($line, $group);
496     }
497 
498     if ($this->startsMappedSequence($line))
499       return $this->returnMappedSequence($line);
500 
501     if ($this->startsMappedValue($line))
502       return $this->returnMappedValue($line);
503 
504     if ($this->isArrayElement($line))
505      return $this->returnArrayElement($line);
506 
507     if ($this->isPlainArray($line))
508      return $this->returnPlainArray($line);
509 
510 
511     return $this->returnKeyValuePair($line);
512 
513   }
514 
515   /**
516      * Finds the type of the passed value, returns the value as the new type.
517      * @access private
518      * @param string $value
519      * @return mixed
520      */
521   private function _toType($value) {
522     if ($value === '') return null;
523     $first_character = $value[0];
524     $last_character = substr($value, -1, 1);
525 
526     $is_quoted = false;
527     do {
528       if (!$value) break;
529       if ($first_character != '"' && $first_character != "'") break;
530       if ($last_character != '"' && $last_character != "'") break;
531       $is_quoted = true;
532     } while (0);
533 
534     if ($is_quoted)
535       return strtr(substr ($value, 1, -1), array ('\\"' => '"', '\'\'' => '\'', '\\\'' => '\''));
536 
537     if (strpos($value, ' #') !== false && !$is_quoted)
538       $value = preg_replace('/\s+#(.+)$/','',$value);
539 
540     if (!$is_quoted) $value = str_replace('\n', "\n", $value);
541 
542     if ($first_character == '[' && $last_character == ']') {
543       // Take out strings sequences and mappings
544       $innerValue = trim(substr ($value, 1, -1));
545       if ($innerValue === '') return array();
546       $explode = $this->_inlineEscape($innerValue);
547       // Propagate value array
548       $value  = array();
549       foreach ($explode as $v) {
550         $value[] = $this->_toType($v);
551       }
552       return $value;
553     }
554 
555     if (strpos($value,': ')!==false && $first_character != '{') {
556       $array = explode(': ',$value);
557       $key   = trim($array[0]);
558       array_shift($array);
559       $value = trim(implode(': ',$array));
560       $value = $this->_toType($value);
561       return array($key => $value);
562     }
563 
564     if ($first_character == '{' && $last_character == '}') {
565       $innerValue = trim(substr ($value, 1, -1));
566       if ($innerValue === '') return array();
567       // Inline Mapping
568       // Take out strings sequences and mappings
569       $explode = $this->_inlineEscape($innerValue);
570       // Propagate value array
571       $array = array();
572       foreach ($explode as $v) {
573         $SubArr = $this->_toType($v);
574         if (empty($SubArr)) continue;
575         if (is_array ($SubArr)) {
576           $array[key($SubArr)] = $SubArr[key($SubArr)]; continue;
577         }
578         $array[] = $SubArr;
579       }
580       return $array;
581     }
582 
583     if ($value == 'null' || $value == 'NULL' || $value == 'Null' || $value == '' || $value == '~') {
584       return null;
585     }
586 
587     if ( is_numeric($value) && preg_match ('/^(-|)[1-9]+[0-9]*$/', $value) ){
588       $intvalue = (int)$value;
589       if ($intvalue != PHP_INT_MAX)
590         $value = $intvalue;
591       return $value;
592     }
593 
594     if (in_array($value,
595                  array('true', 'on', '+', 'yes', 'y', 'True', 'TRUE', 'On', 'ON', 'YES', 'Yes', 'Y'))) {
596       return true;
597     }
598 
599     if (in_array(strtolower($value),
600                  array('false', 'off', '-', 'no', 'n'))) {
601       return false;
602     }
603 
604     if (is_numeric($value)) {
605       if ($value === '0') return 0;
606       if (rtrim ($value, 0) === $value)
607         $value = (float)$value;
608       return $value;
609     }
610 
611     return $value;
612   }
613 
614   /**
615      * Used in inlines to check for more inlines or quoted strings
616      * @access private
617      * @return array
618      */
619   private function _inlineEscape($inline) {
620     // There's gotta be a cleaner way to do this...
621     // While pure sequences seem to be nesting just fine,
622     // pure mappings and mappings with sequences inside can't go very
623     // deep.  This needs to be fixed.
624 
625     $seqs = array();
626     $maps = array();
627     $saved_strings = array();
628 
629     // Check for strings
630     $regex = '/(?:(")|(?:\'))((?(1)[^"]+|[^\']+))(?(1)"|\')/';
631     if (preg_match_all($regex,$inline,$strings)) {
632       $saved_strings = $strings[0];
633       $inline  = preg_replace($regex,'YAMLString',$inline);
634     }
635     unset($regex);
636 
637     $i = 0;
638     do {
639 
640     // Check for sequences
641     while (preg_match('/\[([^{}\[\]]+)\]/U',$inline,$matchseqs)) {
642       $seqs[] = $matchseqs[0];
643       $inline = preg_replace('/\[([^{}\[\]]+)\]/U', ('YAMLSeq' . (count($seqs) - 1) . 's'), $inline, 1);
644     }
645 
646     // Check for mappings
647     while (preg_match('/{([^\[\]{}]+)}/U',$inline,$matchmaps)) {
648       $maps[] = $matchmaps[0];
649       $inline = preg_replace('/{([^\[\]{}]+)}/U', ('YAMLMap' . (count($maps) - 1) . 's'), $inline, 1);
650     }
651 
652     if ($i++ >= 10) break;
653 
654     } while (strpos ($inline, '[') !== false || strpos ($inline, '{') !== false);
655 
656     $explode = explode(', ',$inline);
657     $stringi = 0; $i = 0;
658 
659     while (1) {
660 
661     // Re-add the sequences
662     if (!empty($seqs)) {
663       foreach ($explode as $key => $value) {
664         if (strpos($value,'YAMLSeq') !== false) {
665           foreach ($seqs as $seqk => $seq) {
666             $explode[$key] = str_replace(('YAMLSeq'.$seqk.'s'),$seq,$value);
667             $value = $explode[$key];
668           }
669         }
670       }
671     }
672 
673     // Re-add the mappings
674     if (!empty($maps)) {
675       foreach ($explode as $key => $value) {
676         if (strpos($value,'YAMLMap') !== false) {
677           foreach ($maps as $mapk => $map) {
678             $explode[$key] = str_replace(('YAMLMap'.$mapk.'s'), $map, $value);
679             $value = $explode[$key];
680           }
681         }
682       }
683     }
684 
685 
686     // Re-add the strings
687     if (!empty($saved_strings)) {
688       foreach ($explode as $key => $value) {
689         while (strpos($value,'YAMLString') !== false) {
690           $explode[$key] = preg_replace('/YAMLString/',$saved_strings[$stringi],$value, 1);
691           unset($saved_strings[$stringi]);
692           ++$stringi;
693           $value = $explode[$key];
694         }
695       }
696     }
697 
698     $finished = true;
699     foreach ($explode as $key => $value) {
700       if (strpos($value,'YAMLSeq') !== false) {
701         $finished = false; break;
702       }
703       if (strpos($value,'YAMLMap') !== false) {
704         $finished = false; break;
705       }
706       if (strpos($value,'YAMLString') !== false) {
707         $finished = false; break;
708       }
709     }
710     if ($finished) break;
711 
712     $i++;
713     if ($i > 10)
714       break; // Prevent infinite loops.
715     }
716 
717     return $explode;
718   }
719 
720   private function literalBlockContinues ($line, $lineIndent) {
721     if (!trim($line)) return true;
722     if (strlen($line) - strlen(ltrim($line)) > $lineIndent) return true;
723     return false;
724   }
725 
726   private function referenceContentsByAlias ($alias) {
727     do {
728       if (!isset($this->SavedGroups[$alias])) { echo "Bad group name: $alias."; break; }
729       $groupPath = $this->SavedGroups[$alias];
730       $value = $this->result;
731       foreach ($groupPath as $k) {
732         $value = $value[$k];
733       }
734     } while (false);
735     return $value;
736   }
737 
738   private function addArrayInline ($array, $indent) {
739       $CommonGroupPath = $this->path;
740       if (empty ($array)) return false;
741 
742       foreach ($array as $k => $_) {
743         $this->addArray(array($k => $_), $indent);
744         $this->path = $CommonGroupPath;
745       }
746       return true;
747   }
748 
749   private function addArray ($incoming_data, $incoming_indent) {
750 
751    // print_r ($incoming_data);
752 
753     if (count ($incoming_data) > 1)
754       return $this->addArrayInline ($incoming_data, $incoming_indent);
755 
756     $key = key ($incoming_data);
757     $value = isset($incoming_data[$key]) ? $incoming_data[$key] : null;
758     if ($key === '__!YAMLZero') $key = '0';
759 
760     if ($incoming_indent == 0 && !$this->_containsGroupAlias && !$this->_containsGroupAnchor) { // Shortcut for root-level values.
761       if ($key || $key === '' || $key === '0') {
762         $this->result[$key] = $value;
763       } else {
764         $this->result[] = $value; end ($this->result); $key = key ($this->result);
765       }
766       $this->path[$incoming_indent] = $key;
767       return;
768     }
769 
770 
771 
772     $history = array();
773     // Unfolding inner array tree.
774     $history[] = $_arr = $this->result;
775     foreach ($this->path as $k) {
776       $history[] = $_arr = $_arr[$k];
777     }
778 
779     if ($this->_containsGroupAlias) {
780       $value = $this->referenceContentsByAlias($this->_containsGroupAlias);
781       $this->_containsGroupAlias = false;
782     }
783 
784 
785     // Adding string or numeric key to the innermost level or $this->arr.
786     if (is_string($key) && $key == '<<') {
787       if (!is_array ($_arr)) { $_arr = array (); }
788 
789       $_arr = array_merge ($_arr, $value);
790     } else if ($key || $key === '' || $key === '0') {
791       if (!is_array ($_arr))
792         $_arr = array ($key=>$value);
793       else
794         $_arr[$key] = $value;
795     } else {
796       if (!is_array ($_arr)) { $_arr = array ($value); $key = 0; }
797       else { $_arr[] = $value; end ($_arr); $key = key ($_arr); }
798     }
799 
800     $reverse_path = array_reverse($this->path);
801     $reverse_history = array_reverse ($history);
802     $reverse_history[0] = $_arr;
803     $cnt = count($reverse_history) - 1;
804     for ($i = 0; $i < $cnt; $i++) {
805       $reverse_history[$i+1][$reverse_path[$i]] = $reverse_history[$i];
806     }
807     $this->result = $reverse_history[$cnt];
808 
809     $this->path[$incoming_indent] = $key;
810 
811     if ($this->_containsGroupAnchor) {
812       $this->SavedGroups[$this->_containsGroupAnchor] = $this->path;
813       if (is_array ($value)) {
814         $k = key ($value);
815         if (!is_int ($k)) {
816           $this->SavedGroups[$this->_containsGroupAnchor][$incoming_indent + 2] = $k;
817         }
818       }
819       $this->_containsGroupAnchor = false;
820     }
821 
822   }
823 
824   private static function startsLiteralBlock ($line) {
825     $lastChar = substr (trim($line), -1);
826     if ($lastChar != '>' && $lastChar != '|') return false;
827     if ($lastChar == '|') return $lastChar;
828     // HTML tags should not be counted as literal blocks.
829     if (preg_match ('#<.*?>$#', $line)) return false;
830     return $lastChar;
831   }
832 
833   private static function greedilyNeedNextLine($line) {
834     $line = trim ($line);
835     if (!strlen($line)) return false;
836     if (substr ($line, -1, 1) == ']') return false;
837     if ($line[0] == '[') return true;
838     if (preg_match ('#^[^:]+?:\s*\[#', $line)) return true;
839     return false;
840   }
841 
842   private function addLiteralLine ($literalBlock, $line, $literalBlockStyle, $indent = -1) {
843     $line = self::stripIndent($line, $indent);
844     if ($literalBlockStyle !== '|') {
845         $line = self::stripIndent($line);
846     }
847     $line = rtrim ($line, "\r\n\t ") . "\n";
848     if ($literalBlockStyle == '|') {
849       return $literalBlock . $line;
850     }
851     if (strlen($line) == 0)
852       return rtrim($literalBlock, ' ') . "\n";
853     if ($line == "\n" && $literalBlockStyle == '>') {
854       return rtrim ($literalBlock, " \t") . "\n";
855     }
856     if ($line != "\n")
857       $line = trim ($line, "\r\n ") . " ";
858     return $literalBlock . $line;
859   }
860 
861    function revertLiteralPlaceHolder ($lineArray, $literalBlock) {
862      foreach ($lineArray as $k => $_) {
863       if (is_array($_))
864         $lineArray[$k] = $this->revertLiteralPlaceHolder ($_, $literalBlock);
865       else if (substr($_, -1 * strlen ($this->LiteralPlaceHolder)) == $this->LiteralPlaceHolder)
866 	       $lineArray[$k] = rtrim ($literalBlock, " \r\n");
867      }
868      return $lineArray;
869    }
870 
871   private static function stripIndent ($line, $indent = -1) {
872     if ($indent == -1) $indent = strlen($line) - strlen(ltrim($line));
873     return substr ($line, $indent);
874   }
875 
876   private function getParentPathByIndent ($indent) {
877     if ($indent == 0) return array();
878     $linePath = $this->path;
879     do {
880       end($linePath); $lastIndentInParentPath = key($linePath);
881       if ($indent <= $lastIndentInParentPath) array_pop ($linePath);
882     } while ($indent <= $lastIndentInParentPath);
883     return $linePath;
884   }
885 
886 
887   private function clearBiggerPathValues ($indent) {
888 
889 
890     if ($indent == 0) $this->path = array();
891     if (empty ($this->path)) return true;
892 
893     foreach ($this->path as $k => $_) {
894       if ($k > $indent) unset ($this->path[$k]);
895     }
896 
897     return true;
898   }
899 
900 
901   private static function isComment ($line) {
902     if (!$line) return false;
903     if ($line[0] == '#') return true;
904     if (trim($line, " \r\n\t") == '---') return true;
905     return false;
906   }
907 
908   private static function isEmpty ($line) {
909     return (trim ($line) === '');
910   }
911 
912 
913   private function isArrayElement ($line) {
914     if (!$line) return false;
915     if ($line[0] != '-') return false;
916     if (strlen ($line) > 3)
917       if (substr($line,0,3) == '---') return false;
918 
919     return true;
920   }
921 
922   private function isHashElement ($line) {
923     return strpos($line, ':');
924   }
925 
926   private function isLiteral ($line) {
927     if ($this->isArrayElement($line)) return false;
928     if ($this->isHashElement($line)) return false;
929     return true;
930   }
931 
932 
933   private static function unquote ($value) {
934     if (!$value) return $value;
935     if (!is_string($value)) return $value;
936     if ($value[0] == '\'') return trim ($value, '\'');
937     if ($value[0] == '"') return trim ($value, '"');
938     return $value;
939   }
940 
941   private function startsMappedSequence ($line) {
942     return ($line[0] == '-' && substr ($line, -1, 1) == ':');
943   }
944 
945   private function returnMappedSequence ($line) {
946     $array = array();
947     $key         = self::unquote(trim(substr($line,1,-1)));
948     $array[$key] = array();
949     $this->delayedPath = array(strpos ($line, $key) + $this->indent => $key);
950     return array($array);
951   }
952 
953   private function returnMappedValue ($line) {
954     $array = array();
955     $key         = self::unquote (trim(substr($line,0,-1)));
956     $array[$key] = '';
957     return $array;
958   }
959 
960   private function startsMappedValue ($line) {
961     return (substr ($line, -1, 1) == ':');
962   }
963 
964   private function isPlainArray ($line) {
965     return ($line[0] == '[' && substr ($line, -1, 1) == ']');
966   }
967 
968   private function returnPlainArray ($line) {
969     return $this->_toType($line);
970   }
971 
972   private function returnKeyValuePair ($line) {
973     $array = array();
974     $key = '';
975     if (strpos ($line, ':')) {
976       // It's a key/value pair most likely
977       // If the key is in double quotes pull it out
978       if (($line[0] == '"' || $line[0] == "'") && preg_match('/^(["\'](.*)["\'](\s)*:)/',$line,$matches)) {
979         $value = trim(str_replace($matches[1],'',$line));
980         $key   = $matches[2];
981       } else {
982         // Do some guesswork as to the key and the value
983         $explode = explode(':',$line);
984         $key     = trim($explode[0]);
985         array_shift($explode);
986         $value   = trim(implode(':',$explode));
987       }
988       // Set the type of the value.  Int, string, etc
989       $value = $this->_toType($value);
990       if ($key === '0') $key = '__!YAMLZero';
991       $array[$key] = $value;
992     } else {
993       $array = array ($line);
994     }
995     return $array;
996 
997   }
998 
999 
1000   private function returnArrayElement ($line) {
1001      if (strlen($line) <= 1) return array(array()); // Weird %)
1002      $array = array();
1003      $value   = trim(substr($line,1));
1004      $value   = $this->_toType($value);
1005      $array[] = $value;
1006      return $array;
1007   }
1008 
1009 
1010   private function nodeContainsGroup ($line) {
1011     $symbolsForReference = 'A-z0-9_\-';
1012     if (strpos($line, '&') === false && strpos($line, '*') === false) return false; // Please die fast ;-)
1013     if ($line[0] == '&' && preg_match('/^(&['.$symbolsForReference.']+)/', $line, $matches)) return $matches[1];
1014     if ($line[0] == '*' && preg_match('/^(\*['.$symbolsForReference.']+)/', $line, $matches)) return $matches[1];
1015     if (preg_match('/(&['.$symbolsForReference.']+)$/', $line, $matches)) return $matches[1];
1016     if (preg_match('/(\*['.$symbolsForReference.']+$)/', $line, $matches)) return $matches[1];
1017     if (preg_match ('#^\s*<<\s*:\s*(\*[^\s]+).*$#', $line, $matches)) return $matches[1];
1018     return false;
1019 
1020   }
1021 
1022   private function addGroup ($line, $group) {
1023     if ($group[0] == '&') $this->_containsGroupAnchor = substr ($group, 1);
1024     if ($group[0] == '*') $this->_containsGroupAlias = substr ($group, 1);
1025     //print_r ($this->path);
1026   }
1027 
1028   private function stripGroup ($line, $group) {
1029     $line = trim(str_replace($group, '', $line));
1030     return $line;
1031   }
1032 }
1033 
1034 // Enable use of Spyc from command line
1035 // The syntax is the following: php spyc.php spyc.yaml
1036 
1037 define ('SPYC_FROM_COMMAND_LINE', false);
1038 
1039 do {
1040   if (!SPYC_FROM_COMMAND_LINE) break;
1041   if (empty ($_SERVER['argc']) || $_SERVER['argc'] < 2) break;
1042   if (empty ($_SERVER['PHP_SELF']) || $_SERVER['PHP_SELF'] != 'spyc.php') break;
1043   $file = $argv[1];
1044   printf ("Spyc loading file: %s\n", $file);
1045   print_r (spyc_load_file ($file));
1046 } while (0);