Open-Source PHP Framework - Designed for rapid development of performance-oriented scalable applications

/mvc/components/yaml

[return to app]
1 <?php
2
/*
3 * This file is part of the symfony package, updated for Vork
4 * (c) 2004-2006 Fabien Potencier <fabien.potencier@symfony-project.com>
5 *
6 * Copyright (c) 2004-2010 Fabien Potencier
7 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 
8 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation
 
the 
9
rights to use, copymodifymergepublishdistributesublicense, and/or sell copies of the Software, and to
 
permit
10
persons to whom the Software is furnished to do sosubject to the following conditions:
11
The above copyright notice and this permission notice shall be included in all copies or substantial portions of
 
the 
12
Software.
13
THE SOFTWARE IS PROVIDED "AS IS"WITHOUT WARRANTY OF ANY KINDEXPRESS OR IMPLIEDINCLUDING BUT NOT LIMITED TO
 
THE 
14
WARRANTIES OF MERCHANTABILITYFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENTIN NO EVENT SHALL THE
 
AUTHORS OR
15
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIMDAMAGES OR OTHER LIABILITYWHETHER IN AN ACTION OF CONTRACTTORT OR
 
16
OTHERWISEARISING FROMOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
17
*/
18
19
/**
20 * sfYaml offers convenience methods to load and dump YAML.
21 *
22 * @package    symfony
23 * @subpackage yaml
24 * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
25 * @version    SVN: $Id: sfYaml.class.php 8988 2008-05-15 20:24:26Z fabien $
26 */
27
class yamlComponent {
28     static protected 
$spec '1.2';
29
30     
/**
31     * Sets the YAML specification version to use.
32     *
33     * @param string $version The YAML specification version
34     */
35     
static public function setSpecVersion($version) {
36         if (!
in_array($version, array('1.1''1.2'))) {
37             throw new 
Exception(
38                 
sprintf('Version %s of the YAML specifications is not supported'$version)
39             );
40         }
41
42         
self::$spec $version;
43     }
44
45     
/**
46     * Gets the YAML specification version to use.
47     *
48     * @return string The YAML specification version
49     */
50     
static public function getSpecVersion() {
51         return 
self::$spec;
52     }
53
54     
/**
55     * Loads YAML into a PHP array.
56     * The load method, when supplied with a YAML stream (string or file),
57     * will do its best to convert YAML in a file into a PHP array.
58     *  Usage:
59     *  <code>
60     *   $array = sfYaml::load('config.yml');
61     *   print_r($array);
62     *  </code>
63     *
64     * @param string $input Path of YAML file or string containing YAML
65     * @return array The YAML converted to a PHP array
66     * @throws Exception If the YAML is not valid
67     */
68     
public function load($input) {
69         
$file '';
70
71         
// if input is a file, process it
72         
if (strpos($input"\n") === false && is_file($input)) {
73             
$file $input;
74
75             
ob_start();
76             
$retval = include($input);
77             
$content ob_get_clean();
78
79             
// if an array is returned by the config file assume it's in plain php form else in YAML
80             
$input is_array($retval) ? $retval $content;
81         }
82
83         
// if an array is returned by the config file assume it's in plain php form else in YAML
84         
if (is_array($input)) {
85             return 
$input;
86         }
87
88         
$yaml = new sfYamlParser();
89
90         try{
91             
$ret $yaml->parse($input);
92         }
93         catch (
Exception $e) {
94             throw new 
Exception(
95                 
sprintf('Unable to parse %s: %s'$file sprintf('file "%s"'$file) : 'string',
 
$e->getMessage())
96             );
97         }
98
99         return 
$ret;
100     }
101
102     
/**
103     * Dumps a PHP array to a YAML string.
104     * The dump method, when supplied with an array, will do its best
105     * to convert the array into friendly YAML.
106     *
107     * @param array   $array PHP array
108     * @param integer $inline The level where you switch to inline YAML
109     * @return string A YAML string representing the original PHP array
110     */
111     
public function dump($array$inline 2) {
112         
$yaml = new sfYamlDumper();
113
114         return 
$yaml->dump($array$inline);
115     }
116 }
117
118
/**
119 * sfYamlInline implements a YAML parser/dumper for the YAML inline syntax.
120 *
121 * @package    symfony
122 * @subpackage yaml
123 * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
124 * @version    SVN: $Id: sfYamlInline.class.php 16177 2009-03-11 08:32:48Z fabien $
125 */
126
class sfYamlInline{
127     const 
REGEX_QUOTED_STRING '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\']*(?:\'\'[^\']*)*)\')';
128
129     
/**
130     * Convert a YAML string to a PHP array.
131     *
132     * @param string $value A YAML string
133     * @return array A PHP array representing the YAML string
134     */
135     
static public function load($value) {
136         
$value trim($value);
137
138         if (
== strlen($value)) {
139             return 
'';
140         }
141
142         if (
function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) {
143             
$mbEncoding mb_internal_encoding();
144             
mb_internal_encoding('ASCII');
145         }
146
147         switch (
$value[0]) {
148             case 
'[':
149                 
$result self::_parseSequence($value);
150                 break;
151             case 
'{':
152                 
$result self::_parseMapping($value);
153                 break;
154             default:
155                 
$result self::parseScalar($value);
156         }
157
158         if (isset(
$mbEncoding)) {
159             
mb_internal_encoding($mbEncoding);
160         }
161
162         return 
$result;
163     }
164
165     
/**
166     * Dumps a given PHP variable to a YAML string.
167     *
168     * @param mixed $value The PHP variable to convert
169     * @return string The YAML string representing the PHP array
170     */
171     
static public function dump($value) {
172         if (
'1.1' === yamlComponent::getSpecVersion()) {
173             
$trueValues = array('true''on''+''yes''y');
174             
$falseValues = array('false''off''-''no''n');
175         } else {
176             
$trueValues = array('true');
177             
$falseValues = array('false');
178         }
179
180         switch (
true) {
181             case 
is_resource($value):
182                 throw new 
Exception('Unable to dump PHP resources in a YAML file.');
183             case 
is_object($value):
184                 return 
'!!php/object:'.serialize($value);
185             case 
is_array($value):
186                 return 
self::_dumpArray($value);
187             case 
null === $value:
188                 return 
'null';
189             case 
true === $value:
190                 return 
'true';
191             case 
false === $value:
192                 return 
'false';
193             case 
ctype_digit($value):
194                 return 
is_string($value) ? "'$value'" : (int) $value;
195             case 
is_numeric($value):
196                 return 
is_infinite($value) ? 
197                     
str_ireplace('INF''.Inf'strval($value)) : (is_string($value) ? "'$value'" 
198             
$value);
199             case 
false !== strpos($value"\n") || false !== strpos($value"\r"):
200                 return 
sprintf('"%s"'str_replace(array('"'"\n""\r"), array('\\"''\n''\r'), $value));
201             case 
preg_match('/[ \s \' " \: \{ \} \[ \] , & \* \# \?] | \A[ - ? | < > = ! % @ ` ]/x'$value):
202                 return 
sprintf("'%s'"str_replace('\'''\'\''$value));
203             case 
'' == $value:
204                 return 
"''";
205             case 
preg_match(self::_getTimestampRegex(), $value):
206                 return 
"'$value'";
207             case 
in_array(strtolower($value), $trueValues):
208                 return 
"'$value'";
209             case 
in_array(strtolower($value), $falseValues):
210                 return 
"'$value'";
211             case 
in_array(strtolower($value), array('null''~')):
212                 return 
"'$value'";
213             default:
214                 return 
$value;
215         }
216     }
217
218     
/**
219     * Dumps a PHP array to a YAML string.
220     *
221     * @param array $value The PHP array to dump
222     * @return string The YAML string representing the PHP array
223     */
224     
static protected function _dumpArray($value) {
225         
// array
226         
$keys array_keys($value);
227         if ((
== count($keys) && '0' == $keys[0]) ||
228             (
count($keys) > && array_reduce($keyscreate_function('$v,$w''return (integer) $v + $w;'), 0) == 
229                 
count($keys) * (count($keys) - 1) / 2)) {
230             
$output = array();
231             foreach (
$value as $val) {
232                 
$output[] = self::dump($val);
233             }
234
235             return 
sprintf('[%s]'implode(', '$output));
236         }
237
238         
// mapping
239         
$output = array();
240         foreach (
$value as $key => $val) {
241             
$output[] = sprintf('%s: %s'self::dump($key), self::dump($val));
242         }
243
244         return 
sprintf('{ %s }'implode(', '$output));
245     }
246
247     
/**
248     * Parses a scalar to a YAML string.
249     *
250     * @param scalar  $scalar
251     * @param string  $delimiters
252     * @param array   $stringDelimiter
253     * @param integer $i
254     * @param boolean $evaluate
255     * @return string A YAML string
256     */
257     
static public function parseScalar($scalar$delimiters null$stringDelimiters = array('"'"'"), &$i 0
258             
$evaluate true) {
259         if (
in_array($scalar[$i], $stringDelimiters)) {
260             
// quoted scalar
261             
$output self::_parseQuotedScalar($scalar$i);
262         } else {
263             
// "normal" string
264             
if (!$delimiters) {
265                 
$output substr($scalar$i);
266                 
$i += strlen($output);
267
268                 
// remove comments
269                 
if (false !== $strpos strpos($output' #')) {
270                     
$output rtrim(substr($output0$strpos));
271                 }
272             } else if (
preg_match('/^(.+?)('.implode('|'$delimiters).')/'substr($scalar$i), $match)) {
273                 
$output $match[1];
274                 
$i += strlen($output);
275             } else {
276                 throw new 
Exception(sprintf('Malformed inline YAML string (%s).'$scalar));
277             }
278
279             
$output $evaluate self::_evaluateScalar($output) : $output;
280         }
281
282         return 
$output;
283     }
284
285     
/**
286     * Parses a quoted scalar to YAML.
287     *
288     * @param string  $scalar
289     * @param integer $i
290     * @return string A YAML string
291     */
292     
static protected function _parseQuotedScalar($scalar, &$i) {
293         if (!
preg_match('/'.self::REGEX_QUOTED_STRING.'/Au'substr($scalar$i), $match)) {
294             throw new 
Exception(sprintf('Malformed inline YAML string (%s).'substr($scalar$i)));
295         }
296
297         
$output substr($match[0], 1strlen($match[0]) - 2);
298
299         if (
'"' == $scalar[$i]) {
300             
// evaluate the string
301             
$output str_replace(array('\\"''\\n''\\r'), array('"'"\n""\r"), $output);
302         } else {
303             
// unescape '
304             
$output str_replace('\'\'''\''$output);
305         }
306
307         
$i += strlen($match[0]);
308
309         return 
$output;
310     }
311
312     
/**
313     * Parses a sequence to a YAML string.
314     *
315     * @param string  $sequence
316     * @param integer $i
317     * @return string A YAML string
318     */
319     
static protected function _parseSequence($sequence, &$i 0) {
320         
$output = array();
321         
$len strlen($sequence);
322         
$i += 1;
323
324         
// [foo, bar, ...]
325         
while ($i $len) {
326             switch (
$sequence[$i]) {
327                 case 
'[':
328                     
// nested sequence
329                     
$output[] = self::_parseSequence($sequence$i);
330                     break;
331                 case 
'{':
332                     
// nested mapping
333                     
$output[] = self::_parseMapping($sequence$i);
334                     break;
335                 case 
']':
336                     return 
$output;
337                 case 
',':
338                 case 
' ':
339                     break;
340                 default:
341                     
$isQuoted in_array($sequence[$i], array('"'"'"));
342                     
343                 
$value self::parseScalar($sequence, array(','']'), array('"'"'"), $i);
344
345                 if (!
$isQuoted && false !== strpos($value': ')) {
346                     
// embedded mapping?
347                     
try{
348                         
$value self::_parseMapping('{'.$value.'}');
349                     }
350                     catch (
Exception $e) {
351                     
// no, it's not
352                     
}
353                 }
354
355                 
$output[] = $value;
356
357                 --
$i;
358             }
359
360             ++
$i;
361         }
362
363         throw new 
Exception(sprintf('Malformed inline YAML string %s'$sequence));
364     }
365
366     
/**
367     * Parses a mapping to a YAML string.
368     *
369     * @param string  $mapping
370     * @param integer $i
371     * @return string A YAML string
372     */
373     
static protected function _parseMapping($mapping, &$i 0) {
374         
$output = array();
375         
$len strlen($mapping);
376         
$i += 1;
377
378         
// {foo: bar, bar:foo, ...}
379         
while ($i $len) {
380             switch (
$mapping[$i]) {
381                 case 
' ':
382                 case 
',':
383                     ++
$i;
384                     continue 
2;
385                 case 
'}':
386                     return 
$output;
387             }
388
389             
// key
390             
$key self::parseScalar($mapping, array(':'' '), array('"'"'"), $ifalse);
391
392             
// value
393             
$done false;
394             while (
$i $len) {
395                 switch (
$mapping[$i]) {
396                     case 
'[':
397                         
// nested sequence
398                         
$output[$key] = self::_parseSequence($mapping$i);
399                         
$done true;
400                         break;
401                     case 
'{':
402                         
// nested mapping
403                         
$output[$key] = self::_parseMapping($mapping$i);
404                         
$done true;
405                         break;
406                     case 
':':
407                     case 
' ':
408                         break;
409                     default:
410                         
$output[$key] = self::parseScalar($mapping, array(',''}'), array('"'"'"), $i);
411                     
$done true;
412                     --
$i;
413                 }
414
415                 ++
$i;
416
417                 if (
$done) {
418                     continue 
2;
419                 }
420             }
421         }
422
423         throw new 
Exception(sprintf('Malformed inline YAML string %s'$mapping));
424     }
425
426     
/**
427     * Evaluates scalars and replaces magic values.
428     *
429     * @param string $scalar
430     * @return string A YAML string
431     */
432     
static protected function _evaluateScalar($scalar) {
433         
$scalar trim($scalar);
434
435         if (
'1.1' === yamlComponent::getSpecVersion()) {
436             
$trueValues = array('true''on''+''yes''y');
437             
$falseValues = array('false''off''-''no''n');
438         } else {
439             
$trueValues = array('true');
440             
$falseValues = array('false');
441         }
442
443         switch (
true) {
444             case 
'null' == strtolower($scalar):
445             case 
'' == $scalar:
446             case 
'~' == $scalar:
447                 return 
null;
448             case 
=== strpos($scalar'!str'):
449                 return (string) 
substr($scalar5);
450             case 
=== strpos($scalar'! '):
451                 return 
intval(self::parseScalar(substr($scalar2)));
452             case 
=== strpos($scalar'!!php/object:'):
453                 return 
unserialize(substr($scalar13));
454             case 
ctype_digit($scalar):
455                 
$raw $scalar;
456                 
$cast intval($scalar);
457                 return 
'0' == $scalar[0] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast $raw);
458             case 
in_array(strtolower($scalar), $trueValues):
459                 return 
true;
460             case 
in_array(strtolower($scalar), $falseValues):
461                 return 
false;
462             case 
is_numeric($scalar):
463                 return 
'0x' == $scalar[0].$scalar[1] ? hexdec($scalar) : floatval($scalar);
464             case 
== strcasecmp($scalar'.inf'):
465             case 
== strcasecmp($scalar'.NaN'):
466                 return -
log(0);
467             case 
== strcasecmp($scalar'-.inf'):
468                 return 
log(0);
469             case 
preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/'$scalar):
470                 return 
floatval(str_replace(','''$scalar));
471             case 
preg_match(self::_getTimestampRegex(), $scalar):
472                 return 
strtotime($scalar);
473             default:
474                 return (string) 
$scalar;
475         }
476     }
477
478     static protected function 
_getTimestampRegex() {
479         return <<<EOF
480
~^
481 (?P<year>[0-9][0-9][0-9][0-9])
482 -(?P<month>[0-9][0-9]?)
483 -(?P<day>[0-9][0-9]?)
484 (?:(?:[Tt]|[ \t]+)
485 (?P<hour>[0-9][0-9]?)
486 :(?P<minute>[0-9][0-9])
487 :(?P<second>[0-9][0-9])
488 (?:\.(?P<fraction>[0-9]*))?
489 (?:[ \t]*(?P<tz>Z|(?P<tz_sign>[-+])(?P<tz_hour>[0-9][0-9]?)
490 (?::(?P<tz_minute>[0-9][0-9]))?))?)?
491 $~x
492
EOF;
493     }
494 }
495
496
/**
497 * sfYamlDumper dumps PHP variables to YAML strings.
498 *
499 * @package    symfony
500 * @subpackage yaml
501 * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
502 * @version    SVN: $Id: sfYamlDumper.class.php 10575 2008-08-01 13:08:42Z nicolas $
503 */
504
class sfYamlDumper{
505     
/**
506     * Dumps a PHP value to YAML.
507     *
508     * @param  mixed   $input  The PHP value
509     * @param  integer $inline The level where you switch to inline YAML
510     * @param  integer $indent The level o indentation indentation (used internally)    
511     * @return string  The YAML representation of the PHP value
512     */
513     
public function dump($input$inline 0$indent 0) {
514         
$output '';
515         
$prefix $indent str_repeat(' '$indent) : '';
516
517         if (
$inline <= || !is_array($input) || empty($input)) {
518             
$output .= $prefix.sfYamlInline::dump($input);
519         } else {
520             
$isAHash array_keys($input) !== range(0count($input) - 1);
521
522             foreach (
$input as $key => $value) {
523                 
$willBeInlined $inline <= || !is_array($value) || empty($value);
524
525                 
$output .= sprintf('%s%s%s%s',
526                 
$prefix,
527                 
$isAHash sfYamlInline::dump($key).':' '-',
528                 
$willBeInlined ' ' "\n",
529                 
$this->dump($value$inline 1$willBeInlined $indent 2)
530                 ).(
$willBeInlined "\n" '');
531             }
532         }
533
534         return 
$output;
535     }
536 }
537
538 if (!
defined('PREG_BAD_UTF8_OFFSET_ERROR')) {
539     
define('PREG_BAD_UTF8_OFFSET_ERROR'5);
540 }
541
542
/**
543 * sfYamlParser parses YAML strings to convert them to PHP arrays.
544 *
545 * @package    symfony
546 * @subpackage yaml
547 * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
548 * @version    SVN: $Id: sfYamlParser.class.php 10832 2008-08-13 07:46:08Z fabien $
549 */
550
class sfYamlParser{
551     protected
552     
$offset        0,
553     
$lines         = array(),
554     
$currentLineNb = -1,
555     
$currentLine   '',
556     
$refs          = array();
557
558     
/**
559     * Constructor
560     *
561     * @param integer $offset The offset of YAML document (used for line numbers in error messages)
562     */
563     
public function __construct($offset 0) {
564         
$this->offset $offset;
565     }
566
567     
/**
568     * Parses a YAML string to a PHP value.
569     *
570     * @param  string $value A YAML string
571     * @return mixed  A PHP value
572     * @throws Exception If the YAML is not valid
573     */
574     
public function parse($value) {
575         
$this->currentLineNb = -1;
576         
$this->currentLine '';
577         
$this->lines explode("\n"$this->_cleanup($value));
578
579         if (
function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) {
580             
$mbEncoding mb_internal_encoding();
581             
mb_internal_encoding('UTF-8');
582         }
583
584         
$data = array();
585         while (
$this->_moveToNextLine()) {
586             if (
$this->_isCurrentLineEmpty()) {
587                 continue;
588             }
589
590             
// tab?
591             
if (preg_match('#^\t+#'$this->currentLine)) {
592                 throw new 
Exception(sprintf('A YAML file cannot contain tabs as indentation at line %d (%s).'
593                     
$this->_getRealCurrentLineNb() + 1$this->currentLine));
594             }
595
596             
$isRef $isInPlace $isProcessed false;
597             if (
preg_match('#^\-((?P<leadspaces>\s+)(?P<value>.+?))?\s*$#u'$this->currentLine$values)) {
598                 if (isset(
$values['value']) 
599                     && 
preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u'$values['value'], $matches)) {
600                     
$isRef $matches['ref'];
601                     
$values['value'] = $matches['value'];
602                 }
603
604                 
// array
605                 
if (!isset($values['value']) || '' == trim($values['value'], ' '
606                     || 
=== strpos(ltrim($values['value'], ' '), '#')) {
607                     
$c $this->_getRealCurrentLineNb() + 1;
608                     
$parser = new sfYamlParser($c);
609                     
$parser->refs =& $this->refs;
610                     
$data[] = $parser->parse($this->_getNextEmbedBlock());
611                 } else {
612                     if (isset(
$values['leadspaces']) 
613                         && 
' ' == $values['leadspaces'
614                         && 
preg_match('#^(?P<key>'.sfYamlInline::REGEX_QUOTED_STRING.
615                             
'|[^ \'"\{].*?) *\:(\s+(?P<value>.+?))?\s*$#u'$values['value'], $matches)) {
616                         
// this is a compact notation element, add to next block and parse
617                         
$c $this->_getRealCurrentLineNb();
618                         
$parser = new sfYamlParser($c);
619                         
$parser->refs =& $this->refs;
620
621                         
$block $values['value'];
622                         if (!
$this->_isNextLineIndented()) {
623                             
$block .= "\n".$this->_getNextEmbedBlock($this->_getCurrentLineIndentation() + 2);
624                         }
625
626                         
$data[] = $parser->parse($block);
627                     } else {
628                         
$data[] = $this->_parseValue($values['value']);
629                     }
630                 }
631             } else if (
632                 
preg_match('#^(?P<key>'.sfYamlInline::REGEX_QUOTED_STRING.'|[^ \'"].*?)
 *\:(\s+(?P<value>.+?))?\s*$#u'

633                     
$this->currentLine$values)) {
634                 
$key sfYamlInline::parseScalar($values['key']);
635
636                 if (
'<<' === $key) {
637                     if (isset(
$values['value']) && '*' === substr($values['value'], 01)) {
638                         
$isInPlace substr($values['value'], 1);
639                         if (!
array_key_exists($isInPlace$this->refs)) {
640                             throw new 
Exception(sprintf('Reference "%s" does not exist at line %s (%s).'
641                                 
$isInPlace$this->_getRealCurrentLineNb() + 1$this->currentLine));
642                         }
643                     } else {
644                         if (isset(
$values['value']) && $values['value'] !== '') {
645                             
$value $values['value'];
646                         } else {
647                             
$value $this->_getNextEmbedBlock();
648                         }
649                         
$c $this->_getRealCurrentLineNb() + 1;
650                         
$parser = new sfYamlParser($c);
651                         
$parser->refs =& $this->refs;
652                         
$parsed $parser->parse($value);
653
654                         
$merged = array();
655                         if (!
is_array($parsed)) {
656                             throw new 
Exception(sprintf("YAML merge keys used with a scalar value instead of an
 array 
657                         at line %s (%s)"
$this->_getRealCurrentLineNb() + 1$this->currentLine));
658                         } else if (isset(
$parsed[0])) {
659                             
// Numeric array, merge individual elements
660                             
foreach (array_reverse($parsed) as $parsedItem) {
661                                 if (!
is_array($parsedItem)) {
662                                     throw new 
Exception(sprintf("Merge items must be arrays at line %s (%s)."
663                                     
$this->_getRealCurrentLineNb() + 1$parsedItem));
664                                 }
665                                 
$merged array_merge($parsedItem$merged);
666                             }
667                         } else {
668                             
// Associative array, merge
669                             
$merged array_merge($merge$parsed);
670                         }
671
672                         
$isProcessed $merged;
673                     }
674                 } else if (isset(
$values['value']) 
675                     && 
preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u'$values['value'], $matches)) {
676                     
$isRef $matches['ref'];
677                     
$values['value'] = $matches['value'];
678                 }
679
680                 if (
$isProcessed) {
681                     
// Merge keys
682                     
$data $isProcessed;
683                 } else if (!isset(
$values['value']) || '' == trim($values['value'], ' '
684                     || 
=== strpos(ltrim($values['value'], ' '), '#')) {
685                     
// if next line is less indented or equal, then it means that the current value is null
686                     
if ($this->_isNextLineIndented()) {
687                         
$data[$key] = null;
688                     } else {
689                         
$c $this->_getRealCurrentLineNb() + 1;
690                         
$parser = new sfYamlParser($c);
691                         
$parser->refs =& $this->refs;
692                         
$data[$key] = $parser->parse($this->_getNextEmbedBlock());
693                     }
694                 } else {
695                     if (
$isInPlace) {
696                         
$data $this->refs[$isInPlace];
697                     } else {
698                         
$data[$key] = $this->_parseValue($values['value']);
699                     }
700                 }
701             } else {
702                 
// 1-liner followed by newline
703                 
if (== count($this->lines) && empty($this->lines[1])) {
704                     
$value sfYamlInline::load($this->lines[0]);
705                     if (
is_array($value)) {
706                         
$first reset($value);
707                         if (
'*' === substr($first01)) {
708                             
$data = array();
709                             foreach (
$value as $alias) {
710                                 
$data[] = $this->refs[substr($alias1)];
711                             }
712                             
$value $data;
713                         }
714                     }
715
716                     if (isset(
$mbEncoding)) {
717                         
mb_internal_encoding($mbEncoding);
718                     }
719
720                     return 
$value;
721                 }
722
723                 switch (
preg_last_error()) {
724                     case 
PREG_INTERNAL_ERROR:
725                         
$error 'Internal PCRE error on line';
726                         break;
727                     case 
PREG_BACKTRACK_LIMIT_ERROR:
728                         
$error 'pcre.backtrack_limit reached on line';
729                         break;
730                     case 
PREG_RECURSION_LIMIT_ERROR:
731                         
$error 'pcre.recursion_limit reached on line';
732                         break;
733                     case 
PREG_BAD_UTF8_ERROR:
734                         
$error 'Malformed UTF-8 data on line';
735                         break;
736                     case 
PREG_BAD_UTF8_OFFSET_ERROR:
737                         
$error 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point on line';
738                         break;
739                     default:
740                         
$error 'Unable to parse line';
741                 }
742
743                 throw new 
Exception(sprintf('%s %d (%s).'$error$this->getRealCurrentLineNb() + 1
744                 
$this->currentLine));
745             }
746
747             if (
$isRef) {
748                 
$this->refs[$isRef] = end($data);
749             }
750         }
751
752         if (isset(
$mbEncoding)) {
753             
mb_internal_encoding($mbEncoding);
754         }
755
756         return empty(
$data) ? null $data;
757     }
758
759     
/**
760     * Returns the current line number (takes the offset into account).
761     *
762     * @return integer The current line number
763     */
764     
protected function _getRealCurrentLineNb() {
765         return 
$this->currentLineNb $this->offset;
766     }
767
768     
/**
769     * Returns the current line indentation.
770     *
771     * @return integer The current line indentation
772     */
773     
protected function _getCurrentLineIndentation() {
774         return 
strlen($this->currentLine) - strlen(ltrim($this->currentLine' '));
775     }
776
777     
/**
778     * Returns the next embed block of YAML.
779     *
780     * @param integer $indentation The indent level at which the block is to be read, or null for default
781     * @return string A YAML string
782     */
783     
protected function _getNextEmbedBlock($indentation null) {
784         
$this->_moveToNextLine();
785
786         if (
null === $indentation) {
787             
$newIndent $this->_getCurrentLineIndentation();
788
789             if (!
$this->_isCurrentLineEmpty() && == $newIndent) {
790                 throw new 
Exception(sprintf('Indentation problem at line %d (%s)'
791                     
$this->_getRealCurrentLineNb() + 1$this->currentLine));
792             }
793         } else {
794             
$newIndent $indentation;
795         }
796
797         
$data = array(substr($this->currentLine$newIndent));
798
799         while (
$this->_moveToNextLine()) {
800             if (
$this->_isCurrentLineEmpty()) {
801                 if (
$this->_isCurrentLineBlank()) {
802                     
$data[] = substr($this->currentLine$newIndent);
803                 }
804
805                 continue;
806             }
807
808             
$indent $this->_getCurrentLineIndentation();
809
810             if (
preg_match('#^(?P<text> *)$#'$this->currentLine$match)) {
811                 
// empty line
812                 
$data[] = $match['text'];
813             } else if (
$indent >= $newIndent) {
814                 
$data[] = substr($this->currentLine$newIndent);
815             } else if (
== $indent) {
816                 
$this->_moveToPreviousLine();
817
818                 break;
819             } else {
820                 throw new 
Exception(sprintf('Indentation problem at line %d (%s)'
821                     
$this->_getRealCurrentLineNb() + 1$this->currentLine));
822             }
823         }
824
825         return 
implode("\n"$data);
826     }
827
828     
/**
829     * Moves the parser to the next line.
830     */
831     
protected function _moveToNextLine() {
832         if (
$this->currentLineNb >= count($this->lines) - 1) {
833             return 
false;
834         }
835
836         
$this->currentLine $this->lines[++$this->currentLineNb];
837
838         return 
true;
839     }
840
841     
/**
842     * Moves the parser to the previous line.
843     */
844     
protected function _moveToPreviousLine() {
845         
$this->currentLine $this->lines[--$this->currentLineNb];
846     }
847
848     
/**
849     * Parses a YAML value.
850     *
851     * @param  string $value A YAML value
852     * @return mixed  A PHP value
853     */
854     
protected function _parseValue($value) {
855         if (
'*' === substr($value01)) {
856             if (
false !== $pos strpos($value'#')) {
857                 
$value substr($value1$pos 2);
858             } else {
859                 
$value substr($value1);
860             }
861
862             if (!
array_key_exists($value$this->refs)) {
863                 throw new 
Exception(sprintf('Reference "%s" does not exist (%s).'$value$this->currentLine));
864             }
865             return 
$this->refs[$value];
866         }
867
868         if (
preg_match('/^(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments>
 +#.*)?$/'

869         
$value$matches)) {
870             
$modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : '';
871
872             return 
$this->_parseFoldedScalar($matches['separator'], 
873                                             
preg_replace('#\d+#'''$modifiers), intval(abs($modifiers)));
874         } else {
875             return 
sfYamlInline::load($value);
876         }
877     }
878
879     
/**
880     * Parses a folded scalar.
881     *
882     * @param  string  $separator   The separator that was used to begin this folded scalar (| or >)
883     * @param  string  $indicator   The indicator that was used to begin this folded scalar (+ or -)
884     * @param  integer $indentation The indentation that was used to begin this folded scalar
885     * @return string  The text value
886     */
887     
protected function _parseFoldedScalar($separator$indicator ''$indentation 0) {
888         
$separator '|' == $separator "\n" ' ';
889         
$text '';
890
891         
$notEOF $this->_moveToNextLine();
892
893         while (
$notEOF && $this->_isCurrentLineBlank()) {
894             
$text .= "\n";
895
896             
$notEOF $this->_moveToNextLine();
897         }
898
899         if (!
$notEOF) {
900             return 
'';
901         }
902
903         if (!
preg_match('#^(?P<indent>'.($indentation str_repeat(' '$indentation) : ' +').')(?P<text>.*)$#u'
904         
$this->currentLine$matches)) {
905             
$this->_moveToPreviousLine();
906
907             return 
'';
908         }
909
910         
$textIndent $matches['indent'];
911         
$previousIndent 0;
912
913         
$text .= $matches['text'].$separator;
914         while (
$this->currentLineNb count($this->lines)) {
915             
$this->_moveToNextLine();
916
917             if (
preg_match('#^(?P<indent> {'.strlen($textIndent).',})(?P<text>.+)$#u'$this->currentLine,
 
$matches)) {
918                 if (
' ' == $separator && $previousIndent != $matches['indent']) {
919                     
$text substr($text0, -1)."\n";
920                 }
921                 
$previousIndent $matches['indent'];
922
923                 
$text .= str_repeat(' '$diff strlen($matches['indent']) - 
924                     
strlen($textIndent)).$matches['text'].($diff "\n" $separator);
925             } else if (
preg_match('#^(?P<text> *)$#'$this->currentLine$matches)) {
926                 
$text .= preg_replace('#^ {1,'.strlen($textIndent).'}#'''$matches['text'])."\n";
927             } else {
928                 
$this->_moveToPreviousLine();
929
930                 break;
931             }
932         }
933
934         if (
' ' == $separator) {
935             
// replace last separator by a newline
936             
$text preg_replace('/ (\n*)$/'"\n$1"$text);
937         }
938
939         switch (
$indicator) {
940             case 
'':
941                 
$text preg_replace('#\n+$#s'"\n"$text);
942                 break;
943             case 
'+':
944                 break;
945             case 
'-':
946                 
$text preg_replace('#\n+$#s'''$text);
947                 break;
948         }
949
950         return 
$text;
951     }
952
953     
/**
954     * Returns true if the next line is indented.
955     *
956     * @return Boolean Returns true if the next line is indented, false otherwise
957     */
958     
protected function _isNextLineIndented() {
959         
$currentIndentation $this->_getCurrentLineIndentation();
960         
$notEOF $this->_moveToNextLine();
961
962         while (
$notEOF && $this->_isCurrentLineEmpty()) {
963             
$notEOF $this->_moveToNextLine();
964         }
965
966         if (
false === $notEOF) {
967             return 
false;
968         }
969
970         
$ret false;
971         if (
$this->_getCurrentLineIndentation() <= $currentIndentation) {
972             
$ret true;
973         }
974
975         
$this->_moveToPreviousLine();
976
977         return 
$ret;
978     }
979
980     
/**
981     * Returns true if the current line is blank or if it is a comment line.
982     *
983     * @return Boolean Returns true if the current line is empty or if it is a comment line, false otherwise
984     */
985     
protected function _isCurrentLineEmpty() {
986         return 
$this->_isCurrentLineBlank() || $this->_isCurrentLineComment();
987     }
988
989     
/**
990     * Returns true if the current line is blank.
991     *
992     * @return Boolean Returns true if the current line is blank, false otherwise
993     */
994     
protected function _isCurrentLineBlank() {
995         return 
'' == trim($this->currentLine' ');
996     }
997
998     
/**
999     * Returns true if the current line is a comment line.
1000     *
1001     * @return Boolean Returns true if the current line is a comment line, false otherwise
1002     */
1003     
protected function _isCurrentLineComment() {
1004         
//checking explicitly the first char of the trim is faster than loops or strpos
1005         
$ltrimmedLine ltrim($this->currentLine' ');
1006         return 
$ltrimmedLine[0] === '#';
1007     }
1008
1009     
/**
1010     * Cleanups a YAML string to be parsed.
1011     *
1012     * @param  string $value The input YAML string
1013     * @return string A cleaned up YAML string
1014     */
1015     
protected function _cleanup($value) {
1016         
$value str_replace(array("\r\n""\r"), "\n"$value);
1017
1018         if (!
preg_match("#\n$#"$value)) {
1019             
$value .= "\n";
1020         }
1021
1022         
// strip YAML header
1023         
$count 0;
1024         
$value preg_replace('#^\%YAML[: ][\d\.]+.*\n#su'''$value, -1$count);
1025         
$this->offset += $count;
1026
1027         
// remove leading comments
1028         
$trimmedValue preg_replace('#^(\#.*?\n)+#s'''$value, -1$count);
1029         if (
$count == 1) {
1030             
// items have been removed, update the offset
1031             
$this->offset += substr_count($value"\n") - substr_count($trimmedValue"\n");
1032             
$value $trimmedValue;
1033         }
1034
1035         
// remove start of the document marker (---)
1036         
$trimmedValue preg_replace('#^\-\-\-.*?\n#s'''$value, -1$count);
1037         if (
$count == 1) {
1038             
// items have been removed, update the offset
1039             
$this->offset += substr_count($value"\n") - substr_count($trimmedValue"\n");
1040             
$value $trimmedValue;
1041
1042             
// remove end of the document marker (...)
1043             
$value preg_replace('#\.\.\.\s*$#s'''$value);
1044         }
1045
1046         return 
$value;
1047     }
1048 }
1049