/**
  * Parses a YAML string to a PHP value.
  *
  * @param  string $value A YAML string
  *
  * @return mixed  A PHP value
  *
  * @throws ParserException If the YAML is not valid
  */
 public function parse($value)
 {
     $this->currentLineNb = -1;
     $this->currentLine = '';
     $this->lines = explode("\n", $this->cleanup($value));
     if (function_exists('mb_internal_encoding') && (int) ini_get('mbstring.func_overload') & 2) {
         $mbEncoding = mb_internal_encoding();
         mb_internal_encoding('UTF-8');
     }
     $data = array();
     while ($this->moveToNextLine()) {
         if ($this->isCurrentLineEmpty()) {
             continue;
         }
         // tab?
         if (preg_match('#^\\t+#', $this->currentLine)) {
             throw new ParserException(sprintf('A YAML file cannot contain tabs as indentation at line %d (%s).', $this->getRealCurrentLineNb() + 1, $this->currentLine));
         }
         $isRef = $isInPlace = $isProcessed = false;
         if (preg_match('#^\\-((?P<leadspaces>\\s+)(?P<value>.+?))?\\s*$#u', $this->currentLine, $values)) {
             if (isset($values['value']) && preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
                 $isRef = $matches['ref'];
                 $values['value'] = $matches['value'];
             }
             // array
             if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
                 $c = $this->getRealCurrentLineNb() + 1;
                 $parser = new Parser($c);
                 $parser->refs =& $this->refs;
                 $data[] = $parser->parse($this->getNextEmbedBlock());
             } else {
                 if (isset($values['leadspaces']) && ' ' == $values['leadspaces'] && preg_match('#^(?P<key>' . Inline::REGEX_QUOTED_STRING . '|[^ \'"\\{\\[].*?) *\\:(\\s+(?P<value>.+?))?\\s*$#u', $values['value'], $matches)) {
                     // this is a compact notation element, add to next block and parse
                     $c = $this->getRealCurrentLineNb();
                     $parser = new Parser($c);
                     $parser->refs =& $this->refs;
                     $block = $values['value'];
                     if (!$this->isNextLineIndented()) {
                         $block .= "\n" . $this->getNextEmbedBlock($this->getCurrentLineIndentation() + 2);
                     }
                     $data[] = $parser->parse($block);
                 } else {
                     $data[] = $this->parseValue($values['value']);
                 }
             }
         } else {
             if (preg_match('#^(?P<key>' . Inline::REGEX_QUOTED_STRING . '|[^ \'"\\[\\{].*?) *\\:(\\s+(?P<value>.+?))?\\s*$#u', $this->currentLine, $values)) {
                 $key = Inline::parseScalar($values['key']);
                 if ('<<' === $key) {
                     if (isset($values['value']) && '*' === substr($values['value'], 0, 1)) {
                         $isInPlace = substr($values['value'], 1);
                         if (!array_key_exists($isInPlace, $this->refs)) {
                             throw new ParserException(sprintf('Reference "%s" does not exist at line %s (%s).', $isInPlace, $this->getRealCurrentLineNb() + 1, $this->currentLine));
                         }
                     } else {
                         if (isset($values['value']) && $values['value'] !== '') {
                             $value = $values['value'];
                         } else {
                             $value = $this->getNextEmbedBlock();
                         }
                         $c = $this->getRealCurrentLineNb() + 1;
                         $parser = new Parser($c);
                         $parser->refs =& $this->refs;
                         $parsed = $parser->parse($value);
                         $merged = array();
                         if (!is_array($parsed)) {
                             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));
                         } else {
                             if (isset($parsed[0])) {
                                 // Numeric array, merge individual elements
                                 foreach (array_reverse($parsed) as $parsedItem) {
                                     if (!is_array($parsedItem)) {
                                         throw new ParserException(sprintf('Merge items must be arrays at line %s (%s).', $this->getRealCurrentLineNb() + 1, $parsedItem));
                                     }
                                     $merged = array_merge($parsedItem, $merged);
                                 }
                             } else {
                                 // Associative array, merge
                                 $merged = array_merge($merged, $parsed);
                             }
                         }
                         $isProcessed = $merged;
                     }
                 } else {
                     if (isset($values['value']) && preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
                         $isRef = $matches['ref'];
                         $values['value'] = $matches['value'];
                     }
                 }
                 if ($isProcessed) {
                     // Merge keys
                     $data = $isProcessed;
                     // hash
                 } else {
                     if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
                         // if next line is less indented or equal, then it means that the current value is null
                         if ($this->isNextLineIndented()) {
                             $data[$key] = null;
                         } else {
                             $c = $this->getRealCurrentLineNb() + 1;
                             $parser = new Parser($c);
                             $parser->refs =& $this->refs;
                             $data[$key] = $parser->parse($this->getNextEmbedBlock());
                         }
                     } else {
                         if ($isInPlace) {
                             $data = $this->refs[$isInPlace];
                         } else {
                             $data[$key] = $this->parseValue($values['value']);
                         }
                     }
                 }
             } else {
                 // 1-liner followed by newline
                 if (2 == count($this->lines) && empty($this->lines[1])) {
                     $value = Inline::load($this->lines[0]);
                     if (is_array($value)) {
                         $first = reset($value);
                         if ('*' === substr($first, 0, 1)) {
                             $data = array();
                             foreach ($value as $alias) {
                                 $data[] = $this->refs[substr($alias, 1)];
                             }
                             $value = $data;
                         }
                     }
                     if (isset($mbEncoding)) {
                         mb_internal_encoding($mbEncoding);
                     }
                     return $value;
                 }
                 switch (preg_last_error()) {
                     case PREG_INTERNAL_ERROR:
                         $error = 'Internal PCRE error on line';
                         break;
                     case PREG_BACKTRACK_LIMIT_ERROR:
                         $error = 'pcre.backtrack_limit reached on line';
                         break;
                     case PREG_RECURSION_LIMIT_ERROR:
                         $error = 'pcre.recursion_limit reached on line';
                         break;
                     case PREG_BAD_UTF8_ERROR:
                         $error = 'Malformed UTF-8 data on line';
                         break;
                     case PREG_BAD_UTF8_OFFSET_ERROR:
                         $error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point on line';
                         break;
                     default:
                         $error = 'Unable to parse line';
                 }
                 throw new ParserException(sprintf('%s %d (%s).', $error, $this->getRealCurrentLineNb() + 1, $this->currentLine));
             }
         }
         if ($isRef) {
             $this->refs[$isRef] = end($data);
         }
     }
     if (isset($mbEncoding)) {
         mb_internal_encoding($mbEncoding);
     }
     return empty($data) ? null : $data;
 }
Exemple #2
0
  /**
   * Parses a YAML string to a PHP value.
   *
   * @param  string $value A YAML string
   *
   * @return mixed  A PHP value
   *
   * @throws \InvalidArgumentException If the YAML is not valid
   */
  public function parse($value)
  {
    $this->value = $this->cleanup($value);
    $this->currentLineNb = -1;
    $this->currentLine = '';
    $this->lines = explode("\n", $this->value);

    $data = array();
    while ($this->moveToNextLine())
    {
      if ($this->isCurrentLineEmpty())
      {
        continue;
      }

      // tab?
      if (preg_match('#^\t+#', $this->currentLine))
      {
        throw new \InvalidArgumentException(sprintf('A YAML file cannot contain tabs as indentation at line %d (%s).', $this->getRealCurrentLineNb() + 1, $this->currentLine));
      }

      $isRef = $isInPlace = $isProcessed = false;
      if (preg_match('#^\-(\s+(?P<value>.+?))?\s*$#', $this->currentLine, $values))
      {
        if (isset($values['value']) && preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#', $values['value'], $matches))
        {
          $isRef = $matches['ref'];
          $values['value'] = $matches['value'];
        }

        // array
        if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#'))
        {
          $c = $this->getRealCurrentLineNb() + 1;
          $parser = new Parser($c);
          $parser->refs =& $this->refs;
          $data[] = $parser->parse($this->getNextEmbedBlock());
        }
        else
        {
          if (preg_match('/^([^ ]+)\: +({.*?)$/', $values['value'], $matches))
          {
            $data[] = array($matches[1] => Inline::load($matches[2]));
          }
          else
          {
            $data[] = $this->parseValue($values['value']);
          }
        }
      }
      else if (preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ ].*?) *\:(\s+(?P<value>.+?))?\s*$#', $this->currentLine, $values))
      {
        $key = Inline::parseScalar($values['key']);

        if ('<<' === $key)
        {
          if (isset($values['value']) && '*' === substr($values['value'], 0, 1))
          {
            $isInPlace = substr($values['value'], 1);
            if (!array_key_exists($isInPlace, $this->refs))
            {
              throw new \InvalidArgumentException(sprintf('Reference "%s" does not exist at line %s (%s).', $isInPlace, $this->getRealCurrentLineNb() + 1, $this->currentLine));
            }
          }
          else
          {
            if (isset($values['value']) && $values['value'] !== '')
            {
              $value = $values['value'];
            }
            else
            {
              $value = $this->getNextEmbedBlock();
            }
            $c = $this->getRealCurrentLineNb() + 1;
            $parser = new Parser($c);
            $parser->refs =& $this->refs;
            $parsed = $parser->parse($value);

            $merged = array();
            if (!is_array($parsed))
            {
              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));
            }
            else if (isset($parsed[0]))
            {
              // Numeric array, merge individual elements
              foreach (array_reverse($parsed) as $parsedItem)
              {
                if (!is_array($parsedItem))
                {
                  throw new \InvalidArgumentException(sprintf("Merge items must be arrays at line %s (%s).", $this->getRealCurrentLineNb() + 1, $parsedItem));
                }
                $merged = array_merge($parsedItem, $merged);
              }
            }
            else
            {
              // Associative array, merge
              $merged = array_merge($merge, $parsed);
            }

            $isProcessed = $merged;
          }
        }
        else if (isset($values['value']) && preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#', $values['value'], $matches))
        {
          $isRef = $matches['ref'];
          $values['value'] = $matches['value'];
        }

        if ($isProcessed)
        {
          // Merge keys
          $data = $isProcessed;
        }
        // hash
        else if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#'))
        {
          // if next line is less indented or equal, then it means that the current value is null
          if ($this->isNextLineIndented())
          {
            $data[$key] = null;
          }
          else
          {
            $c = $this->getRealCurrentLineNb() + 1;
            $parser = new Parser($c);
            $parser->refs =& $this->refs;
            $data[$key] = $parser->parse($this->getNextEmbedBlock());
          }
        }
        else
        {
          if ($isInPlace)
          {
            $data = $this->refs[$isInPlace];
          }
          else
          {
            $data[$key] = $this->parseValue($values['value']);
          }
        }
      }
      else
      {
        // one liner?
        if (1 == count(explode("\n", rtrim($this->value, "\n"))))
        {
          $value = Inline::load($this->lines[0]);
          if (is_array($value))
          {
            $first = reset($value);
            if ('*' === substr($first, 0, 1))
            {
              $data = array();
              foreach ($value as $alias)
              {
                $data[] = $this->refs[substr($alias, 1)];
              }
              $value = $data;
            }
          }

          return $value;
        }

        switch (preg_last_error())
        {
          case PREG_INTERNAL_ERROR:
            $error = 'Internal PCRE error on line';
            break;
          case PREG_BACKTRACK_LIMIT_ERROR:
            $error = 'pcre.backtrack_limit reached on line';
            break;
          case PREG_RECURSION_LIMIT_ERROR:
            $error = 'pcre.recursion_limit reached on line';
            break;
          case PREG_BAD_UTF8_ERROR:
            $error = 'Malformed UTF-8 data on line';
            break;
          case PREG_BAD_UTF8_OFFSET_ERROR:
            $error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point on line';
            break;
          default:
            $error = 'Unable to parse line';
        }

        throw new \InvalidArgumentException(sprintf('%s %d (%s).', $error, $this->getRealCurrentLineNb() + 1, $this->currentLine));
      }

      if ($isRef)
      {
        $this->refs[$isRef] = end($data);
      }
    }

    return empty($data) ? null : $data;
  }