MIOLO20
Carregando...
Procurando...
Nenhuma entrada encontrada
Parser.php
Ir para a documentação deste ficheiro.
1<?php
2
4
5/*
6 * This file is part of the symfony package.
7 * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
8 *
9 * For the full copyright and license information, please view the LICENSE
10 * file that was distributed with this source code.
11 */
12
20class Parser
21{
22 protected $offset = 0;
23 protected $lines = array();
24 protected $currentLineNb = -1;
25 protected $currentLine = '';
26 protected $refs = array();
27
33 public function __construct($offset = 0)
34 {
35 $this->offset = $offset;
36 }
37
47 public function parse($value)
48 {
49 $this->currentLineNb = -1;
50 $this->currentLine = '';
51 $this->lines = explode("\n", $this->cleanup($value));
52
53 $data = array();
54 while ($this->moveToNextLine())
55 {
56 if ($this->isCurrentLineEmpty())
57 {
58 continue;
59 }
60
61 // tab?
62 if (preg_match('#^\t+#', $this->currentLine))
63 {
64 throw new ParserException(sprintf('A YAML file cannot contain tabs as indentation at line %d (%s).', $this->getRealCurrentLineNb() + 1, $this->currentLine));
65 }
66
67 $isRef = $isInPlace = $isProcessed = false;
68 if (preg_match('#^\-((?P<leadspaces>\s+)(?P<value>.+?))?\s*$#', $this->currentLine, $values))
69 {
70 if (isset($values['value']) && preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#', $values['value'], $matches))
71 {
72 $isRef = $matches['ref'];
73 $values['value'] = $matches['value'];
74 }
75
76 // array
77 if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#'))
78 {
79 $c = $this->getRealCurrentLineNb() + 1;
80 $parser = new Parser($c);
81 $parser->refs =& $this->refs;
82 $data[] = $parser->parse($this->getNextEmbedBlock());
83 }
84 else
85 {
86 if (isset($values['leadspaces'])
87 && ' ' == $values['leadspaces']
88 && preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{].*?) *\:(\s+(?P<value>.+?))?\s*$#', $values['value'], $matches))
89 {
90 // this is a compact notation element, add to next block and parse
91 $c = $this->getRealCurrentLineNb();
92 $parser = new Parser($c);
93 $parser->refs =& $this->refs;
94
95 $block = $values['value'];
96 if (!$this->isNextLineIndented())
97 {
98 $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + 2);
99 }
100
101 $data[] = $parser->parse($block);
102 }
103 else
104 {
105 $data[] = $this->parseValue($values['value']);
106 }
107 }
108 }
109 else if (preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"].*?) *\:(\s+(?P<value>.+?))?\s*$#', $this->currentLine, $values))
110 {
111 $key = Inline::parseScalar($values['key']);
112
113 if ('<<' === $key)
114 {
115 if (isset($values['value']) && '*' === substr($values['value'], 0, 1))
116 {
117 $isInPlace = substr($values['value'], 1);
118 if (!array_key_exists($isInPlace, $this->refs))
119 {
120 throw new ParserException(sprintf('Reference "%s" does not exist at line %s (%s).', $isInPlace, $this->getRealCurrentLineNb() + 1, $this->currentLine));
121 }
122 }
123 else
124 {
125 if (isset($values['value']) && $values['value'] !== '')
126 {
127 $value = $values['value'];
128 }
129 else
130 {
131 $value = $this->getNextEmbedBlock();
132 }
133 $c = $this->getRealCurrentLineNb() + 1;
134 $parser = new Parser($c);
135 $parser->refs =& $this->refs;
136 $parsed = $parser->parse($value);
137
138 $merged = array();
139 if (!is_array($parsed))
140 {
141 throw new ParserException(sprintf("YAML merge keys used with a scalar value instead of an array at line %s (%s)", $this->getRealCurrentLineNb() + 1, $this->currentLine));
142 }
143 else if (isset($parsed[0]))
144 {
145 // Numeric array, merge individual elements
146 foreach (array_reverse($parsed) as $parsedItem)
147 {
148 if (!is_array($parsedItem))
149 {
150 throw new ParserException(sprintf("Merge items must be arrays at line %s (%s).", $this->getRealCurrentLineNb() + 1, $parsedItem));
151 }
152 $merged = array_merge($parsedItem, $merged);
153 }
154 }
155 else
156 {
157 // Associative array, merge
158 $merged = array_merge($merge, $parsed);
159 }
160
161 $isProcessed = $merged;
162 }
163 }
164 else if (isset($values['value']) && preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#', $values['value'], $matches))
165 {
166 $isRef = $matches['ref'];
167 $values['value'] = $matches['value'];
168 }
169
170 if ($isProcessed)
171 {
172 // Merge keys
173 $data = $isProcessed;
174 }
175 // hash
176 else if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#'))
177 {
178 // if next line is less indented or equal, then it means that the current value is null
179 if ($this->isNextLineIndented())
180 {
181 $data[$key] = null;
182 }
183 else
184 {
185 $c = $this->getRealCurrentLineNb() + 1;
186 $parser = new Parser($c);
187 $parser->refs =& $this->refs;
188 $data[$key] = $parser->parse($this->getNextEmbedBlock());
189 }
190 }
191 else
192 {
193 if ($isInPlace)
194 {
195 $data = $this->refs[$isInPlace];
196 }
197 else
198 {
199 $data[$key] = $this->parseValue($values['value']);
200 }
201 }
202 }
203 else
204 {
205 // 1-liner followed by newline
206 if (2 == count($this->lines) && empty($this->lines[1]))
207 {
208 $value = Inline::load($this->lines[0]);
209 if (is_array($value))
210 {
211 $first = reset($value);
212 if ('*' === substr($first, 0, 1))
213 {
214 $data = array();
215 foreach ($value as $alias)
216 {
217 $data[] = $this->refs[substr($alias, 1)];
218 }
219 $value = $data;
220 }
221 }
222
223 return $value;
224 }
225
226 switch (preg_last_error())
227 {
228 case PREG_INTERNAL_ERROR:
229 $error = 'Internal PCRE error on line';
230 break;
231 case PREG_BACKTRACK_LIMIT_ERROR:
232 $error = 'pcre.backtrack_limit reached on line';
233 break;
234 case PREG_RECURSION_LIMIT_ERROR:
235 $error = 'pcre.recursion_limit reached on line';
236 break;
237 case PREG_BAD_UTF8_ERROR:
238 $error = 'Malformed UTF-8 data on line';
239 break;
240 case PREG_BAD_UTF8_OFFSET_ERROR:
241 $error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point on line';
242 break;
243 default:
244 $error = 'Unable to parse line';
245 }
246
247 throw new ParserException(sprintf('%s %d (%s).', $error, $this->getRealCurrentLineNb() + 1, $this->currentLine));
248 }
249
250 if ($isRef)
251 {
252 $this->refs[$isRef] = end($data);
253 }
254 }
255
256 return empty($data) ? null : $data;
257 }
258
264 protected function getRealCurrentLineNb()
265 {
266 return $this->currentLineNb + $this->offset;
267 }
268
274 protected function getCurrentLineIndentation()
275 {
276 return strlen($this->currentLine) - strlen(ltrim($this->currentLine, ' '));
277 }
278
286 protected function getNextEmbedBlock($indentation = null)
287 {
288 $this->moveToNextLine();
289
290 if (null === $indentation)
291 {
292 $newIndent = $this->getCurrentLineIndentation();
293
294 if (!$this->isCurrentLineEmpty() && 0 == $newIndent)
295 {
296 throw new ParserException(sprintf('Indentation problem at line %d (%s)', $this->getRealCurrentLineNb() + 1, $this->currentLine));
297 }
298 }
299 else
300 {
301 $newIndent = $indentation;
302 }
303
304 $data = array(substr($this->currentLine, $newIndent));
305
306 while ($this->moveToNextLine())
307 {
308 if ($this->isCurrentLineEmpty())
309 {
310 if ($this->isCurrentLineBlank())
311 {
312 $data[] = substr($this->currentLine, $newIndent);
313 }
314
315 continue;
316 }
317
318 $indent = $this->getCurrentLineIndentation();
319
320 if (preg_match('#^(?P<text> *)$#', $this->currentLine, $match))
321 {
322 // empty line
323 $data[] = $match['text'];
324 }
325 else if ($indent >= $newIndent)
326 {
327 $data[] = substr($this->currentLine, $newIndent);
328 }
329 else if (0 == $indent)
330 {
331 $this->moveToPreviousLine();
332
333 break;
334 }
335 else
336 {
337 throw new ParserException(sprintf('Indentation problem at line %d (%s)', $this->getRealCurrentLineNb() + 1, $this->currentLine));
338 }
339 }
340
341 return implode("\n", $data);
342 }
343
347 protected function moveToNextLine()
348 {
349 if ($this->currentLineNb >= count($this->lines) - 1)
350 {
351 return false;
352 }
353
354 $this->currentLine = $this->lines[++$this->currentLineNb];
355
356 return true;
357 }
358
362 protected function moveToPreviousLine()
363 {
364 $this->currentLine = $this->lines[--$this->currentLineNb];
365 }
366
374 protected function parseValue($value)
375 {
376 if ('*' === substr($value, 0, 1))
377 {
378 if (false !== $pos = strpos($value, '#'))
379 {
380 $value = substr($value, 1, $pos - 2);
381 }
382 else
383 {
384 $value = substr($value, 1);
385 }
386
387 if (!array_key_exists($value, $this->refs))
388 {
389 throw new ParserException(sprintf('Reference "%s" does not exist (%s).', $value, $this->currentLine));
390 }
391 return $this->refs[$value];
392 }
393
394 if (preg_match('/^(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments> +#.*)?$/', $value, $matches))
395 {
396 $modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : '';
397
398 return $this->parseFoldedScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), intval(abs($modifiers)));
399 }
400 else
401 {
402 return Inline::load($value);
403 }
404 }
405
415 protected function parseFoldedScalar($separator, $indicator = '', $indentation = 0)
416 {
417 $separator = '|' == $separator ? "\n" : ' ';
418 $text = '';
419
420 $notEOF = $this->moveToNextLine();
421
422 while ($notEOF && $this->isCurrentLineBlank())
423 {
424 $text .= "\n";
425
426 $notEOF = $this->moveToNextLine();
427 }
428
429 if (!$notEOF)
430 {
431 return '';
432 }
433
434 if (!preg_match('#^(?P<indent>'.($indentation ? str_repeat(' ', $indentation) : ' +').')(?P<text>.*)$#', $this->currentLine, $matches))
435 {
436 $this->moveToPreviousLine();
437
438 return '';
439 }
440
441 $textIndent = $matches['indent'];
442 $previousIndent = 0;
443
444 $text .= $matches['text'].$separator;
445 while ($this->currentLineNb + 1 < count($this->lines))
446 {
447 $this->moveToNextLine();
448
449 if (preg_match('#^(?P<indent> {'.strlen($textIndent).',})(?P<text>.+)$#', $this->currentLine, $matches))
450 {
451 if (' ' == $separator && $previousIndent != $matches['indent'])
452 {
453 $text = substr($text, 0, -1)."\n";
454 }
455 $previousIndent = $matches['indent'];
456
457 $text .= str_repeat(' ', $diff = strlen($matches['indent']) - strlen($textIndent)).$matches['text'].($diff ? "\n" : $separator);
458 }
459 else if (preg_match('#^(?P<text> *)$#', $this->currentLine, $matches))
460 {
461 $text .= preg_replace('#^ {1,'.strlen($textIndent).'}#', '', $matches['text'])."\n";
462 }
463 else
464 {
465 $this->moveToPreviousLine();
466
467 break;
468 }
469 }
470
471 if (' ' == $separator)
472 {
473 // replace last separator by a newline
474 $text = preg_replace('/ (\n*)$/', "\n$1", $text);
475 }
476
477 switch ($indicator)
478 {
479 case '':
480 $text = preg_replace('#\n+$#s', "\n", $text);
481 break;
482 case '+':
483 break;
484 case '-':
485 $text = preg_replace('#\n+$#s', '', $text);
486 break;
487 }
488
489 return $text;
490 }
491
497 protected function isNextLineIndented()
498 {
499 $currentIndentation = $this->getCurrentLineIndentation();
500 $notEOF = $this->moveToNextLine();
501
502 while ($notEOF && $this->isCurrentLineEmpty())
503 {
504 $notEOF = $this->moveToNextLine();
505 }
506
507 if (false === $notEOF)
508 {
509 return false;
510 }
511
512 $ret = false;
513 if ($this->getCurrentLineIndentation() <= $currentIndentation)
514 {
515 $ret = true;
516 }
517
518 $this->moveToPreviousLine();
519
520 return $ret;
521 }
522
528 protected function isCurrentLineEmpty()
529 {
530 return $this->isCurrentLineBlank() || $this->isCurrentLineComment();
531 }
532
538 protected function isCurrentLineBlank()
539 {
540 return '' == trim($this->currentLine, ' ');
541 }
542
548 protected function isCurrentLineComment()
549 {
550 //checking explicitly the first char of the trim is faster than loops or strpos
551 $ltrimmedLine = ltrim($this->currentLine, ' ');
552 return $ltrimmedLine[0] === '#';
553 }
554
562 protected function cleanup($value)
563 {
564 $value = str_replace(array("\r\n", "\r"), "\n", $value);
565
566 if (!preg_match("#\n$#", $value))
567 {
568 $value .= "\n";
569 }
570
571 // strip YAML header
572 $count = 0;
573 $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#s', '', $value, -1, $count);
574 $this->offset += $count;
575
576 // remove leading comments and/or ---
577 $trimmedValue = preg_replace('#^((\#.*?\n)|(\-\-\-.*?\n))*#s', '', $value, -1, $count);
578 if ($count == 1)
579 {
580 // items have been removed, update the offset
581 $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n");
582 $value = $trimmedValue;
583 }
584
585 return $value;
586 }
587}
static parseScalar($scalar, $delimiters=null, $stringDelimiters=array('"', "'"), &$i = 0, $evaluate = true)
Definição Inline.php:146
getNextEmbedBlock($indentation=null)
Definição Parser.php:286
parseFoldedScalar($separator, $indicator='', $indentation=0)
Definição Parser.php:415