1: | <?php
|
2: |
|
3: | |
4: | |
5: | |
6: | |
7: | |
8: | |
9: | |
10: |
|
11: |
|
12: | namespace Symfony\Component\Yaml;
|
13: |
|
14: | use Symfony\Component\Yaml\Exception\DumpException;
|
15: | use Symfony\Component\Yaml\Exception\ParseException;
|
16: |
|
17: | |
18: | |
19: | |
20: | |
21: |
|
22: | class Inline
|
23: | {
|
24: | const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*+(?:\\\\.[^"\\\\]*+)*+)"|\'([^\']*+(?:\'\'[^\']*+)*+)\')';
|
25: |
|
26: | private static $exceptionOnInvalidType = false;
|
27: | private static $objectSupport = false;
|
28: | private static $objectForMap = false;
|
29: |
|
30: | |
31: | |
32: | |
33: | |
34: | |
35: | |
36: | |
37: | |
38: | |
39: | |
40: | |
41: | |
42: |
|
43: | public static function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false, $references = array())
|
44: | {
|
45: | self::$exceptionOnInvalidType = $exceptionOnInvalidType;
|
46: | self::$objectSupport = $objectSupport;
|
47: | self::$objectForMap = $objectForMap;
|
48: |
|
49: | $value = trim((string)$value);
|
50: |
|
51: | if ('' === $value) {
|
52: | return '';
|
53: | }
|
54: |
|
55: | if (2 & (int) ini_get('mbstring.func_overload')) {
|
56: | $mbEncoding = mb_internal_encoding();
|
57: | mb_internal_encoding('ASCII');
|
58: | }
|
59: |
|
60: | $i = 0;
|
61: | switch ($value[0]) {
|
62: | case '[':
|
63: | $result = self::parseSequence($value, $i, $references);
|
64: | ++$i;
|
65: | break;
|
66: | case '{':
|
67: | $result = self::parseMapping($value, $i, $references);
|
68: | ++$i;
|
69: | break;
|
70: | default:
|
71: | $result = self::parseScalar($value, null, array('"', "'"), $i, true, $references);
|
72: | }
|
73: |
|
74: |
|
75: | if (preg_replace('/\s+#.*$/A', '', substr($value, $i))) {
|
76: | throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i)));
|
77: | }
|
78: |
|
79: | if (isset($mbEncoding)) {
|
80: | mb_internal_encoding($mbEncoding);
|
81: | }
|
82: |
|
83: | return $result;
|
84: | }
|
85: |
|
86: | |
87: | |
88: | |
89: | |
90: | |
91: | |
92: | |
93: | |
94: | |
95: | |
96: |
|
97: | public static function dump($value, $exceptionOnInvalidType = false, $objectSupport = false)
|
98: | {
|
99: | switch (true) {
|
100: | case \is_resource($value):
|
101: | if ($exceptionOnInvalidType) {
|
102: | throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value)));
|
103: | }
|
104: |
|
105: | return 'null';
|
106: | case \is_object($value):
|
107: | if ($objectSupport) {
|
108: | return '!php/object:'.serialize($value);
|
109: | }
|
110: |
|
111: | if ($exceptionOnInvalidType) {
|
112: | throw new DumpException('Object support when dumping a YAML file has been disabled.');
|
113: | }
|
114: |
|
115: | return 'null';
|
116: | case \is_array($value):
|
117: | return self::dumpArray($value, $exceptionOnInvalidType, $objectSupport);
|
118: | case null === $value:
|
119: | return 'null';
|
120: | case true === $value:
|
121: | return 'true';
|
122: | case false === $value:
|
123: | return 'false';
|
124: | case ctype_digit($value):
|
125: | return \is_string($value) ? "'$value'" : (int) $value;
|
126: | case is_numeric($value):
|
127: | $locale = setlocale(LC_NUMERIC, 0);
|
128: | if (false !== $locale) {
|
129: | setlocale(LC_NUMERIC, 'C');
|
130: | }
|
131: | if (\is_float($value)) {
|
132: | $repr = (string) $value;
|
133: | if (is_infinite($value)) {
|
134: | $repr = str_ireplace('INF', '.Inf', $repr);
|
135: | } elseif (floor($value) == $value && $repr == $value) {
|
136: |
|
137: | $repr = '!!float '.$repr;
|
138: | }
|
139: | } else {
|
140: | $repr = \is_string($value) ? "'$value'" : (string) $value;
|
141: | }
|
142: | if (false !== $locale) {
|
143: | setlocale(LC_NUMERIC, $locale);
|
144: | }
|
145: |
|
146: | return $repr;
|
147: | case '' == $value:
|
148: | return "''";
|
149: | case Escaper::requiresDoubleQuoting($value):
|
150: | return Escaper::escapeWithDoubleQuotes($value);
|
151: | case Escaper::requiresSingleQuoting($value):
|
152: | case Parser::preg_match(self::getHexRegex(), $value):
|
153: | case Parser::preg_match(self::getTimestampRegex(), $value):
|
154: | return Escaper::escapeWithSingleQuotes($value);
|
155: | default:
|
156: | return $value;
|
157: | }
|
158: | }
|
159: |
|
160: | |
161: | |
162: | |
163: | |
164: | |
165: | |
166: | |
167: | |
168: |
|
169: | public static function isHash(array $value)
|
170: | {
|
171: | $expectedKey = 0;
|
172: |
|
173: | foreach ($value as $key => $val) {
|
174: | if ($key !== $expectedKey++) {
|
175: | return true;
|
176: | }
|
177: | }
|
178: |
|
179: | return false;
|
180: | }
|
181: |
|
182: | |
183: | |
184: | |
185: | |
186: | |
187: | |
188: | |
189: | |
190: |
|
191: | private static function dumpArray($value, $exceptionOnInvalidType, $objectSupport)
|
192: | {
|
193: |
|
194: | if ($value && !self::isHash($value)) {
|
195: | $output = array();
|
196: | foreach ($value as $val) {
|
197: | $output[] = self::dump($val, $exceptionOnInvalidType, $objectSupport);
|
198: | }
|
199: |
|
200: | return sprintf('[%s]', implode(', ', $output));
|
201: | }
|
202: |
|
203: |
|
204: | $output = array();
|
205: | foreach ($value as $key => $val) {
|
206: | $output[] = sprintf('%s: %s', self::dump($key, $exceptionOnInvalidType, $objectSupport), self::dump($val, $exceptionOnInvalidType, $objectSupport));
|
207: | }
|
208: |
|
209: | return sprintf('{ %s }', implode(', ', $output));
|
210: | }
|
211: |
|
212: | |
213: | |
214: | |
215: | |
216: | |
217: | |
218: | |
219: | |
220: | |
221: | |
222: | |
223: | |
224: | |
225: | |
226: | |
227: |
|
228: | public static function parseScalar($scalar, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true, $references = array())
|
229: | {
|
230: | if (\in_array($scalar[$i], $stringDelimiters)) {
|
231: |
|
232: | $output = self::parseQuotedScalar($scalar, $i);
|
233: |
|
234: | if (null !== $delimiters) {
|
235: | $tmp = ltrim(substr($scalar, $i), ' ');
|
236: | if ('' === $tmp) {
|
237: | throw new ParseException(sprintf('Unexpected end of line, expected one of "%s".', implode('', $delimiters)));
|
238: | }
|
239: | if (!\in_array($tmp[0], $delimiters)) {
|
240: | throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i)));
|
241: | }
|
242: | }
|
243: | } else {
|
244: |
|
245: | if (!$delimiters) {
|
246: | $output = substr($scalar, $i);
|
247: | $i += \strlen($output);
|
248: |
|
249: |
|
250: | if (Parser::preg_match('/[ \t]+#/', $output, $match, PREG_OFFSET_CAPTURE)) {
|
251: | $output = substr($output, 0, $match[0][1]);
|
252: | }
|
253: | } elseif (Parser::preg_match('/^(.+?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) {
|
254: | $output = $match[1];
|
255: | $i += \strlen($output);
|
256: | } else {
|
257: | throw new ParseException(sprintf('Malformed inline YAML string: %s.', $scalar));
|
258: | }
|
259: |
|
260: |
|
261: | if ($output && ('@' === $output[0] || '`' === $output[0] || '|' === $output[0] || '>' === $output[0])) {
|
262: | @trigger_error(sprintf('Not quoting the scalar "%s" starting with "%s" is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.', $output, $output[0]), E_USER_DEPRECATED);
|
263: |
|
264: |
|
265: |
|
266: | }
|
267: |
|
268: | if ($evaluate) {
|
269: | $output = self::evaluateScalar($output, $references);
|
270: | }
|
271: | }
|
272: |
|
273: | return $output;
|
274: | }
|
275: |
|
276: | |
277: | |
278: | |
279: | |
280: | |
281: | |
282: | |
283: | |
284: | |
285: |
|
286: | private static function parseQuotedScalar($scalar, &$i)
|
287: | {
|
288: | if (!Parser::preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) {
|
289: | throw new ParseException(sprintf('Malformed inline YAML string: %s.', substr($scalar, $i)));
|
290: | }
|
291: |
|
292: | $output = substr($match[0], 1, \strlen($match[0]) - 2);
|
293: |
|
294: | $unescaper = new Unescaper();
|
295: | if ('"' == $scalar[$i]) {
|
296: | $output = $unescaper->unescapeDoubleQuotedString($output);
|
297: | } else {
|
298: | $output = $unescaper->unescapeSingleQuotedString($output);
|
299: | }
|
300: |
|
301: | $i += \strlen($match[0]);
|
302: |
|
303: | return $output;
|
304: | }
|
305: |
|
306: | |
307: | |
308: | |
309: | |
310: | |
311: | |
312: | |
313: | |
314: | |
315: | |
316: |
|
317: | private static function parseSequence($sequence, &$i = 0, $references = array())
|
318: | {
|
319: | $output = array();
|
320: | $len = \strlen($sequence);
|
321: | ++$i;
|
322: |
|
323: |
|
324: | while ($i < $len) {
|
325: | switch ($sequence[$i]) {
|
326: | case '[':
|
327: |
|
328: | $output[] = self::parseSequence($sequence, $i, $references);
|
329: | break;
|
330: | case '{':
|
331: |
|
332: | $output[] = self::parseMapping($sequence, $i, $references);
|
333: | break;
|
334: | case ']':
|
335: | return $output;
|
336: | case ',':
|
337: | case ' ':
|
338: | break;
|
339: | default:
|
340: | $isQuoted = \in_array($sequence[$i], array('"', "'"));
|
341: | $value = self::parseScalar($sequence, array(',', ']'), array('"', "'"), $i, true, $references);
|
342: |
|
343: |
|
344: | if (!\is_array($value) && !$isQuoted && false !== strpos($value, ': ')) {
|
345: |
|
346: | try {
|
347: | $pos = 0;
|
348: | $value = self::parseMapping('{'.$value.'}', $pos, $references);
|
349: | } catch (\InvalidArgumentException $e) {
|
350: |
|
351: | }
|
352: | }
|
353: |
|
354: | $output[] = $value;
|
355: |
|
356: | --$i;
|
357: | }
|
358: |
|
359: | ++$i;
|
360: | }
|
361: |
|
362: | throw new ParseException(sprintf('Malformed inline YAML string: %s.', $sequence));
|
363: | }
|
364: |
|
365: | |
366: | |
367: | |
368: | |
369: | |
370: | |
371: | |
372: | |
373: | |
374: | |
375: |
|
376: | private static function parseMapping($mapping, &$i = 0, $references = array())
|
377: | {
|
378: | $output = array();
|
379: | $len = \strlen($mapping);
|
380: | ++$i;
|
381: | $allowOverwrite = false;
|
382: |
|
383: |
|
384: | while ($i < $len) {
|
385: | switch ($mapping[$i]) {
|
386: | case ' ':
|
387: | case ',':
|
388: | ++$i;
|
389: | continue 2;
|
390: | case '}':
|
391: | if (self::$objectForMap) {
|
392: | return (object) $output;
|
393: | }
|
394: |
|
395: | return $output;
|
396: | }
|
397: |
|
398: |
|
399: | $key = self::parseScalar($mapping, array(':', ' '), array('"', "'"), $i, false);
|
400: |
|
401: | if ('<<' === $key) {
|
402: | $allowOverwrite = true;
|
403: | }
|
404: |
|
405: |
|
406: | $done = false;
|
407: |
|
408: | while ($i < $len) {
|
409: | switch ($mapping[$i]) {
|
410: | case '[':
|
411: |
|
412: | $value = self::parseSequence($mapping, $i, $references);
|
413: |
|
414: |
|
415: |
|
416: |
|
417: | if ('<<' === $key) {
|
418: | foreach ($value as $parsedValue) {
|
419: | $output += $parsedValue;
|
420: | }
|
421: | } elseif ($allowOverwrite || !isset($output[$key])) {
|
422: | $output[$key] = $value;
|
423: | }
|
424: | $done = true;
|
425: | break;
|
426: | case '{':
|
427: |
|
428: | $value = self::parseMapping($mapping, $i, $references);
|
429: |
|
430: |
|
431: |
|
432: |
|
433: | if ('<<' === $key) {
|
434: | $output += $value;
|
435: | } elseif ($allowOverwrite || !isset($output[$key])) {
|
436: | $output[$key] = $value;
|
437: | }
|
438: | $done = true;
|
439: | break;
|
440: | case ':':
|
441: | case ' ':
|
442: | break;
|
443: | default:
|
444: | $value = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i, true, $references);
|
445: |
|
446: |
|
447: |
|
448: |
|
449: | if ('<<' === $key) {
|
450: | $output += $value;
|
451: | } elseif ($allowOverwrite || !isset($output[$key])) {
|
452: | $output[$key] = $value;
|
453: | }
|
454: | $done = true;
|
455: | --$i;
|
456: | }
|
457: |
|
458: | ++$i;
|
459: |
|
460: | if ($done) {
|
461: | continue 2;
|
462: | }
|
463: | }
|
464: | }
|
465: |
|
466: | throw new ParseException(sprintf('Malformed inline YAML string: %s.', $mapping));
|
467: | }
|
468: |
|
469: | |
470: | |
471: | |
472: | |
473: | |
474: | |
475: | |
476: | |
477: | |
478: |
|
479: | private static function evaluateScalar($scalar, $references = array())
|
480: | {
|
481: | $scalar = trim($scalar);
|
482: | $scalarLower = strtolower($scalar);
|
483: |
|
484: | if (0 === strpos($scalar, '*')) {
|
485: | if (false !== $pos = strpos($scalar, '#')) {
|
486: | $value = substr($scalar, 1, $pos - 2);
|
487: | } else {
|
488: | $value = substr($scalar, 1);
|
489: | }
|
490: |
|
491: |
|
492: | if (false === $value || '' === $value) {
|
493: | throw new ParseException('A reference must contain at least one character.');
|
494: | }
|
495: |
|
496: | if (!array_key_exists($value, $references)) {
|
497: | throw new ParseException(sprintf('Reference "%s" does not exist.', $value));
|
498: | }
|
499: |
|
500: | return $references[$value];
|
501: | }
|
502: |
|
503: | switch (true) {
|
504: | case 'null' === $scalarLower:
|
505: | case '' === $scalar:
|
506: | case '~' === $scalar:
|
507: | return;
|
508: | case 'true' === $scalarLower:
|
509: | return true;
|
510: | case 'false' === $scalarLower:
|
511: | return false;
|
512: |
|
513: | case '+' === $scalar[0] || '-' === $scalar[0] || '.' === $scalar[0] || '!' === $scalar[0] || is_numeric($scalar[0]):
|
514: | switch (true) {
|
515: | case 0 === strpos($scalar, '!str'):
|
516: | return (string) substr($scalar, 5);
|
517: | case 0 === strpos($scalar, '! '):
|
518: | return (int) self::parseScalar(substr($scalar, 2));
|
519: | case 0 === strpos($scalar, '!php/object:'):
|
520: | if (self::$objectSupport) {
|
521: | return unserialize(substr($scalar, 12));
|
522: | }
|
523: |
|
524: | if (self::$exceptionOnInvalidType) {
|
525: | throw new ParseException('Object support when parsing a YAML file has been disabled.');
|
526: | }
|
527: |
|
528: | return;
|
529: | case 0 === strpos($scalar, '!!php/object:'):
|
530: | if (self::$objectSupport) {
|
531: | return unserialize(substr($scalar, 13));
|
532: | }
|
533: |
|
534: | if (self::$exceptionOnInvalidType) {
|
535: | throw new ParseException('Object support when parsing a YAML file has been disabled.');
|
536: | }
|
537: |
|
538: | return;
|
539: | case 0 === strpos($scalar, '!!float '):
|
540: | return (float) substr($scalar, 8);
|
541: | case ctype_digit($scalar):
|
542: | $raw = $scalar;
|
543: | $cast = (int) $scalar;
|
544: |
|
545: | return '0' == $scalar[0] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw);
|
546: | case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)):
|
547: | $raw = $scalar;
|
548: | $cast = (int) $scalar;
|
549: |
|
550: | return '0' == $scalar[1] ? octdec($scalar) : (((string) $raw === (string) $cast) ? $cast : $raw);
|
551: | case is_numeric($scalar):
|
552: | case Parser::preg_match(self::getHexRegex(), $scalar):
|
553: | return '0x' === $scalar[0].$scalar[1] ? hexdec($scalar) : (float) $scalar;
|
554: | case '.inf' === $scalarLower:
|
555: | case '.nan' === $scalarLower:
|
556: | return -log(0);
|
557: | case '-.inf' === $scalarLower:
|
558: | return log(0);
|
559: | case Parser::preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar):
|
560: | return (float) str_replace(',', '', $scalar);
|
561: | case Parser::preg_match(self::getTimestampRegex(), $scalar):
|
562: | $timeZone = date_default_timezone_get();
|
563: | date_default_timezone_set('UTC');
|
564: | $time = strtotime($scalar);
|
565: | date_default_timezone_set($timeZone);
|
566: |
|
567: | return $time;
|
568: | }
|
569: |
|
570: | default:
|
571: | return (string) $scalar;
|
572: | }
|
573: | }
|
574: |
|
575: | |
576: | |
577: | |
578: | |
579: | |
580: | |
581: |
|
582: | private static function getTimestampRegex()
|
583: | {
|
584: | return <<<EOF
|
585: | ~^
|
586: | (?P<year>[0-9][0-9][0-9][0-9])
|
587: | -(?P<month>[0-9][0-9]?)
|
588: | -(?P<day>[0-9][0-9]?)
|
589: | (?:(?:[Tt]|[ \t]+)
|
590: | (?P<hour>[0-9][0-9]?)
|
591: | :(?P<minute>[0-9][0-9])
|
592: | :(?P<second>[0-9][0-9])
|
593: | (?:\.(?P<fraction>[0-9]*))?
|
594: | (?:[ \t]*(?P<tz>Z|(?P<tz_sign>[-+])(?P<tz_hour>[0-9][0-9]?)
|
595: | (?::(?P<tz_minute>[0-9][0-9]))?))?)?
|
596: | $~x
|
597: | EOF;
|
598: | }
|
599: |
|
600: | |
601: | |
602: | |
603: | |
604: |
|
605: | private static function getHexRegex()
|
606: | {
|
607: | return '~^0x[0-9a-f]++$~i';
|
608: | }
|
609: | }
|
610: | |