PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / 2.4.4
Booking for Appointments and Events Calendar – Amelia v2.4.4
2.4.4 2.4.3 2.4.2 2.4.1 2.4 trunk 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.15 1.2.16 1.2.17 1.2.18 1.2.19 1.2.2 1.2.20 1.2.21 1.2.22 1.2.23 1.2.24 1.2.25 1.2.26 1.2.27 1.2.28 1.2.29 1.2.3 1.2.30 1.2.31 1.2.32 1.2.33 1.2.34 1.2.35 1.2.36 1.2.37 1.2.38 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.1.3 2.2 2.2.1 2.3
ameliabooking / vendor / sabre / xml / lib / Deserializer / functions.php
ameliabooking / vendor / sabre / xml / lib / Deserializer Last commit date
functions.php 6 months ago
functions.php
389 lines
1 <?php
2
3 declare(strict_types=1);
4
5 namespace AmeliaVendor\Sabre\Xml\Deserializer;
6
7 use AmeliaVendor\Sabre\Xml\Reader;
8
9 /**
10 * This class provides a number of 'deserializer' helper functions.
11 * These can be used to easily specify custom deserializers for specific
12 * XML elements.
13 *
14 * You can either use these functions from within the $elementMap in the
15 * Service or Reader class, or you can call them from within your own
16 * deserializer functions.
17 */
18
19 /**
20 * The 'keyValue' deserializer parses all child elements, and outputs them as
21 * a "key=>value" array.
22 *
23 * For example, keyvalue will parse:
24 *
25 * <?xml version="1.0"?>
26 * <s:root xmlns:s="http://sabredav.org/ns">
27 * <s:elem1>value1</s:elem1>
28 * <s:elem2>value2</s:elem2>
29 * <s:elem3 />
30 * </s:root>
31 *
32 * Into:
33 *
34 * [
35 * "{http://sabredav.org/ns}elem1" => "value1",
36 * "{http://sabredav.org/ns}elem2" => "value2",
37 * "{http://sabredav.org/ns}elem3" => null,
38 * ];
39 *
40 * If you specify the 'namespace' argument, the deserializer will remove
41 * the namespaces of the keys that match that namespace.
42 *
43 * For example, if you call keyValue like this:
44 *
45 * keyValue($reader, 'http://sabredav.org/ns')
46 *
47 * it's output will instead be:
48 *
49 * [
50 * "elem1" => "value1",
51 * "elem2" => "value2",
52 * "elem3" => null,
53 * ];
54 *
55 * Attributes will be removed from the top-level elements. If elements with
56 * the same name appear twice in the list, only the last one will be kept.
57 *
58 * @phpstan-return array<string, mixed>
59 */
60 function keyValue(Reader $reader, ?string $namespace = null): array
61 {
62 // If there's no children, we don't do anything.
63 if ($reader->isEmptyElement) {
64 $reader->next();
65
66 return [];
67 }
68
69 if (!$reader->read()) {
70 $reader->next();
71
72 return [];
73 }
74
75 if (Reader::END_ELEMENT === $reader->nodeType) {
76 $reader->next();
77
78 return [];
79 }
80
81 $values = [];
82
83 do {
84 if (Reader::ELEMENT === $reader->nodeType) {
85 if (null !== $namespace && $reader->namespaceURI === $namespace) {
86 $values[$reader->localName] = $reader->parseCurrentElement()['value'];
87 } else {
88 $clark = $reader->getClark();
89 $values[$clark] = $reader->parseCurrentElement()['value'];
90 }
91 } else {
92 if (!$reader->read()) {
93 break;
94 }
95 }
96 } while (Reader::END_ELEMENT !== $reader->nodeType);
97
98 $reader->read();
99
100 return $values;
101 }
102
103 /**
104 * The 'enum' deserializer parses elements into a simple list
105 * without values or attributes.
106 *
107 * For example, Elements will parse:
108 *
109 * <?xml version="1.0"? >
110 * <s:root xmlns:s="http://sabredav.org/ns">
111 * <s:elem1 />
112 * <s:elem2 />
113 * <s:elem3 />
114 * <s:elem4>content</s:elem4>
115 * <s:elem5 attr="val" />
116 * </s:root>
117 *
118 * Into:
119 *
120 * [
121 * "{http://sabredav.org/ns}elem1",
122 * "{http://sabredav.org/ns}elem2",
123 * "{http://sabredav.org/ns}elem3",
124 * "{http://sabredav.org/ns}elem4",
125 * "{http://sabredav.org/ns}elem5",
126 * ];
127 *
128 * This is useful for 'enum'-like structures.
129 *
130 * If the $namespace argument is specified, it will strip the namespace
131 * for all elements that match that.
132 *
133 * For example,
134 *
135 * enum($reader, 'http://sabredav.org/ns')
136 *
137 * would return:
138 *
139 * [
140 * "elem1",
141 * "elem2",
142 * "elem3",
143 * "elem4",
144 * "elem5",
145 * ];
146 *
147 * @return string[]
148 *
149 * @phpstan-return list<string>
150 */
151 function enum(Reader $reader, ?string $namespace = null): array
152 {
153 // If there's no children, we don't do anything.
154 if ($reader->isEmptyElement) {
155 $reader->next();
156
157 return [];
158 }
159 if (!$reader->read()) {
160 $reader->next();
161
162 return [];
163 }
164
165 if (Reader::END_ELEMENT === $reader->nodeType) {
166 $reader->next();
167
168 return [];
169 }
170 $currentDepth = $reader->depth;
171
172 $values = [];
173 do {
174 if (Reader::ELEMENT !== $reader->nodeType) {
175 continue;
176 }
177 if (!is_null($namespace) && $namespace === $reader->namespaceURI) {
178 $values[] = $reader->localName;
179 } else {
180 $values[] = (string) $reader->getClark();
181 }
182 } while ($reader->depth >= $currentDepth && $reader->next());
183
184 $reader->next();
185
186 return $values;
187 }
188
189 /**
190 * The valueObject deserializer turns an XML element into a PHP object of
191 * a specific class.
192 *
193 * This is primarily used by the mapValueObject function from the Service
194 * class, but it can also easily be used for more specific situations.
195 *
196 * @template C of object
197 *
198 * @param class-string<C> $className
199 *
200 * @phpstan-return C
201 */
202 function valueObject(Reader $reader, string $className, string $namespace): object
203 {
204 $valueObject = new $className();
205 if ($reader->isEmptyElement) {
206 $reader->next();
207
208 return $valueObject;
209 }
210
211 $defaultProperties = get_class_vars($className);
212
213 $reader->read();
214 do {
215 if (Reader::ELEMENT === $reader->nodeType && $reader->namespaceURI == $namespace) {
216 if (property_exists($valueObject, $reader->localName)) {
217 if (is_array($defaultProperties[$reader->localName])) {
218 $valueObject->{$reader->localName}[] = $reader->parseCurrentElement()['value'];
219 } else {
220 $valueObject->{$reader->localName} = $reader->parseCurrentElement()['value'];
221 }
222 } else {
223 // Ignore property
224 $reader->next();
225 }
226 } elseif (Reader::ELEMENT === $reader->nodeType) {
227 // Skipping element from different namespace
228 $reader->next();
229 } else {
230 if (Reader::END_ELEMENT !== $reader->nodeType && !$reader->read()) {
231 break;
232 }
233 }
234 } while (Reader::END_ELEMENT !== $reader->nodeType);
235
236 $reader->read();
237
238 return $valueObject;
239 }
240
241 /**
242 * This deserializer helps you deserialize xml structures that look like
243 * this:.
244 *
245 * <collection>
246 * <item>...</item>
247 * <item>...</item>
248 * <item>...</item>
249 * </collection>
250 *
251 * Many XML documents use patterns like that, and this deserializer
252 * allow you to get all the 'items' as an array.
253 *
254 * In that previous example, you would register the deserializer as such:
255 *
256 * $reader->elementMap['{}collection'] = function($reader) {
257 * return repeatingElements($reader, '{}item');
258 * }
259 *
260 * The repeatingElements deserializer simply returns everything as an array.
261 *
262 * $childElementName must either be a clark-notation element name, or if no
263 * namespace is used, the bare element name.
264 *
265 * @phpstan-return list<mixed>
266 */
267 function repeatingElements(Reader $reader, string $childElementName): array
268 {
269 if ('{' !== $childElementName[0]) {
270 $childElementName = '{}'.$childElementName;
271 }
272 $result = [];
273
274 foreach ($reader->parseGetElements() as $element) {
275 if ($element['name'] === $childElementName) {
276 $result[] = $element['value'];
277 }
278 }
279
280 return $result;
281 }
282
283 /**
284 * This deserializer helps you to deserialize structures which contain mixed content.
285 *
286 * <p>some text <extref>and an inline tag</extref>and even more text</p>
287 *
288 * The above example will return
289 *
290 * [
291 * 'some text',
292 * [
293 * 'name' => '{}extref',
294 * 'value' => 'and an inline tag',
295 * 'attributes' => []
296 * ],
297 * 'and even more text'
298 * ]
299 *
300 * In strict XML documents you won't find this kind of markup but in html this is a quite common pattern.
301 *
302 * @return array<mixed>
303 */
304 function mixedContent(Reader $reader): array
305 {
306 // If there's no children, we don't do anything.
307 if ($reader->isEmptyElement) {
308 $reader->next();
309
310 return [];
311 }
312
313 $previousDepth = $reader->depth;
314
315 $content = [];
316 $reader->read();
317 while (true) {
318 if (Reader::ELEMENT == $reader->nodeType) {
319 $content[] = $reader->parseCurrentElement();
320 } elseif ($reader->depth >= $previousDepth && in_array($reader->nodeType, [Reader::TEXT, Reader::CDATA, Reader::WHITESPACE])) {
321 $content[] = $reader->value;
322 $reader->read();
323 } elseif (Reader::END_ELEMENT == $reader->nodeType) {
324 // Ensuring we are moving the cursor after the end element.
325 $reader->read();
326 break;
327 } else {
328 $reader->read();
329 }
330 }
331
332 return $content;
333 }
334
335 /**
336 * The functionCaller deserializer turns an XML element into whatever your callable returns.
337 *
338 * You can use, e.g., a named constructor (factory method) to create an object using
339 * this function.
340 *
341 * @return mixed whatever the 'func' callable returns
342 *
343 * @throws \InvalidArgumentException|\ReflectionException
344 */
345 function functionCaller(Reader $reader, callable $func, string $namespace)
346 {
347 if ($reader->isEmptyElement) {
348 $reader->next();
349
350 return null;
351 }
352
353 $funcArgs = [];
354 if (is_array($func)) {
355 $ref = new \ReflectionMethod($func[0], $func[1]);
356 } elseif (is_string($func) && false !== strpos($func, '::')) {
357 // We have a string that should refer to a method that exists, like "MyClass::someMethod"
358 // ReflectionMethod knows how to handle that as-is
359 $ref = new \ReflectionMethod($func);
360 } elseif ($func instanceof \Closure || is_string($func)) {
361 // We have an actual Closure (a real function) or a string that is the name of a function
362 // ReflectionFunction can take either of those
363 $ref = new \ReflectionFunction($func);
364 } else {
365 throw new \InvalidArgumentException(__METHOD__.' unable to use func parameter with ReflectionMethod or ReflectionFunction.');
366 }
367
368 foreach ($ref->getParameters() as $parameter) {
369 $funcArgs[$parameter->getName()] = null;
370 }
371
372 $reader->read();
373 do {
374 if (Reader::ELEMENT === $reader->nodeType && $reader->namespaceURI == $namespace) {
375 if (array_key_exists($reader->localName, $funcArgs)) {
376 $funcArgs[$reader->localName] = $reader->parseCurrentElement()['value'];
377 } else {
378 // Ignore property
379 $reader->next();
380 }
381 } else {
382 $reader->read();
383 }
384 } while (Reader::END_ELEMENT !== $reader->nodeType);
385 $reader->read();
386
387 return $func(...array_values($funcArgs));
388 }
389