1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10:
11:
12: namespace Symfony\Component\Yaml;
13:
14: use Symfony\Component\Yaml\Exception\ParseException;
15: use Symfony\Component\Yaml\Exception\DumpException;
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($value);
50:
51: if (0 == strlen($value)) {
52: return '';
53: }
54:
55: if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) {
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 = strval($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'" : strval($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 preg_match(self::getTimestampRegex(), $value):
153: return Escaper::escapeWithSingleQuotes($value);
154: default:
155: return $value;
156: }
157: }
158:
159: 160: 161: 162: 163: 164: 165: 166: 167:
168: private static function dumpArray($value, $exceptionOnInvalidType, $objectSupport)
169: {
170:
171: $keys = array_keys($value);
172: if ((1 == count($keys) && '0' == $keys[0])
173: || (count($keys) > 1 && array_reduce($keys, function ($v, $w) { return (int) $v + $w; }, 0) == count($keys) * (count($keys) - 1) / 2)
174: ) {
175: $output = array();
176: foreach ($value as $val) {
177: $output[] = self::dump($val, $exceptionOnInvalidType, $objectSupport);
178: }
179:
180: return sprintf('[%s]', implode(', ', $output));
181: }
182:
183:
184: $output = array();
185: foreach ($value as $key => $val) {
186: $output[] = sprintf('%s: %s', self::dump($key, $exceptionOnInvalidType, $objectSupport), self::dump($val, $exceptionOnInvalidType, $objectSupport));
187: }
188:
189: return sprintf('{ %s }', implode(', ', $output));
190: }
191:
192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205:
206: public static function parseScalar($scalar, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true, $references = array())
207: {
208: if (in_array($scalar[$i], $stringDelimiters)) {
209:
210: $output = self::parseQuotedScalar($scalar, $i);
211:
212: if (null !== $delimiters) {
213: $tmp = ltrim(substr($scalar, $i), ' ');
214: if (!in_array($tmp[0], $delimiters)) {
215: throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i)));
216: }
217: }
218: } else {
219:
220: if (!$delimiters) {
221: $output = substr($scalar, $i);
222: $i += strlen($output);
223:
224:
225: if (false !== $strpos = strpos($output, ' #')) {
226: $output = rtrim(substr($output, 0, $strpos));
227: }
228: } elseif (preg_match('/^(.+?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) {
229: $output = $match[1];
230: $i += strlen($output);
231: } else {
232: throw new ParseException(sprintf('Malformed inline YAML string (%s).', $scalar));
233: }
234:
235: if ($evaluate) {
236: $output = self::evaluateScalar($output, $references);
237: }
238: }
239:
240: return $output;
241: }
242:
243: 244: 245: 246: 247: 248: 249: 250: 251: 252:
253: private static function parseQuotedScalar($scalar, &$i)
254: {
255: if (!preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) {
256: throw new ParseException(sprintf('Malformed inline YAML string (%s).', substr($scalar, $i)));
257: }
258:
259: $output = substr($match[0], 1, strlen($match[0]) - 2);
260:
261: $unescaper = new Unescaper();
262: if ('"' == $scalar[$i]) {
263: $output = $unescaper->unescapeDoubleQuotedString($output);
264: } else {
265: $output = $unescaper->unescapeSingleQuotedString($output);
266: }
267:
268: $i += strlen($match[0]);
269:
270: return $output;
271: }
272:
273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283:
284: private static function parseSequence($sequence, &$i = 0, $references = array())
285: {
286: $output = array();
287: $len = strlen($sequence);
288: $i += 1;
289:
290:
291: while ($i < $len) {
292: switch ($sequence[$i]) {
293: case '[':
294:
295: $output[] = self::parseSequence($sequence, $i, $references);
296: break;
297: case '{':
298:
299: $output[] = self::parseMapping($sequence, $i, $references);
300: break;
301: case ']':
302: return $output;
303: case ',':
304: case ' ':
305: break;
306: default:
307: $isQuoted = in_array($sequence[$i], array('"', "'"));
308: $value = self::parseScalar($sequence, array(',', ']'), array('"', "'"), $i, true, $references);
309:
310:
311: if (!is_array($value) && !$isQuoted && false !== strpos($value, ': ')) {
312:
313: try {
314: $pos = 0;
315: $value = self::parseMapping('{'.$value.'}', $pos, $references);
316: } catch (\InvalidArgumentException $e) {
317:
318: }
319: }
320:
321: $output[] = $value;
322:
323: --$i;
324: }
325:
326: ++$i;
327: }
328:
329: throw new ParseException(sprintf('Malformed inline YAML string %s', $sequence));
330: }
331:
332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342:
343: private static function parseMapping($mapping, &$i = 0, $references = array())
344: {
345: $output = array();
346: $len = strlen($mapping);
347: $i += 1;
348:
349:
350: while ($i < $len) {
351: switch ($mapping[$i]) {
352: case ' ':
353: case ',':
354: ++$i;
355: continue 2;
356: case '}':
357: if (self::$objectForMap) {
358: return (object) $output;
359: }
360:
361: return $output;
362: }
363:
364:
365: $key = self::parseScalar($mapping, array(':', ' '), array('"', "'"), $i, false);
366:
367:
368: $done = false;
369:
370: while ($i < $len) {
371: switch ($mapping[$i]) {
372: case '[':
373:
374: $value = self::parseSequence($mapping, $i, $references);
375:
376:
377:
378: if (!isset($output[$key])) {
379: $output[$key] = $value;
380: }
381: $done = true;
382: break;
383: case '{':
384:
385: $value = self::parseMapping($mapping, $i, $references);
386:
387:
388:
389: if (!isset($output[$key])) {
390: $output[$key] = $value;
391: }
392: $done = true;
393: break;
394: case ':':
395: case ' ':
396: break;
397: default:
398: $value = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i, true, $references);
399:
400:
401:
402: if (!isset($output[$key])) {
403: $output[$key] = $value;
404: }
405: $done = true;
406: --$i;
407: }
408:
409: ++$i;
410:
411: if ($done) {
412: continue 2;
413: }
414: }
415: }
416:
417: throw new ParseException(sprintf('Malformed inline YAML string %s', $mapping));
418: }
419:
420: 421: 422: 423: 424: 425: 426: 427: 428: 429:
430: private static function evaluateScalar($scalar, $references = array())
431: {
432: $scalar = trim($scalar);
433: $scalarLower = strtolower($scalar);
434:
435: if (0 === strpos($scalar, '*')) {
436: if (false !== $pos = strpos($scalar, '#')) {
437: $value = substr($scalar, 1, $pos - 2);
438: } else {
439: $value = substr($scalar, 1);
440: }
441:
442:
443: if (false === $value || '' === $value) {
444: throw new ParseException('A reference must contain at least one character.');
445: }
446:
447: if (!array_key_exists($value, $references)) {
448: throw new ParseException(sprintf('Reference "%s" does not exist.', $value));
449: }
450:
451: return $references[$value];
452: }
453:
454: switch (true) {
455: case 'null' === $scalarLower:
456: case '' === $scalar:
457: case '~' === $scalar:
458: return;
459: case 'true' === $scalarLower:
460: return true;
461: case 'false' === $scalarLower:
462: return false;
463:
464: case $scalar[0] === '+' || $scalar[0] === '-' || $scalar[0] === '.' || $scalar[0] === '!' || is_numeric($scalar[0]):
465: switch (true) {
466: case 0 === strpos($scalar, '!str'):
467: return (string) substr($scalar, 5);
468: case 0 === strpos($scalar, '! '):
469: return intval(self::parseScalar(substr($scalar, 2)));
470: case 0 === strpos($scalar, '!!php/object:'):
471: if (self::$objectSupport) {
472: return unserialize(substr($scalar, 13));
473: }
474:
475: if (self::$exceptionOnInvalidType) {
476: throw new ParseException('Object support when parsing a YAML file has been disabled.');
477: }
478:
479: return;
480: case 0 === strpos($scalar, '!!float '):
481: return (float) substr($scalar, 8);
482: case ctype_digit($scalar):
483: $raw = $scalar;
484: $cast = intval($scalar);
485:
486: return '0' == $scalar[0] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw);
487: case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)):
488: $raw = $scalar;
489: $cast = intval($scalar);
490:
491: return '0' == $scalar[1] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw);
492: case is_numeric($scalar):
493: return '0x' == $scalar[0].$scalar[1] ? hexdec($scalar) : floatval($scalar);
494: case '.inf' === $scalarLower:
495: case '.nan' === $scalarLower:
496: return -log(0);
497: case '-.inf' === $scalarLower:
498: return log(0);
499: case preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar):
500: return floatval(str_replace(',', '', $scalar));
501: case preg_match(self::getTimestampRegex(), $scalar):
502: return strtotime($scalar);
503: }
504: default:
505: return (string) $scalar;
506: }
507: }
508:
509: 510: 511: 512: 513: 514: 515:
516: private static function getTimestampRegex()
517: {
518: return <<<EOF
519: ~^
520: (?P<year>[0-9][0-9][0-9][0-9])
521: -(?P<month>[0-9][0-9]?)
522: -(?P<day>[0-9][0-9]?)
523: (?:(?:[Tt]|[ \t]+)
524: (?P<hour>[0-9][0-9]?)
525: :(?P<minute>[0-9][0-9])
526: :(?P<second>[0-9][0-9])
527: (?:\.(?P<fraction>[0-9]*))?
528: (?:[ \t]*(?P<tz>Z|(?P<tz_sign>[-+])(?P<tz_hour>[0-9][0-9]?)
529: (?::(?P<tz_minute>[0-9][0-9]))?))?)?
530: $~x
531: EOF;
532: }
533: }
534: