1<?php
2
3namespace Sabre\Xml\Deserializer;
4
5use Sabre\Xml\Reader;
6
7/**
8 * This class provides a number of 'deserializer' helper functions.
9 * These can be used to easily specify custom deserializers for specific
10 * XML elements.
11 *
12 * You can either use these functions from within the $elementMap in the
13 * Service or Reader class, or you can call them from within your own
14 * deserializer functions.
15 */
16
17/**
18 * The 'keyValue' deserializer parses all child elements, and outputs them as
19 * a "key=>value" array.
20 *
21 * For example, keyvalue will parse:
22 *
23 * <?xml version="1.0"?>
24 * <s:root xmlns:s="http://sabredav.org/ns">
25 *   <s:elem1>value1</s:elem1>
26 *   <s:elem2>value2</s:elem2>
27 *   <s:elem3 />
28 * </s:root>
29 *
30 * Into:
31 *
32 * [
33 *   "{http://sabredav.org/ns}elem1" => "value1",
34 *   "{http://sabredav.org/ns}elem2" => "value2",
35 *   "{http://sabredav.org/ns}elem3" => null,
36 * ];
37 *
38 * If you specify the 'namespace' argument, the deserializer will remove
39 * the namespaces of the keys that match that namespace.
40 *
41 * For example, if you call keyValue like this:
42 *
43 * keyValue($reader, 'http://sabredav.org/ns')
44 *
45 * it's output will instead be:
46 *
47 * [
48 *   "elem1" => "value1",
49 *   "elem2" => "value2",
50 *   "elem3" => null,
51 * ];
52 *
53 * Attributes will be removed from the top-level elements. If elements with
54 * the same name appear twice in the list, only the last one will be kept.
55 *
56 *
57 * @param Reader $reader
58 * @param string $namespace
59 * @return array
60 */
61function keyValue(Reader $reader, $namespace = null) {
62
63    // If there's no children, we don't do anything.
64    if ($reader->isEmptyElement) {
65        $reader->next();
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
85        if ($reader->nodeType === Reader::ELEMENT) {
86            if ($namespace !== null && $reader->namespaceURI === $namespace) {
87                $values[$reader->localName] = $reader->parseCurrentElement()['value'];
88            } else {
89                $clark = $reader->getClark();
90                $values[$clark] = $reader->parseCurrentElement()['value'];
91            }
92        } else {
93            if (!$reader->read()) {
94                break;
95            }
96        }
97    } while ($reader->nodeType !== Reader::END_ELEMENT);
98
99    $reader->read();
100
101    return $values;
102
103}
104
105/**
106 * The 'enum' deserializer parses elements into a simple list
107 * without values or attributes.
108 *
109 * For example, Elements will parse:
110 *
111 * <?xml version="1.0"? >
112 * <s:root xmlns:s="http://sabredav.org/ns">
113 *   <s:elem1 />
114 *   <s:elem2 />
115 *   <s:elem3 />
116 *   <s:elem4>content</s:elem4>
117 *   <s:elem5 attr="val" />
118 * </s:root>
119 *
120 * Into:
121 *
122 * [
123 *   "{http://sabredav.org/ns}elem1",
124 *   "{http://sabredav.org/ns}elem2",
125 *   "{http://sabredav.org/ns}elem3",
126 *   "{http://sabredav.org/ns}elem4",
127 *   "{http://sabredav.org/ns}elem5",
128 * ];
129 *
130 * This is useful for 'enum'-like structures.
131 *
132 * If the $namespace argument is specified, it will strip the namespace
133 * for all elements that match that.
134 *
135 * For example,
136 *
137 * enum($reader, 'http://sabredav.org/ns')
138 *
139 * would return:
140 *
141 * [
142 *   "elem1",
143 *   "elem2",
144 *   "elem3",
145 *   "elem4",
146 *   "elem5",
147 * ];
148 *
149 * @param Reader $reader
150 * @param string $namespace
151 * @return string[]
152 */
153function enum(Reader $reader, $namespace = null) {
154
155    // If there's no children, we don't do anything.
156    if ($reader->isEmptyElement) {
157        $reader->next();
158        return [];
159    }
160    if (!$reader->read()) {
161        $reader->next();
162
163        return [];
164    }
165
166    if (Reader::END_ELEMENT === $reader->nodeType) {
167        $reader->next();
168
169        return [];
170    }
171    $currentDepth = $reader->depth;
172
173    $values = [];
174    do {
175
176        if ($reader->nodeType !== Reader::ELEMENT) {
177            continue;
178        }
179        if (!is_null($namespace) && $namespace === $reader->namespaceURI) {
180            $values[] = $reader->localName;
181        } else {
182            $values[] = $reader->getClark();
183        }
184
185    } while ($reader->depth >= $currentDepth && $reader->next());
186
187    $reader->next();
188    return $values;
189
190}
191
192/**
193 * The valueObject deserializer turns an xml element into a PHP object of
194 * a specific class.
195 *
196 * This is primarily used by the mapValueObject function from the Service
197 * class, but it can also easily be used for more specific situations.
198 *
199 * @param Reader $reader
200 * @param string $className
201 * @param string $namespace
202 * @return object
203 */
204function valueObject(Reader $reader, $className, $namespace) {
205
206    $valueObject = new $className();
207    if ($reader->isEmptyElement) {
208        $reader->next();
209        return $valueObject;
210    }
211
212    $defaultProperties = get_class_vars($className);
213
214    $reader->read();
215    do {
216
217        if ($reader->nodeType === Reader::ELEMENT && $reader->namespaceURI == $namespace) {
218
219            if (property_exists($valueObject, $reader->localName)) {
220                if (is_array($defaultProperties[$reader->localName])) {
221                    $valueObject->{$reader->localName}[] = $reader->parseCurrentElement()['value'];
222                } else {
223                    $valueObject->{$reader->localName} = $reader->parseCurrentElement()['value'];
224                }
225            } else {
226                // Ignore property
227                $reader->next();
228            }
229        } else {
230            if (!$reader->read()) {
231                break;
232            }
233        }
234    } while ($reader->nodeType !== Reader::END_ELEMENT);
235
236    $reader->read();
237    return $valueObject;
238
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 * @param Reader $reader
263 * @param string $childElementName Element name in clark-notation
264 * @return array
265 */
266function repeatingElements(Reader $reader, $childElementName) {
267
268    if ($childElementName[0] !== '{') {
269        $childElementName = '{}' . $childElementName;
270    }
271    $result = [];
272
273    foreach ($reader->parseGetElements() as $element) {
274
275        if ($element['name'] === $childElementName) {
276            $result[] = $element['value'];
277        }
278
279    }
280
281    return $result;
282
283}
284