1
1
<?php
2
2
/**
3
- * YAML handler (last modified: 2019.12.26 ).
3
+ * YAML handler (last modified: 2021.05.22 ).
4
4
*
5
5
* This file is a part of the "common classes package", utilised by a number of
6
6
* packages and projects, including CIDRAM and phpMussel.
9
9
* License: GNU/GPLv2
10
10
* @see LICENSE.txt
11
11
*
12
- * "COMMON CLASSES PACKAGE" COPYRIGHT 2019 and beyond by Caleb Mazalevskis
13
- * (Maikuolan). Earliest iteration and deployment of "YAML handler" COPYRIGHT
14
- * 2016 and beyond by Caleb Mazalevskis (Maikuolan).
12
+ * "COMMON CLASSES PACKAGE" COPYRIGHT 2019 and beyond by Caleb Mazalevskis.
13
+ * *This particular class*, COPYRIGHT 2016 and beyond by Caleb Mazalevskis.
15
14
*
16
- * Note: The YAML handler is intended to adequately serve the needs of the
17
- * packages and projects where it is implemented, but isn't a complete YAML
18
- * solution, instead supporting the YAML specification only to the bare minimum
19
- * required by those packages and projects known to implement it.
15
+ * Note: Some parts of the YAML specification aren't supported by this class.
16
+ * See the included documentation for more information.
20
17
*/
21
18
22
19
namespace Maikuolan \Common ;
23
20
24
21
class YAML
25
22
{
26
- /** An array to contain all the data processed by the handler. */
23
+ /**
24
+ * @var array An array to contain all the data processed by the handler.
25
+ */
27
26
public $ Data = [];
28
27
29
- /** Flag used for rendering multi-line values. */
28
+ /**
29
+ * @var bool Whether to render multi-line values.
30
+ */
30
31
private $ MultiLine = false ;
31
32
33
+ /**
34
+ * @var bool Whether to render folded multi-line values.
35
+ */
36
+ private $ MultiLineFolded = false ;
37
+
38
+ /**
39
+ * @var string Default indent to use when reconstructing YAML data.
40
+ */
41
+ public $ Indent = ' ' ;
42
+
43
+ /**
44
+ * @var int Single line to folded multi-line string length limit.
45
+ */
46
+ public $ FoldedAt = 120 ;
47
+
48
+ /**
49
+ * @var array Used to cache any anchors found in the document.
50
+ */
51
+ public $ Anchors = [];
52
+
53
+ /**
54
+ * @var string The tag/release the version of this file belongs to (might
55
+ * be needed by some implementations to ensure compatibility).
56
+ * @link https://github.com/Maikuolan/Common/tags
57
+ */
58
+ const VERSION = '1.6.1 ' ;
59
+
32
60
/**
33
61
* Can optionally begin processing data as soon as the object is
34
62
* instantiated, or just instantiate first, and manually make any needed
35
63
* calls afterwards (though the former is recommended over the latter).
36
64
*
37
65
* @param string $In The data to process.
66
+ * @return void
38
67
*/
39
- public function __construct (string $ In = '' )
68
+ public function __construct ($ In = '' )
40
69
{
41
70
if ($ In ) {
42
71
$ this ->process ($ In , $ this ->Data );
@@ -49,9 +78,30 @@ public function __construct(string $In = '')
49
78
* @param string|int|bool $Value The value to be normalised.
50
79
* @param int $ValueLen The length of the value to be normalised.
51
80
* @param string|int|bool $ValueLow The value to be normalised, lowercased.
81
+ * @return void
52
82
*/
53
- private function normaliseValue (&$ Value , int $ ValueLen , $ ValueLow )
83
+ private function normaliseValue (&$ Value , $ ValueLen , $ ValueLow )
54
84
{
85
+ /** Check for anchors and populate if necessary. */
86
+ $ AnchorMatches = [];
87
+ if (
88
+ preg_match ('~^&([\dA-Za-z]+) +(.*)$~ ' , $ Value , $ AnchorMatches ) &&
89
+ isset ($ AnchorMatches [1 ], $ AnchorMatches [2 ])
90
+ ) {
91
+ $ Value = $ AnchorMatches [2 ];
92
+ $ this ->Anchors [$ AnchorMatches [1 ]] = $ Value ;
93
+ $ ValueLen = strlen ($ Value );
94
+ $ ValueLow = strtolower ($ Value );
95
+ } elseif (
96
+ preg_match ('~^\*([\dA-Za-z]+)$~ ' , $ Value , $ AnchorMatches ) &&
97
+ isset ($ AnchorMatches [1 ], $ this ->Anchors [$ AnchorMatches [1 ]])
98
+ ) {
99
+ $ Value = $ this ->Anchors [$ AnchorMatches [1 ]];
100
+ $ ValueLen = strlen ($ Value );
101
+ $ ValueLow = strtolower ($ Value );
102
+ }
103
+
104
+ /** Check for string quotes. */
55
105
foreach ([
56
106
['" ' , '" ' , 1 ],
57
107
["' " , "' " , 1 ],
@@ -66,15 +116,18 @@ private function normaliseValue(&$Value, int $ValueLen, $ValueLow)
66
116
return ;
67
117
}
68
118
}
69
- if ($ ValueLow === 'true ' || $ ValueLow === 'y ' ) {
119
+
120
+ if ($ ValueLow === 'true ' || $ ValueLow === 'y ' || $ Value === '+ ' ) {
70
121
$ Value = true ;
71
- } elseif ($ ValueLow === 'false ' || $ ValueLow === 'n ' ) {
122
+ } elseif ($ ValueLow === 'false ' || $ ValueLow === 'n ' || $ Value === ' - ' ) {
72
123
$ Value = false ;
124
+ } elseif ($ ValueLow === 'null ' || $ Value === '~ ' ) {
125
+ $ Value = null ;
73
126
} elseif (substr ($ Value , 0 , 2 ) === '0x ' && ($ HexTest = substr ($ Value , 2 )) && !preg_match ('/[^\da-f]/i ' , $ HexTest ) && !($ ValueLen % 2 )) {
74
127
$ Value = hex2bin ($ HexTest );
75
128
} elseif (preg_match ('~^\d+$~ ' , $ Value )) {
76
129
$ Value = (int )$ Value ;
77
- } elseif (preg_match ('~^\d+\.\d+$~ ' , $ Value )) {
130
+ } elseif (preg_match ('~^(?: \d+\.\d+|\d+(?:\.\d+)?[Ee][-+]\d+) $~ ' , $ Value )) {
78
131
$ Value = (float )$ Value ;
79
132
} elseif (!$ ValueLen ) {
80
133
$ Value = false ;
@@ -89,13 +142,14 @@ private function normaliseValue(&$Value, int $ValueLen, $ValueLow)
89
142
* @param int $Depth Tab depth (inherited through recursion; ignore it).
90
143
* @return bool True when entire process completes successfully. False to exit early.
91
144
*/
92
- public function process (string $ In , array &$ Arr , int $ Depth = 0 ): bool
145
+ public function process ($ In , array &$ Arr , $ Depth = 0 )
93
146
{
94
- if (strpos ($ In , "\n" ) === false ) {
147
+ if (! is_string ( $ In ) || strpos ($ In , "\n" ) === false ) {
95
148
return false ;
96
149
}
97
150
if ($ Depth === 0 ) {
98
151
$ this ->MultiLine = false ;
152
+ $ this ->MultiLineFolded = false ;
99
153
}
100
154
$ In = str_replace ("\r" , '' , $ In );
101
155
$ Key = $ Value = $ SendTo = '' ;
@@ -117,11 +171,15 @@ public function process(string $In, array &$Arr, int $Depth = 0): bool
117
171
if ($ TabLen === 0 ) {
118
172
$ TabLen = $ ThisTab ;
119
173
}
120
- if (!$ this ->MultiLine ) {
174
+ if (!$ this ->MultiLine && ! $ this -> MultiLineFolded ) {
121
175
$ SendTo .= $ ThisLine . "\n" ;
122
176
} else {
123
177
if ($ SendTo ) {
124
- $ SendTo .= "\n" ;
178
+ if ($ this ->MultiLine ) {
179
+ $ SendTo .= "\n" ;
180
+ } elseif (substr ($ ThisLine , $ TabLen , 1 ) !== ' ' && substr ($ SendTo , -1 ) !== ' ' ) {
181
+ $ SendTo .= ' ' ;
182
+ }
125
183
}
126
184
$ SendTo .= substr ($ ThisLine , $ TabLen );
127
185
}
@@ -132,7 +190,7 @@ public function process(string $In, array &$Arr, int $Depth = 0): bool
132
190
if (empty ($ Key )) {
133
191
return false ;
134
192
}
135
- if (!$ this ->MultiLine ) {
193
+ if (!$ this ->MultiLine && ! $ this -> MultiLineFolded ) {
136
194
if (!isset ($ Arr [$ Key ]) || !is_array ($ Arr [$ Key ])) {
137
195
$ Arr [$ Key ] = [];
138
196
}
@@ -149,7 +207,7 @@ public function process(string $In, array &$Arr, int $Depth = 0): bool
149
207
}
150
208
}
151
209
if ($ SendTo && !empty ($ Key )) {
152
- if (!$ this ->MultiLine ) {
210
+ if (!$ this ->MultiLine && ! $ this -> MultiLineFolded ) {
153
211
if (!isset ($ Arr [$ Key ]) || !is_array ($ Arr [$ Key ])) {
154
212
$ Arr [$ Key ] = [];
155
213
}
@@ -173,9 +231,13 @@ public function process(string $In, array &$Arr, int $Depth = 0): bool
173
231
* @param array $Arr Where to store the data.
174
232
* @return bool True when entire process completes successfully. False to exit early.
175
233
*/
176
- private function processLine (string &$ ThisLine , int &$ ThisTab , &$ Key , &$ Value , array &$ Arr ): bool
234
+ private function processLine (&$ ThisLine , &$ ThisTab , &$ Key , &$ Value , array &$ Arr )
177
235
{
178
- if (substr ($ ThisLine , -1 ) === ': ' && strpos ($ ThisLine , ': ' ) === false ) {
236
+ if ($ ThisLine === '--- ' ) {
237
+ $ Key = '--- ' ;
238
+ $ Value = false ;
239
+ $ Arr [$ Key ] = $ Value ;
240
+ } elseif (substr ($ ThisLine , -1 ) === ': ' && strpos ($ ThisLine , ': ' ) === false ) {
179
241
$ Key = substr ($ ThisLine , $ ThisTab , -1 );
180
242
$ KeyLen = strlen ($ Key );
181
243
$ KeyLow = strtolower ($ Key );
@@ -227,6 +289,7 @@ private function processLine(string &$ThisLine, int &$ThisTab, &$Key, &$Value, a
227
289
$ Value = false ;
228
290
}
229
291
$ this ->MultiLine = ($ Value === '| ' );
292
+ $ this ->MultiLineFolded = ($ Value === '> ' );
230
293
return true ;
231
294
}
232
295
@@ -236,35 +299,42 @@ private function processLine(string &$ThisLine, int &$ThisTab, &$Key, &$Value, a
236
299
* @param array $Arr The array to reconstruct from.
237
300
* @param string $Out The reconstructed YAML.
238
301
* @param int $Depth The level depth.
302
+ * @return void
239
303
*/
240
- private function processInner (array $ Arr , string &$ Out , int $ Depth = 0 )
304
+ private function processInner (array $ Arr , &$ Out , $ Depth = 0 )
241
305
{
242
306
$ Sequential = (array_keys ($ Arr ) === range (0 , count ($ Arr ) - 1 ));
243
307
foreach ($ Arr as $ Key => $ Value ) {
244
308
if ($ Key === '--- ' && $ Value === false ) {
245
309
$ Out .= "--- \n" ;
246
310
continue ;
247
311
}
248
- $ ThisDepth = str_repeat (' ' , $ Depth );
312
+ $ ThisDepth = str_repeat ($ this -> Indent , $ Depth );
249
313
$ Out .= $ ThisDepth . ($ Sequential ? '- ' : $ Key . ': ' );
250
314
if (is_array ($ Value )) {
251
315
$ Out .= "\n" ;
252
316
$ this ->processInner ($ Value , $ Out , $ Depth + 1 );
253
317
continue ;
254
- } else {
255
- $ Out .= ' ' ;
256
318
}
319
+ $ Out .= $ this ->Indent ;
257
320
if ($ Value === true ) {
258
321
$ Out .= 'true ' ;
259
322
} elseif ($ Value === false ) {
260
323
$ Out .= 'false ' ;
324
+ } elseif ($ Value === null ) {
325
+ $ Out .= 'null ' ;
261
326
} elseif (preg_match ('~[^\t\n\r\x20-\x7e\xa0-\xff]~ ' , $ Value )) {
262
327
$ Out .= '0x ' . strtolower (bin2hex ($ Value ));
263
328
} elseif (strpos ($ Value , "\n" ) !== false ) {
264
- $ Value = str_replace ("\n" , "\n" . $ ThisDepth . ' ' , $ Value );
265
- $ Out .= "| \n" . $ ThisDepth . ' ' . $ Value ;
329
+ $ Value = str_replace ("\n" , "\n" . $ ThisDepth . $ this -> Indent , $ Value );
330
+ $ Out .= "| \n" . $ ThisDepth . $ this -> Indent . $ Value ;
266
331
} elseif (is_string ($ Value )) {
267
- $ Out .= '" ' . $ Value . '" ' ;
332
+ if (strpos ($ Value , ' ' ) !== false && strlen ($ Value ) >= $ this ->FoldedAt ) {
333
+ $ Value = wordwrap ($ Value , $ this ->FoldedAt , "\n" . $ ThisDepth . $ this ->Indent );
334
+ $ Out .= "> \n" . $ ThisDepth . $ this ->Indent . $ Value ;
335
+ } else {
336
+ $ Out .= '" ' . $ Value . '" ' ;
337
+ }
268
338
} else {
269
339
$ Out .= $ Value ;
270
340
}
@@ -278,10 +348,20 @@ private function processInner(array $Arr, string &$Out, int $Depth = 0)
278
348
* @param array $Arr The array to reconstruct from.
279
349
* @return string The reconstructed YAML.
280
350
*/
281
- public function reconstruct (array $ Arr ): string
351
+ public function reconstruct (array $ Arr )
282
352
{
283
353
$ Out = '' ;
284
354
$ this ->processInner ($ Arr , $ Out );
285
355
return $ Out . "\n" ;
286
356
}
357
+
358
+ /**
359
+ * PHP's magic "__toString" method to act as an alias for "reconstruct".
360
+ *
361
+ * @return string
362
+ */
363
+ public function __toString ()
364
+ {
365
+ return $ this ->reconstruct ($ this ->Data );
366
+ }
287
367
}
0 commit comments