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\DumpException ;
2018-07-26 10:03:18 +01:00
use Symfony\Component\Yaml\Exception\ParseException ;
2017-02-06 17:23:56 +00:00
use Symfony\Component\Yaml\Tag\TaggedValue ;
2011-06-14 11:44:54 +01:00
2010-01-04 14:26:20 +00:00
/**
* Inline implements a YAML parser / dumper for the YAML inline syntax .
*
2011-03-06 11:40:06 +00:00
* @ author Fabien Potencier < fabien @ symfony . com >
2016-02-09 19:49:29 +00:00
*
* @ internal
2010-01-04 14:26:20 +00:00
*/
class Inline
{
2017-01-13 16:26:16 +00:00
const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*+(?:\\\\.[^"\\\\]*+)*+)"|\'([^\']*+(?:\'\'[^\']*+)*+)\')' ;
2010-05-06 12:25:53 +01:00
2017-09-19 10:04:34 +01:00
public static $parsedLineNumber = - 1 ;
public static $parsedFilename ;
2016-08-27 10:58:27 +01:00
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
private static $exceptionOnInvalidType = false ;
private static $objectSupport = false ;
2014-03-27 09:46:24 +00:00
private static $objectForMap = false ;
2016-04-23 06:47:09 +01:00
private static $constantSupport = false ;
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
2019-08-20 12:52:38 +01:00
public static function initialize ( int $flags , int $parsedLineNumber = null , string $parsedFilename = null )
2017-09-01 07:00:27 +01:00
{
self :: $exceptionOnInvalidType = ( bool ) ( Yaml :: PARSE_EXCEPTION_ON_INVALID_TYPE & $flags );
self :: $objectSupport = ( bool ) ( Yaml :: PARSE_OBJECT & $flags );
self :: $objectForMap = ( bool ) ( Yaml :: PARSE_OBJECT_FOR_MAP & $flags );
self :: $constantSupport = ( bool ) ( Yaml :: PARSE_CONSTANT & $flags );
2017-09-19 10:04:34 +01:00
self :: $parsedFilename = $parsedFilename ;
2017-10-03 16:01:31 +01:00
if ( null !== $parsedLineNumber ) {
self :: $parsedLineNumber = $parsedLineNumber ;
}
2017-09-01 07:00:27 +01:00
}
2010-05-06 12:25:53 +01:00
/**
2016-11-07 21:01:27 +00:00
* Converts a YAML string to a PHP value .
2010-05-06 12:25:53 +01:00
*
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
* @ param array $references Mapping of variable names to values
2010-05-06 12:25:53 +01:00
*
2016-11-07 21:01:27 +00:00
* @ return mixed A PHP value
2012-12-16 12:02:54 +00:00
*
2014-03-27 09:46:24 +00:00
* @ throws ParseException
2010-05-06 12:25:53 +01:00
*/
2019-01-16 18:24:45 +00:00
public static function parse ( string $value = null , int $flags = 0 , array $references = [])
2010-01-04 14:26:20 +00:00
{
2017-09-01 07:00:27 +01:00
self :: initialize ( $flags );
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
2010-05-06 12:25:53 +01:00
$value = trim ( $value );
2010-01-04 14:26:20 +00:00
2015-06-06 07:25:15 +01:00
if ( '' === $value ) {
2010-05-06 12:25:53 +01:00
return '' ;
}
2010-03-29 11:40:22 +01:00
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 ();
mb_internal_encoding ( 'ASCII' );
}
2010-03-29 11:40:22 +01:00
2018-12-11 13:45:34 +00:00
try {
$i = 0 ;
$tag = self :: parseTag ( $value , $i , $flags );
switch ( $value [ $i ]) {
case '[' :
$result = self :: parseSequence ( $value , $flags , $i , $references );
++ $i ;
break ;
case '{' :
$result = self :: parseMapping ( $value , $flags , $i , $references );
++ $i ;
break ;
default :
$result = self :: parseScalar ( $value , $flags , null , $i , null === $tag , $references );
}
2017-02-06 17:23:56 +00:00
2018-12-13 12:30:33 +00:00
if ( null !== $tag && '' !== $tag ) {
2018-12-11 13:45:34 +00:00
return new TaggedValue ( $tag , $result );
}
2012-06-13 13:57:32 +01:00
2018-12-11 13:45:34 +00:00
// some comments are allowed at the end
if ( preg_replace ( '/\s+#.*$/A' , '' , substr ( $value , $i ))) {
throw new ParseException ( sprintf ( 'Unexpected characters near "%s".' , substr ( $value , $i )), self :: $parsedLineNumber + 1 , $value , self :: $parsedFilename );
}
2010-03-29 11:40:22 +01:00
2018-12-11 13:45:34 +00:00
return $result ;
} finally {
if ( isset ( $mbEncoding )) {
mb_internal_encoding ( $mbEncoding );
}
2017-02-06 17:23:56 +00:00
}
2010-01-04 14:26:20 +00:00
}
2010-05-06 12:25:53 +01:00
/**
* Dumps a given PHP variable to a YAML string .
*
2016-02-09 19:56:50 +00:00
* @ param mixed $value The PHP variable to convert
* @ param int $flags A bit field of Yaml :: DUMP_ * constants to customize the dumped YAML string
2010-05-06 12:25:53 +01:00
*
2016-11-07 21:01:27 +00:00
* @ return string The YAML string representing the PHP value
2010-05-06 12:25:53 +01:00
*
2011-06-14 11:44:54 +01:00
* @ throws DumpException When trying to dump PHP resource
2010-05-06 12:25:53 +01:00
*/
2017-06-26 07:08:08 +01:00
public static function dump ( $value , int $flags = 0 ) : string
2010-01-04 14:26:20 +00:00
{
2010-05-07 15:09:11 +01:00
switch ( true ) {
2018-07-05 12:24:53 +01:00
case \is_resource ( $value ) :
2016-02-09 19:56:50 +00:00
if ( Yaml :: DUMP_EXCEPTION_ON_INVALID_TYPE & $flags ) {
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
throw new DumpException ( sprintf ( 'Unable to dump PHP resources in a YAML file ("%s").' , get_resource_type ( $value )));
}
2019-07-23 07:41:15 +01:00
return self :: dumpNull ( $flags );
2016-02-17 21:09:28 +00:00
case $value instanceof \DateTimeInterface :
return $value -> format ( 'c' );
2018-07-05 12:24:53 +01:00
case \is_object ( $value ) :
2017-02-06 17:23:56 +00:00
if ( $value instanceof TaggedValue ) {
return '!' . $value -> getTag () . ' ' . self :: dump ( $value -> getValue (), $flags );
}
2016-01-27 19:42:59 +00:00
if ( Yaml :: DUMP_OBJECT & $flags ) {
2017-05-25 11:16:49 +01:00
return '!php/object ' . self :: dump ( serialize ( $value ));
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
}
2016-02-08 17:17:53 +00:00
if ( Yaml :: DUMP_OBJECT_AS_MAP & $flags && ( $value instanceof \stdClass || $value instanceof \ArrayObject )) {
2019-01-16 18:24:45 +00:00
$output = [];
2017-06-26 07:08:08 +01:00
foreach ( $value as $key => $val ) {
$output [] = sprintf ( '%s: %s' , self :: dump ( $key , $flags ), self :: dump ( $val , $flags ));
}
return sprintf ( '{ %s }' , implode ( ', ' , $output ));
2016-02-08 17:17:53 +00:00
}
2016-02-09 19:56:50 +00:00
if ( Yaml :: DUMP_EXCEPTION_ON_INVALID_TYPE & $flags ) {
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
throw new DumpException ( 'Object support when dumping a YAML file has been disabled.' );
}
2019-07-23 07:41:15 +01:00
return self :: dumpNull ( $flags );
2018-07-05 12:24:53 +01:00
case \is_array ( $value ) :
2016-02-09 19:56:50 +00:00
return self :: dumpArray ( $value , $flags );
2010-05-06 12:25:53 +01:00
case null === $value :
2019-07-23 07:41:15 +01:00
return self :: dumpNull ( $flags );
2010-05-06 12:25:53 +01:00
case true === $value :
return 'true' ;
case false === $value :
return 'false' ;
case ctype_digit ( $value ) :
2018-07-05 12:24:53 +01:00
return \is_string ( $value ) ? " ' $value ' " : ( int ) $value ;
2010-05-06 12:25:53 +01:00
case is_numeric ( $value ) :
2011-11-04 07:17:10 +00:00
$locale = setlocale ( LC_NUMERIC , 0 );
if ( false !== $locale ) {
setlocale ( LC_NUMERIC , 'C' );
}
2018-07-05 12:24:53 +01:00
if ( \is_float ( $value )) {
2015-03-12 10:28:44 +00:00
$repr = ( string ) $value ;
2014-09-21 20:22:48 +01:00
if ( is_infinite ( $value )) {
$repr = str_ireplace ( 'INF' , '.Inf' , $repr );
} elseif ( floor ( $value ) == $value && $repr == $value ) {
// Preserve float data type since storing a whole number will result in integer value.
$repr = '!!float ' . $repr ;
}
} else {
2018-07-05 12:24:53 +01:00
$repr = \is_string ( $value ) ? " ' $value ' " : ( string ) $value ;
2014-09-21 20:22:48 +01:00
}
2011-11-04 07:17:10 +00:00
if ( false !== $locale ) {
setlocale ( LC_NUMERIC , $locale );
}
return $repr ;
2015-01-16 14:59:09 +00:00
case '' == $value :
return " '' " ;
2016-03-24 12:13:08 +00:00
case self :: isBinaryString ( $value ) :
2016-02-20 09:54:12 +00:00
return '!!binary ' . base64_encode ( $value );
2011-03-05 21:39:48 +00:00
case Escaper :: requiresDoubleQuoting ( $value ) :
return Escaper :: escapeWithDoubleQuotes ( $value );
case Escaper :: requiresSingleQuoting ( $value ) :
2017-03-20 09:32:19 +00:00
case Parser :: preg_match ( '{^[0-9]+[_0-9]*$}' , $value ) :
2017-02-03 16:27:28 +00:00
case Parser :: preg_match ( self :: getHexRegex (), $value ) :
case Parser :: preg_match ( self :: getTimestampRegex (), $value ) :
2011-03-05 21:39:48 +00:00
return Escaper :: escapeWithSingleQuotes ( $value );
2010-05-06 12:25:53 +01:00
default :
return $value ;
2010-01-04 14:26:20 +00:00
}
}
2016-05-24 21:58:05 +01:00
/**
* Check if given array is hash or just normal indexed array .
*
2017-04-12 20:41:43 +01:00
* @ param array | \ArrayObject | \stdClass $value The PHP array or array - like object to check
2016-05-24 21:58:05 +01:00
*
* @ return bool true if value is hash array , false otherwise
*/
2017-06-26 07:08:08 +01:00
public static function isHash ( $value ) : bool
2016-05-24 21:58:05 +01:00
{
2017-04-12 20:41:43 +01:00
if ( $value instanceof \stdClass || $value instanceof \ArrayObject ) {
return true ;
}
2016-05-24 21:58:05 +01:00
$expectedKey = 0 ;
foreach ( $value as $key => $val ) {
if ( $key !== $expectedKey ++ ) {
return true ;
}
}
return false ;
}
2010-05-06 12:25:53 +01:00
/**
* Dumps a PHP array to a YAML string .
*
2016-02-09 19:56:50 +00:00
* @ param array $value The PHP array to dump
* @ param int $flags A bit field of Yaml :: DUMP_ * constants to customize the dumped YAML string
2010-05-06 12:25:53 +01:00
*
* @ return string The YAML string representing the PHP array
*/
2017-06-26 07:08:08 +01:00
private static function dumpArray ( array $value , int $flags ) : string
2010-01-04 14:26:20 +00:00
{
2010-05-06 12:25:53 +01:00
// array
2017-01-31 11:07:40 +00:00
if (( $value || Yaml :: DUMP_EMPTY_ARRAY_AS_SEQUENCE & $flags ) && ! self :: isHash ( $value )) {
2019-01-16 18:24:45 +00:00
$output = [];
2010-05-07 15:09:11 +01:00
foreach ( $value as $val ) {
2016-02-09 19:56:50 +00:00
$output [] = self :: dump ( $val , $flags );
2010-05-06 12:25:53 +01:00
}
return sprintf ( '[%s]' , implode ( ', ' , $output ));
}
2010-01-04 14:26:20 +00:00
2016-05-24 21:58:05 +01:00
// hash
2019-01-16 18:24:45 +00:00
$output = [];
2010-05-07 15:09:11 +01:00
foreach ( $value as $key => $val ) {
2016-02-09 19:56:50 +00:00
$output [] = sprintf ( '%s: %s' , self :: dump ( $key , $flags ), self :: dump ( $val , $flags ));
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 sprintf ( '{ %s }' , implode ( ', ' , $output ));
2010-01-04 14:26:20 +00:00
}
2019-07-23 07:41:15 +01:00
private static function dumpNull ( int $flags ) : string
{
if ( Yaml :: DUMP_NULL_AS_TILDE & $flags ) {
return '~' ;
}
return 'null' ;
}
2010-05-06 12:25:53 +01:00
/**
2016-11-07 21:01:27 +00:00
* Parses a YAML scalar .
2010-05-06 12:25:53 +01:00
*
2017-06-26 07:08:08 +01:00
* @ return mixed
2010-05-06 12:25:53 +01:00
*
2011-06-14 11:44:54 +01:00
* @ throws ParseException When malformed inline YAML string is parsed
2010-05-06 12:25:53 +01:00
*/
2019-01-16 18:24:45 +00:00
public static function parseScalar ( string $scalar , int $flags = 0 , array $delimiters = null , int & $i = 0 , bool $evaluate = true , array $references = [])
2010-01-04 14:26:20 +00:00
{
2019-01-16 18:24:45 +00:00
if ( \in_array ( $scalar [ $i ], [ '"' , " ' " ])) {
2010-05-06 12:25:53 +01:00
// quoted scalar
$output = self :: parseQuotedScalar ( $scalar , $i );
2012-06-13 13:57:32 +01:00
if ( null !== $delimiters ) {
$tmp = ltrim ( substr ( $scalar , $i ), ' ' );
2018-04-06 12:01:31 +01:00
if ( '' === $tmp ) {
2018-10-02 17:33:53 +01:00
throw new ParseException ( sprintf ( 'Unexpected end of line, expected one of "%s".' , implode ( '' , $delimiters )), self :: $parsedLineNumber + 1 , $scalar , self :: $parsedFilename );
2018-04-06 12:01:31 +01:00
}
2018-07-05 12:24:53 +01:00
if ( ! \in_array ( $tmp [ 0 ], $delimiters )) {
2017-09-29 12:19:35 +01:00
throw new ParseException ( sprintf ( 'Unexpected characters (%s).' , substr ( $scalar , $i )), self :: $parsedLineNumber + 1 , $scalar , self :: $parsedFilename );
2012-06-13 13:57:32 +01:00
}
}
2010-05-07 15:09:11 +01:00
} else {
2010-05-06 12:25:53 +01:00
// "normal" string
2010-05-07 15:09:11 +01:00
if ( ! $delimiters ) {
2010-05-06 12:25:53 +01:00
$output = substr ( $scalar , $i );
2018-07-05 12:24:53 +01:00
$i += \strlen ( $output );
2010-05-06 12:25:53 +01:00
// remove comments
2017-02-03 16:27:28 +00:00
if ( Parser :: preg_match ( '/[ \t]+#/' , $output , $match , PREG_OFFSET_CAPTURE )) {
2015-09-14 16:56:48 +01:00
$output = substr ( $output , 0 , $match [ 0 ][ 1 ]);
2010-05-06 12:25:53 +01:00
}
2017-05-19 08:52:04 +01:00
} elseif ( Parser :: preg_match ( '/^(.*?)(' . implode ( '|' , $delimiters ) . ')/' , substr ( $scalar , $i ), $match )) {
2010-05-06 12:25:53 +01:00
$output = $match [ 1 ];
2018-07-05 12:24:53 +01:00
$i += \strlen ( $output );
2018-01-20 14:02:12 +00:00
$output = trim ( $output );
2010-05-07 15:09:11 +01:00
} else {
2017-09-29 12:19:35 +01:00
throw new ParseException ( sprintf ( 'Malformed inline YAML string: %s.' , $scalar ), self :: $parsedLineNumber + 1 , null , self :: $parsedFilename );
2010-01-04 14:26:20 +00:00
}
2015-11-03 07:24:46 +00:00
// a non-quoted string cannot start with @ or ` (reserved) nor with a scalar indicator (| or >)
2017-05-19 08:52:04 +01:00
if ( $output && ( '@' === $output [ 0 ] || '`' === $output [ 0 ] || '|' === $output [ 0 ] || '>' === $output [ 0 ] || '%' === $output [ 0 ])) {
2017-09-29 12:19:35 +01:00
throw new ParseException ( sprintf ( 'The reserved indicator "%s" cannot start a plain scalar; you need to quote the scalar.' , $output [ 0 ]), self :: $parsedLineNumber + 1 , $output , self :: $parsedFilename );
2015-10-19 10:47:48 +01:00
}
2014-02-25 00:33:01 +00:00
if ( $evaluate ) {
2016-02-17 21:09:28 +00:00
$output = self :: evaluateScalar ( $output , $flags , $references );
2014-02-25 00:33:01 +00:00
}
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 $output ;
2010-01-04 14:26:20 +00:00
}
2010-05-06 12:25:53 +01:00
/**
2016-11-07 21:01:27 +00:00
* Parses a YAML quoted scalar .
2010-05-06 12:25:53 +01:00
*
2011-06-14 11:44:54 +01:00
* @ throws ParseException When malformed inline YAML string is parsed
2010-05-06 12:25:53 +01:00
*/
2017-06-26 07:08:08 +01:00
private static function parseQuotedScalar ( string $scalar , int & $i ) : string
2010-01-04 14:26:20 +00:00
{
2017-02-03 16:27:28 +00:00
if ( ! Parser :: preg_match ( '/' . self :: REGEX_QUOTED_STRING . '/Au' , substr ( $scalar , $i ), $match )) {
2017-09-29 12:19:35 +01:00
throw new ParseException ( sprintf ( 'Malformed inline YAML string: %s.' , substr ( $scalar , $i )), self :: $parsedLineNumber + 1 , $scalar , self :: $parsedFilename );
2010-01-04 14:26:20 +00:00
}
2018-07-05 12:24:53 +01:00
$output = substr ( $match [ 0 ], 1 , \strlen ( $match [ 0 ]) - 2 );
2010-01-04 14:26:20 +00:00
2011-03-05 21:39:48 +00:00
$unescaper = new Unescaper ();
2010-05-07 15:09:11 +01:00
if ( '"' == $scalar [ $i ]) {
2011-03-05 21:39:48 +00:00
$output = $unescaper -> unescapeDoubleQuotedString ( $output );
2010-05-07 15:09:11 +01:00
} else {
2011-03-05 21:39:48 +00:00
$output = $unescaper -> unescapeSingleQuotedString ( $output );
2010-05-06 12:25:53 +01:00
}
2018-07-05 12:24:53 +01:00
$i += \strlen ( $match [ 0 ]);
2010-05-06 12:25:53 +01:00
return $output ;
2010-01-04 14:26:20 +00:00
}
2010-05-06 12:25:53 +01:00
/**
2016-11-07 21:01:27 +00:00
* Parses a YAML sequence .
2010-05-06 12:25:53 +01:00
*
2014-03-27 09:46:24 +00:00
* @ throws ParseException When malformed inline YAML string is parsed
2010-05-06 12:25:53 +01:00
*/
2019-01-16 18:24:45 +00:00
private static function parseSequence ( string $sequence , int $flags , int & $i = 0 , array $references = []) : array
2010-05-06 12:25:53 +01:00
{
2019-01-16 18:24:45 +00:00
$output = [];
2018-07-05 12:24:53 +01:00
$len = \strlen ( $sequence );
2015-03-06 19:37:36 +00:00
++ $i ;
2010-05-06 12:25:53 +01:00
// [foo, bar, ...]
2010-05-07 15:09:11 +01:00
while ( $i < $len ) {
2017-02-06 17:23:56 +00:00
if ( ']' === $sequence [ $i ]) {
return $output ;
}
if ( ',' === $sequence [ $i ] || ' ' === $sequence [ $i ]) {
++ $i ;
continue ;
}
$tag = self :: parseTag ( $sequence , $i , $flags );
2010-05-08 14:32:30 +01:00
switch ( $sequence [ $i ]) {
2010-05-06 12:25:53 +01:00
case '[' :
// nested sequence
2017-02-06 17:23:56 +00:00
$value = self :: parseSequence ( $sequence , $flags , $i , $references );
2010-05-06 12:25:53 +01:00
break ;
case '{' :
// nested mapping
2017-02-06 17:23:56 +00:00
$value = self :: parseMapping ( $sequence , $flags , $i , $references );
2010-05-06 12:25:53 +01:00
break ;
default :
2019-01-16 18:24:45 +00:00
$isQuoted = \in_array ( $sequence [ $i ], [ '"' , " ' " ]);
$value = self :: parseScalar ( $sequence , $flags , [ ',' , ']' ], $i , null === $tag , $references );
2010-05-06 12:25:53 +01:00
2014-08-15 13:16:41 +01:00
// the value can be an array if a reference has been resolved to an array var
2018-07-26 09:45:46 +01:00
if ( \is_string ( $value ) && ! $isQuoted && false !== strpos ( $value , ': ' )) {
2010-05-06 12:25:53 +01:00
// embedded mapping?
2010-05-07 15:09:11 +01:00
try {
2014-08-15 13:16:41 +01:00
$pos = 0 ;
2016-02-17 21:09:28 +00:00
$value = self :: parseMapping ( '{' . $value . '}' , $flags , $pos , $references );
2010-05-07 15:09:11 +01:00
} catch ( \InvalidArgumentException $e ) {
2010-05-06 12:25:53 +01:00
// no, it's not
}
}
-- $i ;
}
2010-01-04 14:26:20 +00:00
2017-05-18 18:45:50 +01:00
if ( null !== $tag && '' !== $tag ) {
2017-02-06 17:23:56 +00:00
$value = new TaggedValue ( $tag , $value );
}
$output [] = $value ;
2010-05-06 12:25:53 +01:00
++ $i ;
}
2010-01-04 14:26:20 +00:00
2017-09-29 12:19:35 +01:00
throw new ParseException ( sprintf ( 'Malformed inline YAML string: %s.' , $sequence ), self :: $parsedLineNumber + 1 , null , self :: $parsedFilename );
2010-05-06 12:25:53 +01:00
}
2010-01-04 14:26:20 +00:00
2010-05-06 12:25:53 +01:00
/**
2016-11-07 21:01:27 +00:00
* Parses a YAML mapping .
2010-05-06 12:25:53 +01:00
*
2016-11-07 21:01:27 +00:00
* @ return array | \stdClass
2010-05-06 12:25:53 +01:00
*
2014-03-27 09:46:24 +00:00
* @ throws ParseException When malformed inline YAML string is parsed
2010-05-06 12:25:53 +01:00
*/
2019-01-16 18:24:45 +00:00
private static function parseMapping ( string $mapping , int $flags , int & $i = 0 , array $references = [])
2010-01-04 14:26:20 +00:00
{
2019-01-16 18:24:45 +00:00
$output = [];
2018-07-05 12:24:53 +01:00
$len = \strlen ( $mapping );
2015-03-06 19:37:36 +00:00
++ $i ;
2017-11-20 07:47:12 +00:00
$allowOverwrite = false ;
2010-05-06 12:25:53 +01:00
// {foo: bar, bar:foo, ...}
2010-05-07 15:09:11 +01:00
while ( $i < $len ) {
2010-05-08 14:32:30 +01:00
switch ( $mapping [ $i ]) {
2010-05-06 12:25:53 +01:00
case ' ' :
case ',' :
++ $i ;
continue 2 ;
case '}' :
2014-03-27 09:46:24 +00:00
if ( self :: $objectForMap ) {
2014-01-23 16:32:31 +00:00
return ( object ) $output ;
}
2010-05-06 12:25:53 +01:00
return $output ;
}
// key
2017-05-19 08:52:04 +01:00
$offsetBeforeKeyParsing = $i ;
2019-01-16 18:24:45 +00:00
$isKeyQuoted = \in_array ( $mapping [ $i ], [ '"' , " ' " ], true );
$key = self :: parseScalar ( $mapping , $flags , [ ':' , ' ' ], $i , false , []);
2016-10-28 08:34:49 +01:00
2017-05-19 08:52:04 +01:00
if ( $offsetBeforeKeyParsing === $i ) {
throw new ParseException ( 'Missing mapping key.' , self :: $parsedLineNumber + 1 , $mapping );
2016-10-28 08:34:49 +01:00
}
2016-07-28 22:09:37 +01:00
2017-05-19 08:52:04 +01:00
if ( false === $i = strpos ( $mapping , ':' , $i )) {
break ;
2017-02-17 06:51:07 +00:00
}
2017-05-29 21:10:45 +01:00
if ( ! $isKeyQuoted ) {
2017-02-20 06:08:19 +00:00
$evaluatedKey = self :: evaluateScalar ( $key , $flags , $references );
2018-07-26 09:45:46 +01:00
if ( '' !== $key && $evaluatedKey !== $key && ! \is_string ( $evaluatedKey ) && ! \is_int ( $evaluatedKey )) {
2017-07-26 07:59:36 +01:00
throw new ParseException ( 'Implicit casting of incompatible mapping keys to strings is not supported. Quote your evaluable mapping keys instead.' , self :: $parsedLineNumber + 1 , $mapping );
2017-02-20 06:08:19 +00:00
}
}
2019-01-16 18:24:45 +00:00
if ( ! $isKeyQuoted && ( ! isset ( $mapping [ $i + 1 ]) || ! \in_array ( $mapping [ $i + 1 ], [ ' ' , ',' , '[' , ']' , '{' , '}' ], true ))) {
2017-05-19 08:52:04 +01:00
throw new ParseException ( 'Colons must be followed by a space or an indication character (i.e. " ", ",", "[", "]", "{", "}").' , self :: $parsedLineNumber + 1 , $mapping );
2016-07-28 22:09:37 +01:00
}
2010-05-06 12:25:53 +01:00
2017-11-20 07:47:12 +00:00
if ( '<<' === $key ) {
$allowOverwrite = true ;
}
2010-05-07 15:09:11 +01:00
while ( $i < $len ) {
2017-02-06 17:23:56 +00:00
if ( ':' === $mapping [ $i ] || ' ' === $mapping [ $i ]) {
++ $i ;
continue ;
}
$tag = self :: parseTag ( $mapping , $i , $flags );
2010-05-08 14:32:30 +01:00
switch ( $mapping [ $i ]) {
2010-05-06 12:25:53 +01:00
case '[' :
// nested sequence
2016-02-17 21:09:28 +00:00
$value = self :: parseSequence ( $mapping , $flags , $i , $references );
2014-05-15 01:05:21 +01:00
// Spec: Keys MUST be unique; first one wins.
// Parser cannot abort this mapping earlier, since lines
// are processed sequentially.
2017-11-20 07:47:12 +00:00
// But overwriting is allowed when a merge node is used in current block.
if ( '<<' === $key ) {
foreach ( $value as $parsedValue ) {
$output += $parsedValue ;
}
} elseif ( $allowOverwrite || ! isset ( $output [ $key ])) {
2017-11-29 12:25:49 +00:00
if ( null !== $tag ) {
$output [ $key ] = new TaggedValue ( $tag , $value );
} else {
$output [ $key ] = $value ;
}
} elseif ( isset ( $output [ $key ])) {
2017-05-19 08:52:04 +01:00
throw new ParseException ( sprintf ( 'Duplicate key "%s" detected.' , $key ), self :: $parsedLineNumber + 1 , $mapping );
2014-05-15 01:05:21 +01:00
}
2010-05-06 12:25:53 +01:00
break ;
case '{' :
// nested mapping
2016-02-17 21:09:28 +00:00
$value = self :: parseMapping ( $mapping , $flags , $i , $references );
2014-05-15 01:05:21 +01:00
// Spec: Keys MUST be unique; first one wins.
// Parser cannot abort this mapping earlier, since lines
// are processed sequentially.
2017-11-20 07:47:12 +00:00
// But overwriting is allowed when a merge node is used in current block.
if ( '<<' === $key ) {
$output += $value ;
} elseif ( $allowOverwrite || ! isset ( $output [ $key ])) {
2017-11-29 12:25:49 +00:00
if ( null !== $tag ) {
$output [ $key ] = new TaggedValue ( $tag , $value );
} else {
$output [ $key ] = $value ;
}
} elseif ( isset ( $output [ $key ])) {
2017-05-19 08:52:04 +01:00
throw new ParseException ( sprintf ( 'Duplicate key "%s" detected.' , $key ), self :: $parsedLineNumber + 1 , $mapping );
2014-05-15 01:05:21 +01:00
}
2010-05-06 12:25:53 +01:00
break ;
default :
2019-01-16 18:24:45 +00:00
$value = self :: parseScalar ( $mapping , $flags , [ ',' , '}' ], $i , null === $tag , $references );
2014-05-15 01:05:21 +01:00
// Spec: Keys MUST be unique; first one wins.
// Parser cannot abort this mapping earlier, since lines
// are processed sequentially.
2017-11-20 07:47:12 +00:00
// But overwriting is allowed when a merge node is used in current block.
if ( '<<' === $key ) {
$output += $value ;
} elseif ( $allowOverwrite || ! isset ( $output [ $key ])) {
2017-11-29 12:25:49 +00:00
if ( null !== $tag ) {
$output [ $key ] = new TaggedValue ( $tag , $value );
} else {
$output [ $key ] = $value ;
}
} elseif ( isset ( $output [ $key ])) {
2017-05-19 08:52:04 +01:00
throw new ParseException ( sprintf ( 'Duplicate key "%s" detected.' , $key ), self :: $parsedLineNumber + 1 , $mapping );
2014-05-15 01:05:21 +01:00
}
2010-05-06 12:25:53 +01:00
-- $i ;
}
++ $i ;
2017-02-06 17:23:56 +00:00
continue 2 ;
2010-05-06 12:25:53 +01:00
}
}
2017-09-29 12:19:35 +01:00
throw new ParseException ( sprintf ( 'Malformed inline YAML string: %s.' , $mapping ), self :: $parsedLineNumber + 1 , null , self :: $parsedFilename );
2010-01-04 14:26:20 +00:00
}
2010-05-06 12:25:53 +01:00
/**
* Evaluates scalars and replaces magic values .
*
2017-04-17 04:24:02 +01:00
* @ return mixed The evaluated YAML string
2014-07-26 13:09:47 +01:00
*
2014-09-03 18:52:14 +01:00
* @ throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved
2010-05-06 12:25:53 +01:00
*/
2019-01-16 18:24:45 +00:00
private static function evaluateScalar ( string $scalar , int $flags , array $references = [])
2010-05-06 12:25:53 +01:00
{
$scalar = trim ( $scalar );
2014-02-22 14:29:19 +00:00
$scalarLower = strtolower ( $scalar );
2014-08-15 13:16:41 +01:00
if ( 0 === strpos ( $scalar , '*' )) {
if ( false !== $pos = strpos ( $scalar , '#' )) {
$value = substr ( $scalar , 1 , $pos - 2 );
} else {
$value = substr ( $scalar , 1 );
}
2014-09-03 18:52:14 +01:00
// an unquoted *
if ( false === $value || '' === $value ) {
2017-09-29 12:19:35 +01:00
throw new ParseException ( 'A reference must contain at least one character.' , self :: $parsedLineNumber + 1 , $value , self :: $parsedFilename );
2014-09-03 18:52:14 +01:00
}
2019-02-23 15:06:07 +00:00
if ( ! \array_key_exists ( $value , $references )) {
2017-09-29 12:19:35 +01:00
throw new ParseException ( sprintf ( 'Reference "%s" does not exist.' , $value ), self :: $parsedLineNumber + 1 , $value , self :: $parsedFilename );
2014-08-15 13:16:41 +01:00
}
return $references [ $value ];
}
2010-05-07 15:09:11 +01:00
switch ( true ) {
2014-02-25 00:33:01 +00:00
case 'null' === $scalarLower :
case '' === $scalar :
case '~' === $scalar :
2019-08-19 22:30:37 +01:00
return null ;
2014-02-22 14:29:19 +00:00
case 'true' === $scalarLower :
2010-05-06 12:25:53 +01:00
return true ;
2014-02-22 14:29:19 +00:00
case 'false' === $scalarLower :
2010-05-06 12:25:53 +01:00
return false ;
2017-09-15 11:08:59 +01:00
case '!' === $scalar [ 0 ] :
2014-02-22 14:29:19 +00:00
switch ( true ) {
2017-06-24 15:09:41 +01:00
case 0 === strpos ( $scalar , '!!str ' ) :
return ( string ) substr ( $scalar , 6 );
2014-02-22 14:29:19 +00:00
case 0 === strpos ( $scalar , '! ' ) :
2017-06-24 14:18:23 +01:00
return substr ( $scalar , 2 );
2017-05-25 11:16:49 +01:00
case 0 === strpos ( $scalar , '!php/object' ) :
if ( self :: $objectSupport ) {
return unserialize ( self :: parseScalar ( substr ( $scalar , 12 )));
}
if ( self :: $exceptionOnInvalidType ) {
2017-09-29 12:19:35 +01:00
throw new ParseException ( 'Object support when parsing a YAML file has been disabled.' , self :: $parsedLineNumber + 1 , $scalar , self :: $parsedFilename );
2017-05-25 11:16:49 +01:00
}
2019-08-19 22:30:37 +01:00
return null ;
2017-05-25 11:16:49 +01:00
case 0 === strpos ( $scalar , '!php/const' ) :
if ( self :: $constantSupport ) {
2017-12-05 05:42:45 +00:00
$i = 0 ;
2018-07-26 09:45:46 +01:00
if ( \defined ( $const = self :: parseScalar ( substr ( $scalar , 11 ), 0 , null , $i , false ))) {
return \constant ( $const );
2017-05-25 11:16:49 +01:00
}
2017-09-29 12:19:35 +01:00
throw new ParseException ( sprintf ( 'The constant "%s" is not defined.' , $const ), self :: $parsedLineNumber + 1 , $scalar , self :: $parsedFilename );
2017-05-25 11:16:49 +01:00
}
if ( self :: $exceptionOnInvalidType ) {
2017-09-29 12:19:35 +01:00
throw new ParseException ( sprintf ( 'The string "%s" could not be parsed as a constant. Have you forgotten to pass the "Yaml::PARSE_CONSTANT" flag to the parser?' , $scalar ), self :: $parsedLineNumber + 1 , $scalar , self :: $parsedFilename );
2016-04-23 06:47:09 +01:00
}
2019-08-19 22:30:37 +01:00
return null ;
2014-09-21 20:22:48 +01:00
case 0 === strpos ( $scalar , '!!float ' ) :
return ( float ) substr ( $scalar , 8 );
2017-02-06 17:23:56 +00:00
case 0 === strpos ( $scalar , '!!binary ' ) :
return self :: evaluateBinaryScalar ( substr ( $scalar , 9 ));
default :
2017-09-26 07:12:47 +01:00
throw new ParseException ( sprintf ( 'The string "%s" could not be parsed as it uses an unsupported built-in tag.' , $scalar ), self :: $parsedLineNumber , $scalar , self :: $parsedFilename );
2017-02-06 17:23:56 +00:00
}
// Optimize for returning strings.
2017-09-17 11:10:12 +01:00
// no break
2017-09-15 11:08:59 +01:00
case '+' === $scalar [ 0 ] || '-' === $scalar [ 0 ] || '.' === $scalar [ 0 ] || is_numeric ( $scalar [ 0 ]) :
2017-02-06 17:23:56 +00:00
switch ( true ) {
2017-03-20 09:32:19 +00:00
case Parser :: preg_match ( '{^[+-]?[0-9][0-9_]*$}' , $scalar ) :
2016-03-10 11:33:16 +00:00
$scalar = str_replace ( '_' , '' , ( string ) $scalar );
// omitting the break / return as integers are handled in the next case
2017-09-17 11:10:12 +01:00
// no break
2014-02-22 14:29:19 +00:00
case ctype_digit ( $scalar ) :
$raw = $scalar ;
2015-03-07 19:12:23 +00:00
$cast = ( int ) $scalar ;
2014-02-22 14:29:19 +00:00
return '0' == $scalar [ 0 ] ? octdec ( $scalar ) : ((( string ) $raw == ( string ) $cast ) ? $cast : $raw );
case '-' === $scalar [ 0 ] && ctype_digit ( substr ( $scalar , 1 )) :
$raw = $scalar ;
2015-03-07 19:12:23 +00:00
$cast = ( int ) $scalar ;
2014-02-22 14:29:19 +00:00
2015-03-07 19:12:23 +00:00
return '0' == $scalar [ 1 ] ? octdec ( $scalar ) : ((( string ) $raw === ( string ) $cast ) ? $cast : $raw );
2014-02-22 14:29:19 +00:00
case is_numeric ( $scalar ) :
2017-02-03 16:27:28 +00:00
case Parser :: preg_match ( self :: getHexRegex (), $scalar ) :
2016-03-10 11:33:16 +00:00
$scalar = str_replace ( '_' , '' , $scalar );
2015-03-07 19:12:23 +00:00
return '0x' === $scalar [ 0 ] . $scalar [ 1 ] ? hexdec ( $scalar ) : ( float ) $scalar ;
2014-02-25 00:33:01 +00:00
case '.inf' === $scalarLower :
case '.nan' === $scalarLower :
2014-02-22 14:29:19 +00:00
return - log ( 0 );
2014-02-25 00:33:01 +00:00
case '-.inf' === $scalarLower :
2014-02-22 14:29:19 +00:00
return log ( 0 );
2017-03-20 09:32:19 +00:00
case Parser :: preg_match ( '/^(-|\+)?[0-9][0-9_]*(\.[0-9_]+)?$/' , $scalar ) :
2017-05-19 08:52:04 +01:00
return ( float ) str_replace ( '_' , '' , $scalar );
2017-02-03 16:27:28 +00:00
case Parser :: preg_match ( self :: getTimestampRegex (), $scalar ) :
2016-02-17 21:09:28 +00:00
if ( Yaml :: PARSE_DATETIME & $flags ) {
2016-11-17 15:00:37 +00:00
// When no timezone is provided in the parsed date, YAML spec says we must assume UTC.
return new \DateTime ( $scalar , new \DateTimeZone ( 'UTC' ));
2016-02-17 21:09:28 +00:00
}
2016-02-17 21:15:58 +00:00
$timeZone = date_default_timezone_get ();
date_default_timezone_set ( 'UTC' );
$time = strtotime ( $scalar );
date_default_timezone_set ( $timeZone );
return $time ;
2014-02-22 14:29:19 +00:00
}
2010-05-06 12:25:53 +01:00
}
2017-02-06 17:23:56 +00:00
return ( string ) $scalar ;
}
2017-06-26 07:08:08 +01:00
private static function parseTag ( string $value , int & $i , int $flags ) : ? string
2017-02-06 17:23:56 +00:00
{
if ( '!' !== $value [ $i ]) {
2017-06-26 07:08:08 +01:00
return null ;
2017-02-06 17:23:56 +00:00
}
2017-08-02 08:15:14 +01:00
$tagLength = strcspn ( $value , " \t \n [] { }, " , $i + 1 );
2017-02-06 17:23:56 +00:00
$tag = substr ( $value , $i + 1 , $tagLength );
$nextOffset = $i + $tagLength + 1 ;
$nextOffset += strspn ( $value , ' ' , $nextOffset );
2017-05-18 18:45:50 +01:00
// Is followed by a scalar and is a built-in tag
2019-01-16 18:24:45 +00:00
if ( $tag && ( ! isset ( $value [ $nextOffset ]) || ! \in_array ( $value [ $nextOffset ], [ '[' , '{' ], true )) && ( '!' === $tag [ 0 ] || 'str' === $tag || 'php/const' === $tag || 'php/object' === $tag )) {
2017-05-18 18:45:50 +01:00
// Manage in {@link self::evaluateScalar()}
2017-06-26 07:08:08 +01:00
return null ;
2017-02-06 17:23:56 +00:00
}
2017-05-18 18:45:50 +01:00
$i = $nextOffset ;
2017-02-06 17:23:56 +00:00
// Built-in tags
if ( $tag && '!' === $tag [ 0 ]) {
2017-09-29 12:19:35 +01:00
throw new ParseException ( sprintf ( 'The built-in tag "!%s" is not implemented.' , $tag ), self :: $parsedLineNumber + 1 , $value , self :: $parsedFilename );
2017-02-06 17:23:56 +00:00
}
2017-05-18 18:45:50 +01:00
if ( '' === $tag || Yaml :: PARSE_CUSTOM_TAGS & $flags ) {
2017-02-06 17:23:56 +00:00
return $tag ;
}
2018-09-08 07:40:26 +01:00
throw new ParseException ( sprintf ( 'Tags support is not enabled. Enable the "Yaml::PARSE_CUSTOM_TAGS" flag to use "!%s".' , $tag ), self :: $parsedLineNumber + 1 , $value , self :: $parsedFilename );
2010-05-06 12:25:53 +01:00
}
2017-06-26 07:08:08 +01:00
public static function evaluateBinaryScalar ( string $scalar ) : string
2016-02-20 09:54:12 +00:00
{
$parsedBinaryData = self :: parseScalar ( preg_replace ( '/\s/' , '' , $scalar ));
2018-07-26 09:45:46 +01:00
if ( 0 !== ( \strlen ( $parsedBinaryData ) % 4 )) {
throw new ParseException ( sprintf ( 'The normalized base64 encoded data (data without whitespace characters) length must be a multiple of four (%d bytes given).' , \strlen ( $parsedBinaryData )), self :: $parsedLineNumber + 1 , $scalar , self :: $parsedFilename );
2016-02-20 09:54:12 +00:00
}
2017-03-20 09:32:19 +00:00
if ( ! Parser :: preg_match ( '#^[A-Z0-9+/]+={0,2}$#i' , $parsedBinaryData )) {
2017-09-29 12:19:35 +01:00
throw new ParseException ( sprintf ( 'The base64 encoded data (%s) contains invalid characters.' , $parsedBinaryData ), self :: $parsedLineNumber + 1 , $scalar , self :: $parsedFilename );
2016-02-20 09:54:12 +00:00
}
return base64_decode ( $parsedBinaryData , true );
}
2017-06-26 07:08:08 +01:00
private static function isBinaryString ( string $value )
2016-02-20 09:54:12 +00:00
{
2017-03-07 16:47:02 +00:00
return ! preg_match ( '//u' , $value ) || preg_match ( '/[^\x00\x07-\x0d\x1B\x20-\xff]/' , $value );
2016-02-20 09:54:12 +00:00
}
2011-02-07 12:57:27 +00:00
/**
2012-12-15 13:02:34 +00:00
* Gets a regex that matches a YAML date .
2011-02-07 12:57:27 +00:00
*
* @ return string The regular expression
2012-12-15 13:02:34 +00:00
*
* @ see http :// www . yaml . org / spec / 1.2 / spec . html #id2761573
2011-02-07 12:57:27 +00:00
*/
2017-06-26 07:08:08 +01:00
private static function getTimestampRegex () : string
2010-05-06 12:25:53 +01:00
{
return <<< EOF
~^
( ? P < year > [ 0 - 9 ][ 0 - 9 ][ 0 - 9 ][ 0 - 9 ])
- ( ? P < month > [ 0 - 9 ][ 0 - 9 ] ? )
- ( ? P < day > [ 0 - 9 ][ 0 - 9 ] ? )
( ? : ( ? : [ Tt ] | [ \t ] + )
( ? P < hour > [ 0 - 9 ][ 0 - 9 ] ? )
: ( ? P < minute > [ 0 - 9 ][ 0 - 9 ])
: ( ? P < second > [ 0 - 9 ][ 0 - 9 ])
( ? : \ . ( ? P < fraction > [ 0 - 9 ] * )) ?
( ? : [ \t ] * ( ? P < tz > Z | ( ? P < tz_sign > [ -+ ])( ? P < tz_hour > [ 0 - 9 ][ 0 - 9 ] ? )
( ? :: ( ? P < tz_minute > [ 0 - 9 ][ 0 - 9 ])) ? )) ? ) ?
$ ~ x
2010-01-04 14:26:20 +00:00
EOF ;
2010-05-06 12:25:53 +01:00
}
2015-03-30 08:37:12 +01:00
/**
* Gets a regex that matches a YAML number in hexadecimal notation .
*/
2017-06-26 07:08:08 +01:00
private static function getHexRegex () : string
2015-03-30 08:37:12 +01:00
{
2016-03-10 11:33:16 +00:00
return '~^0x[0-9a-f_]++$~i' ;
2015-03-30 08:37:12 +01:00
}
2010-01-04 14:26:20 +00:00
}