1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Scabbia2 Yaml Component |
4
|
|
|
* https://github.com/eserozvataf/scabbia2 |
5
|
|
|
* |
6
|
|
|
* For the full copyright and license information, please view the LICENSE |
7
|
|
|
* file that was distributed with this source code. |
8
|
|
|
* |
9
|
|
|
* @link https://github.com/eserozvataf/scabbia2-yaml for the canonical source repository |
10
|
|
|
* @copyright 2010-2016 Eser Ozvataf. (http://eser.ozvataf.com/) |
11
|
|
|
* @license http://www.apache.org/licenses/LICENSE-2.0 - Apache License, Version 2.0 |
12
|
|
|
* |
13
|
|
|
* ------------------------- |
14
|
|
|
* Portions of this code are from Symfony YAML Component under the MIT license. |
15
|
|
|
* |
16
|
|
|
* (c) Fabien Potencier <[email protected]> |
17
|
|
|
* |
18
|
|
|
* For the full copyright and license information, please view the LICENSE-MIT |
19
|
|
|
* file that was distributed with this source code. |
20
|
|
|
* |
21
|
|
|
* Modifications made: |
22
|
|
|
* - Scabbia Framework code styles applied. |
23
|
|
|
* - All dump methods are moved under Dumper class. |
24
|
|
|
* - Redundant classes removed. |
25
|
|
|
* - Namespace changed. |
26
|
|
|
* - Tests ported to Scabbia2. |
27
|
|
|
* - Encoding checks removed. |
28
|
|
|
*/ |
29
|
|
|
|
30
|
|
|
namespace Scabbia\Yaml; |
31
|
|
|
|
32
|
|
|
use Scabbia\Yaml\Escaper; |
33
|
|
|
use Scabbia\Yaml\ParseException; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* Inline implements a YAML parser for the YAML inline syntax |
37
|
|
|
* |
38
|
|
|
* @package Scabbia\Yaml |
39
|
|
|
* @author Fabien Potencier <[email protected]> |
40
|
|
|
* @author Eser Ozvataf <[email protected]> |
41
|
|
|
* @since 2.0.0 |
42
|
|
|
*/ |
43
|
|
|
class Inline |
44
|
|
|
{ |
45
|
|
|
/** @type string REGEX_QUOTED_STRING a regular expression pattern to match quoted strings */ |
46
|
|
|
const REGEX_QUOTED_STRING = "(?:\"([^\"\\\\]*(?:\\\\.[^\"\\\\]*)*)\"|'([^']*(?:''[^']*)*)')"; |
47
|
|
|
|
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* Converts a YAML string to a PHP array |
51
|
|
|
* |
52
|
|
|
* @param string $value A YAML string |
53
|
|
|
* @param array $references Mapping of variable names to values |
54
|
|
|
* |
55
|
|
|
* @throws ParseException If the YAML is not valid |
56
|
|
|
* @return array A PHP array representing the YAML string |
57
|
|
|
*/ |
58
|
|
|
public static function parse($value, $references = []) |
59
|
|
|
{ |
60
|
|
|
$value = trim($value); |
61
|
|
|
|
62
|
|
|
if (strlen($value) === 0) { |
63
|
|
|
return ""; |
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
$i = 0; |
67
|
|
|
if ($value[0] === "[") { |
68
|
|
|
$result = self::parseSequence($value, $i, $references); |
69
|
|
|
++$i; |
70
|
|
|
} elseif ($value[0] === "{") { |
71
|
|
|
$result = self::parseMapping($value, $i, $references); |
72
|
|
|
++$i; |
73
|
|
|
} else { |
74
|
|
|
$result = self::parseScalar($value, null, ["\"", "'"], $i, true, $references); |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
// some comments are allowed at the end |
78
|
|
|
if (preg_replace("/\\s+#.*$/A", "", substr($value, $i))) { |
79
|
|
|
throw new ParseException(sprintf("Unexpected characters near \"%s\".", substr($value, $i))); |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
return $result; |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* Parses a scalar to a YAML string |
87
|
|
|
* |
88
|
|
|
* @param scalar $scalar |
89
|
|
|
* @param string $delimiters |
90
|
|
|
* @param array $stringDelimiters |
91
|
|
|
* @param int &$i |
92
|
|
|
* @param bool $evaluate |
93
|
|
|
* @param array $references |
94
|
|
|
* |
95
|
|
|
* @throws ParseException When malformed inline YAML string is parsed |
96
|
|
|
* @return string A YAML string |
97
|
|
|
* |
98
|
|
|
* @internal |
99
|
|
|
*/ |
100
|
|
|
public static function parseScalar( |
101
|
|
|
$scalar, |
102
|
|
|
$delimiters = null, |
103
|
|
|
array $stringDelimiters = array("\"", "'"), |
104
|
|
|
&$i = 0, |
105
|
|
|
$evaluate = true, |
106
|
|
|
$references = [] |
107
|
|
|
) { |
108
|
|
|
if (in_array($scalar[$i], $stringDelimiters)) { |
109
|
|
|
// quoted scalar |
110
|
|
|
$output = self::parseQuotedScalar($scalar, $i); |
111
|
|
|
|
112
|
|
|
if ($delimiters !== null) { |
113
|
|
|
$tmp = ltrim(substr($scalar, $i), " "); |
114
|
|
|
if (!in_array($tmp[0], $delimiters)) { |
115
|
|
|
throw new ParseException(sprintf("Unexpected characters (%s).", substr($scalar, $i))); |
116
|
|
|
} |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
return $output; |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
// "normal" string |
123
|
|
|
if (!$delimiters) { |
|
|
|
|
124
|
|
|
$output = substr($scalar, $i); |
125
|
|
|
$i += strlen($output); |
126
|
|
|
|
127
|
|
|
// remove comments |
128
|
|
|
if (preg_match("/[ \t]+#/", $output, $match, PREG_OFFSET_CAPTURE)) { |
129
|
|
|
$output = substr($output, 0, $match[0][1]); |
130
|
|
|
} |
131
|
|
|
} elseif (preg_match("/^(.+?)(" . implode("|", $delimiters) . ")/", substr($scalar, $i), $match)) { |
132
|
|
|
$output = $match[1]; |
133
|
|
|
$i += strlen($output); |
134
|
|
|
} else { |
135
|
|
|
throw new ParseException(sprintf("Malformed inline YAML string (%s).", $scalar)); |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
// a non-quoted string cannot start with @ or ` (reserved) nor with a scalar indicator (| or >) |
139
|
|
|
if ($output && ($output[0] === "@" || $output[0] === "`" || $output[0] === "|" || $output[0] === ">")) { |
140
|
|
|
throw new ParseException(sprintf("The reserved indicator \"%s\" cannot start a plain scalar; you need to quote the scalar.", $output[0])); |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
if ($evaluate) { |
144
|
|
|
return self::evaluateScalar($output, $references); |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
return $output; |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
/** |
151
|
|
|
* Parses a quoted scalar to YAML |
152
|
|
|
* |
153
|
|
|
* @param string $scalar |
154
|
|
|
* @param int &$i |
155
|
|
|
* |
156
|
|
|
* @throws ParseException When malformed inline YAML string is parsed |
157
|
|
|
* @return string A YAML string |
158
|
|
|
*/ |
159
|
|
|
protected static function parseQuotedScalar($scalar, &$i) |
160
|
|
|
{ |
161
|
|
|
if (!preg_match("/" . self::REGEX_QUOTED_STRING . "/Au", substr($scalar, $i), $match)) { |
162
|
|
|
throw new ParseException(sprintf("Malformed inline YAML string (%s).", substr($scalar, $i))); |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
$output = substr($match[0], 1, strlen($match[0]) - 2); |
166
|
|
|
|
167
|
|
|
$escaper = new Escaper(); |
168
|
|
|
if ($scalar[$i] == "\"") { |
169
|
|
|
$output = $escaper->unescapeDoubleQuotedString($output); |
170
|
|
|
} else { |
171
|
|
|
$output = $escaper->unescapeSingleQuotedString($output); |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
$i += strlen($match[0]); |
175
|
|
|
|
176
|
|
|
return $output; |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
/** |
180
|
|
|
* Parses a sequence to a YAML string |
181
|
|
|
* |
182
|
|
|
* @param string $sequence |
183
|
|
|
* @param int &$i |
184
|
|
|
* @param array $references |
185
|
|
|
* |
186
|
|
|
* @throws ParseException When malformed inline YAML string is parsed |
187
|
|
|
* @return string A YAML string |
188
|
|
|
*/ |
189
|
|
|
protected static function parseSequence($sequence, &$i = 0, $references = []) |
190
|
|
|
{ |
191
|
|
|
$output = []; |
192
|
|
|
$len = strlen($sequence); |
193
|
|
|
++$i; |
194
|
|
|
|
195
|
|
|
// [foo, bar, ...] |
|
|
|
|
196
|
|
|
while ($i < $len) { |
197
|
|
|
if ($sequence[$i] === "[") { |
198
|
|
|
// nested sequence |
199
|
|
|
$output[] = self::parseSequence($sequence, $i, $references); |
200
|
|
|
} elseif ($sequence[$i] === "{") { |
201
|
|
|
// nested mapping |
202
|
|
|
$output[] = self::parseMapping($sequence, $i, $references); |
203
|
|
|
} elseif ($sequence[$i] === "]") { |
204
|
|
|
return $output; |
205
|
|
|
} elseif ($sequence[$i] !== "," && $sequence[$i] !== " ") { |
206
|
|
|
$isQuoted = in_array($sequence[$i], ["\"", "'"]); |
207
|
|
|
$value = self::parseScalar($sequence, [",", "]"], ["\"", "'"], $i, true, $references); |
|
|
|
|
208
|
|
|
|
209
|
|
|
// the value can be an array if a reference has been resolved to an array var |
210
|
|
|
if (!is_array($value) && !$isQuoted && strpos($value, ": ") !== false) { |
211
|
|
|
// embedded mapping? |
212
|
|
|
try { |
213
|
|
|
$pos = 0; |
214
|
|
|
$value = self::parseMapping("{" . $value . "}", $pos, $references); |
215
|
|
|
} catch (\InvalidArgumentException $e) { |
216
|
|
|
// no, it's not |
217
|
|
|
} |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
$output[] = $value; |
221
|
|
|
--$i; |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
++$i; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
throw new ParseException(sprintf("Malformed inline YAML string %s", $sequence)); |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
/** |
231
|
|
|
* Parses a mapping to a YAML string |
232
|
|
|
* |
233
|
|
|
* @param string $mapping |
234
|
|
|
* @param int &$i |
235
|
|
|
* @param array $references |
236
|
|
|
* |
237
|
|
|
* @throws ParseException When malformed inline YAML string is parsed |
238
|
|
|
* @return string A YAML string |
239
|
|
|
*/ |
240
|
|
|
protected static function parseMapping($mapping, &$i = 0, $references = []) |
241
|
|
|
{ |
242
|
|
|
$output = []; |
243
|
|
|
$len = strlen($mapping); |
244
|
|
|
++$i; |
245
|
|
|
|
246
|
|
|
// {foo: bar, bar:foo, ...} |
|
|
|
|
247
|
|
|
while ($i < $len) { |
248
|
|
|
if ($mapping[$i] === " " || $mapping[$i] === ",") { |
249
|
|
|
++$i; |
250
|
|
|
continue; |
251
|
|
|
} elseif ($mapping[$i] === "}") { |
252
|
|
|
return $output; |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
// key |
256
|
|
|
$key = self::parseScalar($mapping, [":", " "], ["\"", "'"], $i, false); |
|
|
|
|
257
|
|
|
|
258
|
|
|
// value |
259
|
|
|
$done = false; |
260
|
|
|
while ($i < $len) { |
261
|
|
|
if ($mapping[$i] === "[") { |
262
|
|
|
// nested sequence |
263
|
|
|
$output[$key] = self::parseSequence($mapping, $i, $references); |
264
|
|
|
$done = true; |
265
|
|
|
} elseif ($mapping[$i] === "{") { |
266
|
|
|
// nested mapping |
267
|
|
|
$output[$key] = self::parseMapping($mapping, $i, $references); |
268
|
|
|
$done = true; |
269
|
|
|
} elseif ($mapping[$i] !== ":" && $mapping[$i] !== " ") { |
270
|
|
|
$output[$key] = self::parseScalar($mapping, [",", "}"], ["\"", "'"], $i, true, $references); |
|
|
|
|
271
|
|
|
$done = true; |
272
|
|
|
--$i; |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
++$i; |
276
|
|
|
|
277
|
|
|
if ($done) { |
278
|
|
|
continue 2; |
279
|
|
|
} |
280
|
|
|
} |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
throw new ParseException(sprintf("Malformed inline YAML string %s", $mapping)); |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
/** |
287
|
|
|
* Evaluates scalars and replaces magic values |
288
|
|
|
* |
289
|
|
|
* @param string $scalar |
290
|
|
|
* @param array $references |
291
|
|
|
* |
292
|
|
|
* @throws ParseException when a reference could not be resolved |
293
|
|
|
* @return string A YAML string |
294
|
|
|
*/ |
295
|
|
|
protected static function evaluateScalar($scalar, $references = []) |
296
|
|
|
{ |
297
|
|
|
$scalar = trim($scalar); |
298
|
|
|
$scalarLower = strtolower($scalar); |
299
|
|
|
|
300
|
|
|
if (strpos($scalar, "*") === 0) { |
301
|
|
View Code Duplication |
if (($pos = strpos($scalar, "#")) !== false) { |
|
|
|
|
302
|
|
|
$value = substr($scalar, 1, $pos - 2); |
303
|
|
|
} else { |
304
|
|
|
$value = substr($scalar, 1); |
305
|
|
|
} |
306
|
|
|
|
307
|
|
|
// an unquoted * |
308
|
|
|
if ($value === false || $value === "") { |
309
|
|
|
throw new ParseException("A reference must contain at least one character."); |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
if (!array_key_exists($value, $references)) { |
313
|
|
|
throw new ParseException(sprintf("Reference \"%s\" does not exist.", $value)); |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
return $references[$value]; |
317
|
|
|
} |
318
|
|
|
|
319
|
|
|
if ($scalarLower === "null" || $scalar === "" || $scalar === "~") { |
320
|
|
|
return null; |
321
|
|
|
} elseif ($scalarLower === "true") { |
322
|
|
|
return true; |
|
|
|
|
323
|
|
|
} elseif ($scalarLower === "false") { |
324
|
|
|
return false; |
|
|
|
|
325
|
|
|
} elseif ($scalar[0] === "+" || $scalar[0] === "-" || $scalar[0] === "." || $scalar[0] === "!" || |
326
|
|
|
is_numeric($scalar[0])) { |
327
|
|
|
// Optimise for returning strings. |
328
|
|
|
if (strpos($scalar, "!str") === 0) { |
329
|
|
|
return (string)substr($scalar, 5); |
330
|
|
|
} elseif (strpos($scalar, "! ") === 0) { |
331
|
|
|
return (int)self::parseScalar(substr($scalar, 2)); |
332
|
|
|
} elseif (strpos($scalar, "!!php/object:") === 0) { |
333
|
|
|
return unserialize(substr($scalar, 13)); |
334
|
|
|
} elseif (strpos($scalar, "!!float ") === 0) { |
335
|
|
|
return (float)substr($scalar, 8); |
336
|
|
|
} elseif (ctype_digit($scalar)) { |
337
|
|
|
$raw = $scalar; |
338
|
|
|
$cast = (int)$scalar; |
339
|
|
|
|
340
|
|
|
return $scalar[0] == "0" ? octdec($scalar) : (((string)$raw == (string)$cast) ? $cast : $raw); |
341
|
|
|
} elseif ($scalar[0] === "-" && ctype_digit(substr($scalar, 1))) { |
342
|
|
|
$raw = $scalar; |
343
|
|
|
$cast = (int)$scalar; |
344
|
|
|
|
345
|
|
|
return $scalar[1] == "0" ? octdec($scalar) : (((string)$raw == (string)$cast) ? $cast : $raw); |
346
|
|
|
} elseif (is_numeric($scalar) || preg_match(self::getHexRegex(), $scalar)) { |
347
|
|
|
return $scalar[0] . $scalar[1] == "0x" ? hexdec($scalar) : (float)$scalar; |
348
|
|
|
} elseif ($scalarLower === ".inf" || $scalarLower === ".nan") { |
349
|
|
|
return -log(0); |
350
|
|
|
} elseif ($scalarLower === "-.inf") { |
351
|
|
|
return log(0); |
352
|
|
|
} elseif (preg_match("/^(-|\\+)?[0-9,]+(\\.[0-9]+)?$/", $scalar)) { |
353
|
|
|
return (float)str_replace(",", "", $scalar); |
354
|
|
|
} elseif (preg_match(self::getTimestampRegex(), $scalar)) { |
355
|
|
|
return strtotime($scalar); |
356
|
|
|
} |
357
|
|
|
} else { |
358
|
|
|
return (string)$scalar; |
359
|
|
|
} |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
/** |
363
|
|
|
* Gets a regex that matches a YAML date |
364
|
|
|
* |
365
|
|
|
* @return string The regular expression |
366
|
|
|
* |
367
|
|
|
* @see http://www.yaml.org/spec/1.2/spec.html#id2761573 |
368
|
|
|
*/ |
369
|
|
|
public static function getTimestampRegex() |
370
|
|
|
{ |
371
|
|
|
return <<<EOF |
372
|
|
|
~^ |
373
|
|
|
(?P<year>[0-9][0-9][0-9][0-9]) |
374
|
|
|
-(?P<month>[0-9][0-9]?) |
375
|
|
|
-(?P<day>[0-9][0-9]?) |
376
|
|
|
(?:(?:[Tt]|[ \t]+) |
377
|
|
|
(?P<hour>[0-9][0-9]?) |
378
|
|
|
:(?P<minute>[0-9][0-9]) |
379
|
|
|
:(?P<second>[0-9][0-9]) |
380
|
|
|
(?:\.(?P<fraction>[0-9]*))? |
381
|
|
|
(?:[ \t]*(?P<tz>Z|(?P<tz_sign>[-+])(?P<tz_hour>[0-9][0-9]?) |
382
|
|
|
(?::(?P<tz_minute>[0-9][0-9]))?))?)? |
383
|
|
|
$~x |
384
|
|
|
EOF; |
385
|
|
|
} |
386
|
|
|
|
387
|
|
|
/** |
388
|
|
|
* Gets a regex that matches a YAML number in hexadecimal notation. |
389
|
|
|
* |
390
|
|
|
* @return string |
391
|
|
|
*/ |
392
|
|
|
public static function getHexRegex() |
393
|
|
|
{ |
394
|
|
|
return "~^0x[0-9a-f]++$~i"; |
395
|
|
|
} |
396
|
|
|
} |
397
|
|
|
|
In PHP, under loose comparison (like
==
, or!=
, orswitch
conditions), values of different types might be equal.For
string
values, the empty string''
is a special case, in particular the following results might be unexpected: