Issue #1470824 by alexpott, sun: Fixed XML encoder can only handle a small subset of PHP arrays, so switch to YAML.
parent
dc151c10ff
commit
9e8d1e85dc
|
@ -28,7 +28,7 @@ function config_get_config_directory() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Moves the default config supplied by a module to the live config directory.
|
||||
* Installs the default configuration of a given module.
|
||||
*
|
||||
* @param
|
||||
* The name of the module we are installing.
|
||||
|
@ -40,55 +40,23 @@ function config_install_default_config($module) {
|
|||
$module_config_dir = drupal_get_path('module', $module) . '/config';
|
||||
$drupal_config_dir = config_get_config_directory();
|
||||
if (is_dir(drupal_get_path('module', $module) . '/config')) {
|
||||
$files = glob($module_config_dir . '/' . '*.xml');
|
||||
$files = glob($module_config_dir . '/*.' . FileStorage::getFileExtension());
|
||||
foreach ($files as $key => $file) {
|
||||
// Load config data into the active store and write it out to the
|
||||
// file system in the drupal config directory. Note the config name
|
||||
// needs to be the same as the file name WITHOUT the extension.
|
||||
$parts = explode('/', $file);
|
||||
$file = array_pop($parts);
|
||||
$config_name = str_replace('.xml', '', $file);
|
||||
$config_name = basename($file, '.' . FileStorage::getFileExtension());
|
||||
|
||||
$storage = new DatabaseStorage($config_name);
|
||||
$data = FileStorage::decode(file_get_contents($module_config_dir . '/' . $file));
|
||||
$storage->write($data);
|
||||
$database_storage = new DatabaseStorage($config_name);
|
||||
$file_storage = new FileStorage($config_name);
|
||||
$file_storage->setPath($module_config_dir);
|
||||
$database_storage->write($file_storage->read());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an iterable array which lists the children under a config 'branch'.
|
||||
*
|
||||
* Given the following configuration files:
|
||||
* - core.entity.node_type.article.xml
|
||||
* - core.entity.node_type.page.xml
|
||||
*
|
||||
* You can pass a prefix 'core.entity.node_type' and get back an array of the
|
||||
* filenames that match. This allows you to iterate through all files in a
|
||||
* branch.
|
||||
*
|
||||
* @param $prefix
|
||||
* The prefix of the files we are searching for.
|
||||
*
|
||||
* @return
|
||||
* An array of file names under a branch.
|
||||
*/
|
||||
function config_get_files_with_prefix($prefix = '') {
|
||||
$files = glob(config_get_config_directory() . '/' . $prefix . '*.xml');
|
||||
$clean_name = function ($value) {
|
||||
return basename($value, '.xml');
|
||||
};
|
||||
return array_map($clean_name, $files);
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo
|
||||
*
|
||||
* @param $prefix
|
||||
* @todo
|
||||
*
|
||||
* @return
|
||||
* @todo
|
||||
* @todo http://drupal.org/node/1552396 renames this into config_load_all().
|
||||
*/
|
||||
function config_get_storage_names_with_prefix($prefix = '') {
|
||||
return DatabaseStorage::getNamesWithPrefix($prefix);
|
||||
|
@ -103,8 +71,8 @@ function config_get_storage_names_with_prefix($prefix = '') {
|
|||
*
|
||||
* @param $name
|
||||
* The name of the configuration object to retrieve. The name corresponds to
|
||||
* an XML configuration file. For @code config(book.admin) @endcode, the
|
||||
* config object returned will contain the contents of book.admin.xml.
|
||||
* a configuration file. For @code config(book.admin) @endcode, the config
|
||||
* object returned will contain the contents of book.admin configuration file.
|
||||
* @param $class
|
||||
* The class name of the config object to be returned. Defaults to
|
||||
* DrupalConfig.
|
||||
|
|
|
@ -398,11 +398,9 @@ function drupal_uninstall_modules($module_list = array(), $uninstall_dependents
|
|||
// by scanning its config directory.
|
||||
$module_config_dir = drupal_get_path('module', $module) . '/config';
|
||||
if (is_dir($module_config_dir)) {
|
||||
$files = glob($module_config_dir . '/' . '*.xml');
|
||||
$files = glob($module_config_dir . '/*.' . FileStorage::getFileExtension());
|
||||
foreach ($files as $file) {
|
||||
$parts = explode('/', $file);
|
||||
$file = array_pop($parts);
|
||||
$config_name = str_replace('.xml', '', $file);
|
||||
$config_name = basename($file, '.' . FileStorage::getFileExtension());
|
||||
$file_storage = new FileStorage($config_name);
|
||||
// Delete the configuration from storage.
|
||||
$file_storage->delete();
|
||||
|
|
|
@ -17,6 +17,11 @@ class DrupalConfig {
|
|||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* The data of the configuration object.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $data = array();
|
||||
|
||||
/**
|
||||
|
@ -36,16 +41,9 @@ class DrupalConfig {
|
|||
* Reads config data from the active store into our object.
|
||||
*/
|
||||
public function read() {
|
||||
$active = (array) $this->storage->read();
|
||||
foreach ($active as $key => $value) {
|
||||
// If the setting is empty, return an empty string rather than an array.
|
||||
// This is necessary because SimpleXML's default behavior is to return
|
||||
// an empty array instead of a string.
|
||||
if (is_array($value) && empty($value)) {
|
||||
$value = '';
|
||||
}
|
||||
$this->set($key, $value);
|
||||
}
|
||||
$data = $this->storage->read();
|
||||
$this->setData($data !== FALSE ? $data : array());
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -66,11 +64,13 @@ class DrupalConfig {
|
|||
*
|
||||
* @param $key
|
||||
* A string that maps to a key within the configuration data.
|
||||
* For instance in the following XML:
|
||||
* For instance in the following configuation array:
|
||||
* @code
|
||||
* <foo>
|
||||
* <bar>baz</bar>
|
||||
* </foo>
|
||||
* array(
|
||||
* 'foo' => array(
|
||||
* 'bar' => 'baz',
|
||||
* ),
|
||||
* );
|
||||
* @endcode
|
||||
* A key of 'foo.bar' would return the string 'baz'. However, a key of 'foo'
|
||||
* would return array('bar' => 'baz').
|
||||
|
@ -113,6 +113,17 @@ class DrupalConfig {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the data of this configuration object.
|
||||
*
|
||||
* @param array $data
|
||||
* The new configuration data.
|
||||
*/
|
||||
public function setData(array $data) {
|
||||
$this->data = $data;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets value in this config object.
|
||||
*
|
||||
|
@ -122,16 +133,11 @@ class DrupalConfig {
|
|||
* @todo
|
||||
*/
|
||||
public function set($key, $value) {
|
||||
// Remove all non-alphanumeric characters from the key.
|
||||
// @todo Reverse this and throw an exception when encountering a key with
|
||||
// invalid name. The identical validation also needs to happen in get().
|
||||
// Furthermore, the dot/period is a reserved character; it may appear
|
||||
// between keys, but not within keys.
|
||||
$key = preg_replace('@[^a-zA-Z0-9_.-]@', '', $key);
|
||||
|
||||
// Type-cast value into a string.
|
||||
$value = $this->castValue($value);
|
||||
|
||||
// The dot/period is a reserved character; it may appear between keys, but
|
||||
// not within keys.
|
||||
$parts = explode('.', $key);
|
||||
if (count($parts) == 1) {
|
||||
$this->data[$key] = $value;
|
||||
|
@ -198,14 +204,14 @@ class DrupalConfig {
|
|||
}
|
||||
|
||||
/**
|
||||
* Saves the configuration object to disk as XML.
|
||||
* Saves the configuration object.
|
||||
*/
|
||||
public function save() {
|
||||
$this->storage->write($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the configuration object on disk.
|
||||
* Deletes the configuration object.
|
||||
*/
|
||||
public function delete() {
|
||||
$this->data = array();
|
||||
|
|
|
@ -2,69 +2,94 @@
|
|||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
/**
|
||||
* Represents the file storage interface.
|
||||
* Represents the file storage controller.
|
||||
*
|
||||
* Classes implementing this interface allow reading and writing configuration
|
||||
* data to and from disk.
|
||||
* @todo Implement StorageInterface after removing DrupalConfig methods.
|
||||
* @todo Consider to extend StorageBase.
|
||||
*/
|
||||
class FileStorage {
|
||||
|
||||
/**
|
||||
* Constructs a FileStorage object.
|
||||
* The name of the configuration object.
|
||||
*
|
||||
* @param string $name
|
||||
* The name for the configuration data. Should be lowercase.
|
||||
* @var string
|
||||
*/
|
||||
public function __construct($name) {
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* The filesystem path containing the configuration object.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* Implements StorageInterface::__construct().
|
||||
*/
|
||||
public function __construct($name = NULL) {
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads and returns a file.
|
||||
* Returns the path containing the configuration file.
|
||||
*
|
||||
* @return
|
||||
* The data of the file.
|
||||
*
|
||||
* @throws
|
||||
* Exception
|
||||
* @return string
|
||||
* The relative path to the configuration object.
|
||||
*/
|
||||
protected function readData() {
|
||||
$data = file_get_contents($this->getFilePath());
|
||||
if ($data === FALSE) {
|
||||
throw new FileStorageReadException('Read file is invalid.');
|
||||
public function getPath() {
|
||||
// If the path has not been set yet, retrieve and assign the default path
|
||||
// for configuration files.
|
||||
if (!isset($this->path)) {
|
||||
$this->setPath(config_get_config_directory());
|
||||
}
|
||||
return $data;
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the XML configuration file already exists on disk.
|
||||
* Sets the path containing the configuration file.
|
||||
*/
|
||||
public function setPath($directory) {
|
||||
$this->path = $directory;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the configuration file.
|
||||
*
|
||||
* @return
|
||||
* @todo
|
||||
* @return string
|
||||
* The path to the configuration file.
|
||||
*/
|
||||
public function getFilePath() {
|
||||
return $this->getPath() . '/' . $this->getName() . '.' . self::getFileExtension();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file extension used by the file storage for all configuration files.
|
||||
*
|
||||
* @return string
|
||||
* The file extension.
|
||||
*/
|
||||
public static function getFileExtension() {
|
||||
return 'yml';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the configuration file exists.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the configuration file exists, FALSE otherwise.
|
||||
*/
|
||||
protected function exists() {
|
||||
return file_exists($this->getFilePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the XML configuration file.
|
||||
* Implements StorageInterface::write().
|
||||
*
|
||||
* @return
|
||||
* @todo
|
||||
*/
|
||||
public function getFilePath() {
|
||||
return config_get_config_directory() . '/' . $this->name . '.xml';
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the contents of the configuration file to disk.
|
||||
*
|
||||
* @param $data
|
||||
* The data to be written to the file.
|
||||
*
|
||||
* @throws
|
||||
* Exception
|
||||
* @throws FileStorageException
|
||||
*/
|
||||
public function write($data) {
|
||||
$data = $this->encode($data);
|
||||
|
@ -74,17 +99,21 @@ class FileStorage {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the contents of the configuration file.
|
||||
* Implements StorageInterface::read().
|
||||
*
|
||||
* @return
|
||||
* @todo
|
||||
* @throws FileStorageReadException
|
||||
*/
|
||||
public function read() {
|
||||
if ($this->exists()) {
|
||||
$data = $this->readData();
|
||||
return $this->decode($data);
|
||||
if (!$this->exists()) {
|
||||
throw new FileStorageReadException('Configuration file does not exist.');
|
||||
}
|
||||
return FALSE;
|
||||
|
||||
$data = file_get_contents($this->getFilePath());
|
||||
$data = $this->decode($data);
|
||||
if ($data === FALSE) {
|
||||
throw new FileStorageReadException('Unable to decode configuration file.');
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -99,43 +128,9 @@ class FileStorage {
|
|||
* Implements StorageInterface::encode().
|
||||
*/
|
||||
public static function encode($data) {
|
||||
// Convert the supplied array into a SimpleXMLElement.
|
||||
$xml_object = new \SimpleXMLElement("<?xml version=\"1.0\"?><config></config>");
|
||||
self::encodeArrayToXml($data, $xml_object);
|
||||
|
||||
// Pretty print the result.
|
||||
$dom = new \DOMDocument('1.0');
|
||||
$dom->preserveWhiteSpace = false;
|
||||
$dom->formatOutput = true;
|
||||
$dom->loadXML($xml_object->asXML());
|
||||
|
||||
return $dom->saveXML();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes an array into XML
|
||||
*
|
||||
* @param $array
|
||||
* An associative array to encode.
|
||||
*
|
||||
* @return
|
||||
* A representation of $array in XML.
|
||||
*/
|
||||
protected static function encodeArrayToXml($array, &$xml_object) {
|
||||
foreach ($array as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
if (!is_numeric($key)){
|
||||
$subnode = $xml_object->addChild("$key");
|
||||
self::encodeArrayToXml($value, $subnode);
|
||||
}
|
||||
else {
|
||||
self::encodeArrayToXml($value, $xml_object);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$xml_object->addChild($key, $value);
|
||||
}
|
||||
}
|
||||
// The level where you switch to inline YAML is set to PHP_INT_MAX to ensure
|
||||
// this does not occur.
|
||||
return Yaml::dump($data, PHP_INT_MAX);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -145,13 +140,33 @@ class FileStorage {
|
|||
if (empty($raw)) {
|
||||
return array();
|
||||
}
|
||||
return Yaml::parse($raw);
|
||||
}
|
||||
|
||||
// This is the fastest and easiest way to get from a string of XML to a PHP
|
||||
// array since SimpleXML and json_decode()/encode() are native to PHP. Our
|
||||
// only other choice would be a custom userspace implementation which would
|
||||
// be a lot less performant and more complex.
|
||||
$xml = new \SimpleXMLElement($raw);
|
||||
$json = json_encode($xml);
|
||||
return json_decode($json, TRUE);
|
||||
/**
|
||||
* Implements StorageInterface::getName().
|
||||
*/
|
||||
public function getName() {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements StorageInterface::setName().
|
||||
*/
|
||||
public function setName($name) {
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements StorageInterface::getNamesWithPrefix().
|
||||
*/
|
||||
public static function getNamesWithPrefix($prefix = '') {
|
||||
// @todo Use $this->getPath() to allow for contextual search of files in
|
||||
// custom paths.
|
||||
$files = glob(config_get_config_directory() . '/' . $prefix . '*.' . FileStorage::getFileExtension());
|
||||
$clean_name = function ($value) {
|
||||
return basename($value, '.' . FileStorage::getFileExtension());
|
||||
};
|
||||
return array_map($clean_name, $files);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,10 +6,15 @@ use Drupal\Core\Config\StorageInterface;
|
|||
use Drupal\Core\Config\FileStorage;
|
||||
|
||||
/**
|
||||
* @todo
|
||||
* Base class for configuration storage controllers.
|
||||
*/
|
||||
abstract class StorageBase implements StorageInterface {
|
||||
|
||||
/**
|
||||
* The name of the configuration object.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
|
@ -22,7 +27,7 @@ abstract class StorageBase implements StorageInterface {
|
|||
/**
|
||||
* Implements StorageInterface::__construct().
|
||||
*/
|
||||
function __construct($name) {
|
||||
function __construct($name = NULL) {
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
|
@ -106,4 +111,11 @@ abstract class StorageBase implements StorageInterface {
|
|||
public function getName() {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements StorageInterface::setName().
|
||||
*/
|
||||
public function setName($name) {
|
||||
$this->name = $name;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,18 +5,20 @@ namespace Drupal\Core\Config;
|
|||
/**
|
||||
* Defines an interface for configuration storage manipulation.
|
||||
*
|
||||
* This class allows reading and writing configuration data from/to the
|
||||
* storage and copying to/from the file storing the same data.
|
||||
* Classes implementing this interface allow reading and writing configuration
|
||||
* data from and to the storage.
|
||||
*
|
||||
* @todo Remove all active/file methods. They belong onto DrupalConfig only.
|
||||
*/
|
||||
interface StorageInterface {
|
||||
|
||||
/**
|
||||
* Constructs a storage manipulation class.
|
||||
*
|
||||
* @param $name
|
||||
* Lowercase string, the name for the configuration data.
|
||||
* @param string $name
|
||||
* (optional) The name of a configuration object to load.
|
||||
*/
|
||||
function __construct($name);
|
||||
function __construct($name = NULL);
|
||||
|
||||
/**
|
||||
* Reads the configuration data from the storage.
|
||||
|
@ -76,24 +78,58 @@ interface StorageInterface {
|
|||
|
||||
/**
|
||||
* Encodes configuration data into the storage-specific format.
|
||||
*
|
||||
* @param array $data
|
||||
* The configuration data to encode.
|
||||
*
|
||||
* @return string
|
||||
* The encoded configuration data.
|
||||
*
|
||||
* This is a publicly accessible static method to allow for alternative
|
||||
* usages in data conversion scripts and also tests.
|
||||
*/
|
||||
public static function encode($data);
|
||||
|
||||
/**
|
||||
* Decodes configuration data from the storage-specific format.
|
||||
*
|
||||
* @param string $raw
|
||||
* The raw configuration data string to decode.
|
||||
*
|
||||
* @return array
|
||||
* The decoded configuration data as an associative array.
|
||||
*
|
||||
* This is a publicly accessible static method to allow for alternative
|
||||
* usages in data conversion scripts and also tests.
|
||||
*/
|
||||
public static function decode($raw);
|
||||
|
||||
/**
|
||||
* Gets names starting with this prefix.
|
||||
*
|
||||
* @param $prefix
|
||||
* @todo
|
||||
*/
|
||||
static function getNamesWithPrefix($prefix);
|
||||
|
||||
/**
|
||||
* Gets the name of this object.
|
||||
*/
|
||||
public function getName();
|
||||
|
||||
/**
|
||||
* Sets the name of this object.
|
||||
*/
|
||||
public function setName($name);
|
||||
|
||||
/**
|
||||
* Gets configuration object names starting with a given prefix.
|
||||
*
|
||||
* Given the following configuration objects:
|
||||
* - node.type.article
|
||||
* - node.type.page
|
||||
*
|
||||
* Passing the prefix 'node.type' will return an array containing the above
|
||||
* names.
|
||||
*
|
||||
* @param string $prefix
|
||||
* (optional) The prefix to search for. If omitted, all configuration object
|
||||
* names that exist are returned.
|
||||
*
|
||||
* @return array
|
||||
* An array containing matching configuration object names.
|
||||
*/
|
||||
static function getNamesWithPrefix($prefix = '');
|
||||
}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<config>
|
||||
<block_cache>0</block_cache>
|
||||
</config>
|
|
@ -0,0 +1 @@
|
|||
block_cache: '0'
|
|
@ -52,7 +52,7 @@ class ConfigFileSecurityTestCase extends WebTestBase {
|
|||
* Tests reading and writing file contents.
|
||||
*/
|
||||
class ConfigFileContentTestCase extends WebTestBase {
|
||||
protected $fileExtension = 'xml';
|
||||
protected $fileExtension;
|
||||
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
|
@ -62,6 +62,12 @@ class ConfigFileContentTestCase extends WebTestBase {
|
|||
);
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->fileExtension = FileStorage::getFileExtension();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests setting, writing, and reading of a configuration setting.
|
||||
*/
|
||||
|
@ -198,22 +204,22 @@ class ConfigFileContentTestCase extends WebTestBase {
|
|||
|
||||
// Get file listing for all files starting with 'foo'. Should return
|
||||
// two elements.
|
||||
$files = config_get_files_with_prefix('foo');
|
||||
$files = FileStorage::getNamesWithPrefix('foo');
|
||||
$this->assertEqual(count($files), 2, 'Two files listed with the prefix \'foo\'.');
|
||||
|
||||
// Get file listing for all files starting with 'biff'. Should return
|
||||
// one element.
|
||||
$files = config_get_files_with_prefix('biff');
|
||||
$files = FileStorage::getNamesWithPrefix('biff');
|
||||
$this->assertEqual(count($files), 1, 'One file listed with the prefix \'biff\'.');
|
||||
|
||||
// Get file listing for all files starting with 'foo.bar'. Should return
|
||||
// one element.
|
||||
$files = config_get_files_with_prefix('foo.bar');
|
||||
$files = FileStorage::getNamesWithPrefix('foo.bar');
|
||||
$this->assertEqual(count($files), 1, 'One file listed with the prefix \'foo.bar\'.');
|
||||
|
||||
// Get file listing for all files starting with 'bar'. Should return
|
||||
// an empty array.
|
||||
$files = config_get_files_with_prefix('bar');
|
||||
$files = FileStorage::getNamesWithPrefix('bar');
|
||||
$this->assertEqual($files, array(), 'No files listed with the prefix \'bar\'.');
|
||||
|
||||
// Delete the configuration.
|
||||
|
@ -223,10 +229,61 @@ class ConfigFileContentTestCase extends WebTestBase {
|
|||
// Verify the database entry no longer exists.
|
||||
$db_config = db_query('SELECT * FROM {config} WHERE name = :name', array(':name' => $name))->fetch();
|
||||
$this->assertIdentical($db_config, FALSE);
|
||||
$this->assertFalse(file_exists($config_dir . '/' . $name . '.' . $this->fileExtension));
|
||||
$this->assertFalse(file_exists($config_dir . '/' . $name . $this->fileExtension));
|
||||
|
||||
// Attempt to delete non-existing configuration.
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests serialization of configuration to file.
|
||||
*/
|
||||
function testConfigSerialization() {
|
||||
$name = $this->randomName(10) . '.' . $this->randomName(10);
|
||||
$config_data = array(
|
||||
// Indexed arrays; the order of elements is essential.
|
||||
'numeric keys' => array('i', 'n', 'd', 'e', 'x', 'e', 'd'),
|
||||
// Infinitely nested keys using arbitrary element names.
|
||||
'nested keys' => array(
|
||||
// HTML/XML in values.
|
||||
'HTML' => '<strong> <bold> <em> <blockquote>',
|
||||
// UTF-8 in values.
|
||||
'UTF-8' => 'FrançAIS is ÜBER-åwesome',
|
||||
// Unicode in keys and values.
|
||||
'ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΣὨ' => 'αβγδεζηθικλμνξοσὠ',
|
||||
),
|
||||
'invalid xml' => '</title><script type="text/javascript">alert("Title XSS!");</script> & < > " \' ',
|
||||
);
|
||||
|
||||
// Attempt to read non-existing configuration.
|
||||
$config = config($name);
|
||||
|
||||
foreach ($config_data as $key => $value) {
|
||||
$config->set($key, $value);
|
||||
}
|
||||
|
||||
$config->save();
|
||||
|
||||
$config_filestorage = new FileStorage($name);
|
||||
$config_parsed = $config_filestorage->read();
|
||||
|
||||
$key = 'numeric keys';
|
||||
$this->assertIdentical($config_data[$key], $config_parsed[$key]);
|
||||
|
||||
$key = 'nested keys';
|
||||
$this->assertIdentical($config_data[$key], $config_parsed[$key]);
|
||||
|
||||
$key = 'HTML';
|
||||
$this->assertIdentical($config_data['nested keys'][$key], $config_parsed['nested keys'][$key]);
|
||||
|
||||
$key = 'UTF-8';
|
||||
$this->assertIdentical($config_data['nested keys'][$key], $config_parsed['nested keys'][$key]);
|
||||
|
||||
$key = 'ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΣὨ';
|
||||
$this->assertIdentical($config_data['nested keys'][$key], $config_parsed['nested keys'][$key]);
|
||||
|
||||
$key = 'invalid xml';
|
||||
$this->assertIdentical($config_data[$key], $config_parsed[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<config>
|
||||
<name>large</name>
|
||||
<effects>
|
||||
<image_scale_480_480_1>
|
||||
<name>image_scale</name>
|
||||
<ieid>image_scale_480_480_1</ieid>
|
||||
<data>
|
||||
<width>480</width>
|
||||
<height>480</height>
|
||||
<upscale>1</upscale>
|
||||
</data>
|
||||
<weight>0</weight>
|
||||
</image_scale_480_480_1>
|
||||
</effects>
|
||||
</config>
|
|
@ -0,0 +1,10 @@
|
|||
name: large
|
||||
effects:
|
||||
image_scale_480_480_1:
|
||||
name: image_scale
|
||||
data:
|
||||
width: '480'
|
||||
height: '480'
|
||||
upscale: '1'
|
||||
weight: '0'
|
||||
ieid: image_scale_480_480_1
|
|
@ -1,16 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<config>
|
||||
<name>medium</name>
|
||||
<effects>
|
||||
<image_scale_220_220_1>
|
||||
<name>image_scale</name>
|
||||
<ieid>image_scale_220_220_1</ieid>
|
||||
<data>
|
||||
<width>220</width>
|
||||
<height>220</height>
|
||||
<upscale>1</upscale>
|
||||
</data>
|
||||
<weight>0</weight>
|
||||
</image_scale_220_220_1>
|
||||
</effects>
|
||||
</config>
|
|
@ -0,0 +1,10 @@
|
|||
name: medium
|
||||
effects:
|
||||
image_scale_220_220_1:
|
||||
name: image_scale
|
||||
data:
|
||||
width: '220'
|
||||
height: '220'
|
||||
upscale: '1'
|
||||
weight: '0'
|
||||
ieid: image_scale_220_220_1
|
|
@ -1,16 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<config>
|
||||
<name>thumbnail</name>
|
||||
<effects>
|
||||
<image_scale_100_100_1>
|
||||
<name>image_scale</name>
|
||||
<ieid>image_scale_100_100_1</ieid>
|
||||
<data>
|
||||
<width>100</width>
|
||||
<height>100</height>
|
||||
<upscale>1</upscale>
|
||||
</data>
|
||||
<weight>0</weight>
|
||||
</image_scale_100_100_1>
|
||||
</effects>
|
||||
</config>
|
|
@ -0,0 +1,10 @@
|
|||
name: thumbnail
|
||||
effects:
|
||||
image_scale_100_100_1:
|
||||
name: image_scale
|
||||
data:
|
||||
width: '100'
|
||||
height: '100'
|
||||
upscale: '1'
|
||||
weight: '0'
|
||||
ieid: image_scale_100_100_1
|
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<config>
|
||||
<cron_max_threshold>10800</cron_max_threshold>
|
||||
<cron_safe_threshold>10800</cron_safe_threshold>
|
||||
<cron_threshold_warning>172800</cron_threshold_warning>
|
||||
<cron_threshold_error>1209600</cron_threshold_error>
|
||||
<cron_key>drupal</cron_key>
|
||||
</config>
|
|
@ -0,0 +1,5 @@
|
|||
cron_max_threshold: '10800'
|
||||
cron_safe_threshold: '10800'
|
||||
cron_threshold_warning: '172800'
|
||||
cron_threshold_error: '1209600'
|
||||
cron_key: drupal
|
|
@ -1,9 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<config>
|
||||
<cache>0</cache>
|
||||
<cache_lifetime>0</cache_lifetime>
|
||||
<page_cache_maximum_age>0</page_cache_maximum_age>
|
||||
<page_compression>0</page_compression>
|
||||
<preprocess_css>0</preprocess_css>
|
||||
<preprocess_js>0</preprocess_js>
|
||||
</config>
|
|
@ -0,0 +1,6 @@
|
|||
cache: '0'
|
||||
cache_lifetime: '0'
|
||||
page_cache_maximum_age: '0'
|
||||
page_compression: '0'
|
||||
preprocess_css: '0'
|
||||
preprocess_js: '0'
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<config>
|
||||
<feed_description></feed_description>
|
||||
<feed_default_items>10</feed_default_items>
|
||||
<feed_item_length>fulltext</feed_item_length>
|
||||
</config>
|
|
@ -0,0 +1,3 @@
|
|||
feed_description: ''
|
||||
feed_default_items: '10'
|
||||
feed_item_length: fulltext
|
|
@ -1,5 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<config>
|
||||
<config_test_foo>bar</config_test_foo>
|
||||
<config_test_bar>foo</config_test_bar>
|
||||
</config>
|
|
@ -0,0 +1,2 @@
|
|||
config_test_foo: bar
|
||||
config_test_bar: foo
|
|
@ -0,0 +1,8 @@
|
|||
CHANGELOG
|
||||
=========
|
||||
|
||||
2.1.0
|
||||
-----
|
||||
|
||||
* Yaml::parse() does not evaluate loaded files as PHP files by default
|
||||
anymore (call Yaml::enablePhpParsing() to get back the old behavior)
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Yaml;
|
||||
|
||||
/**
|
||||
* Dumper dumps PHP variables to YAML strings.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class Dumper
|
||||
{
|
||||
/**
|
||||
* Dumps a PHP value to YAML.
|
||||
*
|
||||
* @param mixed $input The PHP value
|
||||
* @param integer $inline The level where you switch to inline YAML
|
||||
* @param integer $indent The level of indentation (used internally)
|
||||
*
|
||||
* @return string The YAML representation of the PHP value
|
||||
*/
|
||||
public function dump($input, $inline = 0, $indent = 0)
|
||||
{
|
||||
$output = '';
|
||||
$prefix = $indent ? str_repeat(' ', $indent) : '';
|
||||
|
||||
if ($inline <= 0 || !is_array($input) || empty($input)) {
|
||||
$output .= $prefix.Inline::dump($input);
|
||||
} else {
|
||||
$isAHash = array_keys($input) !== range(0, count($input) - 1);
|
||||
|
||||
foreach ($input as $key => $value) {
|
||||
$willBeInlined = $inline - 1 <= 0 || !is_array($value) || empty($value);
|
||||
|
||||
$output .= sprintf('%s%s%s%s',
|
||||
$prefix,
|
||||
$isAHash ? Inline::dump($key).':' : '-',
|
||||
$willBeInlined ? ' ' : "\n",
|
||||
$this->dump($value, $inline - 1, $willBeInlined ? 0 : $indent + 4)
|
||||
).($willBeInlined ? "\n" : '');
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Yaml;
|
||||
|
||||
/**
|
||||
* Escaper encapsulates escaping rules for single and double-quoted
|
||||
* YAML strings.
|
||||
*
|
||||
* @author Matthew Lewinski <matthew@lewinski.org>
|
||||
*/
|
||||
class Escaper
|
||||
{
|
||||
// Characters that would cause a dumped string to require double quoting.
|
||||
const REGEX_CHARACTER_TO_ESCAPE = "[\\x00-\\x1f]|\xc2\x85|\xc2\xa0|\xe2\x80\xa8|\xe2\x80\xa9";
|
||||
|
||||
// Mapping arrays for escaping a double quoted string. The backslash is
|
||||
// first to ensure proper escaping because str_replace operates iteratively
|
||||
// on the input arrays. This ordering of the characters avoids the use of strtr,
|
||||
// which performs more slowly.
|
||||
static private $escapees = array('\\\\', '\\"',
|
||||
"\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07",
|
||||
"\x08", "\x09", "\x0a", "\x0b", "\x0c", "\x0d", "\x0e", "\x0f",
|
||||
"\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17",
|
||||
"\x18", "\x19", "\x1a", "\x1b", "\x1c", "\x1d", "\x1e", "\x1f",
|
||||
"\xc2\x85", "\xc2\xa0", "\xe2\x80\xa8", "\xe2\x80\xa9");
|
||||
static private $escaped = array('\\"', '\\\\',
|
||||
"\\0", "\\x01", "\\x02", "\\x03", "\\x04", "\\x05", "\\x06", "\\a",
|
||||
"\\b", "\\t", "\\n", "\\v", "\\f", "\\r", "\\x0e", "\\x0f",
|
||||
"\\x10", "\\x11", "\\x12", "\\x13", "\\x14", "\\x15", "\\x16", "\\x17",
|
||||
"\\x18", "\\x19", "\\x1a", "\\e", "\\x1c", "\\x1d", "\\x1e", "\\x1f",
|
||||
"\\N", "\\_", "\\L", "\\P");
|
||||
|
||||
/**
|
||||
* Determines if a PHP value would require double quoting in YAML.
|
||||
*
|
||||
* @param string $value A PHP value
|
||||
*
|
||||
* @return Boolean True if the value would require double quotes.
|
||||
*/
|
||||
static public function requiresDoubleQuoting($value)
|
||||
{
|
||||
return preg_match('/'.self::REGEX_CHARACTER_TO_ESCAPE.'/u', $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes and surrounds a PHP value with double quotes.
|
||||
*
|
||||
* @param string $value A PHP value
|
||||
*
|
||||
* @return string The quoted, escaped string
|
||||
*/
|
||||
static public function escapeWithDoubleQuotes($value)
|
||||
{
|
||||
return sprintf('"%s"', str_replace(self::$escapees, self::$escaped, $value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a PHP value would require single quoting in YAML.
|
||||
*
|
||||
* @param string $value A PHP value
|
||||
*
|
||||
* @return Boolean True if the value would require single quotes.
|
||||
*/
|
||||
static public function requiresSingleQuoting($value)
|
||||
{
|
||||
return preg_match('/[ \s \' " \: \{ \} \[ \] , & \* \# \?] | \A[ - ? | < > = ! % @ ` ]/x', $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes and surrounds a PHP value with single quotes.
|
||||
*
|
||||
* @param string $value A PHP value
|
||||
*
|
||||
* @return string The quoted, escaped string
|
||||
*/
|
||||
static public function escapeWithSingleQuotes($value)
|
||||
{
|
||||
return sprintf("'%s'", str_replace('\'', '\'\'', $value));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Yaml\Exception;
|
||||
|
||||
/**
|
||||
* Exception class thrown when an error occurs during dumping.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class DumpException extends \RuntimeException implements ExceptionInterface
|
||||
{
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Yaml\Exception;
|
||||
|
||||
/**
|
||||
* Exception interface for all exceptions thrown by the component.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
interface ExceptionInterface
|
||||
{
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Yaml\Exception;
|
||||
|
||||
/**
|
||||
* Exception class thrown when an error occurs during parsing.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class ParseException extends \RuntimeException implements ExceptionInterface
|
||||
{
|
||||
private $parsedFile;
|
||||
private $parsedLine;
|
||||
private $snippet;
|
||||
private $rawMessage;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $message The error message
|
||||
* @param integer $parsedLine The line where the error occurred
|
||||
* @param integer $snippet The snippet of code near the problem
|
||||
* @param string $parsedFile The file name where the error occurred
|
||||
* @param Exception $previous The previous exception
|
||||
*/
|
||||
public function __construct($message, $parsedLine = -1, $snippet = null, $parsedFile = null, Exception $previous = null)
|
||||
{
|
||||
$this->parsedFile = $parsedFile;
|
||||
$this->parsedLine = $parsedLine;
|
||||
$this->snippet = $snippet;
|
||||
$this->rawMessage = $message;
|
||||
|
||||
$this->updateRepr();
|
||||
|
||||
parent::__construct($this->message, 0, $previous);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the snippet of code near the error.
|
||||
*
|
||||
* @return string The snippet of code
|
||||
*/
|
||||
public function getSnippet()
|
||||
{
|
||||
return $this->snippet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the snippet of code near the error.
|
||||
*
|
||||
* @param string $snippet The code snippet
|
||||
*/
|
||||
public function setSnippet($snippet)
|
||||
{
|
||||
$this->snippet = $snippet;
|
||||
|
||||
$this->updateRepr();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the filename where the error occurred.
|
||||
*
|
||||
* This method returns null if a string is parsed.
|
||||
*
|
||||
* @return string The filename
|
||||
*/
|
||||
public function getParsedFile()
|
||||
{
|
||||
return $this->parsedFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the filename where the error occurred.
|
||||
*
|
||||
* @param string $parsedFile The filename
|
||||
*/
|
||||
public function setParsedFile($parsedFile)
|
||||
{
|
||||
$this->parsedFile = $parsedFile;
|
||||
|
||||
$this->updateRepr();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the line where the error occurred.
|
||||
*
|
||||
* @return integer The file line
|
||||
*/
|
||||
public function getParsedLine()
|
||||
{
|
||||
return $this->parsedLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the line where the error occurred.
|
||||
*
|
||||
* @param integer $parsedLine The file line
|
||||
*/
|
||||
public function setParsedLine($parsedLine)
|
||||
{
|
||||
$this->parsedLine = $parsedLine;
|
||||
|
||||
$this->updateRepr();
|
||||
}
|
||||
|
||||
private function updateRepr()
|
||||
{
|
||||
$this->message = $this->rawMessage;
|
||||
|
||||
$dot = false;
|
||||
if ('.' === substr($this->message, -1)) {
|
||||
$this->message = substr($this->message, 0, -1);
|
||||
$dot = true;
|
||||
}
|
||||
|
||||
if (null !== $this->parsedFile) {
|
||||
$this->message .= sprintf(' in %s', json_encode($this->parsedFile));
|
||||
}
|
||||
|
||||
if ($this->parsedLine >= 0) {
|
||||
$this->message .= sprintf(' at line %d', $this->parsedLine);
|
||||
}
|
||||
|
||||
if ($this->snippet) {
|
||||
$this->message .= sprintf(' (near "%s")', $this->snippet);
|
||||
}
|
||||
|
||||
if ($dot) {
|
||||
$this->message .= '.';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,406 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Yaml;
|
||||
|
||||
use Symfony\Component\Yaml\Exception\ParseException;
|
||||
use Symfony\Component\Yaml\Exception\DumpException;
|
||||
|
||||
/**
|
||||
* Inline implements a YAML parser/dumper for the YAML inline syntax.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class Inline
|
||||
{
|
||||
const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\']*(?:\'\'[^\']*)*)\')';
|
||||
|
||||
/**
|
||||
* Converts a YAML string to a PHP array.
|
||||
*
|
||||
* @param string $value A YAML string
|
||||
*
|
||||
* @return array A PHP array representing the YAML string
|
||||
*/
|
||||
static public function parse($value)
|
||||
{
|
||||
$value = trim($value);
|
||||
|
||||
if (0 == strlen($value)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) {
|
||||
$mbEncoding = mb_internal_encoding();
|
||||
mb_internal_encoding('ASCII');
|
||||
}
|
||||
|
||||
switch ($value[0]) {
|
||||
case '[':
|
||||
$result = self::parseSequence($value);
|
||||
break;
|
||||
case '{':
|
||||
$result = self::parseMapping($value);
|
||||
break;
|
||||
default:
|
||||
$result = self::parseScalar($value);
|
||||
}
|
||||
|
||||
if (isset($mbEncoding)) {
|
||||
mb_internal_encoding($mbEncoding);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps a given PHP variable to a YAML string.
|
||||
*
|
||||
* @param mixed $value The PHP variable to convert
|
||||
*
|
||||
* @return string The YAML string representing the PHP array
|
||||
*
|
||||
* @throws DumpException When trying to dump PHP resource
|
||||
*/
|
||||
static public function dump($value)
|
||||
{
|
||||
switch (true) {
|
||||
case is_resource($value):
|
||||
throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value)));
|
||||
case is_object($value):
|
||||
return '!!php/object:'.serialize($value);
|
||||
case is_array($value):
|
||||
return self::dumpArray($value);
|
||||
case null === $value:
|
||||
return 'null';
|
||||
case true === $value:
|
||||
return 'true';
|
||||
case false === $value:
|
||||
return 'false';
|
||||
case ctype_digit($value):
|
||||
return is_string($value) ? "'$value'" : (int) $value;
|
||||
case is_numeric($value):
|
||||
$locale = setlocale(LC_NUMERIC, 0);
|
||||
if (false !== $locale) {
|
||||
setlocale(LC_NUMERIC, 'C');
|
||||
}
|
||||
$repr = is_string($value) ? "'$value'" : (is_infinite($value) ? str_ireplace('INF', '.Inf', strval($value)) : strval($value));
|
||||
|
||||
if (false !== $locale) {
|
||||
setlocale(LC_NUMERIC, $locale);
|
||||
}
|
||||
|
||||
return $repr;
|
||||
case Escaper::requiresDoubleQuoting($value):
|
||||
return Escaper::escapeWithDoubleQuotes($value);
|
||||
case Escaper::requiresSingleQuoting($value):
|
||||
return Escaper::escapeWithSingleQuotes($value);
|
||||
case '' == $value:
|
||||
return "''";
|
||||
case preg_match(self::getTimestampRegex(), $value):
|
||||
case in_array(strtolower($value), array('null', '~', 'true', 'false')):
|
||||
return "'$value'";
|
||||
default:
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps a PHP array to a YAML string.
|
||||
*
|
||||
* @param array $value The PHP array to dump
|
||||
*
|
||||
* @return string The YAML string representing the PHP array
|
||||
*/
|
||||
static private function dumpArray($value)
|
||||
{
|
||||
// array
|
||||
$keys = array_keys($value);
|
||||
if ((1 == count($keys) && '0' == $keys[0])
|
||||
|| (count($keys) > 1 && array_reduce($keys, function ($v, $w) { return (integer) $v + $w; }, 0) == count($keys) * (count($keys) - 1) / 2)
|
||||
) {
|
||||
$output = array();
|
||||
foreach ($value as $val) {
|
||||
$output[] = self::dump($val);
|
||||
}
|
||||
|
||||
return sprintf('[%s]', implode(', ', $output));
|
||||
}
|
||||
|
||||
// mapping
|
||||
$output = array();
|
||||
foreach ($value as $key => $val) {
|
||||
$output[] = sprintf('%s: %s', self::dump($key), self::dump($val));
|
||||
}
|
||||
|
||||
return sprintf('{ %s }', implode(', ', $output));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a scalar to a YAML string.
|
||||
*
|
||||
* @param scalar $scalar
|
||||
* @param string $delimiters
|
||||
* @param array $stringDelimiters
|
||||
* @param integer &$i
|
||||
* @param Boolean $evaluate
|
||||
*
|
||||
* @return string A YAML string
|
||||
*
|
||||
* @throws ParseException When malformed inline YAML string is parsed
|
||||
*/
|
||||
static public function parseScalar($scalar, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true)
|
||||
{
|
||||
if (in_array($scalar[$i], $stringDelimiters)) {
|
||||
// quoted scalar
|
||||
$output = self::parseQuotedScalar($scalar, $i);
|
||||
} else {
|
||||
// "normal" string
|
||||
if (!$delimiters) {
|
||||
$output = substr($scalar, $i);
|
||||
$i += strlen($output);
|
||||
|
||||
// remove comments
|
||||
if (false !== $strpos = strpos($output, ' #')) {
|
||||
$output = rtrim(substr($output, 0, $strpos));
|
||||
}
|
||||
} elseif (preg_match('/^(.+?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) {
|
||||
$output = $match[1];
|
||||
$i += strlen($output);
|
||||
} else {
|
||||
throw new ParseException(sprintf('Malformed inline YAML string (%s).', $scalar));
|
||||
}
|
||||
|
||||
$output = $evaluate ? self::evaluateScalar($output) : $output;
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a quoted scalar to YAML.
|
||||
*
|
||||
* @param string $scalar
|
||||
* @param integer &$i
|
||||
*
|
||||
* @return string A YAML string
|
||||
*
|
||||
* @throws ParseException When malformed inline YAML string is parsed
|
||||
*/
|
||||
static private function parseQuotedScalar($scalar, &$i)
|
||||
{
|
||||
if (!preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) {
|
||||
throw new ParseException(sprintf('Malformed inline YAML string (%s).', substr($scalar, $i)));
|
||||
}
|
||||
|
||||
$output = substr($match[0], 1, strlen($match[0]) - 2);
|
||||
|
||||
$unescaper = new Unescaper();
|
||||
if ('"' == $scalar[$i]) {
|
||||
$output = $unescaper->unescapeDoubleQuotedString($output);
|
||||
} else {
|
||||
$output = $unescaper->unescapeSingleQuotedString($output);
|
||||
}
|
||||
|
||||
$i += strlen($match[0]);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a sequence to a YAML string.
|
||||
*
|
||||
* @param string $sequence
|
||||
* @param integer &$i
|
||||
*
|
||||
* @return string A YAML string
|
||||
*
|
||||
* @throws ParseException When malformed inline YAML string is parsed
|
||||
*/
|
||||
static private function parseSequence($sequence, &$i = 0)
|
||||
{
|
||||
$output = array();
|
||||
$len = strlen($sequence);
|
||||
$i += 1;
|
||||
|
||||
// [foo, bar, ...]
|
||||
while ($i < $len) {
|
||||
switch ($sequence[$i]) {
|
||||
case '[':
|
||||
// nested sequence
|
||||
$output[] = self::parseSequence($sequence, $i);
|
||||
break;
|
||||
case '{':
|
||||
// nested mapping
|
||||
$output[] = self::parseMapping($sequence, $i);
|
||||
break;
|
||||
case ']':
|
||||
return $output;
|
||||
case ',':
|
||||
case ' ':
|
||||
break;
|
||||
default:
|
||||
$isQuoted = in_array($sequence[$i], array('"', "'"));
|
||||
$value = self::parseScalar($sequence, array(',', ']'), array('"', "'"), $i);
|
||||
|
||||
if (!$isQuoted && false !== strpos($value, ': ')) {
|
||||
// embedded mapping?
|
||||
try {
|
||||
$value = self::parseMapping('{'.$value.'}');
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
// no, it's not
|
||||
}
|
||||
}
|
||||
|
||||
$output[] = $value;
|
||||
|
||||
--$i;
|
||||
}
|
||||
|
||||
++$i;
|
||||
}
|
||||
|
||||
throw new ParseException(sprintf('Malformed inline YAML string %s', $sequence));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a mapping to a YAML string.
|
||||
*
|
||||
* @param string $mapping
|
||||
* @param integer &$i
|
||||
*
|
||||
* @return string A YAML string
|
||||
*
|
||||
* @throws ParseException When malformed inline YAML string is parsed
|
||||
*/
|
||||
static private function parseMapping($mapping, &$i = 0)
|
||||
{
|
||||
$output = array();
|
||||
$len = strlen($mapping);
|
||||
$i += 1;
|
||||
|
||||
// {foo: bar, bar:foo, ...}
|
||||
while ($i < $len) {
|
||||
switch ($mapping[$i]) {
|
||||
case ' ':
|
||||
case ',':
|
||||
++$i;
|
||||
continue 2;
|
||||
case '}':
|
||||
return $output;
|
||||
}
|
||||
|
||||
// key
|
||||
$key = self::parseScalar($mapping, array(':', ' '), array('"', "'"), $i, false);
|
||||
|
||||
// value
|
||||
$done = false;
|
||||
while ($i < $len) {
|
||||
switch ($mapping[$i]) {
|
||||
case '[':
|
||||
// nested sequence
|
||||
$output[$key] = self::parseSequence($mapping, $i);
|
||||
$done = true;
|
||||
break;
|
||||
case '{':
|
||||
// nested mapping
|
||||
$output[$key] = self::parseMapping($mapping, $i);
|
||||
$done = true;
|
||||
break;
|
||||
case ':':
|
||||
case ' ':
|
||||
break;
|
||||
default:
|
||||
$output[$key] = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i);
|
||||
$done = true;
|
||||
--$i;
|
||||
}
|
||||
|
||||
++$i;
|
||||
|
||||
if ($done) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new ParseException(sprintf('Malformed inline YAML string %s', $mapping));
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates scalars and replaces magic values.
|
||||
*
|
||||
* @param string $scalar
|
||||
*
|
||||
* @return string A YAML string
|
||||
*/
|
||||
static private function evaluateScalar($scalar)
|
||||
{
|
||||
$scalar = trim($scalar);
|
||||
|
||||
switch (true) {
|
||||
case 'null' == strtolower($scalar):
|
||||
case '' == $scalar:
|
||||
case '~' == $scalar:
|
||||
return null;
|
||||
case 0 === strpos($scalar, '!str'):
|
||||
return (string) substr($scalar, 5);
|
||||
case 0 === strpos($scalar, '! '):
|
||||
return intval(self::parseScalar(substr($scalar, 2)));
|
||||
case 0 === strpos($scalar, '!!php/object:'):
|
||||
return unserialize(substr($scalar, 13));
|
||||
case ctype_digit($scalar):
|
||||
$raw = $scalar;
|
||||
$cast = intval($scalar);
|
||||
|
||||
return '0' == $scalar[0] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw);
|
||||
case 'true' === strtolower($scalar):
|
||||
return true;
|
||||
case 'false' === strtolower($scalar):
|
||||
return false;
|
||||
case is_numeric($scalar):
|
||||
return '0x' == $scalar[0].$scalar[1] ? hexdec($scalar) : floatval($scalar);
|
||||
case 0 == strcasecmp($scalar, '.inf'):
|
||||
case 0 == strcasecmp($scalar, '.NaN'):
|
||||
return -log(0);
|
||||
case 0 == strcasecmp($scalar, '-.inf'):
|
||||
return log(0);
|
||||
case preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar):
|
||||
return floatval(str_replace(',', '', $scalar));
|
||||
case preg_match(self::getTimestampRegex(), $scalar):
|
||||
return strtotime($scalar);
|
||||
default:
|
||||
return (string) $scalar;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a regex that matches an unix timestamp
|
||||
*
|
||||
* @return string The regular expression
|
||||
*/
|
||||
static private function getTimestampRegex()
|
||||
{
|
||||
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
|
||||
EOF;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2004-2012 Fabien Potencier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,556 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Yaml;
|
||||
|
||||
use Symfony\Component\Yaml\Exception\ParseException;
|
||||
|
||||
/**
|
||||
* Parser parses YAML strings to convert them to PHP arrays.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class Parser
|
||||
{
|
||||
private $offset = 0;
|
||||
private $lines = array();
|
||||
private $currentLineNb = -1;
|
||||
private $currentLine = '';
|
||||
private $refs = array();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param integer $offset The offset of YAML document (used for line numbers in error messages)
|
||||
*/
|
||||
public function __construct($offset = 0)
|
||||
{
|
||||
$this->offset = $offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a YAML string to a PHP value.
|
||||
*
|
||||
* @param string $value A YAML string
|
||||
*
|
||||
* @return mixed A PHP value
|
||||
*
|
||||
* @throws ParseException If the YAML is not valid
|
||||
*/
|
||||
public function parse($value)
|
||||
{
|
||||
$this->currentLineNb = -1;
|
||||
$this->currentLine = '';
|
||||
$this->lines = explode("\n", $this->cleanup($value));
|
||||
|
||||
if (function_exists('mb_detect_encoding') && false === mb_detect_encoding($value, 'UTF-8', true)) {
|
||||
throw new ParseException('The YAML value does not appear to be valid UTF-8.');
|
||||
}
|
||||
|
||||
if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) {
|
||||
$mbEncoding = mb_internal_encoding();
|
||||
mb_internal_encoding('UTF-8');
|
||||
}
|
||||
|
||||
$data = array();
|
||||
while ($this->moveToNextLine()) {
|
||||
if ($this->isCurrentLineEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// tab?
|
||||
if ("\t" === $this->currentLine[0]) {
|
||||
throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
|
||||
}
|
||||
|
||||
$isRef = $isInPlace = $isProcessed = false;
|
||||
if (preg_match('#^\-((?P<leadspaces>\s+)(?P<value>.+?))?\s*$#u', $this->currentLine, $values)) {
|
||||
if (isset($values['value']) && preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
|
||||
$isRef = $matches['ref'];
|
||||
$values['value'] = $matches['value'];
|
||||
}
|
||||
|
||||
// array
|
||||
if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
|
||||
$c = $this->getRealCurrentLineNb() + 1;
|
||||
$parser = new Parser($c);
|
||||
$parser->refs =& $this->refs;
|
||||
$data[] = $parser->parse($this->getNextEmbedBlock());
|
||||
} else {
|
||||
if (isset($values['leadspaces'])
|
||||
&& ' ' == $values['leadspaces']
|
||||
&& preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P<value>.+?))?\s*$#u', $values['value'], $matches)
|
||||
) {
|
||||
// this is a compact notation element, add to next block and parse
|
||||
$c = $this->getRealCurrentLineNb();
|
||||
$parser = new Parser($c);
|
||||
$parser->refs =& $this->refs;
|
||||
|
||||
$block = $values['value'];
|
||||
if (!$this->isNextLineIndented()) {
|
||||
$block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + 2);
|
||||
}
|
||||
|
||||
$data[] = $parser->parse($block);
|
||||
} else {
|
||||
$data[] = $this->parseValue($values['value']);
|
||||
}
|
||||
}
|
||||
} elseif (preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\[\{].*?) *\:(\s+(?P<value>.+?))?\s*$#u', $this->currentLine, $values)) {
|
||||
try {
|
||||
$key = Inline::parseScalar($values['key']);
|
||||
} catch (ParseException $e) {
|
||||
$e->setParsedLine($this->getRealCurrentLineNb() + 1);
|
||||
$e->setSnippet($this->currentLine);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
if ('<<' === $key) {
|
||||
if (isset($values['value']) && 0 === strpos($values['value'], '*')) {
|
||||
$isInPlace = substr($values['value'], 1);
|
||||
if (!array_key_exists($isInPlace, $this->refs)) {
|
||||
throw new ParseException(sprintf('Reference "%s" does not exist.', $isInPlace), $this->getRealCurrentLineNb() + 1, $this->currentLine);
|
||||
}
|
||||
} else {
|
||||
if (isset($values['value']) && $values['value'] !== '') {
|
||||
$value = $values['value'];
|
||||
} else {
|
||||
$value = $this->getNextEmbedBlock();
|
||||
}
|
||||
$c = $this->getRealCurrentLineNb() + 1;
|
||||
$parser = new Parser($c);
|
||||
$parser->refs =& $this->refs;
|
||||
$parsed = $parser->parse($value);
|
||||
|
||||
$merged = array();
|
||||
if (!is_array($parsed)) {
|
||||
throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
|
||||
} elseif (isset($parsed[0])) {
|
||||
// Numeric array, merge individual elements
|
||||
foreach (array_reverse($parsed) as $parsedItem) {
|
||||
if (!is_array($parsedItem)) {
|
||||
throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem);
|
||||
}
|
||||
$merged = array_merge($parsedItem, $merged);
|
||||
}
|
||||
} else {
|
||||
// Associative array, merge
|
||||
$merged = array_merge($merged, $parsed);
|
||||
}
|
||||
|
||||
$isProcessed = $merged;
|
||||
}
|
||||
} elseif (isset($values['value']) && preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
|
||||
$isRef = $matches['ref'];
|
||||
$values['value'] = $matches['value'];
|
||||
}
|
||||
|
||||
if ($isProcessed) {
|
||||
// Merge keys
|
||||
$data = $isProcessed;
|
||||
// hash
|
||||
} elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
|
||||
// if next line is less indented or equal, then it means that the current value is null
|
||||
if ($this->isNextLineIndented()) {
|
||||
$data[$key] = null;
|
||||
} else {
|
||||
$c = $this->getRealCurrentLineNb() + 1;
|
||||
$parser = new Parser($c);
|
||||
$parser->refs =& $this->refs;
|
||||
$data[$key] = $parser->parse($this->getNextEmbedBlock());
|
||||
}
|
||||
} else {
|
||||
if ($isInPlace) {
|
||||
$data = $this->refs[$isInPlace];
|
||||
} else {
|
||||
$data[$key] = $this->parseValue($values['value']);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 1-liner followed by newline
|
||||
if (2 == count($this->lines) && empty($this->lines[1])) {
|
||||
try {
|
||||
$value = Inline::parse($this->lines[0]);
|
||||
} catch (ParseException $e) {
|
||||
$e->setParsedLine($this->getRealCurrentLineNb() + 1);
|
||||
$e->setSnippet($this->currentLine);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
$first = reset($value);
|
||||
if (is_string($first) && 0 === strpos($first, '*')) {
|
||||
$data = array();
|
||||
foreach ($value as $alias) {
|
||||
$data[] = $this->refs[substr($alias, 1)];
|
||||
}
|
||||
$value = $data;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($mbEncoding)) {
|
||||
mb_internal_encoding($mbEncoding);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
switch (preg_last_error()) {
|
||||
case PREG_INTERNAL_ERROR:
|
||||
$error = 'Internal PCRE error.';
|
||||
break;
|
||||
case PREG_BACKTRACK_LIMIT_ERROR:
|
||||
$error = 'pcre.backtrack_limit reached.';
|
||||
break;
|
||||
case PREG_RECURSION_LIMIT_ERROR:
|
||||
$error = 'pcre.recursion_limit reached.';
|
||||
break;
|
||||
case PREG_BAD_UTF8_ERROR:
|
||||
$error = 'Malformed UTF-8 data.';
|
||||
break;
|
||||
case PREG_BAD_UTF8_OFFSET_ERROR:
|
||||
$error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.';
|
||||
break;
|
||||
default:
|
||||
$error = 'Unable to parse.';
|
||||
}
|
||||
|
||||
throw new ParseException($error, $this->getRealCurrentLineNb() + 1, $this->currentLine);
|
||||
}
|
||||
|
||||
if ($isRef) {
|
||||
$this->refs[$isRef] = end($data);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($mbEncoding)) {
|
||||
mb_internal_encoding($mbEncoding);
|
||||
}
|
||||
|
||||
return empty($data) ? null : $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current line number (takes the offset into account).
|
||||
*
|
||||
* @return integer The current line number
|
||||
*/
|
||||
private function getRealCurrentLineNb()
|
||||
{
|
||||
return $this->currentLineNb + $this->offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current line indentation.
|
||||
*
|
||||
* @return integer The current line indentation
|
||||
*/
|
||||
private function getCurrentLineIndentation()
|
||||
{
|
||||
return strlen($this->currentLine) - strlen(ltrim($this->currentLine, ' '));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next embed block of YAML.
|
||||
*
|
||||
* @param integer $indentation The indent level at which the block is to be read, or null for default
|
||||
*
|
||||
* @return string A YAML string
|
||||
*
|
||||
* @throws ParseException When indentation problem are detected
|
||||
*/
|
||||
private function getNextEmbedBlock($indentation = null)
|
||||
{
|
||||
$this->moveToNextLine();
|
||||
|
||||
if (null === $indentation) {
|
||||
$newIndent = $this->getCurrentLineIndentation();
|
||||
|
||||
if (!$this->isCurrentLineEmpty() && 0 == $newIndent) {
|
||||
throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
|
||||
}
|
||||
} else {
|
||||
$newIndent = $indentation;
|
||||
}
|
||||
|
||||
$data = array(substr($this->currentLine, $newIndent));
|
||||
|
||||
while ($this->moveToNextLine()) {
|
||||
if ($this->isCurrentLineEmpty()) {
|
||||
if ($this->isCurrentLineBlank()) {
|
||||
$data[] = substr($this->currentLine, $newIndent);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$indent = $this->getCurrentLineIndentation();
|
||||
|
||||
if (preg_match('#^(?P<text> *)$#', $this->currentLine, $match)) {
|
||||
// empty line
|
||||
$data[] = $match['text'];
|
||||
} elseif ($indent >= $newIndent) {
|
||||
$data[] = substr($this->currentLine, $newIndent);
|
||||
} elseif (0 == $indent) {
|
||||
$this->moveToPreviousLine();
|
||||
|
||||
break;
|
||||
} else {
|
||||
throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
|
||||
}
|
||||
}
|
||||
|
||||
return implode("\n", $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the parser to the next line.
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
private function moveToNextLine()
|
||||
{
|
||||
if ($this->currentLineNb >= count($this->lines) - 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->currentLine = $this->lines[++$this->currentLineNb];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the parser to the previous line.
|
||||
*/
|
||||
private function moveToPreviousLine()
|
||||
{
|
||||
$this->currentLine = $this->lines[--$this->currentLineNb];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a YAML value.
|
||||
*
|
||||
* @param string $value A YAML value
|
||||
*
|
||||
* @return mixed A PHP value
|
||||
*
|
||||
* @throws ParseException When reference does not exist
|
||||
*/
|
||||
private function parseValue($value)
|
||||
{
|
||||
if (0 === strpos($value, '*')) {
|
||||
if (false !== $pos = strpos($value, '#')) {
|
||||
$value = substr($value, 1, $pos - 2);
|
||||
} else {
|
||||
$value = substr($value, 1);
|
||||
}
|
||||
|
||||
if (!array_key_exists($value, $this->refs)) {
|
||||
throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLine);
|
||||
}
|
||||
|
||||
return $this->refs[$value];
|
||||
}
|
||||
|
||||
if (preg_match('/^(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments> +#.*)?$/', $value, $matches)) {
|
||||
$modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : '';
|
||||
|
||||
return $this->parseFoldedScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), intval(abs($modifiers)));
|
||||
}
|
||||
|
||||
try {
|
||||
return Inline::parse($value);
|
||||
} catch (ParseException $e) {
|
||||
$e->setParsedLine($this->getRealCurrentLineNb() + 1);
|
||||
$e->setSnippet($this->currentLine);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a folded scalar.
|
||||
*
|
||||
* @param string $separator The separator that was used to begin this folded scalar (| or >)
|
||||
* @param string $indicator The indicator that was used to begin this folded scalar (+ or -)
|
||||
* @param integer $indentation The indentation that was used to begin this folded scalar
|
||||
*
|
||||
* @return string The text value
|
||||
*/
|
||||
private function parseFoldedScalar($separator, $indicator = '', $indentation = 0)
|
||||
{
|
||||
$separator = '|' == $separator ? "\n" : ' ';
|
||||
$text = '';
|
||||
|
||||
$notEOF = $this->moveToNextLine();
|
||||
|
||||
while ($notEOF && $this->isCurrentLineBlank()) {
|
||||
$text .= "\n";
|
||||
|
||||
$notEOF = $this->moveToNextLine();
|
||||
}
|
||||
|
||||
if (!$notEOF) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!preg_match('#^(?P<indent>'.($indentation ? str_repeat(' ', $indentation) : ' +').')(?P<text>.*)$#u', $this->currentLine, $matches)) {
|
||||
$this->moveToPreviousLine();
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
$textIndent = $matches['indent'];
|
||||
$previousIndent = 0;
|
||||
|
||||
$text .= $matches['text'].$separator;
|
||||
while ($this->currentLineNb + 1 < count($this->lines)) {
|
||||
$this->moveToNextLine();
|
||||
|
||||
if (preg_match('#^(?P<indent> {'.strlen($textIndent).',})(?P<text>.+)$#u', $this->currentLine, $matches)) {
|
||||
if (' ' == $separator && $previousIndent != $matches['indent']) {
|
||||
$text = substr($text, 0, -1)."\n";
|
||||
}
|
||||
$previousIndent = $matches['indent'];
|
||||
|
||||
$text .= str_repeat(' ', $diff = strlen($matches['indent']) - strlen($textIndent)).$matches['text'].($diff ? "\n" : $separator);
|
||||
} elseif (preg_match('#^(?P<text> *)$#', $this->currentLine, $matches)) {
|
||||
$text .= preg_replace('#^ {1,'.strlen($textIndent).'}#', '', $matches['text'])."\n";
|
||||
} else {
|
||||
$this->moveToPreviousLine();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (' ' == $separator) {
|
||||
// replace last separator by a newline
|
||||
$text = preg_replace('/ (\n*)$/', "\n$1", $text);
|
||||
}
|
||||
|
||||
switch ($indicator) {
|
||||
case '':
|
||||
$text = preg_replace('#\n+$#s', "\n", $text);
|
||||
break;
|
||||
case '+':
|
||||
break;
|
||||
case '-':
|
||||
$text = preg_replace('#\n+$#s', '', $text);
|
||||
break;
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the next line is indented.
|
||||
*
|
||||
* @return Boolean Returns true if the next line is indented, false otherwise
|
||||
*/
|
||||
private function isNextLineIndented()
|
||||
{
|
||||
$currentIndentation = $this->getCurrentLineIndentation();
|
||||
$notEOF = $this->moveToNextLine();
|
||||
|
||||
while ($notEOF && $this->isCurrentLineEmpty()) {
|
||||
$notEOF = $this->moveToNextLine();
|
||||
}
|
||||
|
||||
if (false === $notEOF) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$ret = false;
|
||||
if ($this->getCurrentLineIndentation() <= $currentIndentation) {
|
||||
$ret = true;
|
||||
}
|
||||
|
||||
$this->moveToPreviousLine();
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the current line is blank or if it is a comment line.
|
||||
*
|
||||
* @return Boolean Returns true if the current line is empty or if it is a comment line, false otherwise
|
||||
*/
|
||||
private function isCurrentLineEmpty()
|
||||
{
|
||||
return $this->isCurrentLineBlank() || $this->isCurrentLineComment();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the current line is blank.
|
||||
*
|
||||
* @return Boolean Returns true if the current line is blank, false otherwise
|
||||
*/
|
||||
private function isCurrentLineBlank()
|
||||
{
|
||||
return '' == trim($this->currentLine, ' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the current line is a comment line.
|
||||
*
|
||||
* @return Boolean Returns true if the current line is a comment line, false otherwise
|
||||
*/
|
||||
private function isCurrentLineComment()
|
||||
{
|
||||
//checking explicitly the first char of the trim is faster than loops or strpos
|
||||
$ltrimmedLine = ltrim($this->currentLine, ' ');
|
||||
|
||||
return $ltrimmedLine[0] === '#';
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanups a YAML string to be parsed.
|
||||
*
|
||||
* @param string $value The input YAML string
|
||||
*
|
||||
* @return string A cleaned up YAML string
|
||||
*/
|
||||
private function cleanup($value)
|
||||
{
|
||||
$value = str_replace(array("\r\n", "\r"), "\n", $value);
|
||||
|
||||
if (!preg_match("#\n$#", $value)) {
|
||||
$value .= "\n";
|
||||
}
|
||||
|
||||
// strip YAML header
|
||||
$count = 0;
|
||||
$value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#su', '', $value, -1, $count);
|
||||
$this->offset += $count;
|
||||
|
||||
// remove leading comments
|
||||
$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 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 (...)
|
||||
$value = preg_replace('#\.\.\.\s*$#s', '', $value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
Yaml Component
|
||||
==============
|
||||
|
||||
YAML implements most of the YAML 1.2 specification.
|
||||
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
$array = Yaml::parse($file);
|
||||
|
||||
print Yaml::dump($array);
|
||||
|
||||
Resources
|
||||
---------
|
||||
|
||||
You can run the unit tests with the following command:
|
||||
|
||||
phpunit
|
|
@ -0,0 +1,166 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Yaml\Tests;
|
||||
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Symfony\Component\Yaml\Parser;
|
||||
use Symfony\Component\Yaml\Dumper;
|
||||
|
||||
class DumperTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
protected $parser;
|
||||
protected $dumper;
|
||||
protected $path;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->parser = new Parser();
|
||||
$this->dumper = new Dumper();
|
||||
$this->path = __DIR__.'/Fixtures';
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
$this->parser = null;
|
||||
$this->dumper = null;
|
||||
$this->path = null;
|
||||
}
|
||||
|
||||
public function testSpecifications()
|
||||
{
|
||||
$files = $this->parser->parse(file_get_contents($this->path.'/index.yml'));
|
||||
foreach ($files as $file) {
|
||||
$yamls = file_get_contents($this->path.'/'.$file.'.yml');
|
||||
|
||||
// split YAMLs documents
|
||||
foreach (preg_split('/^---( %YAML\:1\.0)?/m', $yamls) as $yaml) {
|
||||
if (!$yaml) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$test = $this->parser->parse($yaml);
|
||||
if (isset($test['dump_skip']) && $test['dump_skip']) {
|
||||
continue;
|
||||
} elseif (isset($test['todo']) && $test['todo']) {
|
||||
// TODO
|
||||
} else {
|
||||
$expected = eval('return '.trim($test['php']).';');
|
||||
|
||||
$this->assertEquals($expected, $this->parser->parse($this->dumper->dump($expected, 10)), $test['test']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function testInlineLevel()
|
||||
{
|
||||
// inline level
|
||||
$array = array(
|
||||
'' => 'bar',
|
||||
'foo' => '#bar',
|
||||
'foo\'bar' => array(),
|
||||
'bar' => array(1, 'foo'),
|
||||
'foobar' => array(
|
||||
'foo' => 'bar',
|
||||
'bar' => array(1, 'foo'),
|
||||
'foobar' => array(
|
||||
'foo' => 'bar',
|
||||
'bar' => array(1, 'foo'),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$expected = <<<EOF
|
||||
{ '': bar, foo: '#bar', 'foo''bar': { }, bar: [1, foo], foobar: { foo: bar, bar: [1, foo], foobar: { foo: bar, bar: [1, foo] } } }
|
||||
EOF;
|
||||
$this->assertEquals($expected, $this->dumper->dump($array, -10), '->dump() takes an inline level argument');
|
||||
$this->assertEquals($expected, $this->dumper->dump($array, 0), '->dump() takes an inline level argument');
|
||||
|
||||
$expected = <<<EOF
|
||||
'': bar
|
||||
foo: '#bar'
|
||||
'foo''bar': { }
|
||||
bar: [1, foo]
|
||||
foobar: { foo: bar, bar: [1, foo], foobar: { foo: bar, bar: [1, foo] } }
|
||||
|
||||
EOF;
|
||||
$this->assertEquals($expected, $this->dumper->dump($array, 1), '->dump() takes an inline level argument');
|
||||
|
||||
$expected = <<<EOF
|
||||
'': bar
|
||||
foo: '#bar'
|
||||
'foo''bar': { }
|
||||
bar:
|
||||
- 1
|
||||
- foo
|
||||
foobar:
|
||||
foo: bar
|
||||
bar: [1, foo]
|
||||
foobar: { foo: bar, bar: [1, foo] }
|
||||
|
||||
EOF;
|
||||
$this->assertEquals($expected, $this->dumper->dump($array, 2), '->dump() takes an inline level argument');
|
||||
|
||||
$expected = <<<EOF
|
||||
'': bar
|
||||
foo: '#bar'
|
||||
'foo''bar': { }
|
||||
bar:
|
||||
- 1
|
||||
- foo
|
||||
foobar:
|
||||
foo: bar
|
||||
bar:
|
||||
- 1
|
||||
- foo
|
||||
foobar:
|
||||
foo: bar
|
||||
bar: [1, foo]
|
||||
|
||||
EOF;
|
||||
$this->assertEquals($expected, $this->dumper->dump($array, 3), '->dump() takes an inline level argument');
|
||||
|
||||
$expected = <<<EOF
|
||||
'': bar
|
||||
foo: '#bar'
|
||||
'foo''bar': { }
|
||||
bar:
|
||||
- 1
|
||||
- foo
|
||||
foobar:
|
||||
foo: bar
|
||||
bar:
|
||||
- 1
|
||||
- foo
|
||||
foobar:
|
||||
foo: bar
|
||||
bar:
|
||||
- 1
|
||||
- foo
|
||||
|
||||
EOF;
|
||||
$this->assertEquals($expected, $this->dumper->dump($array, 4), '->dump() takes an inline level argument');
|
||||
$this->assertEquals($expected, $this->dumper->dump($array, 10), '->dump() takes an inline level argument');
|
||||
}
|
||||
|
||||
public function testObjectsSupport()
|
||||
{
|
||||
$a = array('foo' => new A(), 'bar' => 1);
|
||||
|
||||
$this->assertEquals('{ foo: !!php/object:O:30:"Symfony\Component\Yaml\Tests\A":1:{s:1:"a";s:3:"foo";}, bar: 1 }', $this->dumper->dump($a), '->dump() is able to dump objects');
|
||||
}
|
||||
}
|
||||
|
||||
class A
|
||||
{
|
||||
public $a = 'foo';
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
--- %YAML:1.0
|
||||
test: Simple Alias Example
|
||||
brief: >
|
||||
If you need to refer to the same item of data twice,
|
||||
you can give that item an alias. The alias is a plain
|
||||
string, starting with an ampersand. The item may then
|
||||
be referred to by the alias throughout your document
|
||||
by using an asterisk before the name of the alias.
|
||||
This is called an anchor.
|
||||
yaml: |
|
||||
- &showell Steve
|
||||
- Clark
|
||||
- Brian
|
||||
- Oren
|
||||
- *showell
|
||||
php: |
|
||||
array('Steve', 'Clark', 'Brian', 'Oren', 'Steve')
|
||||
|
||||
---
|
||||
test: Alias of a Mapping
|
||||
brief: >
|
||||
An alias can be used on any item of data, including
|
||||
sequences, mappings, and other complex data types.
|
||||
yaml: |
|
||||
- &hello
|
||||
Meat: pork
|
||||
Starch: potato
|
||||
- banana
|
||||
- *hello
|
||||
php: |
|
||||
array(array('Meat'=>'pork', 'Starch'=>'potato'), 'banana', array('Meat'=>'pork', 'Starch'=>'potato'))
|
|
@ -0,0 +1,178 @@
|
|||
--- %YAML:1.0
|
||||
test: Simple Sequence
|
||||
brief: |
|
||||
You can specify a list in YAML by placing each
|
||||
member of the list on a new line with an opening
|
||||
dash. These lists are called sequences.
|
||||
yaml: |
|
||||
- apple
|
||||
- banana
|
||||
- carrot
|
||||
php: |
|
||||
array('apple', 'banana', 'carrot')
|
||||
---
|
||||
test: Nested Sequences
|
||||
brief: |
|
||||
You can include a sequence within another
|
||||
sequence by giving the sequence an empty
|
||||
dash, followed by an indented list.
|
||||
yaml: |
|
||||
-
|
||||
- foo
|
||||
- bar
|
||||
- baz
|
||||
php: |
|
||||
array(array('foo', 'bar', 'baz'))
|
||||
---
|
||||
test: Mixed Sequences
|
||||
brief: |
|
||||
Sequences can contain any YAML data,
|
||||
including strings and other sequences.
|
||||
yaml: |
|
||||
- apple
|
||||
-
|
||||
- foo
|
||||
- bar
|
||||
- x123
|
||||
- banana
|
||||
- carrot
|
||||
php: |
|
||||
array('apple', array('foo', 'bar', 'x123'), 'banana', 'carrot')
|
||||
---
|
||||
test: Deeply Nested Sequences
|
||||
brief: |
|
||||
Sequences can be nested even deeper, with each
|
||||
level of indentation representing a level of
|
||||
depth.
|
||||
yaml: |
|
||||
-
|
||||
-
|
||||
- uno
|
||||
- dos
|
||||
php: |
|
||||
array(array(array('uno', 'dos')))
|
||||
---
|
||||
test: Simple Mapping
|
||||
brief: |
|
||||
You can add a keyed list (also known as a dictionary or
|
||||
hash) to your document by placing each member of the
|
||||
list on a new line, with a colon seperating the key
|
||||
from its value. In YAML, this type of list is called
|
||||
a mapping.
|
||||
yaml: |
|
||||
foo: whatever
|
||||
bar: stuff
|
||||
php: |
|
||||
array('foo' => 'whatever', 'bar' => 'stuff')
|
||||
---
|
||||
test: Sequence in a Mapping
|
||||
brief: |
|
||||
A value in a mapping can be a sequence.
|
||||
yaml: |
|
||||
foo: whatever
|
||||
bar:
|
||||
- uno
|
||||
- dos
|
||||
php: |
|
||||
array('foo' => 'whatever', 'bar' => array('uno', 'dos'))
|
||||
---
|
||||
test: Nested Mappings
|
||||
brief: |
|
||||
A value in a mapping can be another mapping.
|
||||
yaml: |
|
||||
foo: whatever
|
||||
bar:
|
||||
fruit: apple
|
||||
name: steve
|
||||
sport: baseball
|
||||
php: |
|
||||
array(
|
||||
'foo' => 'whatever',
|
||||
'bar' => array(
|
||||
'fruit' => 'apple',
|
||||
'name' => 'steve',
|
||||
'sport' => 'baseball'
|
||||
)
|
||||
)
|
||||
---
|
||||
test: Mixed Mapping
|
||||
brief: |
|
||||
A mapping can contain any assortment
|
||||
of mappings and sequences as values.
|
||||
yaml: |
|
||||
foo: whatever
|
||||
bar:
|
||||
-
|
||||
fruit: apple
|
||||
name: steve
|
||||
sport: baseball
|
||||
- more
|
||||
-
|
||||
python: rocks
|
||||
perl: papers
|
||||
ruby: scissorses
|
||||
php: |
|
||||
array(
|
||||
'foo' => 'whatever',
|
||||
'bar' => array(
|
||||
array(
|
||||
'fruit' => 'apple',
|
||||
'name' => 'steve',
|
||||
'sport' => 'baseball'
|
||||
),
|
||||
'more',
|
||||
array(
|
||||
'python' => 'rocks',
|
||||
'perl' => 'papers',
|
||||
'ruby' => 'scissorses'
|
||||
)
|
||||
)
|
||||
)
|
||||
---
|
||||
test: Mapping-in-Sequence Shortcut
|
||||
todo: true
|
||||
brief: |
|
||||
If you are adding a mapping to a sequence, you
|
||||
can place the mapping on the same line as the
|
||||
dash as a shortcut.
|
||||
yaml: |
|
||||
- work on YAML.py:
|
||||
- work on Store
|
||||
php: |
|
||||
array(array('work on YAML.py' => array('work on Store')))
|
||||
---
|
||||
test: Sequence-in-Mapping Shortcut
|
||||
todo: true
|
||||
brief: |
|
||||
The dash in a sequence counts as indentation, so
|
||||
you can add a sequence inside of a mapping without
|
||||
needing spaces as indentation.
|
||||
yaml: |
|
||||
allow:
|
||||
- 'localhost'
|
||||
- '%.sourceforge.net'
|
||||
- '%.freepan.org'
|
||||
php: |
|
||||
array('allow' => array('localhost', '%.sourceforge.net', '%.freepan.org'))
|
||||
---
|
||||
todo: true
|
||||
test: Merge key
|
||||
brief: |
|
||||
A merge key ('<<') can be used in a mapping to insert other mappings. If
|
||||
the value associated with the merge key is a mapping, each of its key/value
|
||||
pairs is inserted into the current mapping.
|
||||
yaml: |
|
||||
mapping:
|
||||
name: Joe
|
||||
job: Accountant
|
||||
<<:
|
||||
age: 38
|
||||
php: |
|
||||
array(
|
||||
'mapping' =>
|
||||
array(
|
||||
'name' => 'Joe',
|
||||
'job' => 'Accountant',
|
||||
'age' => 38
|
||||
)
|
||||
)
|
|
@ -0,0 +1,51 @@
|
|||
---
|
||||
test: One Element Mapping
|
||||
brief: |
|
||||
A mapping with one key/value pair
|
||||
yaml: |
|
||||
foo: bar
|
||||
php: |
|
||||
array('foo' => 'bar')
|
||||
---
|
||||
test: Multi Element Mapping
|
||||
brief: |
|
||||
More than one key/value pair
|
||||
yaml: |
|
||||
red: baron
|
||||
white: walls
|
||||
blue: berries
|
||||
php: |
|
||||
array(
|
||||
'red' => 'baron',
|
||||
'white' => 'walls',
|
||||
'blue' => 'berries',
|
||||
)
|
||||
---
|
||||
test: Values aligned
|
||||
brief: |
|
||||
Often times human editors of documents will align the values even
|
||||
though YAML emitters generally don't.
|
||||
yaml: |
|
||||
red: baron
|
||||
white: walls
|
||||
blue: berries
|
||||
php: |
|
||||
array(
|
||||
'red' => 'baron',
|
||||
'white' => 'walls',
|
||||
'blue' => 'berries',
|
||||
)
|
||||
---
|
||||
test: Colons aligned
|
||||
brief: |
|
||||
Spaces can come before the ': ' key/value separator.
|
||||
yaml: |
|
||||
red : baron
|
||||
white : walls
|
||||
blue : berries
|
||||
php: |
|
||||
array(
|
||||
'red' => 'baron',
|
||||
'white' => 'walls',
|
||||
'blue' => 'berries',
|
||||
)
|
|
@ -0,0 +1,85 @@
|
|||
--- %YAML:1.0
|
||||
test: Trailing Document Separator
|
||||
todo: true
|
||||
brief: >
|
||||
You can separate YAML documents
|
||||
with a string of three dashes.
|
||||
yaml: |
|
||||
- foo: 1
|
||||
bar: 2
|
||||
---
|
||||
more: stuff
|
||||
python: |
|
||||
[
|
||||
[ { 'foo': 1, 'bar': 2 } ],
|
||||
{ 'more': 'stuff' }
|
||||
]
|
||||
ruby: |
|
||||
[ { 'foo' => 1, 'bar' => 2 } ]
|
||||
|
||||
---
|
||||
test: Leading Document Separator
|
||||
todo: true
|
||||
brief: >
|
||||
You can explicity give an opening
|
||||
document separator to your YAML stream.
|
||||
yaml: |
|
||||
---
|
||||
- foo: 1
|
||||
bar: 2
|
||||
---
|
||||
more: stuff
|
||||
python: |
|
||||
[
|
||||
[ {'foo': 1, 'bar': 2}],
|
||||
{'more': 'stuff'}
|
||||
]
|
||||
ruby: |
|
||||
[ { 'foo' => 1, 'bar' => 2 } ]
|
||||
|
||||
---
|
||||
test: YAML Header
|
||||
todo: true
|
||||
brief: >
|
||||
The opening separator can contain directives
|
||||
to the YAML parser, such as the version
|
||||
number.
|
||||
yaml: |
|
||||
--- %YAML:1.0
|
||||
foo: 1
|
||||
bar: 2
|
||||
php: |
|
||||
array('foo' => 1, 'bar' => 2)
|
||||
documents: 1
|
||||
|
||||
---
|
||||
test: Red Herring Document Separator
|
||||
brief: >
|
||||
Separators included in blocks or strings
|
||||
are treated as blocks or strings, as the
|
||||
document separator should have no indentation
|
||||
preceding it.
|
||||
yaml: |
|
||||
foo: |
|
||||
---
|
||||
php: |
|
||||
array('foo' => "---\n")
|
||||
|
||||
---
|
||||
test: Multiple Document Separators in Block
|
||||
brief: >
|
||||
This technique allows you to embed other YAML
|
||||
documents within literal blocks.
|
||||
yaml: |
|
||||
foo: |
|
||||
---
|
||||
foo: bar
|
||||
---
|
||||
yo: baz
|
||||
bar: |
|
||||
fooness
|
||||
php: |
|
||||
array(
|
||||
'foo' => "---\nfoo: bar\n---\nyo: baz\n",
|
||||
'bar' => "fooness\n"
|
||||
)
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
test: Missing value for hash item
|
||||
todo: true
|
||||
brief: |
|
||||
Third item in this hash doesn't have a value
|
||||
yaml: |
|
||||
okay: value
|
||||
also okay: ~
|
||||
causes error because no value specified
|
||||
last key: value okay here too
|
||||
python-error: causes error because no value specified
|
||||
|
||||
---
|
||||
test: Not indenting enough
|
||||
brief: |
|
||||
There was a bug in PyYaml where it was off by one
|
||||
in the indentation check. It was allowing the YAML
|
||||
below.
|
||||
# This is actually valid YAML now. Someone should tell showell.
|
||||
yaml: |
|
||||
foo:
|
||||
firstline: 1
|
||||
secondline: 2
|
||||
php: |
|
||||
array('foo' => null, 'firstline' => 1, 'secondline' => 2)
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
---
|
||||
test: Simple Inline Array
|
||||
brief: >
|
||||
Sequences can be contained on a
|
||||
single line, using the inline syntax.
|
||||
Separate each entry with commas and
|
||||
enclose in square brackets.
|
||||
yaml: |
|
||||
seq: [ a, b, c ]
|
||||
php: |
|
||||
array('seq' => array('a', 'b', 'c'))
|
||||
---
|
||||
test: Simple Inline Hash
|
||||
brief: >
|
||||
Mapping can also be contained on
|
||||
a single line, using the inline
|
||||
syntax. Each key-value pair is
|
||||
separated by a colon, with a comma
|
||||
between each entry in the mapping.
|
||||
Enclose with curly braces.
|
||||
yaml: |
|
||||
hash: { name: Steve, foo: bar }
|
||||
php: |
|
||||
array('hash' => array('name' => 'Steve', 'foo' => 'bar'))
|
||||
---
|
||||
test: Multi-line Inline Collections
|
||||
todo: true
|
||||
brief: >
|
||||
Both inline sequences and inline mappings
|
||||
can span multiple lines, provided that you
|
||||
indent the additional lines.
|
||||
yaml: |
|
||||
languages: [ Ruby,
|
||||
Perl,
|
||||
Python ]
|
||||
websites: { YAML: yaml.org,
|
||||
Ruby: ruby-lang.org,
|
||||
Python: python.org,
|
||||
Perl: use.perl.org }
|
||||
php: |
|
||||
array(
|
||||
'languages' => array('Ruby', 'Perl', 'Python'),
|
||||
'websites' => array(
|
||||
'YAML' => 'yaml.org',
|
||||
'Ruby' => 'ruby-lang.org',
|
||||
'Python' => 'python.org',
|
||||
'Perl' => 'use.perl.org'
|
||||
)
|
||||
)
|
||||
---
|
||||
test: Commas in Values (not in the spec!)
|
||||
todo: true
|
||||
brief: >
|
||||
List items in collections are delimited by commas, but
|
||||
there must be a space after each comma. This allows you
|
||||
to add numbers without quoting.
|
||||
yaml: |
|
||||
attendances: [ 45,123, 70,000, 17,222 ]
|
||||
php: |
|
||||
array('attendances' => array(45123, 70000, 17222))
|
|
@ -0,0 +1,176 @@
|
|||
--- %YAML:1.0
|
||||
test: Single ending newline
|
||||
brief: >
|
||||
A pipe character, followed by an indented
|
||||
block of text is treated as a literal
|
||||
block, in which newlines are preserved
|
||||
throughout the block, including the final
|
||||
newline.
|
||||
yaml: |
|
||||
---
|
||||
this: |
|
||||
Foo
|
||||
Bar
|
||||
php: |
|
||||
array('this' => "Foo\nBar\n")
|
||||
---
|
||||
test: The '+' indicator
|
||||
brief: >
|
||||
The '+' indicator says to keep newlines at the end of text
|
||||
blocks.
|
||||
yaml: |
|
||||
normal: |
|
||||
extra new lines not kept
|
||||
|
||||
preserving: |+
|
||||
extra new lines are kept
|
||||
|
||||
|
||||
dummy: value
|
||||
php: |
|
||||
array(
|
||||
'normal' => "extra new lines not kept\n",
|
||||
'preserving' => "extra new lines are kept\n\n\n",
|
||||
'dummy' => 'value'
|
||||
)
|
||||
---
|
||||
test: Three trailing newlines in literals
|
||||
brief: >
|
||||
To give you more control over how space
|
||||
is preserved in text blocks, YAML has
|
||||
the keep '+' and chomp '-' indicators.
|
||||
The keep indicator will preserve all
|
||||
ending newlines, while the chomp indicator
|
||||
will strip all ending newlines.
|
||||
yaml: |
|
||||
clipped: |
|
||||
This has one newline.
|
||||
|
||||
|
||||
|
||||
same as "clipped" above: "This has one newline.\n"
|
||||
|
||||
stripped: |-
|
||||
This has no newline.
|
||||
|
||||
|
||||
|
||||
same as "stripped" above: "This has no newline."
|
||||
|
||||
kept: |+
|
||||
This has four newlines.
|
||||
|
||||
|
||||
|
||||
same as "kept" above: "This has four newlines.\n\n\n\n"
|
||||
php: |
|
||||
array(
|
||||
'clipped' => "This has one newline.\n",
|
||||
'same as "clipped" above' => "This has one newline.\n",
|
||||
'stripped' => 'This has no newline.',
|
||||
'same as "stripped" above' => 'This has no newline.',
|
||||
'kept' => "This has four newlines.\n\n\n\n",
|
||||
'same as "kept" above' => "This has four newlines.\n\n\n\n"
|
||||
)
|
||||
---
|
||||
test: Extra trailing newlines with spaces
|
||||
todo: true
|
||||
brief: >
|
||||
Normally, only a single newline is kept
|
||||
from the end of a literal block, unless the
|
||||
keep '+' character is used in combination
|
||||
with the pipe. The following example
|
||||
will preserve all ending whitespace
|
||||
since the last line of both literal blocks
|
||||
contains spaces which extend past the indentation
|
||||
level.
|
||||
yaml: |
|
||||
---
|
||||
this: |
|
||||
Foo
|
||||
|
||||
|
||||
kept: |+
|
||||
Foo
|
||||
|
||||
|
||||
php: |
|
||||
array('this' => "Foo\n\n \n",
|
||||
'kept' => "Foo\n\n \n" )
|
||||
|
||||
---
|
||||
test: Folded Block in a Sequence
|
||||
brief: >
|
||||
A greater-then character, followed by an indented
|
||||
block of text is treated as a folded block, in
|
||||
which lines of text separated by a single newline
|
||||
are concatenated as a single line.
|
||||
yaml: |
|
||||
---
|
||||
- apple
|
||||
- banana
|
||||
- >
|
||||
can't you see
|
||||
the beauty of yaml?
|
||||
hmm
|
||||
- dog
|
||||
php: |
|
||||
array(
|
||||
'apple',
|
||||
'banana',
|
||||
"can't you see the beauty of yaml? hmm\n",
|
||||
'dog'
|
||||
)
|
||||
---
|
||||
test: Folded Block as a Mapping Value
|
||||
brief: >
|
||||
Both literal and folded blocks can be
|
||||
used in collections, as values in a
|
||||
sequence or a mapping.
|
||||
yaml: |
|
||||
---
|
||||
quote: >
|
||||
Mark McGwire's
|
||||
year was crippled
|
||||
by a knee injury.
|
||||
source: espn
|
||||
php: |
|
||||
array(
|
||||
'quote' => "Mark McGwire's year was crippled by a knee injury.\n",
|
||||
'source' => 'espn'
|
||||
)
|
||||
---
|
||||
test: Three trailing newlines in folded blocks
|
||||
brief: >
|
||||
The keep and chomp indicators can also
|
||||
be applied to folded blocks.
|
||||
yaml: |
|
||||
clipped: >
|
||||
This has one newline.
|
||||
|
||||
|
||||
|
||||
same as "clipped" above: "This has one newline.\n"
|
||||
|
||||
stripped: >-
|
||||
This has no newline.
|
||||
|
||||
|
||||
|
||||
same as "stripped" above: "This has no newline."
|
||||
|
||||
kept: >+
|
||||
This has four newlines.
|
||||
|
||||
|
||||
|
||||
same as "kept" above: "This has four newlines.\n\n\n\n"
|
||||
php: |
|
||||
array(
|
||||
'clipped' => "This has one newline.\n",
|
||||
'same as "clipped" above' => "This has one newline.\n",
|
||||
'stripped' => 'This has no newline.',
|
||||
'same as "stripped" above' => 'This has no newline.',
|
||||
'kept' => "This has four newlines.\n\n\n\n",
|
||||
'same as "kept" above' => "This has four newlines.\n\n\n\n"
|
||||
)
|
|
@ -0,0 +1,45 @@
|
|||
--- %YAML:1.0
|
||||
test: Empty Sequence
|
||||
brief: >
|
||||
You can represent the empty sequence
|
||||
with an empty inline sequence.
|
||||
yaml: |
|
||||
empty: []
|
||||
php: |
|
||||
array('empty' => array())
|
||||
---
|
||||
test: Empty Mapping
|
||||
brief: >
|
||||
You can represent the empty mapping
|
||||
with an empty inline mapping.
|
||||
yaml: |
|
||||
empty: {}
|
||||
php: |
|
||||
array('empty' => array())
|
||||
---
|
||||
test: Empty Sequence as Entire Document
|
||||
yaml: |
|
||||
[]
|
||||
php: |
|
||||
array()
|
||||
---
|
||||
test: Empty Mapping as Entire Document
|
||||
yaml: |
|
||||
{}
|
||||
php: |
|
||||
array()
|
||||
---
|
||||
test: Null as Document
|
||||
yaml: |
|
||||
~
|
||||
php: |
|
||||
null
|
||||
---
|
||||
test: Empty String
|
||||
brief: >
|
||||
You can represent an empty string
|
||||
with a pair of quotes.
|
||||
yaml: |
|
||||
''
|
||||
php: |
|
||||
''
|
1695
core/vendor/Symfony/Component/Yaml/Tests/Fixtures/YtsSpecificationExamples.yml
vendored
Normal file
1695
core/vendor/Symfony/Component/Yaml/Tests/Fixtures/YtsSpecificationExamples.yml
vendored
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,244 @@
|
|||
--- %YAML:1.0
|
||||
test: Strings
|
||||
brief: >
|
||||
Any group of characters beginning with an
|
||||
alphabetic or numeric character is a string,
|
||||
unless it belongs to one of the groups below
|
||||
(such as an Integer or Time).
|
||||
yaml: |
|
||||
String
|
||||
php: |
|
||||
'String'
|
||||
---
|
||||
test: String characters
|
||||
brief: >
|
||||
A string can contain any alphabetic or
|
||||
numeric character, along with many
|
||||
punctuation characters, including the
|
||||
period, dash, space, quotes, exclamation, and
|
||||
question mark.
|
||||
yaml: |
|
||||
- What's Yaml?
|
||||
- It's for writing data structures in plain text.
|
||||
- And?
|
||||
- And what? That's not good enough for you?
|
||||
- No, I mean, "And what about Yaml?"
|
||||
- Oh, oh yeah. Uh.. Yaml for Ruby.
|
||||
php: |
|
||||
array(
|
||||
"What's Yaml?",
|
||||
"It's for writing data structures in plain text.",
|
||||
"And?",
|
||||
"And what? That's not good enough for you?",
|
||||
"No, I mean, \"And what about Yaml?\"",
|
||||
"Oh, oh yeah. Uh.. Yaml for Ruby."
|
||||
)
|
||||
---
|
||||
test: Indicators in Strings
|
||||
brief: >
|
||||
Be careful using indicators in strings. In particular,
|
||||
the comma, colon, and pound sign must be used carefully.
|
||||
yaml: |
|
||||
the colon followed by space is an indicator: but is a string:right here
|
||||
same for the pound sign: here we have it#in a string
|
||||
the comma can, honestly, be used in most cases: [ but not in, inline collections ]
|
||||
php: |
|
||||
array(
|
||||
'the colon followed by space is an indicator' => 'but is a string:right here',
|
||||
'same for the pound sign' => 'here we have it#in a string',
|
||||
'the comma can, honestly, be used in most cases' => array('but not in', 'inline collections')
|
||||
)
|
||||
---
|
||||
test: Forcing Strings
|
||||
brief: >
|
||||
Any YAML type can be forced into a string using the
|
||||
explicit !str method.
|
||||
yaml: |
|
||||
date string: !str 2001-08-01
|
||||
number string: !str 192
|
||||
php: |
|
||||
array(
|
||||
'date string' => '2001-08-01',
|
||||
'number string' => '192'
|
||||
)
|
||||
---
|
||||
test: Single-quoted Strings
|
||||
brief: >
|
||||
You can also enclose your strings within single quotes,
|
||||
which allows use of slashes, colons, and other indicators
|
||||
freely. Inside single quotes, you can represent a single
|
||||
quote in your string by using two single quotes next to
|
||||
each other.
|
||||
yaml: |
|
||||
all my favorite symbols: '#:!/%.)'
|
||||
a few i hate: '&(*'
|
||||
why do i hate them?: 'it''s very hard to explain'
|
||||
entities: '£ me'
|
||||
php: |
|
||||
array(
|
||||
'all my favorite symbols' => '#:!/%.)',
|
||||
'a few i hate' => '&(*',
|
||||
'why do i hate them?' => 'it\'s very hard to explain',
|
||||
'entities' => '£ me'
|
||||
)
|
||||
---
|
||||
test: Double-quoted Strings
|
||||
brief: >
|
||||
Enclosing strings in double quotes allows you
|
||||
to use escapings to represent ASCII and
|
||||
Unicode characters.
|
||||
yaml: |
|
||||
i know where i want my line breaks: "one here\nand another here\n"
|
||||
php: |
|
||||
array(
|
||||
'i know where i want my line breaks' => "one here\nand another here\n"
|
||||
)
|
||||
---
|
||||
test: Multi-line Quoted Strings
|
||||
todo: true
|
||||
brief: >
|
||||
Both single- and double-quoted strings may be
|
||||
carried on to new lines in your YAML document.
|
||||
They must be indented a step and indentation
|
||||
is interpreted as a single space.
|
||||
yaml: |
|
||||
i want a long string: "so i'm going to
|
||||
let it go on and on to other lines
|
||||
until i end it with a quote."
|
||||
php: |
|
||||
array('i want a long string' => "so i'm going to ".
|
||||
"let it go on and on to other lines ".
|
||||
"until i end it with a quote."
|
||||
)
|
||||
|
||||
---
|
||||
test: Plain scalars
|
||||
todo: true
|
||||
brief: >
|
||||
Unquoted strings may also span multiple lines, if they
|
||||
are free of YAML space indicators and indented.
|
||||
yaml: |
|
||||
- My little toe is broken in two places;
|
||||
- I'm crazy to have skied this way;
|
||||
- I'm not the craziest he's seen, since there was always the German guy
|
||||
who skied for 3 hours on a broken shin bone (just below the kneecap);
|
||||
- Nevertheless, second place is respectable, and he doesn't
|
||||
recommend going for the record;
|
||||
- He's going to put my foot in plaster for a month;
|
||||
- This would impair my skiing ability somewhat for the
|
||||
duration, as can be imagined.
|
||||
php: |
|
||||
array(
|
||||
"My little toe is broken in two places;",
|
||||
"I'm crazy to have skied this way;",
|
||||
"I'm not the craziest he's seen, since there was always ".
|
||||
"the German guy who skied for 3 hours on a broken shin ".
|
||||
"bone (just below the kneecap);",
|
||||
"Nevertheless, second place is respectable, and he doesn't ".
|
||||
"recommend going for the record;",
|
||||
"He's going to put my foot in plaster for a month;",
|
||||
"This would impair my skiing ability somewhat for the duration, ".
|
||||
"as can be imagined."
|
||||
)
|
||||
---
|
||||
test: 'Null'
|
||||
brief: >
|
||||
You can use the tilde '~' character for a null value.
|
||||
yaml: |
|
||||
name: Mr. Show
|
||||
hosted by: Bob and David
|
||||
date of next season: ~
|
||||
php: |
|
||||
array(
|
||||
'name' => 'Mr. Show',
|
||||
'hosted by' => 'Bob and David',
|
||||
'date of next season' => null
|
||||
)
|
||||
---
|
||||
test: Boolean
|
||||
brief: >
|
||||
You can use 'true' and 'false' for Boolean values.
|
||||
yaml: |
|
||||
Is Gus a Liar?: true
|
||||
Do I rely on Gus for Sustenance?: false
|
||||
php: |
|
||||
array(
|
||||
'Is Gus a Liar?' => true,
|
||||
'Do I rely on Gus for Sustenance?' => false
|
||||
)
|
||||
---
|
||||
test: Integers
|
||||
dump_skip: true
|
||||
brief: >
|
||||
An integer is a series of numbers, optionally
|
||||
starting with a positive or negative sign. Integers
|
||||
may also contain commas for readability.
|
||||
yaml: |
|
||||
zero: 0
|
||||
simple: 12
|
||||
one-thousand: 1,000
|
||||
negative one-thousand: -1,000
|
||||
php: |
|
||||
array(
|
||||
'zero' => 0,
|
||||
'simple' => 12,
|
||||
'one-thousand' => 1000,
|
||||
'negative one-thousand' => -1000
|
||||
)
|
||||
---
|
||||
test: Integers as Map Keys
|
||||
brief: >
|
||||
An integer can be used a dictionary key.
|
||||
yaml: |
|
||||
1: one
|
||||
2: two
|
||||
3: three
|
||||
php: |
|
||||
array(
|
||||
1 => 'one',
|
||||
2 => 'two',
|
||||
3 => 'three'
|
||||
)
|
||||
---
|
||||
test: Floats
|
||||
dump_skip: true
|
||||
brief: >
|
||||
Floats are represented by numbers with decimals,
|
||||
allowing for scientific notation, as well as
|
||||
positive and negative infinity and "not a number."
|
||||
yaml: |
|
||||
a simple float: 2.00
|
||||
larger float: 1,000.09
|
||||
scientific notation: 1.00009e+3
|
||||
php: |
|
||||
array(
|
||||
'a simple float' => 2.0,
|
||||
'larger float' => 1000.09,
|
||||
'scientific notation' => 1000.09
|
||||
)
|
||||
---
|
||||
test: Time
|
||||
todo: true
|
||||
brief: >
|
||||
You can represent timestamps by using
|
||||
ISO8601 format, or a variation which
|
||||
allows spaces between the date, time and
|
||||
time zone.
|
||||
yaml: |
|
||||
iso8601: 2001-12-14t21:59:43.10-05:00
|
||||
space seperated: 2001-12-14 21:59:43.10 -05:00
|
||||
php: |
|
||||
array(
|
||||
'iso8601' => mktime( 2001, 12, 14, 21, 59, 43, 0.10, "-05:00" ),
|
||||
'space seperated' => mktime( 2001, 12, 14, 21, 59, 43, 0.10, "-05:00" )
|
||||
)
|
||||
---
|
||||
test: Date
|
||||
todo: true
|
||||
brief: >
|
||||
A date can be represented by its year,
|
||||
month and day in ISO8601 order.
|
||||
yaml: |
|
||||
1976-07-31
|
||||
php: |
|
||||
date( 1976, 7, 31 )
|
|
@ -0,0 +1,139 @@
|
|||
test: outside double quotes
|
||||
yaml: |
|
||||
\0 \ \a \b \n
|
||||
php: |
|
||||
"\\0 \\ \\a \\b \\n"
|
||||
---
|
||||
test: null
|
||||
yaml: |
|
||||
"\0"
|
||||
php: |
|
||||
"\x00"
|
||||
---
|
||||
test: bell
|
||||
yaml: |
|
||||
"\a"
|
||||
php: |
|
||||
"\x07"
|
||||
---
|
||||
test: backspace
|
||||
yaml: |
|
||||
"\b"
|
||||
php: |
|
||||
"\x08"
|
||||
---
|
||||
test: horizontal tab (1)
|
||||
yaml: |
|
||||
"\t"
|
||||
php: |
|
||||
"\x09"
|
||||
---
|
||||
test: horizontal tab (2)
|
||||
yaml: |
|
||||
"\ "
|
||||
php: |
|
||||
"\x09"
|
||||
---
|
||||
test: line feed
|
||||
yaml: |
|
||||
"\n"
|
||||
php: |
|
||||
"\x0a"
|
||||
---
|
||||
test: vertical tab
|
||||
yaml: |
|
||||
"\v"
|
||||
php: |
|
||||
"\x0b"
|
||||
---
|
||||
test: form feed
|
||||
yaml: |
|
||||
"\f"
|
||||
php: |
|
||||
"\x0c"
|
||||
---
|
||||
test: carriage return
|
||||
yaml: |
|
||||
"\r"
|
||||
php: |
|
||||
"\x0d"
|
||||
---
|
||||
test: escape
|
||||
yaml: |
|
||||
"\e"
|
||||
php: |
|
||||
"\x1b"
|
||||
---
|
||||
test: space
|
||||
yaml: |
|
||||
"\ "
|
||||
php: |
|
||||
"\x20"
|
||||
---
|
||||
test: slash
|
||||
yaml: |
|
||||
"\/"
|
||||
php: |
|
||||
"\x2f"
|
||||
---
|
||||
test: backslash
|
||||
yaml: |
|
||||
"\\"
|
||||
php: |
|
||||
"\\"
|
||||
---
|
||||
test: Unicode next line
|
||||
yaml: |
|
||||
"\N"
|
||||
php: |
|
||||
"\xc2\x85"
|
||||
---
|
||||
test: Unicode non-breaking space
|
||||
yaml: |
|
||||
"\_"
|
||||
php: |
|
||||
"\xc2\xa0"
|
||||
---
|
||||
test: Unicode line separator
|
||||
yaml: |
|
||||
"\L"
|
||||
php: |
|
||||
"\xe2\x80\xa8"
|
||||
---
|
||||
test: Unicode paragraph separator
|
||||
yaml: |
|
||||
"\P"
|
||||
php: |
|
||||
"\xe2\x80\xa9"
|
||||
---
|
||||
test: Escaped 8-bit Unicode
|
||||
yaml: |
|
||||
"\x42"
|
||||
php: |
|
||||
"B"
|
||||
---
|
||||
test: Escaped 16-bit Unicode
|
||||
yaml: |
|
||||
"\u20ac"
|
||||
php: |
|
||||
"\xe2\x82\xac"
|
||||
---
|
||||
test: Escaped 32-bit Unicode
|
||||
yaml: |
|
||||
"\U00000043"
|
||||
php: |
|
||||
"C"
|
||||
---
|
||||
test: Example 5.13 Escaped Characters
|
||||
note: |
|
||||
Currently throws an error parsing first line. Maybe Symfony Yaml doesn't support
|
||||
continuation of string across multiple lines? Keeping test here but disabled.
|
||||
todo: true
|
||||
yaml: |
|
||||
"Fun with \\
|
||||
\" \a \b \e \f \
|
||||
\n \r \t \v \0 \
|
||||
\ \_ \N \L \P \
|
||||
\x41 \u0041 \U00000041"
|
||||
php: |
|
||||
"Fun with \x5C\n\x22 \x07 \x08 \x1B \x0C\n\x0A \x0D \x09 \x0B \x00\n\x20 \xA0 \x85 \xe2\x80\xa8 \xe2\x80\xa9\nA A A"
|
|
@ -0,0 +1,17 @@
|
|||
- escapedCharacters
|
||||
- sfComments
|
||||
- sfCompact
|
||||
- sfTests
|
||||
- sfObjects
|
||||
- sfMergeKey
|
||||
- sfQuotes
|
||||
- YtsAnchorAlias
|
||||
- YtsBasicTests
|
||||
- YtsBlockMapping
|
||||
- YtsDocumentSeparator
|
||||
- YtsErrorTests
|
||||
- YtsFlowCollections
|
||||
- YtsFoldedScalars
|
||||
- YtsNullsAndEmpties
|
||||
- YtsSpecificationExamples
|
||||
- YtsTypeTransfers
|
|
@ -0,0 +1,51 @@
|
|||
--- %YAML:1.0
|
||||
test: Comments at the end of a line
|
||||
brief: >
|
||||
Comments at the end of a line
|
||||
yaml: |
|
||||
ex1: "foo # bar"
|
||||
ex2: "foo # bar" # comment
|
||||
ex3: 'foo # bar' # comment
|
||||
ex4: foo # comment
|
||||
php: |
|
||||
array('ex1' => 'foo # bar', 'ex2' => 'foo # bar', 'ex3' => 'foo # bar', 'ex4' => 'foo')
|
||||
---
|
||||
test: Comments in the middle
|
||||
brief: >
|
||||
Comments in the middle
|
||||
yaml: |
|
||||
foo:
|
||||
# some comment
|
||||
# some comment
|
||||
bar: foo
|
||||
# some comment
|
||||
# some comment
|
||||
php: |
|
||||
array('foo' => array('bar' => 'foo'))
|
||||
---
|
||||
test: Comments on a hash line
|
||||
brief: >
|
||||
Comments on a hash line
|
||||
yaml: |
|
||||
foo: # a comment
|
||||
foo: bar # a comment
|
||||
php: |
|
||||
array('foo' => array('foo' => 'bar'))
|
||||
---
|
||||
test: 'Value starting with a #'
|
||||
brief: >
|
||||
'Value starting with a #'
|
||||
yaml: |
|
||||
foo: '#bar'
|
||||
php: |
|
||||
array('foo' => '#bar')
|
||||
---
|
||||
test: Document starting with a comment and a separator
|
||||
brief: >
|
||||
Commenting before document start is allowed
|
||||
yaml: |
|
||||
# document comment
|
||||
---
|
||||
foo: bar # a comment
|
||||
php: |
|
||||
array('foo' => 'bar')
|
|
@ -0,0 +1,159 @@
|
|||
--- %YAML:1.0
|
||||
test: Compact notation
|
||||
brief: |
|
||||
Compact notation for sets of mappings with single element
|
||||
yaml: |
|
||||
---
|
||||
# products purchased
|
||||
- item : Super Hoop
|
||||
- item : Basketball
|
||||
quantity: 1
|
||||
- item:
|
||||
name: Big Shoes
|
||||
nick: Biggies
|
||||
quantity: 1
|
||||
php: |
|
||||
array (
|
||||
array (
|
||||
'item' => 'Super Hoop',
|
||||
),
|
||||
array (
|
||||
'item' => 'Basketball',
|
||||
'quantity' => 1,
|
||||
),
|
||||
array (
|
||||
'item' => array(
|
||||
'name' => 'Big Shoes',
|
||||
'nick' => 'Biggies'
|
||||
),
|
||||
'quantity' => 1
|
||||
)
|
||||
)
|
||||
---
|
||||
test: Compact notation combined with inline notation
|
||||
brief: |
|
||||
Combinations of compact and inline notation are allowed
|
||||
yaml: |
|
||||
---
|
||||
items:
|
||||
- { item: Super Hoop, quantity: 1 }
|
||||
- [ Basketball, Big Shoes ]
|
||||
php: |
|
||||
array (
|
||||
'items' => array (
|
||||
array (
|
||||
'item' => 'Super Hoop',
|
||||
'quantity' => 1,
|
||||
),
|
||||
array (
|
||||
'Basketball',
|
||||
'Big Shoes'
|
||||
)
|
||||
)
|
||||
)
|
||||
--- %YAML:1.0
|
||||
test: Compact notation
|
||||
brief: |
|
||||
Compact notation for sets of mappings with single element
|
||||
yaml: |
|
||||
---
|
||||
# products purchased
|
||||
- item : Super Hoop
|
||||
- item : Basketball
|
||||
quantity: 1
|
||||
- item:
|
||||
name: Big Shoes
|
||||
nick: Biggies
|
||||
quantity: 1
|
||||
php: |
|
||||
array (
|
||||
array (
|
||||
'item' => 'Super Hoop',
|
||||
),
|
||||
array (
|
||||
'item' => 'Basketball',
|
||||
'quantity' => 1,
|
||||
),
|
||||
array (
|
||||
'item' => array(
|
||||
'name' => 'Big Shoes',
|
||||
'nick' => 'Biggies'
|
||||
),
|
||||
'quantity' => 1
|
||||
)
|
||||
)
|
||||
---
|
||||
test: Compact notation combined with inline notation
|
||||
brief: |
|
||||
Combinations of compact and inline notation are allowed
|
||||
yaml: |
|
||||
---
|
||||
items:
|
||||
- { item: Super Hoop, quantity: 1 }
|
||||
- [ Basketball, Big Shoes ]
|
||||
php: |
|
||||
array (
|
||||
'items' => array (
|
||||
array (
|
||||
'item' => 'Super Hoop',
|
||||
'quantity' => 1,
|
||||
),
|
||||
array (
|
||||
'Basketball',
|
||||
'Big Shoes'
|
||||
)
|
||||
)
|
||||
)
|
||||
--- %YAML:1.0
|
||||
test: Compact notation
|
||||
brief: |
|
||||
Compact notation for sets of mappings with single element
|
||||
yaml: |
|
||||
---
|
||||
# products purchased
|
||||
- item : Super Hoop
|
||||
- item : Basketball
|
||||
quantity: 1
|
||||
- item:
|
||||
name: Big Shoes
|
||||
nick: Biggies
|
||||
quantity: 1
|
||||
php: |
|
||||
array (
|
||||
array (
|
||||
'item' => 'Super Hoop',
|
||||
),
|
||||
array (
|
||||
'item' => 'Basketball',
|
||||
'quantity' => 1,
|
||||
),
|
||||
array (
|
||||
'item' => array(
|
||||
'name' => 'Big Shoes',
|
||||
'nick' => 'Biggies'
|
||||
),
|
||||
'quantity' => 1
|
||||
)
|
||||
)
|
||||
---
|
||||
test: Compact notation combined with inline notation
|
||||
brief: |
|
||||
Combinations of compact and inline notation are allowed
|
||||
yaml: |
|
||||
---
|
||||
items:
|
||||
- { item: Super Hoop, quantity: 1 }
|
||||
- [ Basketball, Big Shoes ]
|
||||
php: |
|
||||
array (
|
||||
'items' => array (
|
||||
array (
|
||||
'item' => 'Super Hoop',
|
||||
'quantity' => 1,
|
||||
),
|
||||
array (
|
||||
'Basketball',
|
||||
'Big Shoes'
|
||||
)
|
||||
)
|
||||
)
|
|
@ -0,0 +1,27 @@
|
|||
--- %YAML:1.0
|
||||
test: Simple In Place Substitution
|
||||
brief: >
|
||||
If you want to reuse an entire alias, only overwriting what is different
|
||||
you can use a << in place substitution. This is not part of the official
|
||||
YAML spec, but a widely implemented extension. See the following URL for
|
||||
details: http://yaml.org/type/merge.html
|
||||
yaml: |
|
||||
foo: &foo
|
||||
a: Steve
|
||||
b: Clark
|
||||
c: Brian
|
||||
bar: &bar
|
||||
<<: *foo
|
||||
x: Oren
|
||||
foo2: &foo2
|
||||
a: Ballmer
|
||||
ding: &dong [ fi, fei, fo, fam]
|
||||
check:
|
||||
<<:
|
||||
- *foo
|
||||
- *dong
|
||||
isit: tested
|
||||
head:
|
||||
<<: [ *foo , *dong , *foo2 ]
|
||||
php: |
|
||||
array('foo' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian'), 'bar' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'x' => 'Oren'), 'foo2' => array('a' => 'Ballmer'), 'ding' => array('fi', 'fei', 'fo', 'fam'), 'check' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'fi', 'fei', 'fo', 'fam', 'isit' => 'tested'), 'head' => array('a' => 'Ballmer', 'b' => 'Clark', 'c' => 'Brian', 'fi', 'fei', 'fo', 'fam'))
|
|
@ -0,0 +1,11 @@
|
|||
--- %YAML:1.0
|
||||
test: Objects
|
||||
brief: >
|
||||
Comments at the end of a line
|
||||
yaml: |
|
||||
ex1: "foo # bar"
|
||||
ex2: "foo # bar" # comment
|
||||
ex3: 'foo # bar' # comment
|
||||
ex4: foo # comment
|
||||
php: |
|
||||
array('ex1' => 'foo # bar', 'ex2' => 'foo # bar', 'ex3' => 'foo # bar', 'ex4' => 'foo')
|
|
@ -0,0 +1,33 @@
|
|||
--- %YAML:1.0
|
||||
test: Some characters at the beginning of a string must be escaped
|
||||
brief: >
|
||||
Some characters at the beginning of a string must be escaped
|
||||
yaml: |
|
||||
foo: | bar
|
||||
php: |
|
||||
array('foo' => '| bar')
|
||||
---
|
||||
test: A key can be a quoted string
|
||||
brief: >
|
||||
A key can be a quoted string
|
||||
yaml: |
|
||||
"foo1": bar
|
||||
'foo2': bar
|
||||
"foo \" bar": bar
|
||||
'foo '' bar': bar
|
||||
'foo3: ': bar
|
||||
"foo4: ": bar
|
||||
foo5: { "foo \" bar: ": bar, 'foo '' bar: ': bar }
|
||||
php: |
|
||||
array(
|
||||
'foo1' => 'bar',
|
||||
'foo2' => 'bar',
|
||||
'foo " bar' => 'bar',
|
||||
'foo \' bar' => 'bar',
|
||||
'foo3: ' => 'bar',
|
||||
'foo4: ' => 'bar',
|
||||
'foo5' => array(
|
||||
'foo " bar: ' => 'bar',
|
||||
'foo \' bar: ' => 'bar',
|
||||
),
|
||||
)
|
|
@ -0,0 +1,135 @@
|
|||
--- %YAML:1.0
|
||||
test: Multiple quoted string on one line
|
||||
brief: >
|
||||
Multiple quoted string on one line
|
||||
yaml: |
|
||||
stripped_title: { name: "foo bar", help: "bar foo" }
|
||||
php: |
|
||||
array('stripped_title' => array('name' => 'foo bar', 'help' => 'bar foo'))
|
||||
---
|
||||
test: Empty sequence
|
||||
yaml: |
|
||||
foo: [ ]
|
||||
php: |
|
||||
array('foo' => array())
|
||||
---
|
||||
test: Empty value
|
||||
yaml: |
|
||||
foo:
|
||||
php: |
|
||||
array('foo' => null)
|
||||
---
|
||||
test: Inline string parsing
|
||||
brief: >
|
||||
Inline string parsing
|
||||
yaml: |
|
||||
test: ['complex: string', 'another [string]']
|
||||
php: |
|
||||
array('test' => array('complex: string', 'another [string]'))
|
||||
---
|
||||
test: Boolean
|
||||
brief: >
|
||||
Boolean
|
||||
yaml: |
|
||||
- false
|
||||
- true
|
||||
- null
|
||||
- ~
|
||||
- 'false'
|
||||
- 'true'
|
||||
- 'null'
|
||||
- '~'
|
||||
php: |
|
||||
array(
|
||||
false,
|
||||
true,
|
||||
null,
|
||||
null,
|
||||
'false',
|
||||
'true',
|
||||
'null',
|
||||
'~',
|
||||
)
|
||||
---
|
||||
test: Empty lines in folded blocks
|
||||
brief: >
|
||||
Empty lines in folded blocks
|
||||
yaml: |
|
||||
foo:
|
||||
bar: |
|
||||
foo
|
||||
|
||||
|
||||
|
||||
bar
|
||||
php: |
|
||||
array('foo' => array('bar' => "foo\n\n\n \nbar\n"))
|
||||
---
|
||||
test: IP addresses
|
||||
brief: >
|
||||
IP addresses
|
||||
yaml: |
|
||||
foo: 10.0.0.2
|
||||
php: |
|
||||
array('foo' => '10.0.0.2')
|
||||
---
|
||||
test: A sequence with an embedded mapping
|
||||
brief: >
|
||||
A sequence with an embedded mapping
|
||||
yaml: |
|
||||
- foo
|
||||
- bar: { bar: foo }
|
||||
php: |
|
||||
array('foo', array('bar' => array('bar' => 'foo')))
|
||||
---
|
||||
test: A sequence with an unordered array
|
||||
brief: >
|
||||
A sequence with an unordered array
|
||||
yaml: |
|
||||
1: foo
|
||||
0: bar
|
||||
php: |
|
||||
array(1 => 'foo', 0 => 'bar')
|
||||
---
|
||||
test: Octal
|
||||
brief: as in spec example 2.19, octal value is converted
|
||||
yaml: |
|
||||
foo: 0123
|
||||
php: |
|
||||
array('foo' => 83)
|
||||
---
|
||||
test: Octal strings
|
||||
brief: Octal notation in a string must remain a string
|
||||
yaml: |
|
||||
foo: "0123"
|
||||
php: |
|
||||
array('foo' => '0123')
|
||||
---
|
||||
test: Octal strings
|
||||
brief: Octal notation in a string must remain a string
|
||||
yaml: |
|
||||
foo: '0123'
|
||||
php: |
|
||||
array('foo' => '0123')
|
||||
---
|
||||
test: Octal strings
|
||||
brief: Octal notation in a string must remain a string
|
||||
yaml: |
|
||||
foo: |
|
||||
0123
|
||||
php: |
|
||||
array('foo' => "0123\n")
|
||||
---
|
||||
test: Document as a simple hash
|
||||
brief: Document as a simple hash
|
||||
yaml: |
|
||||
{ foo: bar }
|
||||
php: |
|
||||
array('foo' => 'bar')
|
||||
---
|
||||
test: Document as a simple array
|
||||
brief: Document as a simple array
|
||||
yaml: |
|
||||
[ foo, bar ]
|
||||
php: |
|
||||
array('foo', 'bar')
|
|
@ -0,0 +1,172 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Yaml\Tests;
|
||||
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Symfony\Component\Yaml\Inline;
|
||||
|
||||
class InlineTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testParse()
|
||||
{
|
||||
foreach ($this->getTestsForParse() as $yaml => $value) {
|
||||
$this->assertEquals($value, Inline::parse($yaml), sprintf('::parse() converts an inline YAML to a PHP structure (%s)', $yaml));
|
||||
}
|
||||
}
|
||||
|
||||
public function testDump()
|
||||
{
|
||||
$testsForDump = $this->getTestsForDump();
|
||||
|
||||
foreach ($testsForDump as $yaml => $value) {
|
||||
$this->assertEquals($yaml, Inline::dump($value), sprintf('::dump() converts a PHP structure to an inline YAML (%s)', $yaml));
|
||||
}
|
||||
|
||||
foreach ($this->getTestsForParse() as $yaml => $value) {
|
||||
$this->assertEquals($value, Inline::parse(Inline::dump($value)), 'check consistency');
|
||||
}
|
||||
|
||||
foreach ($testsForDump as $yaml => $value) {
|
||||
$this->assertEquals($value, Inline::parse(Inline::dump($value)), 'check consistency');
|
||||
}
|
||||
}
|
||||
|
||||
public function testDumpNumericValueWithLocale()
|
||||
{
|
||||
$locale = setlocale(LC_NUMERIC, 0);
|
||||
if (false === $locale) {
|
||||
$this->markTestSkipped('Your platform does not support locales.');
|
||||
}
|
||||
|
||||
$required_locales = array('fr_FR.UTF-8', 'fr_FR.UTF8', 'fr_FR.utf-8', 'fr_FR.utf8', 'French_France.1252');
|
||||
if (false === setlocale(LC_ALL, $required_locales)) {
|
||||
$this->markTestSkipped('Could not set any of required locales: ' . implode(", ", $required_locales));
|
||||
}
|
||||
|
||||
$this->assertEquals('1.2', Inline::dump(1.2));
|
||||
$this->assertContains('fr', strtolower(setlocale(LC_NUMERIC, 0)));
|
||||
|
||||
setlocale(LC_ALL, $locale);
|
||||
}
|
||||
|
||||
public function testHashStringsResemblingExponentialNumericsShouldNotBeChangedToINF()
|
||||
{
|
||||
$value = '686e444';
|
||||
|
||||
$this->assertSame($value, Inline::parse(Inline::dump($value)));
|
||||
}
|
||||
|
||||
protected function getTestsForParse()
|
||||
{
|
||||
return array(
|
||||
'' => '',
|
||||
'null' => null,
|
||||
'false' => false,
|
||||
'true' => true,
|
||||
'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,
|
||||
'123456789123456789' => '123456789123456789',
|
||||
'"foo\r\nbar"' => "foo\r\nbar",
|
||||
"'foo#bar'" => 'foo#bar',
|
||||
"'foo # bar'" => 'foo # bar',
|
||||
"'#cfcfcf'" => '#cfcfcf',
|
||||
|
||||
'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),
|
||||
|
||||
'"a \\"string\\" with \'quoted strings inside\'"' => 'a "string" with \'quoted strings inside\'',
|
||||
"'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'),
|
||||
|
||||
// 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'),
|
||||
|
||||
// nested sequences and mappings
|
||||
'[foo, [bar, foo]]' => array('foo', array('bar', 'foo')),
|
||||
'[foo, {bar: foo}]' => array('foo', array('bar' => 'foo')),
|
||||
'{ foo: {bar: foo} }' => array('foo' => array('bar' => 'foo')),
|
||||
'{ foo: [bar, foo] }' => array('foo' => array('bar', 'foo')),
|
||||
|
||||
'[ foo, [ bar, foo ] ]' => array('foo', array('bar', 'foo')),
|
||||
|
||||
'[{ foo: {bar: foo} }]' => array(array('foo' => array('bar' => 'foo'))),
|
||||
|
||||
'[foo, [bar, [foo, [bar, foo]], foo]]' => array('foo', array('bar', array('foo', array('bar', 'foo')), 'foo')),
|
||||
|
||||
'[foo, {bar: foo, foo: [foo, {bar: foo}]}, [foo, {bar: foo}]]' => array('foo', array('bar' => 'foo', 'foo' => array('foo', array('bar' => 'foo'))), array('foo', array('bar' => 'foo'))),
|
||||
|
||||
'[foo, bar: { foo: bar }]' => array('foo', '1' => array('bar' => array('foo' => 'bar'))),
|
||||
);
|
||||
}
|
||||
|
||||
protected function getTestsForDump()
|
||||
{
|
||||
return array(
|
||||
'null' => null,
|
||||
'false' => false,
|
||||
'true' => true,
|
||||
'12' => 12,
|
||||
"'quoted string'" => 'quoted string',
|
||||
'12.30e+02' => 12.30e+02,
|
||||
'1234' => 0x4D2,
|
||||
'1243' => 02333,
|
||||
'.Inf' => -log(0),
|
||||
'-.Inf' => log(0),
|
||||
"'686e444'" => '686e444',
|
||||
'.Inf' => 646e444,
|
||||
'"foo\r\nbar"' => "foo\r\nbar",
|
||||
"'foo#bar'" => 'foo#bar',
|
||||
"'foo # bar'" => 'foo # bar',
|
||||
"'#cfcfcf'" => '#cfcfcf',
|
||||
|
||||
"'a \"string\" with ''quoted strings inside'''" => 'a "string" with \'quoted strings inside\'',
|
||||
|
||||
// sequences
|
||||
'[foo, bar, false, null, 12]' => array('foo', 'bar', false, null, 12),
|
||||
'[\'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: bar\' }' => array('foo' => 'bar', 'bar' => 'foo: bar'),
|
||||
|
||||
// nested sequences and mappings
|
||||
'[foo, [bar, foo]]' => array('foo', array('bar', 'foo')),
|
||||
|
||||
'[foo, [bar, [foo, [bar, foo]], foo]]' => array('foo', array('bar', array('foo', array('bar', 'foo')), 'foo')),
|
||||
|
||||
'{ foo: { bar: foo } }' => array('foo' => array('bar' => 'foo')),
|
||||
|
||||
'[foo, { bar: foo }]' => array('foo', array('bar' => 'foo')),
|
||||
|
||||
'[foo, { bar: foo, foo: [foo, { bar: foo }] }, [foo, { bar: foo }]]' => array('foo', array('bar' => 'foo', 'foo' => array('foo', array('bar' => 'foo'))), array('foo', array('bar' => 'foo'))),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Yaml\Tests;
|
||||
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Symfony\Component\Yaml\Parser;
|
||||
use Symfony\Component\Yaml\Exception\ParseException;
|
||||
|
||||
class ParserTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
protected $parser;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->parser = new Parser();
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
$this->parser = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getDataFormSpecifications
|
||||
*/
|
||||
public function testSpecifications($file, $expected, $yaml, $comment)
|
||||
{
|
||||
if ('escapedCharacters' == $file) {
|
||||
if (!function_exists('iconv') && !function_exists('mb_convert_encoding')) {
|
||||
$this->markTestSkipped('The iconv and mbstring extensions are not available.');
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertEquals($expected, var_export($this->parser->parse($yaml), true), $comment);
|
||||
}
|
||||
|
||||
public function getDataFormSpecifications()
|
||||
{
|
||||
$parser = new Parser();
|
||||
$path = __DIR__.'/Fixtures';
|
||||
|
||||
$tests = array();
|
||||
$files = $parser->parse(file_get_contents($path.'/index.yml'));
|
||||
foreach ($files as $file) {
|
||||
$yamls = file_get_contents($path.'/'.$file.'.yml');
|
||||
|
||||
// split YAMLs documents
|
||||
foreach (preg_split('/^---( %YAML\:1\.0)?/m', $yamls) as $yaml) {
|
||||
if (!$yaml) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$test = $parser->parse($yaml);
|
||||
if (isset($test['todo']) && $test['todo']) {
|
||||
// TODO
|
||||
} else {
|
||||
$expected = var_export(eval('return '.trim($test['php']).';'), true);
|
||||
|
||||
$tests[] = array($file, $expected, $test['yaml'], $test['test']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $tests;
|
||||
}
|
||||
|
||||
public function testTabsInYaml()
|
||||
{
|
||||
// test tabs in YAML
|
||||
$yamls = array(
|
||||
"foo:\n bar",
|
||||
"foo:\n bar",
|
||||
"foo:\n bar",
|
||||
"foo:\n bar",
|
||||
);
|
||||
|
||||
foreach ($yamls as $yaml) {
|
||||
try {
|
||||
$content = $this->parser->parse($yaml);
|
||||
|
||||
$this->fail('YAML files must not contain tabs');
|
||||
} catch (\Exception $e) {
|
||||
$this->assertInstanceOf('\Exception', $e, 'YAML files must not contain tabs');
|
||||
$this->assertEquals('A YAML file cannot contain tabs as indentation at line 2 (near "'.strpbrk($yaml, "\t").'").', $e->getMessage(), 'YAML files must not contain tabs');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function testEndOfTheDocumentMarker()
|
||||
{
|
||||
$yaml = <<<EOF
|
||||
--- %YAML:1.0
|
||||
foo
|
||||
...
|
||||
EOF;
|
||||
|
||||
$this->assertEquals('foo', $this->parser->parse($yaml));
|
||||
}
|
||||
|
||||
public function testObjectsSupport()
|
||||
{
|
||||
$b = array('foo' => new B(), 'bar' => 1);
|
||||
$this->assertEquals($this->parser->parse(<<<EOF
|
||||
foo: !!php/object:O:30:"Symfony\Component\Yaml\Tests\B":1:{s:1:"b";s:3:"foo";}
|
||||
bar: 1
|
||||
EOF
|
||||
), $b, '->parse() is able to dump objects');
|
||||
}
|
||||
|
||||
public function testNonUtf8Exception()
|
||||
{
|
||||
if (!function_exists('mb_detect_encoding') || !function_exists('iconv')) {
|
||||
$this->markTestSkipped('Exceptions for non-utf8 charsets require the mb_detect_encoding() and iconv() functions.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$yamls = array(
|
||||
iconv("UTF-8", "ISO-8859-1", "foo: 'äöüß'"),
|
||||
iconv("UTF-8", "ISO-8859-15", "euro: '€'"),
|
||||
iconv("UTF-8", "CP1252", "cp1252: '©ÉÇáñ'")
|
||||
);
|
||||
|
||||
foreach ($yamls as $yaml) {
|
||||
try {
|
||||
$this->parser->parse($yaml);
|
||||
|
||||
$this->fail('charsets other than UTF-8 are rejected.');
|
||||
} catch (\Exception $e) {
|
||||
$this->assertInstanceOf('Symfony\Component\Yaml\Exception\ParseException', $e, 'charsets other than UTF-8 are rejected.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class B
|
||||
{
|
||||
public $b = 'foo';
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
spl_autoload_register(function ($class) {
|
||||
if (0 === strpos(ltrim($class, '/'), 'Symfony\Component\Yaml')) {
|
||||
if (file_exists($file = __DIR__.'/../'.substr(str_replace('\\', '/', $class), strlen('Symfony\Component\Yaml')).'.php')) {
|
||||
require_once $file;
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Yaml;
|
||||
|
||||
/**
|
||||
* Unescaper encapsulates unescaping rules for single and double-quoted
|
||||
* YAML strings.
|
||||
*
|
||||
* @author Matthew Lewinski <matthew@lewinski.org>
|
||||
*/
|
||||
class Unescaper
|
||||
{
|
||||
// Parser and Inline assume UTF-8 encoding, so escaped Unicode characters
|
||||
// must be converted to that encoding.
|
||||
const ENCODING = 'UTF-8';
|
||||
|
||||
// Regex fragment that matches an escaped character in a double quoted
|
||||
// string.
|
||||
const REGEX_ESCAPED_CHARACTER = "\\\\([0abt\tnvfre \\\"\\/\\\\N_LP]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})";
|
||||
|
||||
/**
|
||||
* Unescapes a single quoted string.
|
||||
*
|
||||
* @param string $value A single quoted string.
|
||||
*
|
||||
* @return string The unescaped string.
|
||||
*/
|
||||
public function unescapeSingleQuotedString($value)
|
||||
{
|
||||
return str_replace('\'\'', '\'', $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unescapes a double quoted string.
|
||||
*
|
||||
* @param string $value A double quoted string.
|
||||
*
|
||||
* @return string The unescaped string.
|
||||
*/
|
||||
public function unescapeDoubleQuotedString($value)
|
||||
{
|
||||
$self = $this;
|
||||
$callback = function($match) use($self) {
|
||||
return $self->unescapeCharacter($match[0]);
|
||||
};
|
||||
|
||||
// evaluate the string
|
||||
return preg_replace_callback('/'.self::REGEX_ESCAPED_CHARACTER.'/u', $callback, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unescapes a character that was found in a double-quoted string
|
||||
*
|
||||
* @param string $value An escaped character
|
||||
*
|
||||
* @return string The unescaped character
|
||||
*/
|
||||
public function unescapeCharacter($value)
|
||||
{
|
||||
switch ($value{1}) {
|
||||
case '0':
|
||||
return "\x0";
|
||||
case 'a':
|
||||
return "\x7";
|
||||
case 'b':
|
||||
return "\x8";
|
||||
case 't':
|
||||
return "\t";
|
||||
case "\t":
|
||||
return "\t";
|
||||
case 'n':
|
||||
return "\n";
|
||||
case 'v':
|
||||
return "\xb";
|
||||
case 'f':
|
||||
return "\xc";
|
||||
case 'r':
|
||||
return "\xd";
|
||||
case 'e':
|
||||
return "\x1b";
|
||||
case ' ':
|
||||
return ' ';
|
||||
case '"':
|
||||
return '"';
|
||||
case '/':
|
||||
return '/';
|
||||
case '\\':
|
||||
return '\\';
|
||||
case 'N':
|
||||
// U+0085 NEXT LINE
|
||||
return $this->convertEncoding("\x00\x85", self::ENCODING, 'UCS-2BE');
|
||||
case '_':
|
||||
// U+00A0 NO-BREAK SPACE
|
||||
return $this->convertEncoding("\x00\xA0", self::ENCODING, 'UCS-2BE');
|
||||
case 'L':
|
||||
// U+2028 LINE SEPARATOR
|
||||
return $this->convertEncoding("\x20\x28", self::ENCODING, 'UCS-2BE');
|
||||
case 'P':
|
||||
// U+2029 PARAGRAPH SEPARATOR
|
||||
return $this->convertEncoding("\x20\x29", self::ENCODING, 'UCS-2BE');
|
||||
case 'x':
|
||||
$char = pack('n', hexdec(substr($value, 2, 2)));
|
||||
|
||||
return $this->convertEncoding($char, self::ENCODING, 'UCS-2BE');
|
||||
case 'u':
|
||||
$char = pack('n', hexdec(substr($value, 2, 4)));
|
||||
|
||||
return $this->convertEncoding($char, self::ENCODING, 'UCS-2BE');
|
||||
case 'U':
|
||||
$char = pack('N', hexdec(substr($value, 2, 8)));
|
||||
|
||||
return $this->convertEncoding($char, self::ENCODING, 'UCS-4BE');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string from one encoding to another.
|
||||
*
|
||||
* @param string $value The string to convert
|
||||
* @param string $to The input encoding
|
||||
* @param string $from The output encoding
|
||||
*
|
||||
* @return string The string with the new encoding
|
||||
*
|
||||
* @throws \RuntimeException if no suitable encoding function is found (iconv or mbstring)
|
||||
*/
|
||||
private function convertEncoding($value, $to, $from)
|
||||
{
|
||||
if (function_exists('iconv')) {
|
||||
return iconv($from, $to, $value);
|
||||
} elseif (function_exists('mb_convert_encoding')) {
|
||||
return mb_convert_encoding($value, $to, $from);
|
||||
}
|
||||
|
||||
throw new \RuntimeException('No suitable convert encoding function (install the iconv or mbstring extension).');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Yaml;
|
||||
|
||||
use Symfony\Component\Yaml\Exception\ParseException;
|
||||
|
||||
/**
|
||||
* Yaml offers convenience methods to load and dump YAML.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class Yaml
|
||||
{
|
||||
static public $enablePhpParsing = false;
|
||||
|
||||
static public function enablePhpParsing()
|
||||
{
|
||||
self::$enablePhpParsing = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses YAML into a PHP array.
|
||||
*
|
||||
* The parse method, when supplied with a YAML stream (string or file),
|
||||
* will do its best to convert YAML in a file into a PHP array.
|
||||
*
|
||||
* Usage:
|
||||
* <code>
|
||||
* $array = Yaml::parse('config.yml');
|
||||
* print_r($array);
|
||||
* </code>
|
||||
*
|
||||
* @param string $input Path to a YAML file or a string containing YAML
|
||||
*
|
||||
* @return array The YAML converted to a PHP array
|
||||
*
|
||||
* @throws ParseException If the YAML is not valid
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
static public function parse($input)
|
||||
{
|
||||
// if input is a file, process it
|
||||
$file = '';
|
||||
if (strpos($input, "\n") === false && is_file($input)) {
|
||||
if (false === is_readable($input)) {
|
||||
throw new ParseException(sprintf('Unable to parse "%s" as the file is not readable.', $input));
|
||||
}
|
||||
|
||||
$file = $input;
|
||||
if (self::$enablePhpParsing) {
|
||||
ob_start();
|
||||
$retval = include($file);
|
||||
$content = ob_get_clean();
|
||||
|
||||
// if an array is returned by the config file assume it's in plain php form else in YAML
|
||||
$input = is_array($retval) ? $retval : $content;
|
||||
|
||||
// if an array is returned by the config file assume it's in plain php form else in YAML
|
||||
if (is_array($input)) {
|
||||
return $input;
|
||||
}
|
||||
} else {
|
||||
$input = file_get_contents($file);
|
||||
}
|
||||
}
|
||||
|
||||
$yaml = new Parser();
|
||||
|
||||
try {
|
||||
return $yaml->parse($input);
|
||||
} catch (ParseException $e) {
|
||||
if ($file) {
|
||||
$e->setParsedFile($file);
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps a PHP array to a YAML string.
|
||||
*
|
||||
* The dump method, when supplied with an array, will do its best
|
||||
* to convert the array into friendly YAML.
|
||||
*
|
||||
* @param array $array PHP array
|
||||
* @param integer $inline The level where you switch to inline YAML
|
||||
*
|
||||
* @return string A YAML string representing the original PHP array
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
static public function dump($array, $inline = 2)
|
||||
{
|
||||
$yaml = new Dumper();
|
||||
|
||||
return $yaml->dump($array, $inline);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"name": "symfony/yaml",
|
||||
"type": "library",
|
||||
"description": "Symfony Yaml Component",
|
||||
"keywords": [],
|
||||
"homepage": "http://symfony.com",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "http://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3.2"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": { "Symfony\\Component\\Yaml": "" }
|
||||
},
|
||||
"target-dir": "Symfony/Component/Yaml",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.1-dev"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<phpunit backupGlobals="false"
|
||||
backupStaticAttributes="false"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false"
|
||||
syntaxCheck="false"
|
||||
bootstrap="Tests/bootstrap.php"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="Symfony Yaml Component Test Suite">
|
||||
<directory>./Tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory>./</directory>
|
||||
<exclude>
|
||||
<directory>./vendor</directory>
|
||||
<directory>./Tests</directory>
|
||||
</exclude>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
Loading…
Reference in New Issue