Commit ee394023 authored by catch's avatar catch

Issue #2350917 by alexpott: Update Symfony YAML library to support whole number floats.

parent dce86888
......@@ -4,7 +4,7 @@
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "5dca82e6d2f23a408795c33590f41c8d",
"hash": "84084dce2bc995cf540fa58e5f3c796d",
"packages": [
{
"name": "doctrine/annotations",
......@@ -2256,17 +2256,17 @@
},
{
"name": "symfony/yaml",
"version": "v2.5.5",
"version": "dev-master",
"target-dir": "Symfony/Component/Yaml",
"source": {
"type": "git",
"url": "https://github.com/symfony/Yaml.git",
"reference": "b1dbc53593b98c2d694ebf383660ac9134d30b96"
"reference": "499f7d7aa96747ad97940089bd7a1fb24ad8182a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Yaml/zipball/b1dbc53593b98c2d694ebf383660ac9134d30b96",
"reference": "b1dbc53593b98c2d694ebf383660ac9134d30b96",
"url": "https://api.github.com/repos/symfony/Yaml/zipball/499f7d7aa96747ad97940089bd7a1fb24ad8182a",
"reference": "499f7d7aa96747ad97940089bd7a1fb24ad8182a",
"shasum": ""
},
"require": {
......@@ -2275,7 +2275,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.5-dev"
"dev-master": "2.6-dev"
}
},
"autoload": {
......@@ -2299,7 +2299,7 @@
],
"description": "Symfony Yaml Component",
"homepage": "http://symfony.com",
"time": "2014-09-22 09:14:18"
"time": "2014-10-05 13:53:50"
},
{
"name": "twig/twig",
......@@ -2483,10 +2483,15 @@
"time": "2013-06-12 19:46:58"
}
],
"packages-dev": [],
"aliases": [],
"packages-dev": [
],
"aliases": [
],
"minimum-stability": "stable",
"stability-flags": {
"symfony/yaml": 20,
"doctrine/common": 20,
"kriswallsmith/assetic": 15,
"phpunit/phpunit-mock-objects": 20
......@@ -2495,5 +2500,7 @@
"platform": {
"php": ">=5.4.2"
},
"platform-dev": []
"platform-dev": [
]
}
......@@ -2468,25 +2468,25 @@
"homepage": "http://symfony.com"
},
{
"name": "symfony/yaml",
"name": "symfony/process",
"version": "v2.5.5",
"version_normalized": "2.5.5.0",
"target-dir": "Symfony/Component/Yaml",
"target-dir": "Symfony/Component/Process",
"source": {
"type": "git",
"url": "https://github.com/symfony/Yaml.git",
"reference": "b1dbc53593b98c2d694ebf383660ac9134d30b96"
"url": "https://github.com/symfony/Process.git",
"reference": "8a1ec96c4e519cee0fb971ea48a1eb7369dda54b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Yaml/zipball/b1dbc53593b98c2d694ebf383660ac9134d30b96",
"reference": "b1dbc53593b98c2d694ebf383660ac9134d30b96",
"url": "https://api.github.com/repos/symfony/Process/zipball/8a1ec96c4e519cee0fb971ea48a1eb7369dda54b",
"reference": "8a1ec96c4e519cee0fb971ea48a1eb7369dda54b",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"time": "2014-09-22 09:14:18",
"time": "2014-09-23 05:25:11",
"type": "library",
"extra": {
"branch-alias": {
......@@ -2496,7 +2496,7 @@
"installation-source": "dist",
"autoload": {
"psr-0": {
"Symfony\\Component\\Yaml\\": ""
"Symfony\\Component\\Process\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
......@@ -2513,39 +2513,39 @@
"email": "fabien@symfony.com"
}
],
"description": "Symfony Yaml Component",
"description": "Symfony Process Component",
"homepage": "http://symfony.com"
},
{
"name": "symfony/process",
"version": "v2.5.5",
"version_normalized": "2.5.5.0",
"target-dir": "Symfony/Component/Process",
"name": "symfony/yaml",
"version": "dev-master",
"version_normalized": "9999999-dev",
"target-dir": "Symfony/Component/Yaml",
"source": {
"type": "git",
"url": "https://github.com/symfony/Process.git",
"reference": "8a1ec96c4e519cee0fb971ea48a1eb7369dda54b"
"url": "https://github.com/symfony/Yaml.git",
"reference": "499f7d7aa96747ad97940089bd7a1fb24ad8182a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Process/zipball/8a1ec96c4e519cee0fb971ea48a1eb7369dda54b",
"reference": "8a1ec96c4e519cee0fb971ea48a1eb7369dda54b",
"url": "https://api.github.com/repos/symfony/Yaml/zipball/499f7d7aa96747ad97940089bd7a1fb24ad8182a",
"reference": "499f7d7aa96747ad97940089bd7a1fb24ad8182a",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"time": "2014-09-23 05:25:11",
"time": "2014-10-05 13:53:50",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.5-dev"
"dev-master": "2.6-dev"
}
},
"installation-source": "dist",
"installation-source": "source",
"autoload": {
"psr-0": {
"Symfony\\Component\\Process\\": ""
"Symfony\\Component\\Yaml\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
......@@ -2562,7 +2562,7 @@
"email": "fabien@symfony.com"
}
],
"description": "Symfony Process Component",
"description": "Symfony Yaml Component",
"homepage": "http://symfony.com"
}
]
......@@ -25,23 +25,26 @@ class Inline
private static $exceptionOnInvalidType = false;
private static $objectSupport = false;
private static $objectForMap = false;
/**
* Converts a YAML string to a PHP array.
*
* @param string $value A YAML string
* @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
* @param bool $objectSupport true if object support is enabled, false otherwise
* @param array $references Mapping of variable names to values
* @param string $value A YAML string
* @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
* @param bool $objectSupport true if object support is enabled, false otherwise
* @param bool $objectForMap true if maps should return a stdClass instead of array()
* @param array $references Mapping of variable names to values
*
* @return array A PHP array representing the YAML string
*
* @throws ParseException
*/
public static function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $references = array())
public static function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false, $references = array())
{
self::$exceptionOnInvalidType = $exceptionOnInvalidType;
self::$objectSupport = $objectSupport;
self::$objectForMap = $objectForMap;
$value = trim($value);
......@@ -125,8 +128,17 @@ public static function dump($value, $exceptionOnInvalidType = false, $objectSupp
if (false !== $locale) {
setlocale(LC_NUMERIC, 'C');
}
$repr = is_string($value) ? "'$value'" : (is_infinite($value) ? str_ireplace('INF', '.Inf', strval($value)) : strval($value));
if (is_float($value)) {
$repr = strval($value);
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 {
$repr = is_string($value) ? "'$value'" : strval($value);
}
if (false !== $locale) {
setlocale(LC_NUMERIC, $locale);
}
......@@ -234,7 +246,7 @@ public static function parseScalar($scalar, $delimiters = null, $stringDelimiter
* Parses a quoted scalar to YAML.
*
* @param string $scalar
* @param int &$i
* @param int &$i
*
* @return string A YAML string
*
......@@ -344,6 +356,10 @@ private static function parseMapping($mapping, &$i = 0, $references = array())
++$i;
continue 2;
case '}':
if (self::$objectForMap) {
return (object) $output;
}
return $output;
}
......@@ -352,6 +368,7 @@ private static function parseMapping($mapping, &$i = 0, $references = array())
// value
$done = false;
while ($i < $len) {
switch ($mapping[$i]) {
case '[':
......@@ -462,6 +479,8 @@ private static function evaluateScalar($scalar, $references = array())
}
return;
case 0 === strpos($scalar, '!!float '):
return (float) substr($scalar, 8);
case ctype_digit($scalar):
$raw = $scalar;
$cast = intval($scalar);
......
......@@ -44,12 +44,13 @@ public function __construct($offset = 0)
* @param string $value A YAML string
* @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
* @param bool $objectSupport true if object support is enabled, false otherwise
* @param bool $objectForMap true if maps should return a stdClass instead of array()
*
* @return mixed A PHP value
*
* @throws ParseException If the YAML is not valid
*/
public function parse($value, $exceptionOnInvalidType = false, $objectSupport = false)
public function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false)
{
$this->currentLineNb = -1;
$this->currentLine = '';
......@@ -94,7 +95,7 @@ public function parse($value, $exceptionOnInvalidType = false, $objectSupport =
$c = $this->getRealCurrentLineNb() + 1;
$parser = new Parser($c);
$parser->refs = & $this->refs;
$data[] = $parser->parse($this->getNextEmbedBlock(null, true), $exceptionOnInvalidType, $objectSupport);
$data[] = $parser->parse($this->getNextEmbedBlock(null, true), $exceptionOnInvalidType, $objectSupport, $objectForMap);
} else {
if (isset($values['leadspaces'])
&& ' ' == $values['leadspaces']
......@@ -110,9 +111,9 @@ public function parse($value, $exceptionOnInvalidType = false, $objectSupport =
$block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + 2);
}
$data[] = $parser->parse($block, $exceptionOnInvalidType, $objectSupport);
$data[] = $parser->parse($block, $exceptionOnInvalidType, $objectSupport, $objectForMap);
} else {
$data[] = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport);
$data[] = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport, $objectForMap);
}
}
} 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('"', "'")))) {
......@@ -122,7 +123,7 @@ public function parse($value, $exceptionOnInvalidType = false, $objectSupport =
$context = 'mapping';
// force correct settings
Inline::parse(null, $exceptionOnInvalidType, $objectSupport, $this->refs);
Inline::parse(null, $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs);
try {
$key = Inline::parseScalar($values['key']);
} catch (ParseException $e) {
......@@ -161,7 +162,7 @@ public function parse($value, $exceptionOnInvalidType = false, $objectSupport =
$c = $this->getRealCurrentLineNb() + 1;
$parser = new Parser($c);
$parser->refs = & $this->refs;
$parsed = $parser->parse($value, $exceptionOnInvalidType, $objectSupport);
$parsed = $parser->parse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap);
if (!is_array($parsed)) {
throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
......@@ -212,7 +213,7 @@ public function parse($value, $exceptionOnInvalidType = false, $objectSupport =
$c = $this->getRealCurrentLineNb() + 1;
$parser = new Parser($c);
$parser->refs = & $this->refs;
$value = $parser->parse($this->getNextEmbedBlock(), $exceptionOnInvalidType, $objectSupport);
$value = $parser->parse($this->getNextEmbedBlock(), $exceptionOnInvalidType, $objectSupport, $objectForMap);
// Spec: Keys MUST be unique; first one wins.
// But overwriting is allowed when a merge node is used in current block.
if ($allowOverwrite || !isset($data[$key])) {
......@@ -220,7 +221,7 @@ public function parse($value, $exceptionOnInvalidType = false, $objectSupport =
}
}
} else {
$value = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport);
$value = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport, $objectForMap);
// Spec: Keys MUST be unique; first one wins.
// But overwriting is allowed when a merge node is used in current block.
if ($allowOverwrite || !isset($data[$key])) {
......@@ -232,7 +233,7 @@ public function parse($value, $exceptionOnInvalidType = false, $objectSupport =
$lineCount = count($this->lines);
if (1 === $lineCount || (2 === $lineCount && empty($this->lines[1]))) {
try {
$value = Inline::parse($this->lines[0], $exceptionOnInvalidType, $objectSupport, $this->refs);
$value = Inline::parse($this->lines[0], $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs);
} catch (ParseException $e) {
$e->setParsedLine($this->getRealCurrentLineNb() + 1);
$e->setSnippet($this->currentLine);
......@@ -421,15 +422,16 @@ private function moveToPreviousLine()
/**
* Parses a YAML value.
*
* @param string $value A YAML value
* @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types false otherwise
* @param bool $objectSupport True if object support is enabled, false otherwise
* @param string $value A YAML value
* @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types false otherwise
* @param bool $objectSupport True if object support is enabled, false otherwise
* @param bool $objectForMap true if maps should return a stdClass instead of array()
*
* @return mixed A PHP value
* @return mixed A PHP value
*
* @throws ParseException When reference does not exist
*/
private function parseValue($value, $exceptionOnInvalidType, $objectSupport)
private function parseValue($value, $exceptionOnInvalidType, $objectSupport, $objectForMap)
{
if (0 === strpos($value, '*')) {
if (false !== $pos = strpos($value, '#')) {
......@@ -452,7 +454,7 @@ private function parseValue($value, $exceptionOnInvalidType, $objectSupport)
}
try {
return Inline::parse($value, $exceptionOnInvalidType, $objectSupport, $this->refs);
return Inline::parse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs);
} catch (ParseException $e) {
$e->setParsedLine($this->getRealCurrentLineNb() + 1);
$e->setSnippet($this->currentLine);
......
......@@ -3,11 +3,13 @@ Yaml Component
YAML implements most of the YAML 1.2 specification.
use Symfony\Component\Yaml\Yaml;
```php
use Symfony\Component\Yaml\Yaml;
$array = Yaml::parse($file);
$array = Yaml::parse($file);
print Yaml::dump($array);
print Yaml::dump($array);
```
Resources
---------
......
......@@ -96,8 +96,7 @@ public function testSpecifications()
// TODO
} else {
eval('$expected = '.trim($test['php']).';');
$this->assertEquals($expected, $this->parser->parse($this->dumper->dump($expected, 10)), $test['test']);
$this->assertSame($expected, $this->parser->parse($this->dumper->dump($expected, 10)), $test['test']);
}
}
}
......
......@@ -529,6 +529,7 @@ yaml: |
fixed: 1,230.15
negative infinity: -.inf
not a number: .NaN
float as whole number: !!float 1
php: |
array(
'canonical' => 1230.15,
......@@ -536,6 +537,7 @@ php: |
'fixed' => 1230.15,
'negative infinity' => log(0),
'not a number' => -log(0),
'float as whole number' => (float) 1
)
---
test: Miscellaneous
......
......@@ -15,28 +15,32 @@
class InlineTest extends \PHPUnit_Framework_TestCase
{
public function testParse()
/**
* @dataProvider getTestsForParse
*/
public function testParse($yaml, $value)
{
foreach ($this->getTestsForParse() as $yaml => $value) {
$this->assertSame($value, Inline::parse($yaml), sprintf('::parse() converts an inline YAML to a PHP structure (%s)', $yaml));
}
$this->assertSame($value, Inline::parse($yaml), sprintf('::parse() converts an inline YAML to a PHP structure (%s)', $yaml));
}
public function testDump()
/**
* @dataProvider getTestsForParseWithMapObjects
*/
public function testParseWithMapObjects($yaml, $value)
{
$testsForDump = $this->getTestsForDump();
$actual = Inline::parse($yaml, false, false, true);
foreach ($testsForDump as $yaml => $value) {
$this->assertEquals($yaml, Inline::dump($value), sprintf('::dump() converts a PHP structure to an inline YAML (%s)', $yaml));
}
$this->assertSame(serialize($value), serialize($actual));
}
foreach ($this->getTestsForParse() as $value) {
$this->assertEquals($value, Inline::parse(Inline::dump($value)), 'check consistency');
}
/**
* @dataProvider getTestsForDump
*/
public function testDump($yaml, $value)
{
$this->assertEquals($yaml, Inline::dump($value), sprintf('::dump() converts a PHP structure to an inline YAML (%s)', $yaml));
foreach ($testsForDump as $value) {
$this->assertEquals($value, Inline::parse(Inline::dump($value)), 'check consistency');
}
$this->assertSame($value, Inline::parse(Inline::dump($value)), 'check consistency');
}
public function testDumpNumericValueWithLocale()
......@@ -120,7 +124,7 @@ public function testParseScalarWithCorrectlyQuotedStringShouldReturnString()
*/
public function testParseReferences($yaml, $expected)
{
$this->assertSame($expected, Inline::parse($yaml, false, false, array('var' => 'var-value')));
$this->assertSame($expected, Inline::parse($yaml, false, false, false, array('var' => 'var-value')));
}
public function getDataForParseReferences()
......@@ -144,7 +148,7 @@ public function testParseMapReferenceInSequence()
'b' => 'Clark',
'c' => 'Brian',
);
$this->assertSame(array($foo), Inline::parse('[*foo]', false, false, array('foo' => $foo)));
$this->assertSame(array($foo), Inline::parse('[*foo]', false, false, false, array('foo' => $foo)));
}
/**
......@@ -165,117 +169,196 @@ public function testParseUnquotedAsteriskFollowedByAComment()
Inline::parse('{ foo: * #foo }');
}
protected function getTestsForParse()
public function getTestsForParse()
{
return array(
'' => '',
'null' => null,
'false' => false,
'true' => true,
'12' => 12,
'-12' => -12,
'"quoted string"' => 'quoted string',
"'quoted string'" => 'quoted string',
'12.30e+02' => 12.30e+02,
'0x4D2' => 0x4D2,
'02333' => 02333,
'.Inf' => -log(0),
'-.Inf' => log(0),
"'686e444'" => '686e444',
'686e444' => 646e444,
'123456789123456789123456789123456789' => '123456789123456789123456789123456789',
'"foo\r\nbar"' => "foo\r\nbar",
"'foo#bar'" => 'foo#bar',
"'foo # bar'" => 'foo # bar',
"'#cfcfcf'" => '#cfcfcf',
'::form_base.html.twig' => '::form_base.html.twig',
'2007-10-30' => mktime(0, 0, 0, 10, 30, 2007),
'2007-10-30T02:59:43Z' => gmmktime(2, 59, 43, 10, 30, 2007),
'2007-10-30 02:59:43 Z' => gmmktime(2, 59, 43, 10, 30, 2007),
'1960-10-30 02:59:43 Z' => gmmktime(2, 59, 43, 10, 30, 1960),
'1730-10-30T02:59:43Z' => gmmktime(2, 59, 43, 10, 30, 1730),
'"a \\"string\\" with \'quoted strings inside\'"' => 'a "string" with \'quoted strings inside\'',
"'a \"string\" with ''quoted strings inside'''" => 'a "string" with \'quoted strings inside\'',
array('', ''),
array('null', null),
array('false', false),
array('true', true),
array('12', 12),
array('-12', -12),
array('"quoted string"', 'quoted string'),
array("'quoted string'", 'quoted string'),
array('12.30e+02', 12.30e+02),
array('0x4D2', 0x4D2),
array('02333', 02333),
array('.Inf', -log(0)),
array('-.Inf', log(0)),
array("'686e444'", '686e444'),
array('686e444', 646e444),
array('123456789123456789123456789123456789', '123456789123456789123456789123456789'),
array('"foo\r\nbar"', "foo\r\nbar"),
array("'foo#bar'", 'foo#bar'),
array("'foo # bar'", 'foo # bar'),
array("'#cfcfcf'", '#cfcfcf'),
array('::form_base.html.twig', '::form_base.html.twig'),
array('2007-10-30', mktime(0, 0, 0, 10, 30, 2007)),
array('2007-10-30T02:59:43Z', gmmktime(2, 59, 43, 10, 30, 2007)),
array('2007-10-30 02:59:43 Z', gmmktime(2, 59, 43, 10, 30, 2007)),
array('1960-10-30 02:59:43 Z', gmmktime(2, 59, 43, 10, 30, 1960)),
array('1730-10-30T02:59:43Z', gmmktime(2, 59, 43, 10, 30, 1730)),
array('"a \\"string\\" with \'quoted strings inside\'"', 'a "string" with \'quoted strings inside\''),
array("'a \"string\" with ''quoted strings inside'''", 'a "string" with \'quoted strings inside\''),
// sequences
// urls are no key value mapping. see #3609. Valid yaml "key: value" mappings require a space after the colon
'[foo, http://urls.are/no/mappings, false, null, 12]' => array('foo', 'http://urls.are/no/mappings', false, null, 12),
'[ foo , bar , false , null , 12 ]' => array('foo', 'bar', false, null, 12),
'[\'foo,bar\', \'foo bar\']' => array('foo,bar', 'foo bar'),
array('[foo, http://urls.are/no/mappings, false, null, 12]', array('foo', 'http://urls.are/no/mappings', false, null, 12)),
array('[ foo , bar , false , null , 12 ]', array('foo', 'bar', false, null, 12)),
array('[\'foo,bar\', \'foo bar\']', array('foo,bar', 'foo bar')),
// mappings
'{foo:bar,bar:foo,false:false,null:null,integer:12}' => array('foo' => 'bar', 'bar' => 'foo', 'false' => false, 'null' => null, 'integer' => 12),
'{ foo : bar, bar : foo, false : false, null : null, integer : 12 }' => array('foo' => 'bar', 'bar' => 'foo', 'false' => false, 'null' => null, 'integer' => 12),
'{foo: \'bar\', bar: \'foo: bar\'}' => array('foo' => 'bar', 'bar' => 'foo: bar'),
'{\'foo\': \'bar\', "bar": \'foo: bar\'}' => array('foo' => 'bar', 'bar' => 'foo: bar'),
'{\'foo\'\'\': \'bar\', "bar\"": \'foo: bar\'}' => array('foo\'' => 'bar', "bar\"" => 'foo: bar'),
'{\'foo: \': \'bar\', "bar: ": \'foo: bar\'}' => array('foo: ' => 'bar', "bar: " => 'foo: bar'),
array('{foo:bar,bar:foo,false:false,null:null,integer:12}', array('foo' => 'bar', 'bar' => 'foo', 'false' => false, 'null' => null, 'integer' => 12)),