1 <?php
  2 
  3   4   5   6   7   8   9  10 
 11 
 12 namespace Symfony\Component\Yaml;
 13 
 14 use Symfony\Component\Yaml\Exception\ParseException;
 15 
 16  17  18  19  20 
 21 class Parser
 22 {
 23     const  = '(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments> +#.*)?';
 24     
 25     const FOLDED_SCALAR_PATTERN = self::BLOCK_SCALAR_HEADER_PATTERN;
 26 
 27     private $offset = 0;
 28     private $totalNumberOfLines;
 29     private $lines = array();
 30     private $currentLineNb = -1;
 31     private $currentLine = '';
 32     private $refs = array();
 33     private $skippedLineNumbers = array();
 34     private $locallySkippedLineNumbers = array();
 35 
 36      37  38  39  40  41  42 
 43     public function __construct($offset = 0, $totalNumberOfLines = null, array $skippedLineNumbers = array())
 44     {
 45         $this->offset = $offset;
 46         $this->totalNumberOfLines = $totalNumberOfLines;
 47         $this->skippedLineNumbers = $skippedLineNumbers;
 48     }
 49 
 50      51  52  53  54  55  56  57  58  59  60  61 
 62     public function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false)
 63     {
 64         if (false === preg_match('//u', $value)) {
 65             throw new ParseException('The YAML value does not appear to be valid UTF-8.');
 66         }
 67 
 68         $this->refs = array();
 69 
 70         $mbEncoding = null;
 71         $e = null;
 72         $data = null;
 73 
 74         if (2  & (int) ini_get('mbstring.func_overload')) {
 75             $mbEncoding = mb_internal_encoding();
 76             mb_internal_encoding('UTF-8');
 77         }
 78 
 79         try {
 80             $data = $this->doParse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap);
 81         } catch (\Exception $e) {
 82         } catch (\Throwable $e) {
 83         }
 84 
 85         if (null !== $mbEncoding) {
 86             mb_internal_encoding($mbEncoding);
 87         }
 88 
 89         $this->lines = array();
 90         $this->currentLine = '';
 91         $this->refs = array();
 92         $this->skippedLineNumbers = array();
 93         $this->locallySkippedLineNumbers = array();
 94 
 95         if (null !== $e) {
 96             throw $e;
 97         }
 98 
 99         return $data;
