sfYamlInline.class.php 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. <?php
  2. /*
  3. * This file is part of the symfony package.
  4. * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
  5. *
  6. * For the full copyright and license information, please view the LICENSE
  7. * file that was distributed with this source code.
  8. */
  9. /**
  10. * sfYamlInline implements a YAML parser/dumper for the YAML inline syntax.
  11. *
  12. * @package symfony
  13. * @subpackage util
  14. * @author Fabien Potencier <fabien.potencier@symfony-project.com>
  15. * @version SVN: $Id: sfYamlInline.class.php 16177 2009-03-11 08:32:48Z fabien $
  16. */
  17. class sfYamlInline
  18. {
  19. /**
  20. * Load YAML into a PHP array.
  21. *
  22. * @param string YAML
  23. *
  24. * @return array PHP array
  25. */
  26. static public function load($value)
  27. {
  28. $value = trim($value);
  29. if (0 == strlen($value))
  30. {
  31. return '';
  32. }
  33. switch ($value[0])
  34. {
  35. case '[':
  36. return self::parseSequence($value);
  37. case '{':
  38. return self::parseMapping($value);
  39. default:
  40. return self::parseScalar($value);
  41. }
  42. }
  43. /**
  44. * Dumps PHP array to YAML.
  45. *
  46. * @param mixed PHP
  47. *
  48. * @return string YAML
  49. */
  50. static public function dump($value)
  51. {
  52. switch (true)
  53. {
  54. case is_resource($value):
  55. throw new InvalidArgumentException('Unable to dump PHP resources in a YAML file.');
  56. case is_object($value):
  57. return '!!php/object:'.serialize($value);
  58. case is_array($value):
  59. return self::dumpArray($value);
  60. case is_null($value):
  61. return 'null';
  62. case true === $value:
  63. return 'true';
  64. case false === $value:
  65. return 'false';
  66. case ctype_digit($value):
  67. return is_string($value) ? "'$value'" : (int) $value;
  68. case is_numeric($value):
  69. return is_infinite($value) ? str_ireplace('INF', '.Inf', strval($value)) : (is_string($value) ? "'$value'" : $value);
  70. case false !== strpos($value, "\n"):
  71. return sprintf('"%s"', str_replace(array('"', "\n", "\r"), array('\\"', '\n', '\r'), $value));
  72. case preg_match('/[ \s \' " \: \{ \} \[ \] , & \* \#]/x', $value):
  73. return sprintf("'%s'", str_replace('\'', '\'\'', $value));
  74. case '' == $value:
  75. return "''";
  76. case preg_match(self::getTimestampRegex(), $value):
  77. return "'$value'";
  78. case in_array(strtolower($value), array('true', 'on', '+', 'yes', 'y')):
  79. return "'$value'";
  80. case in_array(strtolower($value), array('false', 'off', '-', 'no', 'n')):
  81. return "'$value'";
  82. default:
  83. return $value;
  84. }
  85. }
  86. /**
  87. * Dumps PHP array to YAML
  88. *
  89. * @param array The array to dump
  90. *
  91. * @return string YAML
  92. */
  93. static protected function dumpArray($value)
  94. {
  95. // array
  96. $keys = array_keys($value);
  97. if (
  98. (1 == count($keys) && '0' == $keys[0])
  99. ||
  100. (count($keys) > 1 && array_reduce($keys, create_function('$v,$w', 'return (integer) $v + $w;'), 0) == count($keys) * (count($keys) - 1) / 2))
  101. {
  102. $output = array();
  103. foreach ($value as $val)
  104. {
  105. $output[] = self::dump($val);
  106. }
  107. return sprintf('[%s]', implode(', ', $output));
  108. }
  109. // mapping
  110. $output = array();
  111. foreach ($value as $key => $val)
  112. {
  113. $output[] = sprintf('%s: %s', self::dump($key), self::dump($val));
  114. }
  115. return sprintf('{ %s }', implode(', ', $output));
  116. }
  117. /**
  118. * Parses scalar to yaml
  119. *
  120. * @param scalar $scalar
  121. * @param string $delimiters
  122. * @param array String delimiter
  123. * @param integer $i
  124. * @param boolean $evaluate
  125. *
  126. * @return string YAML
  127. */
  128. static public function parseScalar($scalar, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true)
  129. {
  130. if (in_array($scalar[$i], $stringDelimiters))
  131. {
  132. // quoted scalar
  133. $output = self::parseQuotedScalar($scalar, $i);
  134. // skip next delimiter
  135. ++$i;
  136. }
  137. else
  138. {
  139. // "normal" string
  140. if (!$delimiters)
  141. {
  142. $output = substr($scalar, $i);
  143. $i += strlen($output);
  144. // remove comments
  145. if (false !== $strpos = strpos($output, ' #'))
  146. {
  147. $output = rtrim(substr($output, 0, $strpos));
  148. }
  149. }
  150. else if (preg_match('/^(.+?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match))
  151. {
  152. $output = $match[1];
  153. $i += strlen($output);
  154. }
  155. else
  156. {
  157. throw new InvalidArgumentException(sprintf('Malformed inline YAML string (%s).', $scalar));
  158. }
  159. $output = $evaluate ? self::evaluateScalar($output) : $output;
  160. }
  161. return $output;
  162. }
  163. /**
  164. * Parses quotes scalar
  165. *
  166. * @param string $scalar
  167. * @param integer $i
  168. *
  169. * @return string YAML
  170. */
  171. static protected function parseQuotedScalar($scalar, &$i)
  172. {
  173. $delimiter = $scalar[$i];
  174. ++$i;
  175. $buffer = '';
  176. $len = strlen($scalar);
  177. $escaped = '"' == $delimiter ? '\\"' : "''";
  178. while ($i < $len)
  179. {
  180. if (isset($scalar[$i + 1]) && $escaped == $scalar[$i].$scalar[$i + 1])
  181. {
  182. $buffer .= $delimiter;
  183. ++$i;
  184. }
  185. else if ($delimiter == $scalar[$i])
  186. {
  187. break;
  188. }
  189. else
  190. {
  191. $buffer .= $scalar[$i];
  192. }
  193. ++$i;
  194. }
  195. if ('"' == $delimiter)
  196. {
  197. // evaluate the string
  198. $buffer = str_replace(array('\\n', '\\r'), array("\n", "\r"), $buffer);
  199. }
  200. return $buffer;
  201. }
  202. /**
  203. * Parse sequence to yaml
  204. *
  205. * @param string $sequence
  206. * @param integer $i
  207. *
  208. * @return string YAML
  209. */
  210. static protected function parseSequence($sequence, &$i = 0)
  211. {
  212. $output = array();
  213. $len = strlen($sequence);
  214. $i += 1;
  215. // [foo, bar, ...]
  216. while ($i < $len)
  217. {
  218. switch ($sequence[$i])
  219. {
  220. case '[':
  221. // nested sequence
  222. $output[] = self::parseSequence($sequence, $i);
  223. break;
  224. case '{':
  225. // nested mapping
  226. $output[] = self::parseMapping($sequence, $i);
  227. break;
  228. case ']':
  229. return $output;
  230. case ',':
  231. case ' ':
  232. break;
  233. default:
  234. $isQuoted = in_array($sequence[$i], array('"', "'"));
  235. $value = self::parseScalar($sequence, array(',', ']'), array('"', "'"), $i);
  236. if (!$isQuoted && false !== strpos($value, ': '))
  237. {
  238. // embedded mapping?
  239. try
  240. {
  241. $value = self::parseMapping('{'.$value.'}');
  242. }
  243. catch (InvalidArgumentException $e)
  244. {
  245. // no, it's not
  246. }
  247. }
  248. $output[] = $value;
  249. --$i;
  250. }
  251. ++$i;
  252. }
  253. throw new InvalidArgumentException(sprintf('Malformed inline YAML string %s', $sequence));
  254. }
  255. /**
  256. * Parses mapping.
  257. *
  258. * @param string $mapping
  259. * @param integer $i
  260. *
  261. * @return string YAML
  262. */
  263. static protected function parseMapping($mapping, &$i = 0)
  264. {
  265. $output = array();
  266. $len = strlen($mapping);
  267. $i += 1;
  268. // {foo: bar, bar:foo, ...}
  269. while ($i < $len)
  270. {
  271. switch ($mapping[$i])
  272. {
  273. case ' ':
  274. case ',':
  275. ++$i;
  276. continue 2;
  277. case '}':
  278. return $output;
  279. }
  280. // key
  281. $key = self::parseScalar($mapping, array(':', ' '), array('"', "'"), $i, false);
  282. // value
  283. $done = false;
  284. while ($i < $len)
  285. {
  286. switch ($mapping[$i])
  287. {
  288. case '[':
  289. // nested sequence
  290. $output[$key] = self::parseSequence($mapping, $i);
  291. $done = true;
  292. break;
  293. case '{':
  294. // nested mapping
  295. $output[$key] = self::parseMapping($mapping, $i);
  296. $done = true;
  297. break;
  298. case ':':
  299. case ' ':
  300. break;
  301. default:
  302. $output[$key] = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i);
  303. $done = true;
  304. --$i;
  305. }
  306. ++$i;
  307. if ($done)
  308. {
  309. continue 2;
  310. }
  311. }
  312. }
  313. throw new InvalidArgumentException(sprintf('Malformed inline YAML string %s', $mapping));
  314. }
  315. /**
  316. * Evaluates scalars and replaces magic values.
  317. *
  318. * @param string $scalar
  319. *
  320. * @return string YAML
  321. */
  322. static protected function evaluateScalar($scalar)
  323. {
  324. $scalar = trim($scalar);
  325. switch (true)
  326. {
  327. case 'null' == strtolower($scalar):
  328. case '' == $scalar:
  329. case '~' == $scalar:
  330. return null;
  331. case 0 === strpos($scalar, '!str'):
  332. return (string) substr($scalar, 5);
  333. case 0 === strpos($scalar, '! '):
  334. return intval(self::parseScalar(substr($scalar, 2)));
  335. case 0 === strpos($scalar, '!!php/object:'):
  336. return unserialize(substr($scalar, 13));
  337. case ctype_digit($scalar):
  338. $raw = $scalar;
  339. $cast = intval($scalar);
  340. return '0' == $scalar[0] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw);
  341. case in_array(strtolower($scalar), array('true', 'on', '+', 'yes', 'y')):
  342. return true;
  343. case in_array(strtolower($scalar), array('false', 'off', '-', 'no', 'n')):
  344. return false;
  345. case is_numeric($scalar):
  346. return '0x' == $scalar[0].$scalar[1] ? hexdec($scalar) : floatval($scalar);
  347. case 0 == strcasecmp($scalar, '.inf'):
  348. case 0 == strcasecmp($scalar, '.NaN'):
  349. return -log(0);
  350. case 0 == strcasecmp($scalar, '-.inf'):
  351. return log(0);
  352. case preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar):
  353. return floatval(str_replace(',', '', $scalar));
  354. case preg_match(self::getTimestampRegex(), $scalar):
  355. return strtotime($scalar);
  356. default:
  357. return (string) $scalar;
  358. }
  359. }
  360. static protected function getTimestampRegex()
  361. {
  362. return <<<EOF
  363. ~^
  364. (?P<year>[0-9][0-9][0-9][0-9])
  365. -(?P<month>[0-9][0-9]?)
  366. -(?P<day>[0-9][0-9]?)
  367. (?:(?:[Tt]|[ \t]+)
  368. (?P<hour>[0-9][0-9]?)
  369. :(?P<minute>[0-9][0-9])
  370. :(?P<second>[0-9][0-9])
  371. (?:\.(?P<fraction>[0-9]*))?
  372. (?:[ \t]*(?P<tz>Z|(?P<tz_sign>[-+])(?P<tz_hour>[0-9][0-9]?)
  373. (?::(?P<tz_minute>[0-9][0-9]))?))?)?
  374. $~x
  375. EOF;
  376. }
  377. }