Development

/branches/1.1/lib/yaml/sfYamlInline.class.php

You must first sign up to be able to contribute.

root/branches/1.1/lib/yaml/sfYamlInline.class.php

Revision 16177, 9.9 kB (checked in by fabien, 5 years ago)

[1.1, 1.2, 1.3] fixed sfYaml::dump dumps doesn't quote strings containing # (closes #6050)

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