PageRenderTime 42ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/symfony/yaml/Inline.php

https://gitlab.com/ealexis.t/trends
PHP | 568 lines | 376 code | 59 blank | 133 comment | 63 complexity | 795b556406f979e867644a43c1f510bf MD5 | raw file
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Yaml;
  11. use Symfony\Component\Yaml\Exception\ParseException;
  12. use Symfony\Component\Yaml\Exception\DumpException;
  13. /**
  14. * Inline implements a YAML parser/dumper for the YAML inline syntax.
  15. *
  16. * @author Fabien Potencier <fabien@symfony.com>
  17. */
  18. class Inline
  19. {
  20. const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\']*(?:\'\'[^\']*)*)\')';
  21. private static $exceptionOnInvalidType = false;
  22. private static $objectSupport = false;
  23. private static $objectForMap = false;
  24. /**
  25. * Converts a YAML string to a PHP array.
  26. *
  27. * @param string $value A YAML string
  28. * @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
  29. * @param bool $objectSupport true if object support is enabled, false otherwise
  30. * @param bool $objectForMap true if maps should return a stdClass instead of array()
  31. * @param array $references Mapping of variable names to values
  32. *
  33. * @return array A PHP array representing the YAML string
  34. *
  35. * @throws ParseException
  36. */
  37. public static function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false, $references = array())
  38. {
  39. self::$exceptionOnInvalidType = $exceptionOnInvalidType;
  40. self::$objectSupport = $objectSupport;
  41. self::$objectForMap = $objectForMap;
  42. $value = trim($value);
  43. if ('' === $value) {
  44. return '';
  45. }
  46. if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) {
  47. $mbEncoding = mb_internal_encoding();
  48. mb_internal_encoding('ASCII');
  49. }
  50. $i = 0;
  51. switch ($value[0]) {
  52. case '[':
  53. $result = self::parseSequence($value, $i, $references);
  54. ++$i;
  55. break;
  56. case '{':
  57. $result = self::parseMapping($value, $i, $references);
  58. ++$i;
  59. break;
  60. default:
  61. $result = self::parseScalar($value, null, array('"', "'"), $i, true, $references);
  62. }
  63. // some comments are allowed at the end
  64. if (preg_replace('/\s+#.*$/A', '', substr($value, $i))) {
  65. throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i)));
  66. }
  67. if (isset($mbEncoding)) {
  68. mb_internal_encoding($mbEncoding);
  69. }
  70. return $result;
  71. }
  72. /**
  73. * Dumps a given PHP variable to a YAML string.
  74. *
  75. * @param mixed $value The PHP variable to convert
  76. * @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
  77. * @param bool $objectSupport true if object support is enabled, false otherwise
  78. *
  79. * @return string The YAML string representing the PHP array
  80. *
  81. * @throws DumpException When trying to dump PHP resource
  82. */
  83. public static function dump($value, $exceptionOnInvalidType = false, $objectSupport = false)
  84. {
  85. switch (true) {
  86. case is_resource($value):
  87. if ($exceptionOnInvalidType) {
  88. throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value)));
  89. }
  90. return 'null';
  91. case is_object($value):
  92. if ($objectSupport) {
  93. return '!php/object:'.serialize($value);
  94. }
  95. if ($exceptionOnInvalidType) {
  96. throw new DumpException('Object support when dumping a YAML file has been disabled.');
  97. }
  98. return 'null';
  99. case is_array($value):
  100. return self::dumpArray($value, $exceptionOnInvalidType, $objectSupport);
  101. case null === $value:
  102. return 'null';
  103. case true === $value:
  104. return 'true';
  105. case false === $value:
  106. return 'false';
  107. case ctype_digit($value):
  108. return is_string($value) ? "'$value'" : (int) $value;
  109. case is_numeric($value):
  110. $locale = setlocale(LC_NUMERIC, 0);
  111. if (false !== $locale) {
  112. setlocale(LC_NUMERIC, 'C');
  113. }
  114. if (is_float($value)) {
  115. $repr = (string) $value;
  116. if (is_infinite($value)) {
  117. $repr = str_ireplace('INF', '.Inf', $repr);
  118. } elseif (floor($value) == $value && $repr == $value) {
  119. // Preserve float data type since storing a whole number will result in integer value.
  120. $repr = '!!float '.$repr;
  121. }
  122. } else {
  123. $repr = is_string($value) ? "'$value'" : (string) $value;
  124. }
  125. if (false !== $locale) {
  126. setlocale(LC_NUMERIC, $locale);
  127. }
  128. return $repr;
  129. case '' == $value:
  130. return "''";
  131. case Escaper::requiresDoubleQuoting($value):
  132. return Escaper::escapeWithDoubleQuotes($value);
  133. case Escaper::requiresSingleQuoting($value):
  134. case preg_match(self::getHexRegex(), $value):
  135. case preg_match(self::getTimestampRegex(), $value):
  136. return Escaper::escapeWithSingleQuotes($value);
  137. default:
  138. return $value;
  139. }
  140. }
  141. /**
  142. * Dumps a PHP array to a YAML string.
  143. *
  144. * @param array $value The PHP array to dump
  145. * @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
  146. * @param bool $objectSupport true if object support is enabled, false otherwise
  147. *
  148. * @return string The YAML string representing the PHP array
  149. */
  150. private static function dumpArray($value, $exceptionOnInvalidType, $objectSupport)
  151. {
  152. // array
  153. $keys = array_keys($value);
  154. $keysCount = count($keys);
  155. if ((1 === $keysCount && '0' == $keys[0])
  156. || ($keysCount > 1 && array_reduce($keys, function ($v, $w) { return (int) $v + $w; }, 0) === $keysCount * ($keysCount - 1) / 2)
  157. ) {
  158. $output = array();
  159. foreach ($value as $val) {
  160. $output[] = self::dump($val, $exceptionOnInvalidType, $objectSupport);
  161. }
  162. return sprintf('[%s]', implode(', ', $output));
  163. }
  164. // mapping
  165. $output = array();
  166. foreach ($value as $key => $val) {
  167. $output[] = sprintf('%s: %s', self::dump($key, $exceptionOnInvalidType, $objectSupport), self::dump($val, $exceptionOnInvalidType, $objectSupport));
  168. }
  169. return sprintf('{ %s }', implode(', ', $output));
  170. }
  171. /**
  172. * Parses a scalar to a YAML string.
  173. *
  174. * @param string $scalar
  175. * @param string $delimiters
  176. * @param array $stringDelimiters
  177. * @param int &$i
  178. * @param bool $evaluate
  179. * @param array $references
  180. *
  181. * @return string A YAML string
  182. *
  183. * @throws ParseException When malformed inline YAML string is parsed
  184. *
  185. * @internal
  186. */
  187. public static function parseScalar($scalar, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true, $references = array())
  188. {
  189. if (in_array($scalar[$i], $stringDelimiters)) {
  190. // quoted scalar
  191. $output = self::parseQuotedScalar($scalar, $i);
  192. if (null !== $delimiters) {
  193. $tmp = ltrim(substr($scalar, $i), ' ');
  194. if (!in_array($tmp[0], $delimiters)) {
  195. throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i)));
  196. }
  197. }
  198. } else {
  199. // "normal" string
  200. if (!$delimiters) {
  201. $output = substr($scalar, $i);
  202. $i += strlen($output);
  203. // remove comments
  204. if (preg_match('/[ \t]+#/', $output, $match, PREG_OFFSET_CAPTURE)) {
  205. $output = substr($output, 0, $match[0][1]);
  206. }
  207. } elseif (preg_match('/^(.+?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) {
  208. $output = $match[1];
  209. $i += strlen($output);
  210. } else {
  211. throw new ParseException(sprintf('Malformed inline YAML string (%s).', $scalar));
  212. }
  213. // a non-quoted string cannot start with @ or ` (reserved) nor with a scalar indicator (| or >)
  214. if ($output && ('@' === $output[0] || '`' === $output[0] || '|' === $output[0] || '>' === $output[0])) {
  215. throw new ParseException(sprintf('The reserved indicator "%s" cannot start a plain scalar; you need to quote the scalar.', $output[0]));
  216. }
  217. if ($evaluate) {
  218. $output = self::evaluateScalar($output, $references);
  219. }
  220. }
  221. return $output;
  222. }
  223. /**
  224. * Parses a quoted scalar to YAML.
  225. *
  226. * @param string $scalar
  227. * @param int &$i
  228. *
  229. * @return string A YAML string
  230. *
  231. * @throws ParseException When malformed inline YAML string is parsed
  232. */
  233. private static function parseQuotedScalar($scalar, &$i)
  234. {
  235. if (!preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) {
  236. throw new ParseException(sprintf('Malformed inline YAML string (%s).', substr($scalar, $i)));
  237. }
  238. $output = substr($match[0], 1, strlen($match[0]) - 2);
  239. $unescaper = new Unescaper();
  240. if ('"' == $scalar[$i]) {
  241. $output = $unescaper->unescapeDoubleQuotedString($output);
  242. } else {
  243. $output = $unescaper->unescapeSingleQuotedString($output);
  244. }
  245. $i += strlen($match[0]);
  246. return $output;
  247. }
  248. /**
  249. * Parses a sequence to a YAML string.
  250. *
  251. * @param string $sequence
  252. * @param int &$i
  253. * @param array $references
  254. *
  255. * @return string A YAML string
  256. *
  257. * @throws ParseException When malformed inline YAML string is parsed
  258. */
  259. private static function parseSequence($sequence, &$i = 0, $references = array())
  260. {
  261. $output = array();
  262. $len = strlen($sequence);
  263. ++$i;
  264. // [foo, bar, ...]
  265. while ($i < $len) {
  266. switch ($sequence[$i]) {
  267. case '[':
  268. // nested sequence
  269. $output[] = self::parseSequence($sequence, $i, $references);
  270. break;
  271. case '{':
  272. // nested mapping
  273. $output[] = self::parseMapping($sequence, $i, $references);
  274. break;
  275. case ']':
  276. return $output;
  277. case ',':
  278. case ' ':
  279. break;
  280. default:
  281. $isQuoted = in_array($sequence[$i], array('"', "'"));
  282. $value = self::parseScalar($sequence, array(',', ']'), array('"', "'"), $i, true, $references);
  283. // the value can be an array if a reference has been resolved to an array var
  284. if (!is_array($value) && !$isQuoted && false !== strpos($value, ': ')) {
  285. // embedded mapping?
  286. try {
  287. $pos = 0;
  288. $value = self::parseMapping('{'.$value.'}', $pos, $references);
  289. } catch (\InvalidArgumentException $e) {
  290. // no, it's not
  291. }
  292. }
  293. $output[] = $value;
  294. --$i;
  295. }
  296. ++$i;
  297. }
  298. throw new ParseException(sprintf('Malformed inline YAML string %s', $sequence));
  299. }
  300. /**
  301. * Parses a mapping to a YAML string.
  302. *
  303. * @param string $mapping
  304. * @param int &$i
  305. * @param array $references
  306. *
  307. * @return string A YAML string
  308. *
  309. * @throws ParseException When malformed inline YAML string is parsed
  310. */
  311. private static function parseMapping($mapping, &$i = 0, $references = array())
  312. {
  313. $output = array();
  314. $len = strlen($mapping);
  315. ++$i;
  316. // {foo: bar, bar:foo, ...}
  317. while ($i < $len) {
  318. switch ($mapping[$i]) {
  319. case ' ':
  320. case ',':
  321. ++$i;
  322. continue 2;
  323. case '}':
  324. if (self::$objectForMap) {
  325. return (object) $output;
  326. }
  327. return $output;
  328. }
  329. // key
  330. $key = self::parseScalar($mapping, array(':', ' '), array('"', "'"), $i, false);
  331. // value
  332. $done = false;
  333. while ($i < $len) {
  334. switch ($mapping[$i]) {
  335. case '[':
  336. // nested sequence
  337. $value = self::parseSequence($mapping, $i, $references);
  338. // Spec: Keys MUST be unique; first one wins.
  339. // Parser cannot abort this mapping earlier, since lines
  340. // are processed sequentially.
  341. if (!isset($output[$key])) {
  342. $output[$key] = $value;
  343. }
  344. $done = true;
  345. break;
  346. case '{':
  347. // nested mapping
  348. $value = self::parseMapping($mapping, $i, $references);
  349. // Spec: Keys MUST be unique; first one wins.
  350. // Parser cannot abort this mapping earlier, since lines
  351. // are processed sequentially.
  352. if (!isset($output[$key])) {
  353. $output[$key] = $value;
  354. }
  355. $done = true;
  356. break;
  357. case ':':
  358. case ' ':
  359. break;
  360. default:
  361. $value = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i, true, $references);
  362. // Spec: Keys MUST be unique; first one wins.
  363. // Parser cannot abort this mapping earlier, since lines
  364. // are processed sequentially.
  365. if (!isset($output[$key])) {
  366. $output[$key] = $value;
  367. }
  368. $done = true;
  369. --$i;
  370. }
  371. ++$i;
  372. if ($done) {
  373. continue 2;
  374. }
  375. }
  376. }
  377. throw new ParseException(sprintf('Malformed inline YAML string %s', $mapping));
  378. }
  379. /**
  380. * Evaluates scalars and replaces magic values.
  381. *
  382. * @param string $scalar
  383. * @param array $references
  384. *
  385. * @return string A YAML string
  386. *
  387. * @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved
  388. */
  389. private static function evaluateScalar($scalar, $references = array())
  390. {
  391. $scalar = trim($scalar);
  392. $scalarLower = strtolower($scalar);
  393. if (0 === strpos($scalar, '*')) {
  394. if (false !== $pos = strpos($scalar, '#')) {
  395. $value = substr($scalar, 1, $pos - 2);
  396. } else {
  397. $value = substr($scalar, 1);
  398. }
  399. // an unquoted *
  400. if (false === $value || '' === $value) {
  401. throw new ParseException('A reference must contain at least one character.');
  402. }
  403. if (!array_key_exists($value, $references)) {
  404. throw new ParseException(sprintf('Reference "%s" does not exist.', $value));
  405. }
  406. return $references[$value];
  407. }
  408. switch (true) {
  409. case 'null' === $scalarLower:
  410. case '' === $scalar:
  411. case '~' === $scalar:
  412. return;
  413. case 'true' === $scalarLower:
  414. return true;
  415. case 'false' === $scalarLower:
  416. return false;
  417. // Optimise for returning strings.
  418. case $scalar[0] === '+' || $scalar[0] === '-' || $scalar[0] === '.' || $scalar[0] === '!' || is_numeric($scalar[0]):
  419. switch (true) {
  420. case 0 === strpos($scalar, '!str'):
  421. return (string) substr($scalar, 5);
  422. case 0 === strpos($scalar, '! '):
  423. return (int) self::parseScalar(substr($scalar, 2));
  424. case 0 === strpos($scalar, '!php/object:'):
  425. if (self::$objectSupport) {
  426. return unserialize(substr($scalar, 12));
  427. }
  428. if (self::$exceptionOnInvalidType) {
  429. throw new ParseException('Object support when parsing a YAML file has been disabled.');
  430. }
  431. return;
  432. case 0 === strpos($scalar, '!!php/object:'):
  433. if (self::$objectSupport) {
  434. return unserialize(substr($scalar, 13));
  435. }
  436. if (self::$exceptionOnInvalidType) {
  437. throw new ParseException('Object support when parsing a YAML file has been disabled.');
  438. }
  439. return;
  440. case 0 === strpos($scalar, '!!float '):
  441. return (float) substr($scalar, 8);
  442. case ctype_digit($scalar):
  443. $raw = $scalar;
  444. $cast = (int) $scalar;
  445. return '0' == $scalar[0] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw);
  446. case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)):
  447. $raw = $scalar;
  448. $cast = (int) $scalar;
  449. return '0' == $scalar[1] ? octdec($scalar) : (((string) $raw === (string) $cast) ? $cast : $raw);
  450. case is_numeric($scalar):
  451. case preg_match(self::getHexRegex(), $scalar):
  452. return '0x' === $scalar[0].$scalar[1] ? hexdec($scalar) : (float) $scalar;
  453. case '.inf' === $scalarLower:
  454. case '.nan' === $scalarLower:
  455. return -log(0);
  456. case '-.inf' === $scalarLower:
  457. return log(0);
  458. case preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar):
  459. return (float) str_replace(',', '', $scalar);
  460. case preg_match(self::getTimestampRegex(), $scalar):
  461. $timeZone = date_default_timezone_get();
  462. date_default_timezone_set('UTC');
  463. $time = strtotime($scalar);
  464. date_default_timezone_set($timeZone);
  465. return $time;
  466. }
  467. default:
  468. return (string) $scalar;
  469. }
  470. }
  471. /**
  472. * Gets a regex that matches a YAML date.
  473. *
  474. * @return string The regular expression
  475. *
  476. * @see http://www.yaml.org/spec/1.2/spec.html#id2761573
  477. */
  478. private static function getTimestampRegex()
  479. {
  480. return <<<EOF
  481. ~^
  482. (?P<year>[0-9][0-9][0-9][0-9])
  483. -(?P<month>[0-9][0-9]?)
  484. -(?P<day>[0-9][0-9]?)
  485. (?:(?:[Tt]|[ \t]+)
  486. (?P<hour>[0-9][0-9]?)
  487. :(?P<minute>[0-9][0-9])
  488. :(?P<second>[0-9][0-9])
  489. (?:\.(?P<fraction>[0-9]*))?
  490. (?:[ \t]*(?P<tz>Z|(?P<tz_sign>[-+])(?P<tz_hour>[0-9][0-9]?)
  491. (?::(?P<tz_minute>[0-9][0-9]))?))?)?
  492. $~x
  493. EOF;
  494. }
  495. /**
  496. * Gets a regex that matches a YAML number in hexadecimal notation.
  497. *
  498. * @return string
  499. */
  500. private static function getHexRegex()
  501. {
  502. return '~^0x[0-9a-f]++$~i';
  503. }
  504. }