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    $values = [];
70
71    $reader->read();
72    do {
73
74        if ($reader->nodeType === Reader::ELEMENT) {
75            if ($namespace !== null && $reader->namespaceURI === $namespace) {
76                $values[$reader->localName] = $reader->parseCurrentElement()['value'];
77            } else {
78                $clark = $reader->getClark();
79                $values[$clark] = $reader->parseCurrentElement()['value'];
80            }
81        } else {
82            $reader->read();
83        }
84    } while ($reader->nodeType !== Reader::END_ELEMENT);
85
86    $reader->read();
87
88    return $values;
89
90}
91
92/**
93 * The 'enum' deserializer parses elements into a simple list
94 * without values or attributes.
95 *
96 * For example, Elements will parse:
97 *
98 * <?xml version="1.0"? >
99 * <s:root xmlns:s="http://sabredav.org/ns">
100 *   <s:elem1 />
101 *   <s:elem2 />
102 *   <s:elem3 />
103 *   <s:elem4>content</s:elem4>
104 *   <s:elem5 attr="val" />
105 * </s:root>
106 *
107 * Into:
108 *
109 * [
110 *   "{http://sabredav.org/ns}elem1",
111 *   "{http://sabredav.org/ns}elem2",
112 *   "{http://sabredav.org/ns}elem3",
113 *   "{http://sabredav.org/ns}elem4",
114 *   "{http://sabredav.org/ns}elem5",
115 * ];
116 *
117 * This is useful for 'enum'-like structures.
118 *
119 * If the $namespace argument is specified, it will strip the namespace
120 * for all elements that match that.
121 *
122 * For example,
123 *
124 * enum($reader, 'http://sabredav.org/ns')
125 *
126 * would return:
127 *
128 * [
129 *   "elem1",
130 *   "elem2",
131 *   "elem3",
132 *   "elem4",
133 *   "elem5",
134 * ];
135 *
136 * @param Reader $reader
137 * @param string $namespace
138 * @return string[]
139 */
140function enum(Reader $reader, $namespace = null) {
141
142    // If there's no children, we don't do anything.
143    if ($reader->isEmptyElement) {
144        $reader->next();
145        return [];
146    }
147    $reader->read();
148    $currentDepth = $reader->depth;
149
150    $values = [];
151    do {
152
153        if ($reader->nodeType !== Reader::ELEMENT) {
154            continue;
155        }
156        if (!is_null($namespace) && $namespace === $reader->namespaceURI) {
157            $values[] = $reader->localName;
158        } else {
159            $values[] = $reader->getClark();
160        }
161
162    } while ($reader->depth >= $currentDepth && $reader->next());
163
164    $reader->next();
165    return $values;
166
167}
168
169/**
170 * The valueObject deserializer turns an xml element into a PHP object of
171 * a specific class.
172 *
173 * This is primarily used by the mapValueObject function from the Service
174 * class, but it can also easily be used for more specific situations.
175 *
176 * @param Reader $reader
177 * @param string $className
178 * @param string $namespace
179 * @return object
180 */
181function valueObject(Reader $reader, $className, $namespace) {
182
183    $valueObject = new $className();
184    if ($reader->isEmptyElement) {
185        $reader->next();
186        return $valueObject;
187    }
188
189    $defaultProperties = get_class_vars($className);
190
191    $reader->read();
192    do {
193
194        if ($reader->nodeType === Reader::ELEMENT && $reader->namespaceURI == $namespace) {
195
196            if (property_exists($valueObject, $reader->localName)) {
197                if (is_array($defaultProperties[$reader->localName])) {
198                    $valueObject->{$reader->localName}[] = $reader->parseCurrentElement()['value'];
199                } else {
200                    $valueObject->{$reader->localName} = $reader->parseCurrentElement()['value'];
201                }
202            } else {
203                // Ignore property
204                $reader->next();
205            }
206        } else {
207            $reader->read();
208        }
209    } while ($reader->nodeType !== Reader::END_ELEMENT);
210
211    $reader->read();
212    return $valueObject;
213
214}
215
216/**
217 * This deserializer helps you deserialize xml structures that look like
218 * this:
219 *
220 * <collection>
221 *    <item>...</item>
222 *    <item>...</item>
223 *    <item>...</item>
224 * </collection>
225 *
226 * Many XML documents use  patterns like that, and this deserializer
227 * allow you to get all the 'items' as an array.
228 *
229 * In that previous example, you would register the deserializer as such:
230 *
231 * $reader->elementMap['{}collection'] = function($reader) {
232 *     return repeatingElements($reader, '{}item');
233 * }
234 *
235 * The repeatingElements deserializer simply returns everything as an array.
236 *
237 * @param Reader $reader
238 * @param string $childElementName Element name in clark-notation
239 * @return array
240 */
241function repeatingElements(Reader $reader, $childElementName) {
242
243    if ($childElementName[0] !== '{') {
244        $childElementName = '{}' . $childElementName;
245    }
246    $result = [];
247
248    foreach ($reader->parseGetElements() as $element) {
249
250        if ($element['name'] === $childElementName) {
251            $result[] = $element['value'];
252        }
253
254    }
255
256    return $result;
257
258}
259