functions.php 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. <?php
  2. declare(strict_types=1);
  3. namespace Sabre\Xml\Deserializer;
  4. use Sabre\Xml\Reader;
  5. /**
  6. * This class provides a number of 'deserializer' helper functions.
  7. * These can be used to easily specify custom deserializers for specific
  8. * XML elements.
  9. *
  10. * You can either use these functions from within the $elementMap in the
  11. * Service or Reader class, or you can call them from within your own
  12. * deserializer functions.
  13. */
  14. /**
  15. * The 'keyValue' deserializer parses all child elements, and outputs them as
  16. * a "key=>value" array.
  17. *
  18. * For example, keyvalue will parse:
  19. *
  20. * <?xml version="1.0"?>
  21. * <s:root xmlns:s="http://sabredav.org/ns">
  22. * <s:elem1>value1</s:elem1>
  23. * <s:elem2>value2</s:elem2>
  24. * <s:elem3 />
  25. * </s:root>
  26. *
  27. * Into:
  28. *
  29. * [
  30. * "{http://sabredav.org/ns}elem1" => "value1",
  31. * "{http://sabredav.org/ns}elem2" => "value2",
  32. * "{http://sabredav.org/ns}elem3" => null,
  33. * ];
  34. *
  35. * If you specify the 'namespace' argument, the deserializer will remove
  36. * the namespaces of the keys that match that namespace.
  37. *
  38. * For example, if you call keyValue like this:
  39. *
  40. * keyValue($reader, 'http://sabredav.org/ns')
  41. *
  42. * it's output will instead be:
  43. *
  44. * [
  45. * "elem1" => "value1",
  46. * "elem2" => "value2",
  47. * "elem3" => null,
  48. * ];
  49. *
  50. * Attributes will be removed from the top-level elements. If elements with
  51. * the same name appear twice in the list, only the last one will be kept.
  52. */
  53. function keyValue(Reader $reader, ?string $namespace = null): array
  54. {
  55. // If there's no children, we don't do anything.
  56. if ($reader->isEmptyElement) {
  57. $reader->next();
  58. return [];
  59. }
  60. if (!$reader->read()) {
  61. $reader->next();
  62. return [];
  63. }
  64. if (Reader::END_ELEMENT === $reader->nodeType) {
  65. $reader->next();
  66. return [];
  67. }
  68. $values = [];
  69. do {
  70. if (Reader::ELEMENT === $reader->nodeType) {
  71. if (null !== $namespace && $reader->namespaceURI === $namespace) {
  72. $values[$reader->localName] = $reader->parseCurrentElement()['value'];
  73. } else {
  74. $clark = $reader->getClark();
  75. $values[$clark] = $reader->parseCurrentElement()['value'];
  76. }
  77. } else {
  78. if (!$reader->read()) {
  79. break;
  80. }
  81. }
  82. } while (Reader::END_ELEMENT !== $reader->nodeType);
  83. $reader->read();
  84. return $values;
  85. }
  86. /**
  87. * The 'enum' deserializer parses elements into a simple list
  88. * without values or attributes.
  89. *
  90. * For example, Elements will parse:
  91. *
  92. * <?xml version="1.0"? >
  93. * <s:root xmlns:s="http://sabredav.org/ns">
  94. * <s:elem1 />
  95. * <s:elem2 />
  96. * <s:elem3 />
  97. * <s:elem4>content</s:elem4>
  98. * <s:elem5 attr="val" />
  99. * </s:root>
  100. *
  101. * Into:
  102. *
  103. * [
  104. * "{http://sabredav.org/ns}elem1",
  105. * "{http://sabredav.org/ns}elem2",
  106. * "{http://sabredav.org/ns}elem3",
  107. * "{http://sabredav.org/ns}elem4",
  108. * "{http://sabredav.org/ns}elem5",
  109. * ];
  110. *
  111. * This is useful for 'enum'-like structures.
  112. *
  113. * If the $namespace argument is specified, it will strip the namespace
  114. * for all elements that match that.
  115. *
  116. * For example,
  117. *
  118. * enum($reader, 'http://sabredav.org/ns')
  119. *
  120. * would return:
  121. *
  122. * [
  123. * "elem1",
  124. * "elem2",
  125. * "elem3",
  126. * "elem4",
  127. * "elem5",
  128. * ];
  129. *
  130. * @return string[]
  131. */
  132. function enum(Reader $reader, ?string $namespace = null): array
  133. {
  134. // If there's no children, we don't do anything.
  135. if ($reader->isEmptyElement) {
  136. $reader->next();
  137. return [];
  138. }
  139. if (!$reader->read()) {
  140. $reader->next();
  141. return [];
  142. }
  143. if (Reader::END_ELEMENT === $reader->nodeType) {
  144. $reader->next();
  145. return [];
  146. }
  147. $currentDepth = $reader->depth;
  148. $values = [];
  149. do {
  150. if (Reader::ELEMENT !== $reader->nodeType) {
  151. continue;
  152. }
  153. if (!is_null($namespace) && $namespace === $reader->namespaceURI) {
  154. $values[] = $reader->localName;
  155. } else {
  156. $values[] = (string) $reader->getClark();
  157. }
  158. } while ($reader->depth >= $currentDepth && $reader->next());
  159. $reader->next();
  160. return $values;
  161. }
  162. /**
  163. * The valueObject deserializer turns an xml element into a PHP object of
  164. * a specific class.
  165. *
  166. * This is primarily used by the mapValueObject function from the Service
  167. * class, but it can also easily be used for more specific situations.
  168. *
  169. * @return object
  170. */
  171. function valueObject(Reader $reader, string $className, string $namespace)
  172. {
  173. $valueObject = new $className();
  174. if ($reader->isEmptyElement) {
  175. $reader->next();
  176. return $valueObject;
  177. }
  178. $defaultProperties = get_class_vars($className);
  179. $reader->read();
  180. do {
  181. if (Reader::ELEMENT === $reader->nodeType && $reader->namespaceURI == $namespace) {
  182. if (property_exists($valueObject, $reader->localName)) {
  183. if (is_array($defaultProperties[$reader->localName])) {
  184. $valueObject->{$reader->localName}[] = $reader->parseCurrentElement()['value'];
  185. } else {
  186. $valueObject->{$reader->localName} = $reader->parseCurrentElement()['value'];
  187. }
  188. } else {
  189. // Ignore property
  190. $reader->next();
  191. }
  192. } elseif (Reader::ELEMENT === $reader->nodeType) {
  193. // Skipping element from different namespace
  194. $reader->next();
  195. } else {
  196. if (Reader::END_ELEMENT !== $reader->nodeType && !$reader->read()) {
  197. break;
  198. }
  199. }
  200. } while (Reader::END_ELEMENT !== $reader->nodeType);
  201. $reader->read();
  202. return $valueObject;
  203. }
  204. /**
  205. * This deserializer helps you deserialize xml structures that look like
  206. * this:.
  207. *
  208. * <collection>
  209. * <item>...</item>
  210. * <item>...</item>
  211. * <item>...</item>
  212. * </collection>
  213. *
  214. * Many XML documents use patterns like that, and this deserializer
  215. * allow you to get all the 'items' as an array.
  216. *
  217. * In that previous example, you would register the deserializer as such:
  218. *
  219. * $reader->elementMap['{}collection'] = function($reader) {
  220. * return repeatingElements($reader, '{}item');
  221. * }
  222. *
  223. * The repeatingElements deserializer simply returns everything as an array.
  224. *
  225. * $childElementName must either be a a clark-notation element name, or if no
  226. * namespace is used, the bare element name.
  227. */
  228. function repeatingElements(Reader $reader, string $childElementName): array
  229. {
  230. if ('{' !== $childElementName[0]) {
  231. $childElementName = '{}'.$childElementName;
  232. }
  233. $result = [];
  234. foreach ($reader->parseGetElements() as $element) {
  235. if ($element['name'] === $childElementName) {
  236. $result[] = $element['value'];
  237. }
  238. }
  239. return $result;
  240. }
  241. /**
  242. * This deserializer helps you to deserialize structures which contain mixed content like this:.
  243. *
  244. * <p>some text <extref>and a inline tag</extref>and even more text</p>
  245. *
  246. * The above example will return
  247. *
  248. * [
  249. * 'some text',
  250. * [
  251. * 'name' => '{}extref',
  252. * 'value' => 'and a inline tag',
  253. * 'attributes' => []
  254. * ],
  255. * 'and even more text'
  256. * ]
  257. *
  258. * In strict XML documents you wont find this kind of markup but in html this is a quite common pattern.
  259. */
  260. function mixedContent(Reader $reader): array
  261. {
  262. // If there's no children, we don't do anything.
  263. if ($reader->isEmptyElement) {
  264. $reader->next();
  265. return [];
  266. }
  267. $previousDepth = $reader->depth;
  268. $content = [];
  269. $reader->read();
  270. while (true) {
  271. if (Reader::ELEMENT == $reader->nodeType) {
  272. $content[] = $reader->parseCurrentElement();
  273. } elseif ($reader->depth >= $previousDepth && in_array($reader->nodeType, [Reader::TEXT, Reader::CDATA, Reader::WHITESPACE])) {
  274. $content[] = $reader->value;
  275. $reader->read();
  276. } elseif (Reader::END_ELEMENT == $reader->nodeType) {
  277. // Ensuring we are moving the cursor after the end element.
  278. $reader->read();
  279. break;
  280. } else {
  281. $reader->read();
  282. }
  283. }
  284. return $content;
  285. }
  286. /**
  287. * The functionCaller deserializer turns an xml element into whatever your callable return.
  288. *
  289. * You can use, e.g., a named constructor (factory method) to create an object using
  290. * this function.
  291. */
  292. function functionCaller(Reader $reader, callable $func, string $namespace)
  293. {
  294. if ($reader->isEmptyElement) {
  295. $reader->next();
  296. return null;
  297. }
  298. $funcArgs = [];
  299. $func = is_string($func) && false !== strpos($func, '::') ? explode('::', $func) : $func;
  300. $ref = is_array($func) ? new \ReflectionMethod($func[0], $func[1]) : new \ReflectionFunction($func);
  301. foreach ($ref->getParameters() as $parameter) {
  302. $funcArgs[$parameter->getName()] = null;
  303. }
  304. $reader->read();
  305. do {
  306. if (Reader::ELEMENT === $reader->nodeType && $reader->namespaceURI == $namespace) {
  307. if (array_key_exists($reader->localName, $funcArgs)) {
  308. $funcArgs[$reader->localName] = $reader->parseCurrentElement()['value'];
  309. } else {
  310. // Ignore property
  311. $reader->next();
  312. }
  313. } else {
  314. $reader->read();
  315. }
  316. } while (Reader::END_ELEMENT !== $reader->nodeType);
  317. $reader->read();
  318. return $func(...array_values($funcArgs));
  319. }