2010-01-04 14:26:20 +00:00
< ? php
/*
2010-04-07 01:51:29 +01:00
* This file is part of the Symfony package .
2013-12-28 07:37:38 +00:00
*
2011-03-06 11:40:06 +00:00
* ( c ) Fabien Potencier < fabien @ symfony . com >
2010-01-04 14:26:20 +00:00
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
2011-01-15 13:29:43 +00:00
namespace Symfony\Component\Yaml ;
2011-06-14 11:44:54 +01:00
use Symfony\Component\Yaml\Exception\ParseException ;
2010-01-04 14:26:20 +00:00
/**
* Parser parses YAML strings to convert them to PHP arrays .
*
2011-03-06 11:40:06 +00:00
* @ author Fabien Potencier < fabien @ symfony . com >
2010-01-04 14:26:20 +00:00
*/
class Parser
{
2016-02-20 09:54:12 +00:00
const TAG_PATTERN = '((?P<tag>![\w!.\/:-]+) +)?' ;
2015-08-07 06:28:03 +01:00
const BLOCK_SCALAR_HEADER_PATTERN = '(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments> +#.*)?' ;
2013-08-24 07:35:53 +01:00
2014-10-22 19:27:13 +01:00
private $offset = 0 ;
2016-05-14 16:57:37 +01:00
private $totalNumberOfLines ;
2014-10-22 19:27:13 +01:00
private $lines = array ();
private $currentLineNb = - 1 ;
private $currentLine = '' ;
private $refs = array ();
2010-05-06 12:25:53 +01:00
/**
2014-12-21 17:00:50 +00:00
* Constructor .
2010-05-06 12:25:53 +01:00
*
2016-05-14 16:57:37 +01:00
* @ param int $offset The offset of YAML document ( used for line numbers in error messages )
* @ param int | null $totalNumberOfLines The overall number of lines being parsed
2010-05-06 12:25:53 +01:00
*/
2016-05-14 16:57:37 +01:00
public function __construct ( $offset = 0 , $totalNumberOfLines = null )
2010-03-29 11:40:22 +01:00
{
2010-05-06 12:25:53 +01:00
$this -> offset = $offset ;
2016-05-14 16:57:37 +01:00
$this -> totalNumberOfLines = $totalNumberOfLines ;
2010-03-29 11:40:22 +01:00
}
2010-05-06 12:25:53 +01:00
/**
* Parses a YAML string to a PHP value .
*
2016-02-08 18:49:14 +00:00
* @ param string $value A YAML string
* @ param int $flags A bit field of PARSE_ * constants to customize the YAML parser behavior
2010-05-06 12:25:53 +01:00
*
2014-11-30 13:33:44 +00:00
* @ return mixed A PHP value
2010-05-06 12:25:53 +01:00
*
2011-06-14 11:44:54 +01:00
* @ throws ParseException If the YAML is not valid
2010-05-06 12:25:53 +01:00
*/
2016-02-08 18:49:14 +00:00
public function parse ( $value , $flags = 0 )
2010-01-04 14:26:20 +00:00
{
2016-02-08 18:49:14 +00:00
if ( is_bool ( $flags )) {
@ trigger_error ( 'Passing a boolean flag to toggle exception handling is deprecated since version 3.1 and will be removed in 4.0. Use the Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE flag instead.' , E_USER_DEPRECATED );
if ( $flags ) {
$flags = Yaml :: PARSE_EXCEPTION_ON_INVALID_TYPE ;
} else {
$flags = 0 ;
}
}
if ( func_num_args () >= 3 ) {
@ trigger_error ( 'Passing a boolean flag to toggle object support is deprecated since version 3.1 and will be removed in 4.0. Use the Yaml::PARSE_OBJECT flag instead.' , E_USER_DEPRECATED );
if ( func_get_arg ( 2 )) {
$flags |= Yaml :: PARSE_OBJECT ;
}
}
if ( func_num_args () >= 4 ) {
@ trigger_error ( 'Passing a boolean flag to toggle object for map support is deprecated since version 3.1 and will be removed in 4.0. Use the Yaml::PARSE_OBJECT_FOR_MAP flag instead.' , E_USER_DEPRECATED );
if ( func_get_arg ( 3 )) {
$flags |= Yaml :: PARSE_OBJECT_FOR_MAP ;
}
}
2014-05-12 09:37:25 +01:00
if ( ! preg_match ( '//u' , $value )) {
2011-06-14 11:44:54 +01:00
throw new ParseException ( 'The YAML value does not appear to be valid UTF-8.' );
2011-05-12 21:02:56 +01:00
}
2015-01-12 11:05:05 +00:00
$this -> currentLineNb = - 1 ;
$this -> currentLine = '' ;
$value = $this -> cleanup ( $value );
$this -> lines = explode ( " \n " , $value );
2011-05-12 21:02:56 +01:00
2016-05-14 16:57:37 +01:00
if ( null === $this -> totalNumberOfLines ) {
$this -> totalNumberOfLines = count ( $this -> lines );
}
2015-10-14 15:40:43 +01:00
if ( 2 /* MB_OVERLOAD_STRING */ & ( int ) ini_get ( 'mbstring.func_overload' )) {
2010-05-06 12:25:53 +01:00
$mbEncoding = mb_internal_encoding ();
2010-06-27 12:59:29 +01:00
mb_internal_encoding ( 'UTF-8' );
2010-01-04 14:26:20 +00:00
}
2010-05-06 12:25:53 +01:00
$data = array ();
2012-07-01 10:19:53 +01:00
$context = null ;
2014-06-18 18:47:01 +01:00
$allowOverwrite = false ;
2010-05-07 15:09:11 +01:00
while ( $this -> moveToNextLine ()) {
2010-05-08 14:32:30 +01:00
if ( $this -> isCurrentLineEmpty ()) {
2010-05-06 12:25:53 +01:00
continue ;
2010-01-04 14:26:20 +00:00
}
2010-05-06 12:25:53 +01:00
// tab?
2011-08-22 14:06:47 +01:00
if ( " \t " === $this -> currentLine [ 0 ]) {
2011-06-14 11:44:54 +01:00
throw new ParseException ( 'A YAML file cannot contain tabs as indentation.' , $this -> getRealCurrentLineNb () + 1 , $this -> currentLine );
2010-01-04 14:26:20 +00:00
}
2014-06-18 16:42:59 +01:00
$isRef = $mergeNode = false ;
2010-06-27 12:59:29 +01:00
if ( preg_match ( '#^\-((?P<leadspaces>\s+)(?P<value>.+?))?\s*$#u' , $this -> currentLine , $values )) {
2012-07-01 10:19:53 +01:00
if ( $context && 'mapping' == $context ) {
2016-05-23 10:57:38 +01:00
throw new ParseException ( 'You cannot define a sequence item when in a mapping' , $this -> getRealCurrentLineNb () + 1 , $this -> currentLine );
2012-07-01 10:19:53 +01:00
}
$context = 'sequence' ;
2010-06-27 12:59:29 +01:00
if ( isset ( $values [ 'value' ]) && preg_match ( '#^&(?P<ref>[^ ]+) *(?P<value>.*)#u' , $values [ 'value' ], $matches )) {
2010-05-06 12:25:53 +01:00
$isRef = $matches [ 'ref' ];
$values [ 'value' ] = $matches [ 'value' ];
}
// array
2010-05-07 15:09:11 +01:00
if ( ! isset ( $values [ 'value' ]) || '' == trim ( $values [ 'value' ], ' ' ) || 0 === strpos ( ltrim ( $values [ 'value' ], ' ' ), '#' )) {
2010-05-06 12:25:53 +01:00
$c = $this -> getRealCurrentLineNb () + 1 ;
2016-05-14 16:57:37 +01:00
$parser = new self ( $c , $this -> totalNumberOfLines );
2015-03-27 22:06:33 +00:00
$parser -> refs = & $this -> refs ;
2016-02-08 18:49:14 +00:00
$data [] = $parser -> parse ( $this -> getNextEmbedBlock ( null , true ), $flags );
2010-05-07 15:09:11 +01:00
} else {
2010-05-06 12:25:53 +01:00
if ( isset ( $values [ 'leadspaces' ])
2010-11-29 20:09:02 +00:00
&& preg_match ( '#^(?P<key>' . Inline :: REGEX_QUOTED_STRING . '|[^ \'"\{\[].*?) *\:(\s+(?P<value>.+?))?\s*$#u' , $values [ 'value' ], $matches )
2010-05-08 14:53:22 +01:00
) {
2010-05-06 12:25:53 +01:00
// this is a compact notation element, add to next block and parse
$c = $this -> getRealCurrentLineNb ();
2016-05-14 16:57:37 +01:00
$parser = new self ( $c , $this -> totalNumberOfLines );
2015-03-27 22:06:33 +00:00
$parser -> refs = & $this -> refs ;
2010-05-06 12:25:53 +01:00
$block = $values [ 'value' ];
2013-04-12 10:29:40 +01:00
if ( $this -> isNextLineIndented ()) {
2015-01-10 08:51:00 +00:00
$block .= " \n " . $this -> getNextEmbedBlock ( $this -> getCurrentLineIndentation () + strlen ( $values [ 'leadspaces' ]) + 1 );
2010-05-06 12:25:53 +01:00
}
2016-02-08 18:49:14 +00:00
$data [] = $parser -> parse ( $block , $flags );
2010-05-07 15:09:11 +01:00
} else {
2016-02-08 18:49:14 +00:00
$data [] = $this -> parseValue ( $values [ 'value' ], $flags , $context );
2010-05-06 12:25:53 +01:00
}
}
2015-07-08 16:38:36 +01:00
if ( $isRef ) {
$this -> refs [ $isRef ] = end ( $data );
}
2014-12-02 19:42:47 +00:00
} 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 ( '"' , " ' " )))) {
2012-07-01 10:19:53 +01:00
if ( $context && 'sequence' == $context ) {
2016-05-23 10:57:38 +01:00
throw new ParseException ( 'You cannot define a mapping item when in a sequence' , $this -> currentLineNb + 1 , $this -> currentLine );
2012-07-01 10:19:53 +01:00
}
$context = 'mapping' ;
added a way to enable/disable object support when parsing/dumping
By default, object support is disabled, and instead of throwing an
exception when an object is handled, null is returned.
If you do need object support, enable it via:
Yaml::dump($data, false, true);
If you want an exception to be thrown in case an invalid type is handled
(a PHP resource or a PHP object), pass true as the second argument:
Yaml::dump($data, true, true);
The same can be done when parsing:
Yaml::parse($data, 2, false, true);
2013-01-17 08:34:45 +00:00
// force correct settings
2016-02-08 18:49:14 +00:00
Inline :: parse ( null , $flags , $this -> refs );
2011-06-14 11:44:54 +01:00
try {
$key = Inline :: parseScalar ( $values [ 'key' ]);
} catch ( ParseException $e ) {
$e -> setParsedLine ( $this -> getRealCurrentLineNb () + 1 );
$e -> setSnippet ( $this -> currentLine );
throw $e ;
}
2010-05-06 12:25:53 +01:00
2015-09-12 16:58:59 +01:00
// Convert float keys to strings, to avoid being converted to integers by PHP
if ( is_float ( $key )) {
$key = ( string ) $key ;
}
2010-05-07 15:09:11 +01:00
if ( '<<' === $key ) {
2014-06-18 16:42:59 +01:00
$mergeNode = true ;
2014-06-18 18:47:01 +01:00
$allowOverwrite = true ;
2012-01-11 19:20:58 +00:00
if ( isset ( $values [ 'value' ]) && 0 === strpos ( $values [ 'value' ], '*' )) {
2014-06-18 16:42:59 +01:00
$refName = substr ( $values [ 'value' ], 1 );
if ( ! array_key_exists ( $refName , $this -> refs )) {
throw new ParseException ( sprintf ( 'Reference "%s" does not exist.' , $refName ), $this -> getRealCurrentLineNb () + 1 , $this -> currentLine );
}
$refValue = $this -> refs [ $refName ];
if ( ! is_array ( $refValue )) {
throw new ParseException ( 'YAML merge keys used with a scalar value instead of an array.' , $this -> getRealCurrentLineNb () + 1 , $this -> currentLine );
}
foreach ( $refValue as $key => $value ) {
if ( ! isset ( $data [ $key ])) {
$data [ $key ] = $value ;
}
2010-05-06 12:25:53 +01:00
}
2010-05-07 15:09:11 +01:00
} else {
2010-05-08 14:32:30 +01:00
if ( isset ( $values [ 'value' ]) && $values [ 'value' ] !== '' ) {
2010-05-06 12:25:53 +01:00
$value = $values [ 'value' ];
2010-05-07 15:09:11 +01:00
} else {
2010-05-06 12:25:53 +01:00
$value = $this -> getNextEmbedBlock ();
}
$c = $this -> getRealCurrentLineNb () + 1 ;
2016-05-14 16:57:37 +01:00
$parser = new self ( $c , $this -> totalNumberOfLines );
2015-03-27 22:06:33 +00:00
$parser -> refs = & $this -> refs ;
2016-02-08 18:49:14 +00:00
$parsed = $parser -> parse ( $value , $flags );
2010-05-06 12:25:53 +01:00
2010-05-07 15:09:11 +01:00
if ( ! is_array ( $parsed )) {
2011-06-14 11:44:54 +01:00
throw new ParseException ( 'YAML merge keys used with a scalar value instead of an array.' , $this -> getRealCurrentLineNb () + 1 , $this -> currentLine );
2014-06-18 15:18:19 +01:00
}
if ( isset ( $parsed [ 0 ])) {
2014-06-18 16:42:59 +01:00
// If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes
// and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier
// in the sequence override keys specified in later mapping nodes.
2014-06-18 15:18:19 +01:00
foreach ( $parsed as $parsedItem ) {
2010-05-08 14:32:30 +01:00
if ( ! is_array ( $parsedItem )) {
2011-06-14 11:44:54 +01:00
throw new ParseException ( 'Merge items must be arrays.' , $this -> getRealCurrentLineNb () + 1 , $parsedItem );
2010-05-06 12:25:53 +01:00
}
2014-06-18 16:42:59 +01:00
foreach ( $parsedItem as $key => $value ) {
if ( ! isset ( $data [ $key ])) {
$data [ $key ] = $value ;
}
}
2010-05-06 12:25:53 +01:00
}
2010-05-07 15:09:11 +01:00
} else {
2014-06-18 16:42:59 +01:00
// If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the
// current mapping, unless the key already exists in it.
foreach ( $parsed as $key => $value ) {
if ( ! isset ( $data [ $key ])) {
$data [ $key ] = $value ;
}
}
2010-05-06 12:25:53 +01:00
}
}
2011-12-18 13:42:59 +00:00
} elseif ( isset ( $values [ 'value' ]) && preg_match ( '#^&(?P<ref>[^ ]+) *(?P<value>.*)#u' , $values [ 'value' ], $matches )) {
2010-05-06 12:25:53 +01:00
$isRef = $matches [ 'ref' ];
$values [ 'value' ] = $matches [ 'value' ];
}
2014-06-18 16:42:59 +01:00
if ( $mergeNode ) {
2010-05-06 12:25:53 +01:00
// Merge keys
2011-12-18 13:42:59 +00:00
} elseif ( ! isset ( $values [ 'value' ]) || '' == trim ( $values [ 'value' ], ' ' ) || 0 === strpos ( ltrim ( $values [ 'value' ], ' ' ), '#' )) {
2014-06-18 15:18:19 +01:00
// hash
2010-05-06 12:25:53 +01:00
// if next line is less indented or equal, then it means that the current value is null
2013-04-12 10:29:40 +01:00
if ( ! $this -> isNextLineIndented () && ! $this -> isNextLineUnIndentedCollection ()) {
2014-05-15 01:05:21 +01:00
// Spec: Keys MUST be unique; first one wins.
2014-06-18 18:47:01 +01:00
// But overwriting is allowed when a merge node is used in current block.
if ( $allowOverwrite || ! isset ( $data [ $key ])) {
2014-05-15 01:05:21 +01:00
$data [ $key ] = null ;
}
2010-05-07 15:09:11 +01:00
} else {
2010-05-06 12:25:53 +01:00
$c = $this -> getRealCurrentLineNb () + 1 ;
2016-05-14 16:57:37 +01:00
$parser = new self ( $c , $this -> totalNumberOfLines );
2015-03-27 22:06:33 +00:00
$parser -> refs = & $this -> refs ;
2016-02-08 18:49:14 +00:00
$value = $parser -> parse ( $this -> getNextEmbedBlock (), $flags );
2014-05-15 01:05:21 +01:00
// Spec: Keys MUST be unique; first one wins.
2014-06-18 18:47:01 +01:00
// But overwriting is allowed when a merge node is used in current block.
if ( $allowOverwrite || ! isset ( $data [ $key ])) {
2014-05-15 01:05:21 +01:00
$data [ $key ] = $value ;
}
2010-05-06 12:25:53 +01:00
}
2010-05-07 15:09:11 +01:00
} else {
2016-02-08 18:49:14 +00:00
$value = $this -> parseValue ( $values [ 'value' ], $flags , $context );
2014-06-18 16:42:59 +01:00
// Spec: Keys MUST be unique; first one wins.
2014-06-18 18:47:01 +01:00
// But overwriting is allowed when a merge node is used in current block.
if ( $allowOverwrite || ! isset ( $data [ $key ])) {
2014-06-18 16:42:59 +01:00
$data [ $key ] = $value ;
2010-05-06 12:25:53 +01:00
}
2010-01-04 14:26:20 +00:00
}
2015-07-08 16:38:36 +01:00
if ( $isRef ) {
$this -> refs [ $isRef ] = $data [ $key ];
}
2010-05-07 15:09:11 +01:00
} else {
2014-11-01 12:21:10 +00:00
// multiple documents are not supported
if ( '---' === $this -> currentLine ) {
2016-05-23 10:57:38 +01:00
throw new ParseException ( 'Multiple documents are not supported.' , $this -> currentLineNb + 1 , $this -> currentLine );
2014-11-01 12:21:10 +00:00
}
2015-01-12 11:05:05 +00:00
// 1-liner optionally followed by newline(s)
2015-07-22 07:30:32 +01:00
if ( is_string ( $value ) && $this -> lines [ 0 ] === trim ( $value )) {
2011-06-14 11:44:54 +01:00
try {
2016-02-08 18:49:14 +00:00
$value = Inline :: parse ( $this -> lines [ 0 ], $flags , $this -> refs );
2011-06-14 11:44:54 +01:00
} catch ( ParseException $e ) {
$e -> setParsedLine ( $this -> getRealCurrentLineNb () + 1 );
$e -> setSnippet ( $this -> currentLine );
throw $e ;
}
2010-05-07 15:09:11 +01:00
if ( is_array ( $value )) {
2010-05-06 12:25:53 +01:00
$first = reset ( $value );
2012-01-11 19:20:58 +00:00
if ( is_string ( $first ) && 0 === strpos ( $first , '*' )) {
2010-05-06 12:25:53 +01:00
$data = array ();
2010-05-07 15:09:11 +01:00
foreach ( $value as $alias ) {
2010-05-06 12:25:53 +01:00
$data [] = $this -> refs [ substr ( $alias , 1 )];
}
$value = $data ;
}
}
2010-05-07 15:09:11 +01:00
if ( isset ( $mbEncoding )) {
2010-05-06 12:25:53 +01:00
mb_internal_encoding ( $mbEncoding );
}
return $value ;
}
2010-05-07 15:09:11 +01:00
switch ( preg_last_error ()) {
2010-05-06 12:25:53 +01:00
case PREG_INTERNAL_ERROR :
2011-06-14 11:44:54 +01:00
$error = 'Internal PCRE error.' ;
2010-05-06 12:25:53 +01:00
break ;
case PREG_BACKTRACK_LIMIT_ERROR :
2011-06-14 11:44:54 +01:00
$error = 'pcre.backtrack_limit reached.' ;
2010-05-06 12:25:53 +01:00
break ;
case PREG_RECURSION_LIMIT_ERROR :
2011-06-14 11:44:54 +01:00
$error = 'pcre.recursion_limit reached.' ;
2010-05-06 12:25:53 +01:00
break ;
case PREG_BAD_UTF8_ERROR :
2011-06-14 11:44:54 +01:00
$error = 'Malformed UTF-8 data.' ;
2010-05-06 12:25:53 +01:00
break ;
case PREG_BAD_UTF8_OFFSET_ERROR :
2011-06-14 11:44:54 +01:00
$error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.' ;
2010-05-06 12:25:53 +01:00
break ;
default :
2011-06-14 11:44:54 +01:00
$error = 'Unable to parse.' ;
2010-05-06 12:25:53 +01:00
}
2011-06-14 11:44:54 +01:00
throw new ParseException ( $error , $this -> getRealCurrentLineNb () + 1 , $this -> currentLine );
2010-01-04 14:26:20 +00:00
}
}
2010-05-07 15:09:11 +01:00
if ( isset ( $mbEncoding )) {
2010-05-06 12:25:53 +01:00
mb_internal_encoding ( $mbEncoding );
2010-01-04 14:26:20 +00:00
}
2010-05-06 12:25:53 +01:00
2016-02-16 08:48:00 +00:00
if ( Yaml :: PARSE_OBJECT_FOR_MAP & $flags && ! is_object ( $data ) && 'mapping' === $context ) {
2016-02-08 18:04:09 +00:00
$object = new \stdClass ();
foreach ( $data as $key => $value ) {
$object -> $key = $value ;
}
$data = $object ;
2015-12-31 09:55:37 +00:00
}
2010-05-06 12:25:53 +01:00
return empty ( $data ) ? null : $data ;
}
/**
* Returns the current line number ( takes the offset into account ) .
*
2014-11-30 13:33:44 +00:00
* @ return int The current line number
2010-05-06 12:25:53 +01:00
*/
2011-03-08 19:33:19 +00:00
private function getRealCurrentLineNb ()
2010-05-06 12:25:53 +01:00
{
return $this -> currentLineNb + $this -> offset ;
}
/**
* Returns the current line indentation .
*
2014-11-30 13:33:44 +00:00
* @ return int The current line indentation
2010-05-06 12:25:53 +01:00
*/
2011-03-08 19:33:19 +00:00
private function getCurrentLineIndentation ()
2010-05-06 12:25:53 +01:00
{
return strlen ( $this -> currentLine ) - strlen ( ltrim ( $this -> currentLine , ' ' ));
}
/**
* Returns the next embed block of YAML .
*
2014-08-29 12:30:22 +01:00
* @ param int $indentation The indent level at which the block is to be read , or null for default
* @ param bool $inSequence True if the enclosing data structure is a sequence
2010-05-06 12:25:53 +01:00
*
* @ return string A YAML string
*
2011-06-14 11:44:54 +01:00
* @ throws ParseException When indentation problem are detected
2010-05-06 12:25:53 +01:00
*/
2014-08-29 12:30:22 +01:00
private function getNextEmbedBlock ( $indentation = null , $inSequence = false )
2010-05-06 12:25:53 +01:00
{
2014-08-29 12:30:22 +01:00
$oldLineIndentation = $this -> getCurrentLineIndentation ();
2015-12-28 14:55:02 +00:00
$blockScalarIndentations = array ();
if ( $this -> isBlockScalarHeader ()) {
$blockScalarIndentations [] = $this -> getCurrentLineIndentation ();
}
2014-08-29 12:30:22 +01:00
if ( ! $this -> moveToNextLine ()) {
return ;
}
2010-05-06 12:25:53 +01:00
2010-05-07 15:09:11 +01:00
if ( null === $indentation ) {
2010-05-06 12:25:53 +01:00
$newIndent = $this -> getCurrentLineIndentation ();
2015-11-29 07:43:05 +00:00
$unindentedEmbedBlock = $this -> isStringUnIndentedCollectionItem ();
2012-04-26 18:34:19 +01:00
if ( ! $this -> isCurrentLineEmpty () && 0 === $newIndent && ! $unindentedEmbedBlock ) {
2011-06-14 11:44:54 +01:00
throw new ParseException ( 'Indentation problem.' , $this -> getRealCurrentLineNb () + 1 , $this -> currentLine );
2010-05-06 12:25:53 +01:00
}
2010-05-07 15:09:11 +01:00
} else {
2010-05-06 12:25:53 +01:00
$newIndent = $indentation ;
2010-01-04 14:26:20 +00:00
}
2010-05-06 12:25:53 +01:00
2015-01-10 08:51:00 +00:00
$data = array ();
if ( $this -> getCurrentLineIndentation () >= $newIndent ) {
$data [] = substr ( $this -> currentLine , $newIndent );
} else {
$this -> moveToPreviousLine ();
return ;
}
2010-05-06 12:25:53 +01:00
2015-07-28 13:35:05 +01:00
if ( $inSequence && $oldLineIndentation === $newIndent && isset ( $data [ 0 ][ 0 ]) && '-' === $data [ 0 ][ 0 ]) {
2014-08-29 12:30:22 +01:00
// the previous line contained a dash but no item content, this line is a sequence item with the same indentation
// and therefore no nested list or mapping
$this -> moveToPreviousLine ();
return ;
}
2015-11-29 07:43:05 +00:00
$isItUnindentedCollection = $this -> isStringUnIndentedCollectionItem ();
2012-04-26 18:34:19 +01:00
2015-12-28 14:55:02 +00:00
if ( empty ( $blockScalarIndentations ) && $this -> isBlockScalarHeader ()) {
$blockScalarIndentations [] = $this -> getCurrentLineIndentation ();
2015-12-05 10:55:16 +00:00
}
$previousLineIndentation = $this -> getCurrentLineIndentation ();
2015-10-02 11:12:23 +01:00
2013-07-11 10:21:24 +01:00
while ( $this -> moveToNextLine ()) {
2014-02-23 10:43:36 +00:00
$indent = $this -> getCurrentLineIndentation ();
2015-12-28 14:55:02 +00:00
// terminate all block scalars that are more indented than the current line
if ( ! empty ( $blockScalarIndentations ) && $indent < $previousLineIndentation && trim ( $this -> currentLine ) !== '' ) {
foreach ( $blockScalarIndentations as $key => $blockScalarIndentation ) {
if ( $blockScalarIndentation >= $this -> getCurrentLineIndentation ()) {
unset ( $blockScalarIndentations [ $key ]);
}
}
}
if ( empty ( $blockScalarIndentations ) && ! $this -> isCurrentLineComment () && $this -> isBlockScalarHeader ()) {
$blockScalarIndentations [] = $this -> getCurrentLineIndentation ();
2015-10-02 11:12:23 +01:00
}
2015-12-05 10:55:16 +00:00
$previousLineIndentation = $indent ;
2015-11-29 07:43:05 +00:00
if ( $isItUnindentedCollection && ! $this -> isStringUnIndentedCollectionItem () && $newIndent === $indent ) {
2012-04-26 18:34:19 +01:00
$this -> moveToPreviousLine ();
break ;
}
2014-02-23 21:20:36 +00:00
if ( $this -> isCurrentLineBlank ()) {
$data [] = substr ( $this -> currentLine , $newIndent );
continue ;
}
2010-05-06 12:25:53 +01:00
2015-12-05 10:55:16 +00:00
// we ignore "comment" lines only when we are not inside a scalar block
2015-12-28 14:55:02 +00:00
if ( empty ( $blockScalarIndentations ) && $this -> isCurrentLineComment ()) {
2015-10-02 11:12:23 +01:00
continue ;
}
2014-02-23 21:20:36 +00:00
if ( $indent >= $newIndent ) {
2010-05-06 12:25:53 +01:00
$data [] = substr ( $this -> currentLine , $newIndent );
2011-12-18 13:42:59 +00:00
} elseif ( 0 == $indent ) {
2010-05-06 12:25:53 +01:00
$this -> moveToPreviousLine ();
2010-03-29 11:40:22 +01:00
2010-05-06 12:25:53 +01:00
break ;
2010-05-07 15:09:11 +01:00
} else {
2011-06-14 11:44:54 +01:00
throw new ParseException ( 'Indentation problem.' , $this -> getRealCurrentLineNb () + 1 , $this -> currentLine );
2010-05-06 12:25:53 +01:00
}
2010-01-04 14:26:20 +00:00
}
2010-05-06 12:25:53 +01:00
return implode ( " \n " , $data );
}
/**
* Moves the parser to the next line .
2011-02-13 14:31:54 +00:00
*
2014-04-16 11:30:19 +01:00
* @ return bool
2010-05-06 12:25:53 +01:00
*/
2011-03-08 19:33:19 +00:00
private function moveToNextLine ()
2010-05-06 12:25:53 +01:00
{
2010-05-07 15:09:11 +01:00
if ( $this -> currentLineNb >= count ( $this -> lines ) - 1 ) {
2010-05-06 12:25:53 +01:00
return false ;
2010-01-04 14:26:20 +00:00
}
2010-05-06 12:25:53 +01:00
$this -> currentLine = $this -> lines [ ++ $this -> currentLineNb ];
2010-01-04 14:26:20 +00:00
2010-05-06 12:25:53 +01:00
return true ;
2010-01-04 14:26:20 +00:00
}
2010-05-06 12:25:53 +01:00
/**
* Moves the parser to the previous line .
*/
2011-03-08 19:33:19 +00:00
private function moveToPreviousLine ()
2010-03-29 11:40:22 +01:00
{
2010-05-06 12:25:53 +01:00
$this -> currentLine = $this -> lines [ -- $this -> currentLineNb ];
2010-03-29 11:40:22 +01:00
}
2010-05-06 12:25:53 +01:00
/**
* Parses a YAML value .
*
2016-02-08 18:49:14 +00:00
* @ param string $value A YAML value
* @ param int $flags A bit field of PARSE_ * constants to customize the YAML parser behavior
* @ param string $context The parser context ( either sequence or mapping )
2010-05-06 12:25:53 +01:00
*
2014-03-27 09:46:24 +00:00
* @ return mixed A PHP value
2010-05-06 12:25:53 +01:00
*
2011-06-14 11:44:54 +01:00
* @ throws ParseException When reference does not exist
2010-05-06 12:25:53 +01:00
*/
2016-02-08 18:49:14 +00:00
private function parseValue ( $value , $flags , $context )
2010-03-24 14:02:49 +00:00
{
2012-01-11 19:20:58 +00:00
if ( 0 === strpos ( $value , '*' )) {
2010-05-08 14:32:30 +01:00
if ( false !== $pos = strpos ( $value , '#' )) {
2010-05-06 12:25:53 +01:00
$value = substr ( $value , 1 , $pos - 2 );
2010-05-07 15:09:11 +01:00
} else {
2010-05-06 12:25:53 +01:00
$value = substr ( $value , 1 );
}
2010-01-04 14:26:20 +00:00
2010-05-07 15:09:11 +01:00
if ( ! array_key_exists ( $value , $this -> refs )) {
2016-05-23 10:57:38 +01:00
throw new ParseException ( sprintf ( 'Reference "%s" does not exist.' , $value ), $this -> currentLineNb + 1 , $this -> currentLine );
2010-05-06 12:25:53 +01:00
}
2011-06-08 11:12:55 +01:00
2010-05-06 12:25:53 +01:00
return $this -> refs [ $value ];
}
2016-02-20 09:54:12 +00:00
if ( preg_match ( '/^' . self :: TAG_PATTERN . self :: BLOCK_SCALAR_HEADER_PATTERN . '$/' , $value , $matches )) {
2010-05-06 12:25:53 +01:00
$modifiers = isset ( $matches [ 'modifiers' ]) ? $matches [ 'modifiers' ] : '' ;
2016-02-20 09:54:12 +00:00
$data = $this -> parseBlockScalar ( $matches [ 'separator' ], preg_replace ( '#\d+#' , '' , $modifiers ), ( int ) abs ( $modifiers ));
if ( isset ( $matches [ 'tag' ]) && '!!binary' === $matches [ 'tag' ]) {
return Inline :: evaluateBinaryScalar ( $data );
}
return $data ;
2010-05-06 12:25:53 +01:00
}
2011-02-27 17:30:31 +00:00
2011-06-14 11:44:54 +01:00
try {
2016-02-08 18:49:14 +00:00
$parsedValue = Inline :: parse ( $value , $flags , $this -> refs );
2015-12-01 16:13:59 +00:00
if ( 'mapping' === $context && '"' !== $value [ 0 ] && " ' " !== $value [ 0 ] && '[' !== $value [ 0 ] && '{' !== $value [ 0 ] && '!' !== $value [ 0 ] && false !== strpos ( $parsedValue , ': ' )) {
throw new ParseException ( 'A colon cannot be used in an unquoted mapping value.' );
}
return $parsedValue ;
2011-06-14 11:44:54 +01:00
} catch ( ParseException $e ) {
$e -> setParsedLine ( $this -> getRealCurrentLineNb () + 1 );
$e -> setSnippet ( $this -> currentLine );
throw $e ;
}
2010-03-24 14:02:49 +00:00
}
2010-05-06 12:25:53 +01:00
/**
2015-08-07 06:28:03 +01:00
* Parses a block scalar .
2010-05-06 12:25:53 +01:00
*
2015-08-07 06:28:03 +01:00
* @ param string $style The style indicator that was used to begin this block scalar ( | or > )
* @ param string $chomping The chomping indicator that was used to begin this block scalar ( + or - )
* @ param int $indentation The indentation indicator that was used to begin this block scalar
2010-05-06 12:25:53 +01:00
*
2014-11-30 13:33:44 +00:00
* @ return string The text value
2010-05-06 12:25:53 +01:00
*/
2015-08-07 06:28:03 +01:00
private function parseBlockScalar ( $style , $chomping = '' , $indentation = 0 )
2010-01-04 14:26:20 +00:00
{
2010-05-06 12:25:53 +01:00
$notEOF = $this -> moveToNextLine ();
2010-05-07 15:09:11 +01:00
if ( ! $notEOF ) {
2010-05-06 12:25:53 +01:00
return '' ;
}
2010-01-04 14:26:20 +00:00
2013-05-10 01:09:46 +01:00
$isCurrentLineBlank = $this -> isCurrentLineBlank ();
2015-12-05 16:57:30 +00:00
$blockLines = array ();
2013-05-10 01:09:46 +01:00
// leading blank lines are consumed before determining indentation
while ( $notEOF && $isCurrentLineBlank ) {
// newline only if not EOF
if ( $notEOF = $this -> moveToNextLine ()) {
2015-12-05 16:57:30 +00:00
$blockLines [] = '' ;
2013-05-10 01:09:46 +01:00
$isCurrentLineBlank = $this -> isCurrentLineBlank ();
}
}
2013-03-23 01:54:33 +00:00
// determine indentation if not specified
if ( 0 === $indentation ) {
if ( preg_match ( '/^ +/' , $this -> currentLine , $matches )) {
$indentation = strlen ( $matches [ 0 ]);
}
2010-05-06 12:25:53 +01:00
}
2010-01-04 14:26:20 +00:00
2013-03-23 01:54:33 +00:00
if ( $indentation > 0 ) {
$pattern = sprintf ( '/^ {%d}(.*)$/' , $indentation );
while (
$notEOF && (
$isCurrentLineBlank ||
preg_match ( $pattern , $this -> currentLine , $matches )
)
) {
2015-12-05 16:57:30 +00:00
if ( $isCurrentLineBlank && strlen ( $this -> currentLine ) > $indentation ) {
$blockLines [] = substr ( $this -> currentLine , $indentation );
} elseif ( $isCurrentLineBlank ) {
$blockLines [] = '' ;
2013-03-23 01:54:33 +00:00
} else {
2015-12-05 16:57:30 +00:00
$blockLines [] = $matches [ 1 ];
2010-05-06 12:25:53 +01:00
}
2010-01-04 14:26:20 +00:00
2013-03-23 01:54:33 +00:00
// newline only if not EOF
if ( $notEOF = $this -> moveToNextLine ()) {
$isCurrentLineBlank = $this -> isCurrentLineBlank ();
}
2010-05-06 12:25:53 +01:00
}
2013-03-23 01:54:33 +00:00
} elseif ( $notEOF ) {
2015-12-05 16:57:30 +00:00
$blockLines [] = '' ;
2010-05-06 12:25:53 +01:00
}
2010-01-04 14:26:20 +00:00
2013-03-23 01:54:33 +00:00
if ( $notEOF ) {
2015-12-05 16:57:30 +00:00
$blockLines [] = '' ;
2013-03-23 01:54:33 +00:00
$this -> moveToPreviousLine ();
2016-05-14 16:57:37 +01:00
} elseif ( ! $notEOF && ! $this -> isCurrentLineLastLineInDocument ()) {
$blockLines [] = '' ;
2010-05-06 12:25:53 +01:00
}
2010-01-04 14:26:20 +00:00
2015-08-07 06:28:03 +01:00
// folded style
if ( '>' === $style ) {
2015-12-05 16:57:30 +00:00
$text = '' ;
$previousLineIndented = false ;
$previousLineBlank = false ;
2015-12-28 14:55:02 +00:00
for ( $i = 0 ; $i < count ( $blockLines ); ++ $i ) {
2015-12-05 16:57:30 +00:00
if ( '' === $blockLines [ $i ]) {
$text .= " \n " ;
$previousLineIndented = false ;
$previousLineBlank = true ;
} elseif ( ' ' === $blockLines [ $i ][ 0 ]) {
$text .= " \n " . $blockLines [ $i ];
$previousLineIndented = true ;
$previousLineBlank = false ;
} elseif ( $previousLineIndented ) {
$text .= " \n " . $blockLines [ $i ];
$previousLineIndented = false ;
$previousLineBlank = false ;
} elseif ( $previousLineBlank || 0 === $i ) {
$text .= $blockLines [ $i ];
$previousLineIndented = false ;
$previousLineBlank = false ;
} else {
$text .= ' ' . $blockLines [ $i ];
$previousLineIndented = false ;
$previousLineBlank = false ;
}
}
} else {
$text = implode ( " \n " , $blockLines );
2013-03-23 01:54:33 +00:00
}
2015-08-07 06:28:03 +01:00
// deal with trailing newlines
if ( '' === $chomping ) {
2015-06-06 07:25:15 +01:00
$text = preg_replace ( '/\n+$/' , " \n " , $text );
2015-08-07 06:28:03 +01:00
} elseif ( '-' === $chomping ) {
2015-06-06 07:25:15 +01:00
$text = preg_replace ( '/\n+$/' , '' , $text );
2010-05-06 12:25:53 +01:00
}
2010-01-04 14:26:20 +00:00
2010-05-06 12:25:53 +01:00
return $text ;
2010-01-04 14:26:20 +00:00
}
2010-05-06 12:25:53 +01:00
/**
* Returns true if the next line is indented .
*
2014-11-30 13:33:44 +00:00
* @ return bool Returns true if the next line is indented , false otherwise
2010-05-06 12:25:53 +01:00
*/
2011-03-08 19:33:19 +00:00
private function isNextLineIndented ()
2010-01-04 14:26:20 +00:00
{
2010-05-06 12:25:53 +01:00
$currentIndentation = $this -> getCurrentLineIndentation ();
2013-04-12 10:31:52 +01:00
$EOF = ! $this -> moveToNextLine ();
2010-01-04 14:26:20 +00:00
2013-04-12 10:31:52 +01:00
while ( ! $EOF && $this -> isCurrentLineEmpty ()) {
$EOF = ! $this -> moveToNextLine ();
2010-01-04 14:26:20 +00:00
}
2010-05-06 12:25:53 +01:00
2013-04-12 10:31:52 +01:00
if ( $EOF ) {
2010-05-06 12:25:53 +01:00
return false ;
}
$ret = false ;
2013-04-12 10:29:40 +01:00
if ( $this -> getCurrentLineIndentation () > $currentIndentation ) {
2010-05-06 12:25:53 +01:00
$ret = true ;
}
2010-01-04 14:26:20 +00:00
$this -> moveToPreviousLine ();
2010-05-06 12:25:53 +01:00
return $ret ;
2010-01-04 14:26:20 +00:00
}
2010-05-06 12:25:53 +01:00
/**
* Returns true if the current line is blank or if it is a comment line .
*
2014-11-30 13:33:44 +00:00
* @ return bool Returns true if the current line is empty or if it is a comment line , false otherwise
2010-05-06 12:25:53 +01:00
*/
2011-03-08 19:33:19 +00:00
private function isCurrentLineEmpty ()
2010-01-04 14:26:20 +00:00
{
2010-05-06 12:25:53 +01:00
return $this -> isCurrentLineBlank () || $this -> isCurrentLineComment ();
2010-01-04 14:26:20 +00:00
}
2010-05-06 12:25:53 +01:00
/**
* Returns true if the current line is blank .
*
2014-11-30 13:33:44 +00:00
* @ return bool Returns true if the current line is blank , false otherwise
2010-05-06 12:25:53 +01:00
*/
2011-03-08 19:33:19 +00:00
private function isCurrentLineBlank ()
2010-01-04 14:26:20 +00:00
{
2010-05-06 12:25:53 +01:00
return '' == trim ( $this -> currentLine , ' ' );
2010-01-04 14:26:20 +00:00
}
2010-05-06 12:25:53 +01:00
/**
* Returns true if the current line is a comment line .
*
2014-11-30 13:33:44 +00:00
* @ return bool Returns true if the current line is a comment line , false otherwise
2010-05-06 12:25:53 +01:00
*/
2011-03-08 19:33:19 +00:00
private function isCurrentLineComment ()
2010-01-04 14:26:20 +00:00
{
2010-05-06 12:25:53 +01:00
//checking explicitly the first char of the trim is faster than loops or strpos
$ltrimmedLine = ltrim ( $this -> currentLine , ' ' );
2011-06-08 11:12:55 +01:00
2015-12-28 14:55:02 +00:00
return '' !== $ltrimmedLine && $ltrimmedLine [ 0 ] === '#' ;
2010-01-04 14:26:20 +00:00
}
2016-05-14 16:57:37 +01:00
private function isCurrentLineLastLineInDocument ()
{
return ( $this -> offset + $this -> currentLineNb ) >= ( $this -> totalNumberOfLines - 1 );
}
2010-05-06 12:25:53 +01:00
/**
* Cleanups a YAML string to be parsed .
*
2012-05-15 21:19:31 +01:00
* @ param string $value The input YAML string
2010-05-06 12:25:53 +01:00
*
* @ return string A cleaned up YAML string
*/
2011-03-08 19:33:19 +00:00
private function cleanup ( $value )
2010-01-04 14:26:20 +00:00
{
2010-05-06 12:25:53 +01:00
$value = str_replace ( array ( " \r \n " , " \r " ), " \n " , $value );
2010-01-04 14:26:20 +00:00
2010-05-06 12:25:53 +01:00
// strip YAML header
$count = 0 ;
2015-01-08 15:45:16 +00:00
$value = preg_replace ( '#^\%YAML[: ][\d\.]+.*\n#u' , '' , $value , - 1 , $count );
2010-05-06 12:25:53 +01:00
$this -> offset += $count ;
2010-01-04 14:26:20 +00:00
2010-06-29 16:51:05 +01:00
// remove leading comments
$trimmedValue = preg_replace ( '#^(\#.*?\n)+#s' , '' , $value , - 1 , $count );
2010-05-07 15:09:11 +01:00
if ( $count == 1 ) {
2010-05-06 12:25:53 +01:00
// items have been removed, update the offset
$this -> offset += substr_count ( $value , " \n " ) - substr_count ( $trimmedValue , " \n " );
$value = $trimmedValue ;
}
2010-01-04 14:26:20 +00:00
2010-06-29 16:51:05 +01:00
// remove start of the document marker (---)
$trimmedValue = preg_replace ( '#^\-\-\-.*?\n#s' , '' , $value , - 1 , $count );
if ( $count == 1 ) {
// items have been removed, update the offset
$this -> offset += substr_count ( $value , " \n " ) - substr_count ( $trimmedValue , " \n " );
$value = $trimmedValue ;
// remove end of the document marker (...)
2015-06-06 07:25:15 +01:00
$value = preg_replace ( '#\.\.\.\s*$#' , '' , $value );
2010-06-29 16:51:05 +01:00
}
2010-05-06 12:25:53 +01:00
return $value ;
2010-02-23 10:52:41 +00:00
}
2012-04-26 18:34:19 +01:00
/**
2014-12-21 17:00:50 +00:00
* Returns true if the next line starts unindented collection .
2012-04-26 18:34:19 +01:00
*
2014-11-30 13:33:44 +00:00
* @ return bool Returns true if the next line starts unindented collection , false otherwise
2012-04-26 18:34:19 +01:00
*/
private function isNextLineUnIndentedCollection ()
{
$currentIndentation = $this -> getCurrentLineIndentation ();
$notEOF = $this -> moveToNextLine ();
while ( $notEOF && $this -> isCurrentLineEmpty ()) {
$notEOF = $this -> moveToNextLine ();
}
if ( false === $notEOF ) {
return false ;
}
$ret = false ;
if (
$this -> getCurrentLineIndentation () == $currentIndentation
&&
2015-11-29 07:43:05 +00:00
$this -> isStringUnIndentedCollectionItem ()
2012-04-26 18:34:19 +01:00
) {
$ret = true ;
}
$this -> moveToPreviousLine ();
return $ret ;
}
/**
2014-12-21 17:00:50 +00:00
* Returns true if the string is un - indented collection item .
2012-04-26 18:34:19 +01:00
*
2014-11-30 13:33:44 +00:00
* @ return bool Returns true if the string is un - indented collection item , false otherwise
2012-04-26 18:34:19 +01:00
*/
2012-10-19 17:43:59 +01:00
private function isStringUnIndentedCollectionItem ()
2012-04-26 18:34:19 +01:00
{
2016-05-22 17:02:36 +01:00
return '-' === rtrim ( $this -> currentLine ) || 0 === strpos ( $this -> currentLine , '- ' );
2012-04-26 18:34:19 +01:00
}
2015-12-05 10:55:16 +00:00
/**
* Tests whether or not the current line is the header of a block scalar .
*
* @ return bool
*/
private function isBlockScalarHeader ()
{
return ( bool ) preg_match ( '~' . self :: BLOCK_SCALAR_HEADER_PATTERN . '$~' , $this -> currentLine );
}
2010-01-04 14:26:20 +00:00
}