de96ac2283900d4d707cd056669cbd3a210d0153
4 * This file is part of the symfony package.
5 * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
11 if (!defined('_ECRIRE_INC_VERSION')) return;
13 require_once(dirname(__FILE__
).'/sfYamlInline.php');
16 * sfYamlParser parses YAML strings to convert them to PHP arrays.
20 * @author Fabien Potencier <fabien.potencier@symfony-project.com>
21 * @version SVN: $Id: sfYamlParser.class.php 10832 2008-08-13 07:46:08Z fabien $
36 * @param integer $offset The offset of YAML document (used for line numbers in error messages)
38 public function __construct($offset = 0)
40 $this->offset
= $offset;
44 * Parses a YAML string to a PHP value.
46 * @param string $value A YAML string
48 * @return mixed A PHP value
50 * @throws InvalidArgumentException If the YAML is not valid
52 public function parse($value)
54 $this->value
= $this->cleanup($value);
55 $this->currentLineNb
= -1;
56 $this->currentLine
= '';
57 $this->lines
= explode("\n", $this->value
);
60 while ($this->moveToNextLine())
62 if ($this->isCurrentLineEmpty())
68 if (preg_match('#^\t+#', $this->currentLine
))
70 throw new InvalidArgumentException(sprintf('A YAML file cannot contain tabs as indentation at line %d (%s).', $this->getRealCurrentLineNb() +
1, $this->currentLine
));
73 $isRef = $isInPlace = $isProcessed = false;
74 if (preg_match('#^\-(\s+(?P<value>.+?))?\s*$#', $this->currentLine
, $values))
76 if (isset($values['value']) && preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#', $values['value'], $matches))
78 $isRef = $matches['ref'];
79 $values['value'] = $matches['value'];
83 if (!isset($values['value']) ||
'' == trim($values['value'], ' ') ||
0 === strpos(ltrim($values['value'], ' '), '#'))
85 $c = $this->getRealCurrentLineNb() +
1;
86 $parser = new sfYamlParser($c);
87 $parser->refs
=& $this->refs
;
88 $data[] = $parser->parse($this->getNextEmbedBlock());
92 if (preg_match('/^([^ ]+)\: +({.*?)$/', $values['value'], $matches))
94 $data[] = array($matches[1] => sfYamlInline
::load($matches[2]));
98 $data[] = $this->parseValue($values['value']);
102 else if (preg_match('#^(?P<key>[^ ].*?) *\:(\s+(?P<value>.+?))?\s*$#', $this->currentLine
, $values))
104 $key = sfYamlInline
::parseScalar($values['key']);
108 if (isset($values['value']) && '*' === substr($values['value'], 0, 1))
110 $isInPlace = substr($values['value'], 1);
111 if (!array_key_exists($isInPlace, $this->refs
))
113 throw new InvalidArgumentException(sprintf('Reference "%s" does not exist at line %s (%s).', $isInPlace, $this->getRealCurrentLineNb() +
1, $this->currentLine
));
118 if (isset($values['value']) && $values['value'] !== '')
120 $value = $values['value'];
124 $value = $this->getNextEmbedBlock();
126 $c = $this->getRealCurrentLineNb() +
1;
127 $parser = new sfYamlParser($c);
128 $parser->refs
=& $this->refs
;
129 $parsed = $parser->parse($value);
132 if (!is_array($parsed))
134 throw new InvalidArgumentException(sprintf("YAML merge keys used with a scalar value instead of an array at line %s (%s)", $this->getRealCurrentLineNb() +
1, $this->currentLine
));
136 else if (isset($parsed[0]))
138 // Numeric array, merge individual elements
139 foreach (array_reverse($parsed) as $parsedItem)
141 if (!is_array($parsedItem))
143 throw new InvalidArgumentException(sprintf("Merge items must be arrays at line %s (%s).", $this->getRealCurrentLineNb() +
1, $parsedItem));
145 $merged = array_merge($parsedItem, $merged);
150 // Associative array, merge
151 $merged = array_merge($merge, $parsed);
154 $isProcessed = $merged;
157 else if (isset($values['value']) && preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#', $values['value'], $matches))
159 $isRef = $matches['ref'];
160 $values['value'] = $matches['value'];
166 $data = $isProcessed;
169 else if (!isset($values['value']) ||
'' == trim($values['value'], ' ') ||
0 === strpos(ltrim($values['value'], ' '), '#'))
171 // if next line is less indented or equal, then it means that the current value is null
172 if ($this->isNextLineIndented())
178 $c = $this->getRealCurrentLineNb() +
1;
179 $parser = new sfYamlParser($c);
180 $parser->refs
=& $this->refs
;
181 $data[$key] = $parser->parse($this->getNextEmbedBlock());
188 $data = $this->refs
[$isInPlace];
192 $data[$key] = $this->parseValue($values['value']);
199 if (1 == count(explode("\n", rtrim($this->value
, "\n"))))
201 $value = sfYamlInline
::load($this->lines
[0]);
202 if (is_array($value))
204 $first = reset($value);
205 if ('*' === substr($first, 0, 1))
208 foreach ($value as $alias)
210 $data[] = $this->refs
[substr($alias, 1)];
219 throw new InvalidArgumentException(sprintf('Unable to parse line %d (%s).', $this->getRealCurrentLineNb() +
1, $this->currentLine
));
224 $this->refs
[$isRef] = end($data);
228 return empty($data) ?
null : $data;
232 * Returns the current line number (takes the offset into account).
234 * @return integer The current line number
236 protected function getRealCurrentLineNb()
238 return $this->currentLineNb +
$this->offset
;
242 * Returns the current line indentation.
244 * @return integer The current line indentation
246 protected function getCurrentLineIndentation()
248 return strlen($this->currentLine
) - strlen(ltrim($this->currentLine
, ' '));
252 * Returns the next embed block of YAML.
254 * @return string A YAML string
256 protected function getNextEmbedBlock()
258 $this->moveToNextLine();
260 $newIndent = $this->getCurrentLineIndentation();
262 if (!$this->isCurrentLineEmpty() && 0 == $newIndent)
264 throw new InvalidArgumentException(sprintf('Indentation problem at line %d (%s)', $this->getRealCurrentLineNb() +
1, $this->currentLine
));
267 $data = array(substr($this->currentLine
, $newIndent));
269 while ($this->moveToNextLine())
271 if ($this->isCurrentLineEmpty())
273 if ($this->isCurrentLineBlank())
275 $data[] = substr($this->currentLine
, $newIndent);
281 $indent = $this->getCurrentLineIndentation();
283 if (preg_match('#^(?P<text> *)$#', $this->currentLine
, $match))
286 $data[] = $match['text'];
288 else if ($indent >= $newIndent)
290 $data[] = substr($this->currentLine
, $newIndent);
292 else if (0 == $indent)
294 $this->moveToPreviousLine();
300 throw new InvalidArgumentException(sprintf('Indentation problem at line %d (%s)', $this->getRealCurrentLineNb() +
1, $this->currentLine
));
304 return implode("\n", $data);
308 * Moves the parser to the next line.
310 protected function moveToNextLine()
312 if ($this->currentLineNb
>= count($this->lines
) - 1)
317 $this->currentLine
= $this->lines
[++
$this->currentLineNb
];
323 * Moves the parser to the previous line.
325 protected function moveToPreviousLine()
327 $this->currentLine
= $this->lines
[--$this->currentLineNb
];
331 * Parses a YAML value.
333 * @param string $value A YAML value
335 * @return mixed A PHP value
337 protected function parseValue($value)
339 if ('*' === substr($value, 0, 1))
341 if (false !== $pos = strpos($value, '#'))
343 $value = substr($value, 1, $pos - 2);
347 $value = substr($value, 1);
350 if (!array_key_exists($value, $this->refs
))
352 throw new InvalidArgumentException(sprintf('Reference "%s" does not exist (%s).', $value, $this->currentLine
));
354 return $this->refs
[$value];
357 if (preg_match('/^(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments> +#.*)?$/', $value, $matches))
359 $modifiers = isset($matches['modifiers']) ?
$matches['modifiers'] : '';
361 return $this->parseFoldedScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), intval(abs($modifiers)));
365 return sfYamlInline
::load($value);
370 * Parses a folded scalar.
372 * @param string $separator The separator that was used to begin this folded scalar (| or >)
373 * @param string $indicator The indicator that was used to begin this folded scalar (+ or -)
374 * @param integer $indentation The indentation that was used to begin this folded scalar
376 * @return string The text value
378 protected function parseFoldedScalar($separator, $indicator = '', $indentation = 0)
380 $separator = '|' == $separator ?
"\n" : ' ';
383 $notEOF = $this->moveToNextLine();
385 while ($notEOF && $this->isCurrentLineBlank())
389 $notEOF = $this->moveToNextLine();
397 if (!preg_match('#^(?P<indent>'.($indentation ?
str_repeat(' ', $indentation) : ' +').')(?P<text>.*)$#', $this->currentLine
, $matches))
399 $this->moveToPreviousLine();
404 $textIndent = $matches['indent'];
407 $text .= $matches['text'].$separator;
408 while ($this->currentLineNb +
1 < count($this->lines
))
410 $this->moveToNextLine();
412 if (preg_match('#^(?P<indent> {'.strlen($textIndent).',})(?P<text>.+)$#', $this->currentLine
, $matches))
414 if (' ' == $separator && $previousIndent != $matches['indent'])
416 $text = substr($text, 0, -1)."\n";
418 $previousIndent = $matches['indent'];
420 $text .= str_repeat(' ', $diff = strlen($matches['indent']) - strlen($textIndent)).$matches['text'].($diff ?
"\n" : $separator);
422 else if (preg_match('#^(?P<text> *)$#', $this->currentLine
, $matches))
424 $text .= preg_replace('#^ {1,'.strlen($textIndent).'}#', '', $matches['text'])."\n";
428 $this->moveToPreviousLine();
434 if (' ' == $separator)
436 // replace last separator by a newline
437 $text = preg_replace('/ (\n*)$/', "\n$1", $text);
443 $text = preg_replace('#\n+$#s', "\n", $text);
448 $text = preg_replace('#\n+$#s', '', $text);
456 * Returns true if the next line is indented.
458 * @return Boolean Returns true if the next line is indented, false otherwise
460 protected function isNextLineIndented()
462 $currentIndentation = $this->getCurrentLineIndentation();
463 $notEOF = $this->moveToNextLine();
465 while ($notEOF && $this->isCurrentLineEmpty())
467 $notEOF = $this->moveToNextLine();
470 if (false === $notEOF)
476 if ($this->getCurrentLineIndentation() <= $currentIndentation)
481 $this->moveToPreviousLine();
487 * Returns true if the current line is blank or if it is a comment line.
489 * @return Boolean Returns true if the current line is empty or if it is a comment line, false otherwise
491 protected function isCurrentLineEmpty()
493 return $this->isCurrentLineBlank() ||
$this->isCurrentLineComment();
497 * Returns true if the current line is blank.
499 * @return Boolean Returns true if the current line is blank, false otherwise
501 protected function isCurrentLineBlank()
503 return '' == trim($this->currentLine
, ' ');
507 * Returns true if the current line is a comment line.
509 * @return Boolean Returns true if the current line is a comment line, false otherwise
511 protected function isCurrentLineComment()
513 //checking explicitly the first char of the trim is faster than loops or strpos
514 $ltrimmedLine = ltrim($this->currentLine
, ' ');
515 return $ltrimmedLine[0] === '#';
519 * Cleanups a YAML string to be parsed.
521 * @param string $value The input YAML string
523 * @return string A cleaned up YAML string
525 protected function cleanup($value)
527 $value = str_replace(array("\r\n", "\r"), "\n", $value);
529 if (!preg_match("#\n$#", $value))
535 preg_replace('#^\%YAML[: ][\d\.]+.*\n#s', '', $value);
538 $value = preg_replace('#^\-\-\-.*?\n#s', '', $value);