1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Dallgoot\Yaml; |
4
|
|
|
|
5
|
|
|
use Dallgoot\Yaml\Nodes\NodeGeneric; |
6
|
|
|
use Dallgoot\Yaml\NodeList; |
7
|
|
|
use Dallgoot\Yaml\Nodes\Root; |
8
|
|
|
use Dallgoot\Yaml\Nodes\DocEnd; |
9
|
|
|
use Dallgoot\Yaml\Nodes\DocStart; |
10
|
|
|
|
11
|
|
|
/** |
12
|
|
|
* Constructs the result (YamlObject or array) according to every Nodes and their values |
13
|
|
|
* |
14
|
|
|
* @author Stéphane Rebai <[email protected]> |
15
|
|
|
* @license Apache 2.0 |
16
|
|
|
* @link https://github.com/dallgoot/yaml |
17
|
|
|
*/ |
18
|
|
|
final class Builder |
19
|
|
|
{ |
20
|
|
|
/** @var boolean */ |
21
|
|
|
public static $dateAsObject = false; |
22
|
|
|
|
23
|
|
|
private static $_debug; |
24
|
|
|
|
25
|
|
|
const INVALID_DOCUMENT = "DOCUMENT %d is invalid,"; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* Builds a file. check multiple documents & split if more than one documents |
29
|
|
|
* |
30
|
|
|
* @param Root $root The NodeRoot node |
31
|
|
|
* @param int $_debug the level of debugging requested |
32
|
|
|
* |
33
|
|
|
* @return array|YamlObject|null list of documents or just one. |
34
|
|
|
*/ |
35
|
2 |
|
public static function buildContent(Root $root, int $_debug = 0) |
36
|
|
|
{ |
37
|
2 |
|
if ($_debug === 2) { |
38
|
1 |
|
print_r($root); |
39
|
1 |
|
return null; |
40
|
|
|
} |
41
|
2 |
|
self::$_debug = $_debug; |
42
|
2 |
|
$documents = []; |
43
|
2 |
|
$buffer = new NodeList(); |
44
|
|
|
try { |
45
|
2 |
|
foreach ($root->value as $child) { |
46
|
1 |
|
if ($child instanceof DocEnd && $child !== $root->value->top()) { |
47
|
1 |
|
self::pushAndSave($child, $buffer, $documents); |
48
|
1 |
|
} elseif ($child instanceof DocStart && $buffer->count() > 0 && $buffer->hasContent()) { |
49
|
1 |
|
self::saveAndPush($child, $buffer, $documents); |
50
|
|
|
} else { |
51
|
1 |
|
$buffer->push($child); |
52
|
|
|
} |
53
|
|
|
} |
54
|
1 |
|
$documents[] = self::buildDocument($buffer, count($documents) +1); |
55
|
1 |
|
} catch (\Exception|\Error|\ParseError $e) { |
56
|
1 |
|
throw new \Exception($e->getMessage(), 1, $e); |
57
|
|
|
} |
58
|
1 |
|
return count($documents) === 1 ? $documents[0] : $documents; |
59
|
|
|
} |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* Builds the tree of Node (NodeList) for this document |
63
|
|
|
* |
64
|
|
|
* @param NodeList $list the list of nodes that constitutes the current document |
65
|
|
|
* @param int $docNum the index (starts @ 0) of this document in the whole YAML content provided to self::buildContent |
66
|
|
|
* |
67
|
|
|
* @return YamlObject the YAML document as an object |
68
|
|
|
*/ |
69
|
6 |
|
public static function buildDocument(NodeList &$list, int $docNum):YamlObject |
70
|
|
|
{ |
71
|
6 |
|
$yamlObject = new YamlObject; |
72
|
6 |
|
$rootNode = new Root(); |
73
|
6 |
|
$list->setIteratorMode(NodeList::IT_MODE_DELETE); |
74
|
|
|
try { |
75
|
6 |
|
foreach ($list as $child) { |
76
|
5 |
|
$rootNode->add($child); |
77
|
|
|
} |
78
|
5 |
|
if (self::$_debug === 3) { |
79
|
1 |
|
echo "Document #$docNum\n"; |
80
|
1 |
|
print_r($rootNode); |
81
|
|
|
} |
82
|
5 |
|
return $rootNode->build($yamlObject); |
83
|
1 |
|
} catch (\Exception|\Error|\ParseError $e) { |
84
|
1 |
|
throw new \ParseError(sprintf(self::INVALID_DOCUMENT, $docNum).':'.$e->getMessage(), 2, $e); |
85
|
|
|
} |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* Returns the correct PHP type according to the string value |
90
|
|
|
* |
91
|
|
|
* @param string $v a string value |
92
|
|
|
* |
93
|
|
|
* @return mixed The value with appropriate PHP type |
94
|
|
|
* @throws \Exception if it happens in Regex::isDate or Regex::isNumber |
95
|
|
|
* @todo implement date as DateTime Object |
96
|
|
|
*/ |
97
|
4 |
|
public static function getScalar(string $v, bool $onlyScalar = false) |
98
|
|
|
{ |
99
|
|
|
/* |
100
|
|
|
10.3.2. Tag Resolution |
101
|
|
|
|
102
|
|
|
The core schema tag resolution is an extension of the JSON schema tag resolution. |
103
|
|
|
|
104
|
|
|
All nodes with the “!” non-specific tag are resolved, by the standard convention, to “tag:yaml.org,2002:seq”, “tag:yaml.org,2002:map”, or “tag:yaml.org,2002:str”, according to their kind. |
105
|
|
|
|
106
|
|
|
Collections with the “?” non-specific tag (that is, untagged collections) are resolved to “tag:yaml.org,2002:seq” or “tag:yaml.org,2002:map” according to their kind. |
107
|
|
|
|
108
|
|
|
Scalars with the “?” non-specific tag (that is, plain scalars) are matched with an extended list of regular expressions. However, in this case, if none of the regular expressions matches, the scalar is resolved to tag:yaml.org,2002:str (that is, considered to be a string). |
109
|
|
|
Regular expression Resolved to tag |
110
|
|
|
null | Null | NULL | ~ tag:yaml.org,2002:null |
111
|
|
|
Empty tag:yaml.org,2002:null |
112
|
|
|
true | True | TRUE | false | False | FALSE tag:yaml.org,2002:bool |
113
|
|
|
[-+]? [0-9]+ tag:yaml.org,2002:int (Base 10) |
114
|
|
|
0o [0-7]+ tag:yaml.org,2002:int (Base 8) |
115
|
|
|
0x [0-9a-fA-F]+ tag:yaml.org,2002:int (Base 16) |
116
|
|
|
[-+]? ( \. [0-9]+ | [0-9]+ ( \. [0-9]* )? ) ( [eE] [-+]? [0-9]+ )? tag:yaml.org,2002:float (Number) |
117
|
|
|
[-+]? ( \.inf | \.Inf | \.INF ) tag:yaml.org,2002:float (Infinity) |
118
|
|
|
\.nan | \.NaN | \.NAN tag:yaml.org,2002:float (Not a number) |
119
|
|
|
* tag:yaml.org,2002:str (Default) |
120
|
|
|
*/ |
121
|
4 |
|
if (Regex::isDate($v)) return self::$dateAsObject && !$onlyScalar ? date_create($v) : $v; |
122
|
4 |
|
if (Regex::isNumber($v)) return self::getNumber($v); |
123
|
4 |
|
$types = ['yes' => true, |
124
|
|
|
'no' => false, |
125
|
|
|
'true' => true, |
126
|
|
|
'false' => false, |
127
|
|
|
'null' => null, |
128
|
|
|
'.inf' => \INF, |
129
|
|
|
'-.inf' => -\INF, |
130
|
|
|
'.nan' => \NAN |
131
|
|
|
]; |
132
|
4 |
|
return array_key_exists(strtolower($v), $types) ? $types[strtolower($v)] : $v; |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* Returns the correct PHP type according to the string value |
137
|
|
|
* |
138
|
|
|
* @param string $v a string value |
139
|
|
|
* |
140
|
|
|
* @return int|float The scalar value with appropriate PHP type |
141
|
|
|
* @todo or scientific notation matching the regular expression -? [1-9] ( \. [0-9]* [1-9] )? ( e [-+] [1-9] [0-9]* )? |
142
|
|
|
*/ |
143
|
1 |
|
private static function getNumber(string $v) |
144
|
|
|
{ |
145
|
1 |
|
if ((bool) preg_match(Regex::OCTAL_NUM, $v)) return intval(base_convert($v, 8, 10)); |
146
|
1 |
|
if ((bool) preg_match(Regex::HEX_NUM, $v)) return intval(base_convert($v, 16, 10)); |
147
|
1 |
|
return is_bool(strpos($v, '.')) || substr_count($v, '.') > 1 ? intval($v) : floatval($v); |
148
|
|
|
} |
149
|
|
|
|
150
|
2 |
|
public static function pushAndSave(NodeGeneric $child, NodeList &$buffer, array &$documents) |
151
|
|
|
{ |
152
|
2 |
|
$buffer->push($child); |
153
|
2 |
|
$documents[] = self::buildDocument($buffer, count($documents) + 1); |
154
|
2 |
|
$buffer = new NodeList(); |
155
|
2 |
|
} |
156
|
|
|
|
157
|
2 |
|
public static function saveAndPush(NodeGeneric $child, NodeList &$buffer, array &$documents) |
158
|
|
|
{ |
159
|
2 |
|
$documents[] = self::buildDocument($buffer, count($documents) + 1); |
160
|
2 |
|
$buffer = new NodeList($child); |
161
|
2 |
|
} |
162
|
|
|
|
163
|
|
|
|
164
|
|
|
} |
165
|
|
|
|