Verzeichnisstruktur phpBB-3.1.0


Veröffentlicht
27.10.2014

So funktioniert es


Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück

Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

(Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

Parser.php

Zuletzt modifiziert: 09.10.2024, 12:58 - Dateigröße: 23.46 KiB


001  <?php
002   
003  /*
004   * This file is part of the Symfony package.
005   *
006   * (c) Fabien Potencier <fabien@symfony.com>
007   *
008   * For the full copyright and license information, please view the LICENSE
009   * file that was distributed with this source code.
010   */
011   
012  namespace Symfony\Component\Yaml;
013   
014  use Symfony\Component\Yaml\Exception\ParseException;
015   
016  /**
017   * Parser parses YAML strings to convert them to PHP arrays.
018   *
019   * @author Fabien Potencier <fabien@symfony.com>
020   */
021  class Parser
022  {
023      const FOLDED_SCALAR_PATTERN = '(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments> +#.*)?';
024   
025      private $offset         = 0;
026      private $lines          = array();
027      private $currentLineNb  = -1;
028      private $currentLine    = '';
029      private $refs           = array();
030   
031      /**
032       * Constructor
033       *
034       * @param int     $offset The offset of YAML document (used for line numbers in error messages)
035       */
036      public function __construct($offset = 0)
037      {
038          $this->offset = $offset;
039      }
040   
041      /**
042       * Parses a YAML string to a PHP value.
043       *
044       * @param string  $value                  A YAML string
045       * @param bool    $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
046       * @param bool    $objectSupport          true if object support is enabled, false otherwise
047       *
048       * @return mixed  A PHP value
049       *
050       * @throws ParseException If the YAML is not valid
051       */
052      public function parse($value, $exceptionOnInvalidType = false, $objectSupport = false)
053      {
054          $this->currentLineNb = -1;
055          $this->currentLine = '';
056          $this->lines = explode("\n", $this->cleanup($value));
057   
058          if (!preg_match('//u', $value)) {
059              throw new ParseException('The YAML value does not appear to be valid UTF-8.');
060          }
061   
062          if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) {
063              $mbEncoding = mb_internal_encoding();
064              mb_internal_encoding('UTF-8');
065          }
066   
067          $data = array();
068          $context = null;
069          while ($this->moveToNextLine()) {
070              if ($this->isCurrentLineEmpty()) {
071                  continue;
072              }
073   
074              // tab?
075              if ("\t" === $this->currentLine[0]) {
076                  throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
077              }
078   
079              $isRef = $isInPlace = $isProcessed = false;
080              if (preg_match('#^\-((?P<leadspaces>\s+)(?P<value>.+?))?\s*$#u', $this->currentLine, $values)) {
081                  if ($context && 'mapping' == $context) {
082                      throw new ParseException('You cannot define a sequence item when in a mapping');
083                  }
084                  $context = 'sequence';
085   
086                  if (isset($values['value']) && preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
087                      $isRef = $matches['ref'];
088                      $values['value'] = $matches['value'];
089                  }
090   
091                  // array
092                  if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
093                      $c = $this->getRealCurrentLineNb() + 1;
094                      $parser = new Parser($c);
095                      $parser->refs = & $this->refs;
096                      $data[] = $parser->parse($this->getNextEmbedBlock(null, true), $exceptionOnInvalidType, $objectSupport);
097                  } else {
098                      if (isset($values['leadspaces'])
099                          && ' ' == $values['leadspaces']
100                          && preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P<value>.+?))?\s*$#u', $values['value'], $matches)
101                      ) {
102                          // this is a compact notation element, add to next block and parse
103                          $c = $this->getRealCurrentLineNb();
104                          $parser = new Parser($c);
105                          $parser->refs = & $this->refs;
106   
107                          $block = $values['value'];
108                          if ($this->isNextLineIndented()) {
109                              $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + 2);
110                          }
111   
112                          $data[] = $parser->parse($block, $exceptionOnInvalidType, $objectSupport);
113                      } else {
114                          $data[] = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport);
115                      }
116                  }
117              } elseif (preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\[\{].*?) *\:(\s+(?P<value>.+?))?\s*$#u', $this->currentLine, $values) && (false === strpos($values['key'],' #') || in_array($values['key'][0], array('"', "'")))) {
118                  if ($context && 'sequence' == $context) {
119                      throw new ParseException('You cannot define a mapping item when in a sequence');
120                  }
121                  $context = 'mapping';
122   
123                  // force correct settings
124                  Inline::parse(null, $exceptionOnInvalidType, $objectSupport, $this->refs);
125                  try {
126                      $key = Inline::parseScalar($values['key']);
127                  } catch (ParseException $e) {
128                      $e->setParsedLine($this->getRealCurrentLineNb() + 1);
129                      $e->setSnippet($this->currentLine);
130   
131                      throw $e;
132                  }
133   
134                  if ('<<' === $key) {
135                      if (isset($values['value']) && 0 === strpos($values['value'], '*')) {
136                          $isInPlace = substr($values['value'], 1);
137                          if (!array_key_exists($isInPlace, $this->refs)) {
138                              throw new ParseException(sprintf('Reference "%s" does not exist.', $isInPlace), $this->getRealCurrentLineNb() + 1, $this->currentLine);
139                          }
140                      } else {
141                          if (isset($values['value']) && $values['value'] !== '') {
142                              $value = $values['value'];
143                          } else {
144                              $value = $this->getNextEmbedBlock();
145                          }
146                          $c = $this->getRealCurrentLineNb() + 1;
147                          $parser = new Parser($c);
148                          $parser->refs = & $this->refs;
149                          $parsed = $parser->parse($value, $exceptionOnInvalidType, $objectSupport);
150   
151                          $merged = array();
152                          if (!is_array($parsed)) {
153                              throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
154                          } elseif (isset($parsed[0])) {
155                              // Numeric array, merge individual elements
156                              foreach (array_reverse($parsed) as $parsedItem) {
157                                  if (!is_array($parsedItem)) {
158                                      throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem);
159                                  }
160                                  $merged = array_merge($parsedItem, $merged);
161                              }
162                          } else {
163                              // Associative array, merge
164                              $merged = array_merge($merged, $parsed);
165                          }
166   
167                          $isProcessed = $merged;
168                      }
169                  } elseif (isset($values['value']) && preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
170                      $isRef = $matches['ref'];
171                      $values['value'] = $matches['value'];
172                  }
173   
174                  if ($isProcessed) {
175                      // Merge keys
176                      $data = $isProcessed;
177                  // hash
178                  } elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
179                      // if next line is less indented or equal, then it means that the current value is null
180                      if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) {
181                          $data[$key] = null;
182                      } else {
183                          $c = $this->getRealCurrentLineNb() + 1;
184                          $parser = new Parser($c);
185                          $parser->refs = & $this->refs;
186                          $data[$key] = $parser->parse($this->getNextEmbedBlock(), $exceptionOnInvalidType, $objectSupport);
187                      }
188                  } else {
189                      if ($isInPlace) {
190                          $data = $this->refs[$isInPlace];
191                      } else {
192                          $data[$key] = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport);
193                      }
194                  }
195              } else {
196                  // 1-liner optionally followed by newline
197                  $lineCount = count($this->lines);
198                  if (1 === $lineCount || (2 === $lineCount && empty($this->lines[1]))) {
199                      try {
200                          $value = Inline::parse($this->lines[0], $exceptionOnInvalidType, $objectSupport, $this->refs);
201                      } catch (ParseException $e) {
202                          $e->setParsedLine($this->getRealCurrentLineNb() + 1);
203                          $e->setSnippet($this->currentLine);
204   
205                          throw $e;
206                      }
207   
208                      if (is_array($value)) {
209                          $first = reset($value);
210                          if (is_string($first) && 0 === strpos($first, '*')) {
211                              $data = array();
212                              foreach ($value as $alias) {
213                                  $data[] = $this->refs[substr($alias, 1)];
214                              }
215                              $value = $data;
216                          }
217                      }
218   
219                      if (isset($mbEncoding)) {
220                          mb_internal_encoding($mbEncoding);
221                      }
222   
223                      return $value;
224                  }
225   
226                  switch (preg_last_error()) {
227                      case PREG_INTERNAL_ERROR:
228                          $error = 'Internal PCRE error.';
229                          break;
230                      case PREG_BACKTRACK_LIMIT_ERROR:
231                          $error = 'pcre.backtrack_limit reached.';
232                          break;
233                      case PREG_RECURSION_LIMIT_ERROR:
234                          $error = 'pcre.recursion_limit reached.';
235                          break;
236                      case PREG_BAD_UTF8_ERROR:
237                          $error = 'Malformed UTF-8 data.';
238                          break;
239                      case PREG_BAD_UTF8_OFFSET_ERROR:
240                          $error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.';
241                          break;
242                      default:
243                          $error = 'Unable to parse.';
244                  }
245   
246                  throw new ParseException($error, $this->getRealCurrentLineNb() + 1, $this->currentLine);
247              }
248   
249              if ($isRef) {
250                  $this->refs[$isRef] = end($data);
251              }
252          }
253   
254          if (isset($mbEncoding)) {
255              mb_internal_encoding($mbEncoding);
256          }
257   
258          return empty($data) ? null : $data;
259      }
260   
261      /**
262       * Returns the current line number (takes the offset into account).
263       *
264       * @return int     The current line number
265       */
266      private function getRealCurrentLineNb()
267      {
268          return $this->currentLineNb + $this->offset;
269      }
270   
271      /**
272       * Returns the current line indentation.
273       *
274       * @return int     The current line indentation
275       */
276      private function getCurrentLineIndentation()
277      {
278          return strlen($this->currentLine) - strlen(ltrim($this->currentLine, ' '));
279      }
280   
281      /**
282       * Returns the next embed block of YAML.
283       *
284       * @param int  $indentation The indent level at which the block is to be read, or null for default
285       * @param bool $inSequence  True if the enclosing data structure is a sequence
286       *
287       * @return string A YAML string
288       *
289       * @throws ParseException When indentation problem are detected
290       */
291      private function getNextEmbedBlock($indentation = null, $inSequence = false)
292      {
293          $oldLineIndentation = $this->getCurrentLineIndentation();
294   
295          if (!$this->moveToNextLine()) {
296              return;
297          }
298   
299          if (null === $indentation) {
300              $newIndent = $this->getCurrentLineIndentation();
301   
302              $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem($this->currentLine);
303   
304              if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) {
305                  throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
306              }
307          } else {
308              $newIndent = $indentation;
309          }
310   
311          $data = array(substr($this->currentLine, $newIndent));
312   
313          if ($inSequence && $oldLineIndentation === $newIndent && '-' === $data[0][0]) {
314              // the previous line contained a dash but no item content, this line is a sequence item with the same indentation
315              // and therefore no nested list or mapping
316              $this->moveToPreviousLine();
317   
318              return;
319          }
320   
321          $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem($this->currentLine);
322   
323          // Comments must not be removed inside a string block (ie. after a line ending with "|")
324          $removeCommentsPattern = '~'.self::FOLDED_SCALAR_PATTERN.'$~';
325          $removeComments = !preg_match($removeCommentsPattern, $this->currentLine);
326   
327          while ($this->moveToNextLine()) {
328              $indent = $this->getCurrentLineIndentation();
329   
330              if ($indent === $newIndent) {
331                  $removeComments = !preg_match($removeCommentsPattern, $this->currentLine);
332              }
333   
334              if ($isItUnindentedCollection && !$this->isStringUnIndentedCollectionItem($this->currentLine)) {
335                  $this->moveToPreviousLine();
336                  break;
337              }
338   
339              if ($this->isCurrentLineBlank()) {
340                  $data[] = substr($this->currentLine, $newIndent);
341                  continue;
342              }
343   
344              if ($removeComments && $this->isCurrentLineComment()) {
345                  continue;
346              }
347   
348              if ($indent >= $newIndent) {
349                  $data[] = substr($this->currentLine, $newIndent);
350              } elseif (0 == $indent) {
351                  $this->moveToPreviousLine();
352   
353                  break;
354              } else {
355                  throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
356              }
357          }
358   
359          return implode("\n", $data);
360      }
361   
362      /**
363       * Moves the parser to the next line.
364       *
365       * @return bool
366       */
367      private function moveToNextLine()
368      {
369          if ($this->currentLineNb >= count($this->lines) - 1) {
370              return false;
371          }
372   
373          $this->currentLine = $this->lines[++$this->currentLineNb];
374   
375          return true;
376      }
377   
378      /**
379       * Moves the parser to the previous line.
380       */
381      private function moveToPreviousLine()
382      {
383          $this->currentLine = $this->lines[--$this->currentLineNb];
384      }
385   
386      /**
387       * Parses a YAML value.
388       *
389       * @param string  $value                  A YAML value
390       * @param bool    $exceptionOnInvalidType True if an exception must be thrown on invalid types false otherwise
391       * @param bool    $objectSupport          True if object support is enabled, false otherwise
392       *
393       * @return mixed  A PHP value
394       *
395       * @throws ParseException When reference does not exist
396       */
397      private function parseValue($value, $exceptionOnInvalidType, $objectSupport)
398      {
399          if (0 === strpos($value, '*')) {
400              if (false !== $pos = strpos($value, '#')) {
401                  $value = substr($value, 1, $pos - 2);
402              } else {
403                  $value = substr($value, 1);
404              }
405   
406              if (!array_key_exists($value, $this->refs)) {
407                  throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLine);
408              }
409   
410              return $this->refs[$value];
411          }
412   
413          if (preg_match('/^'.self::FOLDED_SCALAR_PATTERN.'$/', $value, $matches)) {
414              $modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : '';
415   
416              return $this->parseFoldedScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), intval(abs($modifiers)));
417          }
418   
419          try {
420              return Inline::parse($value, $exceptionOnInvalidType, $objectSupport, $this->refs);
421          } catch (ParseException $e) {
422              $e->setParsedLine($this->getRealCurrentLineNb() + 1);
423              $e->setSnippet($this->currentLine);
424   
425              throw $e;
426          }
427      }
428   
429      /**
430       * Parses a folded scalar.
431       *
432       * @param string  $separator   The separator that was used to begin this folded scalar (| or >)
433       * @param string  $indicator   The indicator that was used to begin this folded scalar (+ or -)
434       * @param int     $indentation The indentation that was used to begin this folded scalar
435       *
436       * @return string  The text value
437       */
438      private function parseFoldedScalar($separator, $indicator = '', $indentation = 0)
439      {
440          $notEOF = $this->moveToNextLine();
441          if (!$notEOF) {
442              return '';
443          }
444   
445          $isCurrentLineBlank = $this->isCurrentLineBlank();
446          $text = '';
447   
448          // leading blank lines are consumed before determining indentation
449          while ($notEOF && $isCurrentLineBlank) {
450              // newline only if not EOF
451              if ($notEOF = $this->moveToNextLine()) {
452                  $text .= "\n";
453                  $isCurrentLineBlank = $this->isCurrentLineBlank();
454              }
455          }
456   
457          // determine indentation if not specified
458          if (0 === $indentation) {
459              if (preg_match('/^ +/', $this->currentLine, $matches)) {
460                  $indentation = strlen($matches[0]);
461              }
462          }
463   
464          if ($indentation > 0) {
465              $pattern = sprintf('/^ {%d}(.*)$/', $indentation);
466   
467              while (
468                  $notEOF && (
469                      $isCurrentLineBlank ||
470                      preg_match($pattern, $this->currentLine, $matches)
471                  )
472              ) {
473                  if ($isCurrentLineBlank) {
474                      $text .= substr($this->currentLine, $indentation);
475                  } else {
476                      $text .= $matches[1];
477                  }
478   
479                  // newline only if not EOF
480                  if ($notEOF = $this->moveToNextLine()) {
481                      $text .= "\n";
482                      $isCurrentLineBlank = $this->isCurrentLineBlank();
483                  }
484              }
485          } elseif ($notEOF) {
486              $text .= "\n";
487          }
488   
489          if ($notEOF) {
490              $this->moveToPreviousLine();
491          }
492   
493          // replace all non-trailing single newlines with spaces in folded blocks
494          if ('>' === $separator) {
495              preg_match('/(\n*)$/', $text, $matches);
496              $text = preg_replace('/(?<!\n)\n(?!\n)/', ' ', rtrim($text, "\n"));
497              $text .= $matches[1];
498          }
499   
500          // deal with trailing newlines as indicated
501          if ('' === $indicator) {
502              $text = preg_replace('/\n+$/s', "\n", $text);
503          } elseif ('-' === $indicator) {
504              $text = preg_replace('/\n+$/s', '', $text);
505          }
506   
507          return $text;
508      }
509   
510      /**
511       * Returns true if the next line is indented.
512       *
513       * @return bool    Returns true if the next line is indented, false otherwise
514       */
515      private function isNextLineIndented()
516      {
517          $currentIndentation = $this->getCurrentLineIndentation();
518          $EOF = !$this->moveToNextLine();
519   
520          while (!$EOF && $this->isCurrentLineEmpty()) {
521              $EOF = !$this->moveToNextLine();
522          }
523   
524          if ($EOF) {
525              return false;
526          }
527   
528          $ret = false;
529          if ($this->getCurrentLineIndentation() > $currentIndentation) {
530              $ret = true;
531          }
532   
533          $this->moveToPreviousLine();
534   
535          return $ret;
536      }
537   
538      /**
539       * Returns true if the current line is blank or if it is a comment line.
540       *
541       * @return bool    Returns true if the current line is empty or if it is a comment line, false otherwise
542       */
543      private function isCurrentLineEmpty()
544      {
545          return $this->isCurrentLineBlank() || $this->isCurrentLineComment();
546      }
547   
548      /**
549       * Returns true if the current line is blank.
550       *
551       * @return bool    Returns true if the current line is blank, false otherwise
552       */
553      private function isCurrentLineBlank()
554      {
555          return '' == trim($this->currentLine, ' ');
556      }
557   
558      /**
559       * Returns true if the current line is a comment line.
560       *
561       * @return bool    Returns true if the current line is a comment line, false otherwise
562       */
563      private function isCurrentLineComment()
564      {
565          //checking explicitly the first char of the trim is faster than loops or strpos
566          $ltrimmedLine = ltrim($this->currentLine, ' ');
567   
568          return $ltrimmedLine[0] === '#';
569      }
570   
571      /**
572       * Cleanups a YAML string to be parsed.
573       *
574       * @param string $value The input YAML string
575       *
576       * @return string A cleaned up YAML string
577       */
578      private function cleanup($value)
579      {
580          $value = str_replace(array("\r\n", "\r"), "\n", $value);
581   
582          // strip YAML header
583          $count = 0;
584          $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#su', '', $value, -1, $count);
585          $this->offset += $count;
586   
587          // remove leading comments
588          $trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count);
589          if ($count == 1) {
590              // items have been removed, update the offset
591              $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n");
592              $value = $trimmedValue;
593          }
594   
595          // remove start of the document marker (---)
596          $trimmedValue = preg_replace('#^\-\-\-.*?\n#s', '', $value, -1, $count);
597          if ($count == 1) {
598              // items have been removed, update the offset
599              $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n");
600              $value = $trimmedValue;
601   
602              // remove end of the document marker (...)
603              $value = preg_replace('#\.\.\.\s*$#s', '', $value);
604          }
605   
606          return $value;
607      }
608   
609      /**
610       * Returns true if the next line starts unindented collection
611       *
612       * @return bool    Returns true if the next line starts unindented collection, false otherwise
613       */
614      private function isNextLineUnIndentedCollection()
615      {
616          $currentIndentation = $this->getCurrentLineIndentation();
617          $notEOF = $this->moveToNextLine();
618   
619          while ($notEOF && $this->isCurrentLineEmpty()) {
620              $notEOF = $this->moveToNextLine();
621          }
622   
623          if (false === $notEOF) {
624              return false;
625          }
626   
627          $ret = false;
628          if (
629              $this->getCurrentLineIndentation() == $currentIndentation
630              &&
631              $this->isStringUnIndentedCollectionItem($this->currentLine)
632          ) {
633              $ret = true;
634          }
635   
636          $this->moveToPreviousLine();
637   
638          return $ret;
639      }
640   
641      /**
642       * Returns true if the string is un-indented collection item
643       *
644       * @return bool    Returns true if the string is un-indented collection item, false otherwise
645       */
646      private function isStringUnIndentedCollectionItem()
647      {
648          return (0 === strpos($this->currentLine, '- '));
649      }
650  }
651