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