11<?php
22/**
3- * YAML handler (last modified: 2019.12.26 ).
3+ * YAML handler (last modified: 2021.05.22 ).
44 *
55 * This file is a part of the "common classes package", utilised by a number of
66 * packages and projects, including CIDRAM and phpMussel.
99 * License: GNU/GPLv2
1010 * @see LICENSE.txt
1111 *
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.
1514 *
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.
2017 */
2118
2219namespace Maikuolan \Common ;
2320
2421class YAML
2522{
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+ */
2726 public $ Data = [];
2827
29- /** Flag used for rendering multi-line values. */
28+ /**
29+ * @var bool Whether to render multi-line values.
30+ */
3031 private $ MultiLine = false ;
3132
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+
3260 /**
3361 * Can optionally begin processing data as soon as the object is
3462 * instantiated, or just instantiate first, and manually make any needed
3563 * calls afterwards (though the former is recommended over the latter).
3664 *
3765 * @param string $In The data to process.
66+ * @return void
3867 */
39- public function __construct (string $ In = '' )
68+ public function __construct ($ In = '' )
4069 {
4170 if ($ In ) {
4271 $ this ->process ($ In , $ this ->Data );
@@ -49,9 +78,30 @@ public function __construct(string $In = '')
4978 * @param string|int|bool $Value The value to be normalised.
5079 * @param int $ValueLen The length of the value to be normalised.
5180 * @param string|int|bool $ValueLow The value to be normalised, lowercased.
81+ * @return void
5282 */
53- private function normaliseValue (&$ Value , int $ ValueLen , $ ValueLow )
83+ private function normaliseValue (&$ Value , $ ValueLen , $ ValueLow )
5484 {
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. */
55105 foreach ([
56106 ['" ' , '" ' , 1 ],
57107 ["' " , "' " , 1 ],
@@ -66,15 +116,18 @@ private function normaliseValue(&$Value, int $ValueLen, $ValueLow)
66116 return ;
67117 }
68118 }
69- if ($ ValueLow === 'true ' || $ ValueLow === 'y ' ) {
119+
120+ if ($ ValueLow === 'true ' || $ ValueLow === 'y ' || $ Value === '+ ' ) {
70121 $ Value = true ;
71- } elseif ($ ValueLow === 'false ' || $ ValueLow === 'n ' ) {
122+ } elseif ($ ValueLow === 'false ' || $ ValueLow === 'n ' || $ Value === ' - ' ) {
72123 $ Value = false ;
124+ } elseif ($ ValueLow === 'null ' || $ Value === '~ ' ) {
125+ $ Value = null ;
73126 } elseif (substr ($ Value , 0 , 2 ) === '0x ' && ($ HexTest = substr ($ Value , 2 )) && !preg_match ('/[^\da-f]/i ' , $ HexTest ) && !($ ValueLen % 2 )) {
74127 $ Value = hex2bin ($ HexTest );
75128 } elseif (preg_match ('~^\d+$~ ' , $ Value )) {
76129 $ Value = (int )$ Value ;
77- } elseif (preg_match ('~^\d+\.\d+$~ ' , $ Value )) {
130+ } elseif (preg_match ('~^(?: \d+\.\d+|\d+(?:\.\d+)?[Ee][-+]\d+) $~ ' , $ Value )) {
78131 $ Value = (float )$ Value ;
79132 } elseif (!$ ValueLen ) {
80133 $ Value = false ;
@@ -89,13 +142,14 @@ private function normaliseValue(&$Value, int $ValueLen, $ValueLow)
89142 * @param int $Depth Tab depth (inherited through recursion; ignore it).
90143 * @return bool True when entire process completes successfully. False to exit early.
91144 */
92- public function process (string $ In , array &$ Arr , int $ Depth = 0 ): bool
145+ public function process ($ In , array &$ Arr , $ Depth = 0 )
93146 {
94- if (strpos ($ In , "\n" ) === false ) {
147+ if (! is_string ( $ In ) || strpos ($ In , "\n" ) === false ) {
95148 return false ;
96149 }
97150 if ($ Depth === 0 ) {
98151 $ this ->MultiLine = false ;
152+ $ this ->MultiLineFolded = false ;
99153 }
100154 $ In = str_replace ("\r" , '' , $ In );
101155 $ Key = $ Value = $ SendTo = '' ;
@@ -117,11 +171,15 @@ public function process(string $In, array &$Arr, int $Depth = 0): bool
117171 if ($ TabLen === 0 ) {
118172 $ TabLen = $ ThisTab ;
119173 }
120- if (!$ this ->MultiLine ) {
174+ if (!$ this ->MultiLine && ! $ this -> MultiLineFolded ) {
121175 $ SendTo .= $ ThisLine . "\n" ;
122176 } else {
123177 if ($ SendTo ) {
124- $ SendTo .= "\n" ;
178+ if ($ this ->MultiLine ) {
179+ $ SendTo .= "\n" ;
180+ } elseif (substr ($ ThisLine , $ TabLen , 1 ) !== ' ' && substr ($ SendTo , -1 ) !== ' ' ) {
181+ $ SendTo .= ' ' ;
182+ }
125183 }
126184 $ SendTo .= substr ($ ThisLine , $ TabLen );
127185 }
@@ -132,7 +190,7 @@ public function process(string $In, array &$Arr, int $Depth = 0): bool
132190 if (empty ($ Key )) {
133191 return false ;
134192 }
135- if (!$ this ->MultiLine ) {
193+ if (!$ this ->MultiLine && ! $ this -> MultiLineFolded ) {
136194 if (!isset ($ Arr [$ Key ]) || !is_array ($ Arr [$ Key ])) {
137195 $ Arr [$ Key ] = [];
138196 }
@@ -149,7 +207,7 @@ public function process(string $In, array &$Arr, int $Depth = 0): bool
149207 }
150208 }
151209 if ($ SendTo && !empty ($ Key )) {
152- if (!$ this ->MultiLine ) {
210+ if (!$ this ->MultiLine && ! $ this -> MultiLineFolded ) {
153211 if (!isset ($ Arr [$ Key ]) || !is_array ($ Arr [$ Key ])) {
154212 $ Arr [$ Key ] = [];
155213 }
@@ -173,9 +231,13 @@ public function process(string $In, array &$Arr, int $Depth = 0): bool
173231 * @param array $Arr Where to store the data.
174232 * @return bool True when entire process completes successfully. False to exit early.
175233 */
176- private function processLine (string &$ ThisLine , int &$ ThisTab , &$ Key , &$ Value , array &$ Arr ): bool
234+ private function processLine (&$ ThisLine , &$ ThisTab , &$ Key , &$ Value , array &$ Arr )
177235 {
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 ) {
179241 $ Key = substr ($ ThisLine , $ ThisTab , -1 );
180242 $ KeyLen = strlen ($ Key );
181243 $ KeyLow = strtolower ($ Key );
@@ -227,6 +289,7 @@ private function processLine(string &$ThisLine, int &$ThisTab, &$Key, &$Value, a
227289 $ Value = false ;
228290 }
229291 $ this ->MultiLine = ($ Value === '| ' );
292+ $ this ->MultiLineFolded = ($ Value === '> ' );
230293 return true ;
231294 }
232295
@@ -236,35 +299,42 @@ private function processLine(string &$ThisLine, int &$ThisTab, &$Key, &$Value, a
236299 * @param array $Arr The array to reconstruct from.
237300 * @param string $Out The reconstructed YAML.
238301 * @param int $Depth The level depth.
302+ * @return void
239303 */
240- private function processInner (array $ Arr , string &$ Out , int $ Depth = 0 )
304+ private function processInner (array $ Arr , &$ Out , $ Depth = 0 )
241305 {
242306 $ Sequential = (array_keys ($ Arr ) === range (0 , count ($ Arr ) - 1 ));
243307 foreach ($ Arr as $ Key => $ Value ) {
244308 if ($ Key === '--- ' && $ Value === false ) {
245309 $ Out .= "--- \n" ;
246310 continue ;
247311 }
248- $ ThisDepth = str_repeat (' ' , $ Depth );
312+ $ ThisDepth = str_repeat ($ this -> Indent , $ Depth );
249313 $ Out .= $ ThisDepth . ($ Sequential ? '- ' : $ Key . ': ' );
250314 if (is_array ($ Value )) {
251315 $ Out .= "\n" ;
252316 $ this ->processInner ($ Value , $ Out , $ Depth + 1 );
253317 continue ;
254- } else {
255- $ Out .= ' ' ;
256318 }
319+ $ Out .= $ this ->Indent ;
257320 if ($ Value === true ) {
258321 $ Out .= 'true ' ;
259322 } elseif ($ Value === false ) {
260323 $ Out .= 'false ' ;
324+ } elseif ($ Value === null ) {
325+ $ Out .= 'null ' ;
261326 } elseif (preg_match ('~[^\t\n\r\x20-\x7e\xa0-\xff]~ ' , $ Value )) {
262327 $ Out .= '0x ' . strtolower (bin2hex ($ Value ));
263328 } 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 ;
266331 } 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+ }
268338 } else {
269339 $ Out .= $ Value ;
270340 }
@@ -278,10 +348,20 @@ private function processInner(array $Arr, string &$Out, int $Depth = 0)
278348 * @param array $Arr The array to reconstruct from.
279349 * @return string The reconstructed YAML.
280350 */
281- public function reconstruct (array $ Arr ): string
351+ public function reconstruct (array $ Arr )
282352 {
283353 $ Out = '' ;
284354 $ this ->processInner ($ Arr , $ Out );
285355 return $ Out . "\n" ;
286356 }
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+ }
287367}
0 commit comments