100     }
101 
102     private function doParse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false)
103     {
104         $this->currentLineNb = -1;
105         $this->currentLine = '';
106         $value = $this->cleanup($value);
107         $this->lines = explode("\n", $value);
108         $this->locallySkippedLineNumbers = array();
109 
110         if (null === $this->totalNumberOfLines) {
111             $this->totalNumberOfLines = count($this->lines);
112         }
113 
114         $data = array();
115         $context = null;
116         $allowOverwrite = false;
117 
118         while ($this->moveToNextLine()) {
119             if ($this->isCurrentLineEmpty()) {
120                 continue;
121             }
122 
123             
124             if ("\t" === $this->currentLine[0]) {
125                 throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
126             }
127 
128             $isRef = $mergeNode = false;
129             if (self::preg_match('#^\-((?P<leadspaces>\s+)(?P<value>.+))?$#u', rtrim($this->currentLine), $values)) {
130                 if ($context && 'mapping' == $context) {
131                     throw new ParseException('You cannot define a sequence item when in a mapping', $this->getRealCurrentLineNb() + 1, $this->currentLine);
132                 }
133                 $context = 'sequence';
134 
135                 if (isset($values['value']) && self::preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
136                     $isRef = $matches['ref'];
137                     $values['value'] = $matches['value'];
138                 }
139 
140                 
141                 if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
142                     $data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $exceptionOnInvalidType, $objectSupport, $objectForMap);
143                 } else {
144                     if (isset($values['leadspaces'])
145                         && self::preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P<value>.+))?$#u', rtrim($values['value']), $matches)
146                     ) {
147                         
148                         $block = $values['value'];
149                         if ($this->isNextLineIndented()) {
150                             $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + strlen($values['leadspaces']) + 1);
151                         }
152 
153                         $data[] = $this->parseBlock($this->getRealCurrentLineNb(), $block, $exceptionOnInvalidType, $objectSupport, $objectForMap);
154                     } else {
155                         $data[] = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport, $objectForMap, $context);
156                     }
157                 }
158                 if ($isRef) {
159                     $this->refs[$isRef] = end($data);
160                 }
161             } elseif (
162                 self::preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\[\{].*?) *\:(\s+(?P<value>.+))?$#u', rtrim($this->currentLine), $values)
163                 && (false === strpos($values['key'], ' #') || in_array($values['key'][0], array('"', "'")))
164             ) {
165                 if ($context && 'sequence' == $context) {
166                     throw new ParseException('You cannot define a mapping item when in a sequence', $this->currentLineNb + 1, $this->currentLine);
167                 }
168                 $context = 'mapping';
169 
170                 
171                 Inline::parse(null, $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs);
172                 try {
173                     $key = Inline::parseScalar($values['key']);
174                 } catch (ParseException $e) {
175                     $e->setParsedLine($this->getRealCurrentLineNb() + 1);
176                     $e->setSnippet($this->currentLine);
177 
178                     throw $e;
179                 }
180 
181                 
182                 if (is_float($key)) {
183                     $key = (string) $key;
184                 }
185 
186                 if ('<<' === $key) {
187                     $mergeNode = true;
188                     $allowOverwrite = true;
189                     if (isset($values['value']) && 0 === strpos($values['value'], '*')) {
190                         $refName = substr($values['value'], 1);
191                         if (!array_key_exists($refName, $this->refs)) {
192                             throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine);
193                         }
194 
195                         $refValue = $this->refs[$refName];
196 
197                         if (!is_array($refValue)) {
198                             throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
199                         }
200 
201                         $data += $refValue; 
202                     } else {
203                         if (isset($values['value']) && $values['value'] !== '') {
204                             $value = $values['value'];
205                         } else {
206                             $value = $this->getNextEmbedBlock();
207                         }
208                         $parsed = $this->parseBlock($this->getRealCurrentLineNb() + 1, $value, $exceptionOnInvalidType, $objectSupport, $objectForMap);
209 
210                         if (!is_array($parsed)) {
211                             throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
212                         }
213 
214                         if (isset($parsed[0])) {
215                             
216                             
217                             
218                             foreach ($parsed as $parsedItem) {
219                                 if (!is_array($parsedItem)) {
220                                     throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem);
221                                 }
222 
223                                 $data += $parsedItem; 
224                             }
225                         } else {
226                             
227                             
228                             $data += $parsed; 
229                         }
230                     }
231                 } elseif (isset($values['value']) && self::preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
232                     $isRef = $matches['ref'];
233                     $values['value'] = $matches['value'];
234                 }
235 
236                 if ($mergeNode) {
237                     
238                 } elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
239                     
240                     
241                     if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) {
242                         
243                         
244                         if ($allowOverwrite || !isset($data[$key])) {
245                             $data[$key] = null;
246                         }
247                     } else {
248                         $value = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(), $exceptionOnInvalidType, $objectSupport, $objectForMap);
249                         
250                         
251                         if ($allowOverwrite || !isset($data[$key])) {
252                             $data[$key] = $value;
253                         }
254                     }
255                 } else {
256                     $value = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport, $objectForMap, $context);
257                     
258                     
259                     if ($allowOverwrite || !isset($data[$key])) {
260                         $data[$key] = $value;
261                     }
262                 }
263                 if ($isRef) {
264                     $this->refs[$isRef] = $data[$key];
265                 }
266             } else {
267                 
268                 if ('---' === $this->currentLine) {
269                     throw new ParseException('Multiple documents are not supported.', $this->currentLineNb + 1, $this->currentLine);
270                 }
271 
272                 
273                 if (is_string($value) && $this->lines[0] === trim($value)) {
274                     try {
275                         $value = Inline::parse($this->lines[0], $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs);
276                     } catch (ParseException $e) {
277                         $e->setParsedLine($this->getRealCurrentLineNb() + 1);
278                         $e->setSnippet($this->currentLine);
279 
280                         throw $e;
281                     }
282 
283                     return $value;
284                 }
285 
286                 throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
287             }
288         }
289 
290         if ($objectForMap && !is_object($data) && 'mapping' === $context) {
291             $object = new \stdClass();
292 
293             foreach ($data as $key => $value) {
294                 $object->$key = $value;
295             }
296 
297             $data = $object;
298         }
299 
300         return empty($data) ? null : $data;
301     }
302 
303     private function parseBlock($offset, $yaml, $exceptionOnInvalidType, $objectSupport, $objectForMap)
304     {
305         $skippedLineNumbers = $this->skippedLineNumbers;
306 
307         foreach ($this->locallySkippedLineNumbers as $lineNumber) {
308             if ($lineNumber < $offset) {
309                 continue;
310             }
311 
312             $skippedLineNumbers[] = $lineNumber;
313         }
314 
315         $parser = new self($offset, $this->totalNumberOfLines, $skippedLineNumbers);
316         $parser->refs = &$this->refs;
317 
318         return $parser->doParse($yaml, $exceptionOnInvalidType, $objectSupport, $objectForMap);
319     }
320 
321     322 323 324 325 
326     private function getRealCurrentLineNb()
327     {
328         $realCurrentLineNumber = $this->currentLineNb + $this->offset;
329 
330         foreach ($this->skippedLineNumbers as $skippedLineNumber) {
331             if ($skippedLineNumber > $realCurrentLineNumber) {
332                 break;
333             }
334 
335             ++$realCurrentLineNumber;
336         }
337 
338         return $realCurrentLineNumber;
339     }
340 
341     342 343 344 345 
346     private function getCurrentLineIndentation()
347     {
348         return strlen($this->currentLine) - strlen(ltrim($this->currentLine, ' '));
349     }
350 
351     352 353 354 355 356 357 358 359 360 
361     private function getNextEmbedBlock($indentation = null, $inSequence = false)
362     {
363         $oldLineIndentation = $this->getCurrentLineIndentation();
364         $blockScalarIndentations = array();
365 
366         if ($this->isBlockScalarHeader()) {
367             $blockScalarIndentations[] = $this->getCurrentLineIndentation();
368         }
369 
370         if (!$this->moveToNextLine()) {
371             return;
372         }
373 
374         if (null === $indentation) {
375             $newIndent = $this->getCurrentLineIndentation();
376 
377             $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem();
378 
379             if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) {
380                 throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
381             }
382         } else {
383             $newIndent = $indentation;
384         }
385 
386         $data = array();
387         if ($this->getCurrentLineIndentation() >= $newIndent) {
388             $data[] = substr($this->currentLine, $newIndent);
389         } else {
390             $this->moveToPreviousLine();
391 
392             return;
393         }
394 
395         if ($inSequence && $oldLineIndentation === $newIndent && isset($data[0][0]) && '-' === $data[0][0]) {
396             
397             
398             $this->moveToPreviousLine();
399 
400             return;
401         }
402 
403         $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem();
404 
405         if (empty($blockScalarIndentations) && $this->isBlockScalarHeader()) {
406             $blockScalarIndentations[] = $this->getCurrentLineIndentation();
407         }
408 
409         $previousLineIndentation = $this->getCurrentLineIndentation();
410 
411         while ($this->moveToNextLine()) {
412             $indent = $this->getCurrentLineIndentation();
413 
414             
415             if (!empty($blockScalarIndentations) && $indent < $previousLineIndentation && trim($this->currentLine) !== '') {
416                 foreach ($blockScalarIndentations as $key => $blockScalarIndentation) {
417                     if ($blockScalarIndentation >= $this->getCurrentLineIndentation()) {
418                         unset($blockScalarIndentations[$key]);
419                     }
420                 }
421             }
422 
423             if (empty($blockScalarIndentations) && !$this->isCurrentLineComment() && $this->isBlockScalarHeader()) {
424                 $blockScalarIndentations[] = $this->getCurrentLineIndentation();
425             }
426 
427             $previousLineIndentation = $indent;
428 
429             if ($isItUnindentedCollection && !$this->isCurrentLineEmpty() && !$this->isStringUnIndentedCollectionItem() && $newIndent === $indent) {
430                 $this->moveToPreviousLine();
431                 break;
432             }
433 
434             if ($this->isCurrentLineBlank()) {
435                 $data[] = substr($this->currentLine, $newIndent);
436                 continue;
437             }
438 
439             
440             if (empty($blockScalarIndentations) && $this->isCurrentLineComment()) {
441                 
442                 
443                 
444                 
445                 
446                 
447                 $this->locallySkippedLineNumbers[] = $this->getRealCurrentLineNb();
448 
449                 continue;
450             }
451 
452             if ($indent >= $newIndent) {
453                 $data[] = substr($this->currentLine, $newIndent);
454             } elseif (0 == $indent) {
455                 $this->moveToPreviousLine();
456 
457                 break;
458             } else {
459                 throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
460             }
461         }
462 
463         return implode("\n", $data);
464     }
465 
466     467 468 469 470 
471     private function moveToNextLine()
472     {
473         if ($this->currentLineNb >= count($this->lines) - 1) {
474             return false;
475         }
476 
477         $this->currentLine = $this->lines[++$this->currentLineNb];
478 
479         return true;
480     }
481 
482     483 484 485 486 
487     private function moveToPreviousLine()
488     {
489         if ($this->currentLineNb < 1) {
490             return false;
491         }
492 
493         $this->currentLine = $this->lines[--$this->currentLineNb];
494 
495         return true;
496     }
497 
498     499 500 501 502 503 504 505 506 507 508 509 510 
511     private function parseValue($value, $exceptionOnInvalidType, $objectSupport, $objectForMap, $context)
512     {
513         if (0 === strpos($value, '*')) {
514             if (false !== $pos = strpos($value, '#')) {
515                 $value = substr($value, 1, $pos - 2);
516             } else {
517                 $value = substr($value, 1);
518             }
519 
520             if (!array_key_exists($value, $this->refs)) {
521                 throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLineNb + 1, $this->currentLine);
522             }
523 
524             return $this->refs[$value];
525         }
526 
527         if (self::preg_match('/^'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value, $matches)) {
528             $modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : '';
529 
530             return $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), (int) abs($modifiers));
531         }
532 
533         try {
534             $parsedValue = Inline::parse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs);
535 
536             if ('mapping' === $context && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($parsedValue, ': ')) {
537                 @trigger_error(sprintf('Using a colon in the unquoted mapping value "%s" in line %d is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.', $value, $this->getRealCurrentLineNb() + 1), E_USER_DEPRECATED);
538 
539                 
540                 
541             }
542 
543             return $parsedValue;
544         } catch (ParseException $e) {
545             $e->setParsedLine($this->getRealCurrentLineNb() + 1);
546             $e->setSnippet($this->currentLine);
547 
548             throw $e;
549         }
550     }
551 
552     553 554 555 556 557 558 559 560 
561     private function parseBlockScalar($style, $chomping = '', $indentation = 0)
562     {
563         $notEOF = $this->moveToNextLine();
564         if (!$notEOF) {
565             return '';
566         }
567 
568         $isCurrentLineBlank = $this->isCurrentLineBlank();
569         $blockLines = array();
570 
571         
572         while ($notEOF && $isCurrentLineBlank) {
573             
574             if ($notEOF = $this->moveToNextLine()) {
575                 $blockLines[] = '';
576                 $isCurrentLineBlank = $this->isCurrentLineBlank();
577             }
578         }
579 
580         
581         if (0 === $indentation) {
582             if (self::preg_match('/^ +/', $this->currentLine, $matches)) {
583                 $indentation = strlen($matches[0]);
584             }
585         }
586 
587         if ($indentation > 0) {
588             $pattern = sprintf('/^ {%d}(.*)$/', $indentation);
589 
590             while (
591                 $notEOF && (
592                     $isCurrentLineBlank ||
593                     self::preg_match($pattern, $this->currentLine, $matches)
594                 )
595             ) {
596                 if ($isCurrentLineBlank && strlen($this->currentLine) > $indentation) {
597                     $blockLines[] = substr($this->currentLine, $indentation);
598                 } elseif ($isCurrentLineBlank) {
599                     $blockLines[] = '';
600                 } else {
601                     $blockLines[] = $matches[1];
602                 }
603 
604                 
605                 if ($notEOF = $this->moveToNextLine()) {
606                     $isCurrentLineBlank = $this->isCurrentLineBlank();
607                 }
608             }
609         } elseif ($notEOF) {
610             $blockLines[] = '';
611         }
612 
613         if ($notEOF) {
614             $blockLines[] = '';
615             $this->moveToPreviousLine();
616         } elseif (!$notEOF && !$this->isCurrentLineLastLineInDocument()) {
617             $blockLines[] = '';
618         }
619 
620         
621         if ('>' === $style) {
622             $text = '';
623             $previousLineIndented = false;
624             $previousLineBlank = false;
625 
626             for ($i = 0, $blockLinesCount = count($blockLines); $i < $blockLinesCount; ++$i) {
627                 if ('' === $blockLines[$i]) {
628                     $text .= "\n";
629                     $previousLineIndented = false;
630                     $previousLineBlank = true;
631                 } elseif (' ' === $blockLines[$i][0]) {
632                     $text .= "\n".$blockLines[$i];
633                     $previousLineIndented = true;
634                     $previousLineBlank = false;
635                 } elseif ($previousLineIndented) {
636                     $text .= "\n".$blockLines[$i];
637                     $previousLineIndented = false;
638                     $previousLineBlank = false;
639                 } elseif ($previousLineBlank || 0 === $i) {
640                     $text .= $blockLines[$i];
641                     $previousLineIndented = false;
642                     $previousLineBlank = false;
643                 } else {
644                     $text .= ' '.$blockLines[$i];
645                     $previousLineIndented = false;
646                     $previousLineBlank = false;
647                 }
648             }
649         } else {
650             $text = implode("\n", $blockLines);
651         }
652 
653         
654         if ('' === $chomping) {
655             $text = preg_replace('/\n+$/', "\n", $text);
656         } elseif ('-' === $chomping) {
657             $text = preg_replace('/\n+$/', '', $text);
658         }
659 
660         return $text;
661     }
662 
663     664 665 666 667 
668     private function isNextLineIndented()
669     {
670         $currentIndentation = $this->getCurrentLineIndentation();
671         $EOF = !$this->moveToNextLine();
672 
673         while (!$EOF && $this->isCurrentLineEmpty()) {
674             $EOF = !$this->moveToNextLine();
675         }
676 
677         if ($EOF) {
678             return false;
679         }
680 
681         $ret = $this->getCurrentLineIndentation() > $currentIndentation;
682 
683         $this->moveToPreviousLine();
684 
685         return $ret;
686     }
687 
688     689 690 691 692 
693     private function isCurrentLineEmpty()
694     {
695         return $this->isCurrentLineBlank() || $this->isCurrentLineComment();
696     }
697 
698     699 700 701 702 
703     private function isCurrentLineBlank()
704     {
705         return '' == trim($this->currentLine, ' ');
706     }
707 
708     709 710 711 712 
713     private function ()
714     {
715         
716         $ltrimmedLine = ltrim($this->currentLine, ' ');
717 
718         return '' !== $ltrimmedLine && $ltrimmedLine[0] === '#';
719     }
720 
721     private function isCurrentLineLastLineInDocument()
722     {
723         return ($this->offset + $this->currentLineNb) >= ($this->totalNumberOfLines - 1);
724     }
725 
726     727 728 729 730 731 732 
733     private function cleanup($value)
734     {
735         $value = str_replace(array("\r\n", "\r"), "\n", $value);
736 
737         
738         $count = 0;
739         $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#u', '', $value, -1, $count);
740         $this->offset += $count;
741 
742         
743         $trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count);
744         if ($count == 1) {
745             
746             $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n");
747             $value = $trimmedValue;
748         }
749 
750         
751         $trimmedValue = preg_replace('#^\-\-\-.*?\n#s', '', $value, -1, $count);
752         if ($count == 1) {
753             
754             $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n");
755             $value = $trimmedValue;
756 
757             
758             $value = preg_replace('#\.\.\.\s*$#', '', $value);
759         }
760 
761         return $value;
762     }
763 
764     765 766 767 768 
769     private function isNextLineUnIndentedCollection()
770     {
771         $currentIndentation = $this->getCurrentLineIndentation();
772         $notEOF = $this->moveToNextLine();
773 
774         while ($notEOF && $this->isCurrentLineEmpty()) {
775             $notEOF = $this->moveToNextLine();
776         }
777 
778         if (false === $notEOF) {
779             return false;
780         }
781 
782         $ret = $this->getCurrentLineIndentation() === $currentIndentation && $this->isStringUnIndentedCollectionItem();
783 
784         $this->moveToPreviousLine();
785 
786         return $ret;
787     }
788 
789     790 791 792 793 
794     private function isStringUnIndentedCollectionItem()
795     {
796         return '-' === rtrim($this->currentLine) || 0 === strpos($this->currentLine, '- ');
797     }
798 
799     800 801 802 803 
804     private function ()
805     {
806         return (bool) self::preg_match('~'.self::BLOCK_SCALAR_HEADER_PATTERN.'$~', $this->currentLine);
807     }
808 
809     810 811 812 813 814 815 816 817 818 819 820 821 
822     public static function preg_match($pattern, $subject, &$matches = null, $flags = 0, $offset = 0)
823     {
824         if (false === $ret = preg_match($pattern, $subject, $matches, $flags, $offset)) {
825             switch (preg_last_error()) {
826                 case PREG_INTERNAL_ERROR:
827                     $error = 'Internal PCRE error.';
828                     break;
829                 case PREG_BACKTRACK_LIMIT_ERROR:
830                     $error = 'pcre.backtrack_limit reached.';
831                     break;
832                 case PREG_RECURSION_LIMIT_ERROR:
833                     $error = 'pcre.recursion_limit reached.';
834                     break;
835                 case PREG_BAD_UTF8_ERROR:
836                     $error = 'Malformed UTF-8 data.';
837                     break;
838                 case PREG_BAD_UTF8_OFFSET_ERROR:
839                     $error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.';
840                     break;
841                 default:
842                     $error = 'Error.';
843             }
844 
845             throw new ParseException($error);
846         }
847 
848         return $ret;
849     }
850 }
851