* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ if (!defined('_ECRIRE_INC_VERSION')) return; /** * sfYamlInline implements a YAML parser/dumper for the YAML inline syntax. * * @package symfony * @subpackage yaml * @author Fabien Potencier * @version SVN: $Id: sfYamlInline.class.php 16177 2009-03-11 08:32:48Z fabien $ */ class sfYamlInline { /** * Convert 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 load($value) { $value = trim($value); if (0 == strlen($value)) { return ''; } switch ($value[0]) { case '[': return self::parseSequence($value); case '{': return self::parseMapping($value); default: return self::parseScalar($value); } } /** * 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 */ static public function dump($value) { $trueValues = '1.1' == sfYaml::getSpecVersion() ? array('true', 'on', '+', 'yes', 'y') : array('true'); $falseValues = '1.1' == sfYaml::getSpecVersion() ? array('false', 'off', '-', 'no', 'n') : array('false'); switch (true) { case is_resource($value): throw new InvalidArgumentException('Unable to dump PHP resources in a YAML file.'); 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): return is_infinite($value) ? str_ireplace('INF', '.Inf', strval($value)) : (is_string($value) ? "'$value'" : $value); case false !== strpos($value, "\n"): return sprintf('"%s"', str_replace(array('"', "\n", "\r"), array('\\"', '\n', '\r'), $value)); case preg_match('/[ \s \' " \: \{ \} \[ \] , & \* \#] | \A[ - ? | < > = ! % @ ]/x', $value): return sprintf("'%s'", str_replace('\'', '\'\'', $value)); case '' == $value: return "''"; case preg_match(self::getTimestampRegex(), $value): return "'$value'"; case in_array(strtolower($value), $trueValues): return "'$value'"; case in_array(strtolower($value), $falseValues): return "'$value'"; case in_array(strtolower($value), array('null', '~')): 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 protected function dumpArray($value) { // array $keys = array_keys($value); if ( (1 == count($keys) && '0' == $keys[0]) || (count($keys) > 1 && array_reduce($keys, create_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 $stringDelimiter * @param integer $i * @param boolean $evaluate * * @return string A YAML string */ 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); // skip next delimiter ++$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)); } } else if (preg_match('/^(.+?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) { $output = $match[1]; $i += strlen($output); } else { throw new InvalidArgumentException(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 */ static protected function parseQuotedScalar($scalar, &$i) { $delimiter = $scalar[$i]; ++$i; $buffer = ''; $len = strlen($scalar); $escaped = '"' == $delimiter ? '\\"' : "''"; while ($i < $len) { if (isset($scalar[$i + 1]) && $escaped == $scalar[$i].$scalar[$i + 1]) { $buffer .= $delimiter; ++$i; } else if ($delimiter == $scalar[$i]) { break; } else { $buffer .= $scalar[$i]; } ++$i; } if ('"' == $delimiter) { // evaluate the string $buffer = str_replace(array('\\n', '\\r'), array("\n", "\r"), $buffer); if (strpos($buffer,'\\x')!==false){ $buffer = preg_replace_callback(',\\\\x([0-9a-f]+),', create_function('$m', 'return chr(hexdec($m[1]));'), $buffer); } } return $buffer; } /** * Parses a sequence to a YAML string. * * @param string $sequence * @param integer $i * * @return string A YAML string */ static protected 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 InvalidArgumentException(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 */ static protected 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 InvalidArgumentException(sprintf('Malformed inline YAML string %s', $mapping)); } /** * Evaluates scalars and replaces magic values. * * @param string $scalar * * @return string A YAML string */ static protected function evaluateScalar($scalar) { $scalar = trim($scalar); $trueValues = '1.1' == sfYaml::getSpecVersion() ? array('true', 'on', '+', 'yes', 'y') : array('true'); $falseValues = '1.1' == sfYaml::getSpecVersion() ? array('false', 'off', '-', 'no', 'n') : array('false'); 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 in_array(strtolower($scalar), $trueValues): return true; case in_array(strtolower($scalar), $falseValues): 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; } } static protected function getTimestampRegex() { return <<[0-9][0-9][0-9][0-9]) -(?P[0-9][0-9]?) -(?P[0-9][0-9]?) (?:(?:[Tt]|[ \t]+) (?P[0-9][0-9]?) :(?P[0-9][0-9]) :(?P[0-9][0-9]) (?:\.(?P[0-9]*))? (?:[ \t]*(?PZ|(?P[-+])(?P[0-9][0-9]?) (?::(?P[0-9][0-9]))?))?)? $~x EOF; } }