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