-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathXml.hh
More file actions
261 lines (215 loc) · 7.4 KB
/
Xml.hh
File metadata and controls
261 lines (215 loc) · 7.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
<?hh // strict
/**
* @copyright 2010-2015, The Titon Project
* @license http://opensource.org/licenses/bsd-license.php
* @link http://titon.io
*/
namespace Titon\Type;
use Titon\Common\Exception\MissingFileException;
use Titon\Type\Xml\Element;
use Titon\Type\Xml\XmlMap;
use Titon\Utility\Col;
use \SimpleXMLElement;
/**
* The Xml class provides helper methods for XML parsing and building as well as static methods
* for generating Element trees based on maps and raw XML files.
*
* @package Titon\Type
*/
class Xml {
/**
* Box a value by type casting it from a string to a scalar.
*
* @param string $value
* @return mixed
*/
public static function box(string $value): mixed {
if (is_numeric($value)) {
if (strpos($value, '.') !== false) {
return (float) $value;
} else {
return (int) $value;
}
} else if ($value === 'true' || $value === 'false') {
return ($value === 'true');
} else if ($value === 'null') {
return null;
}
return (string) $value;
}
/**
* Unbox values by type casting to a string equivalent.
*
* @param mixed $value
* @return string
*/
public static function unbox(mixed $value): string {
if (is_bool($value)) {
return $value ? 'true' : 'false';
} else if ($value === null) {
return 'null';
}
return (string) $value;
}
/**
* Format an element or attribute name for converting to camel case.
* If the element starts with a number, prefix it with an underscore.
*
* @return string
*/
public static function formatName(string $name): string {
$name = preg_replace('/[^a-z0-9:\.\-_]+/i', '', $name);
$firstChar = mb_substr($name, 0, 1);
if (is_numeric($firstChar) || $firstChar === '-' || $firstChar === '.') {
$name = '_' . $name;
}
return $name;
}
/**
* Depending on the type of data, return an Element tree.
*
* @param mixed $data
* @param string $root
* @return \Titon\Type\Xml\Element
*/
public static function from(mixed $data, string $root = 'root'): Element {
if ($data instanceof Map) {
return static::fromMap($root, $data);
} else if ($data instanceof Vector) {
return static::fromVector($root, 'item', $data);
} else if (is_array($data)) {
return static::fromMap($root, Col::toMap($data));
}
return static::fromString((string) $data);
}
/**
* Load an XML file from the file system and transform it into an Element tree.
*
* @param string $path
* @return \Titon\Type\Xml\Element
* @throws \Titon\Common\Exception\MissingFileException
*/
public static function fromFile(string $path): Element {
if (file_exists($path)) {
return static::fromString(file_get_contents($path));
}
throw new MissingFileException(sprintf('File %s does not exist', $path));
}
/**
* Transform a structure consisting of maps and vectors into an Element tree.
*
* @param string $root
* @param \Titon\Type\XmlMap $map
* @return \Titon\Type\Xml\Element
*/
public static function fromMap(string $root, XmlMap $map): Element {
$root = new Element($root);
static::addAttributes($root, $map);
foreach ($map as $key => $value) {
static::createElement($root, $key, $value);
}
return $root;
}
/**
* Transform a string representation of an XML document into an Element tree.
*
* @param string $string
* @return \Titon\Type\Xml\Element
*/
public static function fromString(string $string): Element {
return static::convertSimpleXml(simplexml_load_string($string));
}
/**
* Transform a list of items into an Element tree.
*
* @param string $root
* @param string $item
* @param Vector<Tv> $list
* @return \Titon\Type\Xml\Element
*/
public static function fromVector<Tv>(string $root, string $item, Vector<Tv> $list): Element {
$root = new Element($root);
static::createElement($root, $item, $list);
return $root;
}
/**
* Add attributes to an element if the special `@attributes` map exists.
*
* @param \Titon\Type\Xml\Element $element
* @param \Titon\Type\XmlMap $map
*/
protected static function addAttributes(Element $element, XmlMap $map): void {
if ($map->contains('@attributes')) {
$attributes = $map->get('@attributes');
if ($attributes instanceof Map) {
$element->setAttributes($attributes);
}
$map->remove('@attributes');
}
}
/**
* Create an element and set the value/children depending on the data structure.
*
* - If a map is provided, it is either an element with a value, or a list of children with different names.
* - If a vector is provided, it is a list of elements with the same name.
* - If a scalar value is provided, it is a literal element with a value.
*
* @param \Titon\Type\Xml\Element $parent
* @param string $key
* @param mixed $value
*/
protected static function createElement(Element $parent, string $key, mixed $value): void {
// One of two things:
// An element that contains other children
// An element that has attributes or a value
if ($value instanceof Map) {
// An element with a value
if ($value->contains('@value')) {
$child = new Element($key);
$child->setValue($value['@value'], (bool) $value->get('@cdata'));
// Add attributes to the child
static::addAttributes($child, $value);
$parent->addChild($child);
// Multiple elements as children
} else {
$parent->addChild( static::fromMap($key, $value) );
}
// Multiple children with the same element name
} else if ($value instanceof Vector) {
foreach ($value as $item) {
static::createElement($parent, $key, $item);
}
// Child element with a value
} else {
$parent->addChild( (new Element($key))->setValue($value) );
}
}
/**
* Converts a SimpleXmlElement tree into an Element tree.
*
* @param \SimpleXMLElement $xml
* @return \Titon\Type\Xml\Element
*/
protected static function convertSimpleXml(SimpleXMLElement $xml): Element {
$element = new Element($xml->getName());
// Set attributes
if ($attributes = $xml->attributes()) {
foreach ($attributes as $key => $value) {
$element->setAttribute((string) $key, (string) $value);
}
}
// Set namespaces
if ($namespaces = $xml->getNamespaces()) {
foreach ($namespaces as $key => $value) {
$element->setNamespace((string) $key, (string) $value);
}
}
// Set value
$element->setValue(trim((string) $xml));
// Add children
foreach ($xml->children() as $child) {
$element->addChild( static::convertSimpleXml($child) );
}
return $element;
}
}