Development

/components/yaml/trunk/lib/sfYamlInline.php

You must first sign up to be able to contribute.

root/components/yaml/trunk/lib/sfYamlInline.php

Revision 24656, 10.9 kB (checked in by fabien, 3 years ago)

[YAML] fixed \ usage in quoted string (closes #7756)

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