/** * Test method for scalar with correct quoted string * * @return void */ public function testParseScalarWithCorrectlyQuotedStringShouldReturnString() { $value = "'don''t do somthin'' like that'"; $expect = "don't do somthin' like that"; $this->assertSame($expect, Inline::parseScalar($value)); }
/** * Parses a YAML string to a PHP value * * @param string $value A YAML string * * @throws ParseException If the YAML is not valid * @return mixed A PHP value */ public function parse($value) { $this->currentLineNb = -1; $this->currentLine = ""; $value = $this->cleanup($value); $this->lines = explode("\n", $value); $data = []; $context = null; while ($this->moveToNextLine()) { if ($this->isCurrentLineEmpty()) { continue; } // tab? if ($this->currentLine[0] === "\t") { throw new ParseException("A YAML file cannot contain tabs as indentation.", $this->getRealCurrentLineNb() + 1, $this->currentLine); } $isRef = false; $isInPlace = false; $isProcessed = false; if (preg_match("#^\\-((?P<leadspaces>\\s+)(?P<value>.+?))?\\s*\$#u", $this->currentLine, $values)) { if ($context && $context === "mapping") { throw new ParseException("You cannot define a sequence item when in a mapping"); } $context = "sequence"; 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"], " ") === "" || strpos(ltrim($values["value"], " "), "#") === 0) { $c = $this->getRealCurrentLineNb() + 1; $parser = new static($c); $parser->refs =& $this->refs; $data[] = $parser->parse($this->getNextEmbedBlock(null, true)); } else { if (isset($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 static($c); $parser->refs =& $this->refs; $block = $values["value"]; if ($this->isNextLineIndented()) { $block .= "\n" . $this->getNextEmbedBlock($this->getCurrentLineIndentation() + strlen($values["leadspaces"]) + 1); } $data[] = $parser->parse($block); } else { $data[] = $this->parseValue($values["value"], $context); } } if ($isRef) { $this->refs[$isRef] = end($data); } } elseif (preg_match("#^(?P<key>" . Inline::REGEX_QUOTED_STRING . "|[^ '\"\\[\\{].*?) *\\:(\\s+(?P<value>.+?))?\\s*\$#u", $this->currentLine, $values) && (strpos($values["key"], " #") === false || in_array($values["key"][0], ["\"", "'"]))) { if ($context && $context === "sequence") { throw new ParseException("You cannot define a mapping item when in a sequence"); } $context = "mapping"; // force correct settings Inline::parse(null, $this->refs); try { $key = Inline::parseScalar($values["key"]); } catch (ParseException $e) { $e->setParsedLine($this->getRealCurrentLineNb() + 1); $e->setSnippet($this->currentLine); throw $e; } // Convert float keys to strings, to avoid being converted to integers by PHP if (is_float($key)) { $key = (string) $key; } if ($key === "<<") { if (isset($values["value"]) && strpos($values["value"], "*") === 0) { $isInPlace = substr($values["value"], 1); if (!array_key_exists($isInPlace, $this->refs)) { throw new ParseException(sprintf("Reference \"%s\" does not exist.", $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 static($c); $parser->refs =& $this->refs; $parsed = $parser->parse($value); $merged = []; if (!is_array($parsed)) { throw new ParseException("YAML merge keys used with a scalar value instead of an array.", $this->getRealCurrentLineNb() + 1, $this->currentLine); } elseif (isset($parsed[0])) { // Numeric array, merge individual elements foreach (array_reverse($parsed) as $parsedItem) { if (!is_array($parsedItem)) { throw new ParseException("Merge items must be arrays.", $this->getRealCurrentLineNb() + 1, $parsedItem); } $merged = array_merge($parsedItem, $merged); } } else { // Associative array, merge $merged = array_merge($merged, $parsed); } $isProcessed = $merged; } } elseif (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; } elseif (!isset($values["value"]) || trim($values["value"], " ") === "" || strpos(ltrim($values["value"], " "), "#") === 0) { // if next line is less indented or equal, then it means that the current value is null if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) { $data[$key] = null; } else { $c = $this->getRealCurrentLineNb() + 1; $parser = new static($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"], $context); } } if ($isRef) { $this->refs[$isRef] = $data[$key]; } } else { // multiple documents are not supported if ($this->currentLine === "---") { throw new ParseException("Multiple documents are not supported."); } // 1-liner optionally followed by newline(s) if (is_string($value) && $this->lines[0] === trim($value)) { try { $value = Inline::parse($this->lines[0], $this->refs); } catch (ParseException $e) { $e->setParsedLine($this->getRealCurrentLineNb() + 1); $e->setSnippet($this->currentLine); throw $e; } if (is_array($value)) { $first = reset($value); if (is_string($first) && strpos($first, "*") === 0) { $data = []; 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."; break; case PREG_BACKTRACK_LIMIT_ERROR: $error = "pcre.backtrack_limit reached."; break; case PREG_RECURSION_LIMIT_ERROR: $error = "pcre.recursion_limit reached."; break; case PREG_BAD_UTF8_ERROR: $error = "Malformed UTF-8 data."; break; case PREG_BAD_UTF8_OFFSET_ERROR: $error = "Offset doesn't correspond to the begin of a valid UTF-8 code point."; break; default: $error = "Unable to parse."; } throw new ParseException($error, $this->getRealCurrentLineNb() + 1, $this->currentLine); } if ($isRef) { $this->refs[$isRef] = end($data); } } if (count($data) === 0) { return null; } return $data; }