<?php
namespace Sabre\Xml\Deserializer;
use Sabre\Xml\Reader;
/**
* This class provides a number of 'deserializer' helper functions.
* These can be used to easily specify custom deserializers for specific
* XML elements.
*
* You can either use these functions from within the $elementMap in the
* Service or Reader class, or you can call them from within your own
* deserializer functions.
*/
/**
* The 'keyValue' deserializer parses all child elements, and outputs them as
* a "key=>value" array.
*
* For example, keyvalue will parse:
*
* <?xml version="1.0"?>
* <s:root xmlns:s="http://sabredav.org/ns">
* <s:elem1>value1</s:elem1>
* <s:elem2>value2</s:elem2>
* <s:elem3 />
* </s:root>
*
* Into:
*
* [
* "{http://sabredav.org/ns}elem1" => "value1",
* "{http://sabredav.org/ns}elem2" => "value2",
* "{http://sabredav.org/ns}elem3" => null,
* ];
*
* If you specify the 'namespace' argument, the deserializer will remove
* the namespaces of the keys that match that namespace.
*
* For example, if you call keyValue like this:
*
* keyValue($reader, 'http://sabredav.org/ns')
*
* it's output will instead be:
*
* [
* "elem1" => "value1",
* "elem2" => "value2",
* "elem3" => null,
* ];
*
* Attributes will be removed from the top-level elements. If elements with
* the same name appear twice in the list, only the last one will be kept.
*
*
* @param Reader $reader
* @param string $namespace
* @return array
*/
function keyValue(Reader $reader, $namespace = null) {
// If there's no children, we don't do anything.
if ($reader->isEmptyElement) {
$reader->next();
return [];
}
if (!$reader->read()) {
$reader->next();
return [];
}
if (Reader::END_ELEMENT === $reader->nodeType) {
$reader->next();
return [];
}
$values = [];
do {
if ($reader->nodeType === Reader::ELEMENT) {
if ($namespace !== null && $reader->namespaceURI === $namespace) {
$values[$reader->localName] = $reader->parseCurrentElement()['value'];
} else {
$clark = $reader->getClark();
$values[$clark] = $reader->parseCurrentElement()['value'];
}
} else {
if (!$reader->read()) {
break;
}
}
} while ($reader->nodeType !== Reader::END_ELEMENT);
$reader->read();
return $values;
}
/**
* The 'enum' deserializer parses elements into a simple list
* without values or attributes.
*
* For example, Elements will parse:
*
* <?xml version="1.0"? >
* <s:root xmlns:s="http://sabredav.org/ns">
* <s:elem1 />
* <s:elem2 />
* <s:elem3 />
* <s:elem4>content</s:elem4>
* <s:elem5 attr="val" />
* </s:root>
*
* Into:
*
* [
* "{http://sabredav.org/ns}elem1",
* "{http://sabredav.org/ns}elem2",
* "{http://sabredav.org/ns}elem3",
* "{http://sabredav.org/ns}elem4",
* "{http://sabredav.org/ns}elem5",
* ];
*
* This is useful for 'enum'-like structures.
*
* If the $namespace argument is specified, it will strip the namespace
* for all elements that match that.
*
* For example,
*
* enum($reader, 'http://sabredav.org/ns')
*
* would return:
*
* [
* "elem1",
* "elem2",
* "elem3",
* "elem4",
* "elem5",
* ];
*
* @param Reader $reader
* @param string $namespace
* @return string[]
*/
function enum(Reader $reader, $namespace = null) {
// If there's no children, we don't do anything.
if ($reader->isEmptyElement) {
$reader->next();
return [];
}
if (!$reader->read()) {
$reader->next();
return [];
}
if (Reader::END_ELEMENT === $reader->nodeType) {
$reader->next();
return [];
}
$currentDepth = $reader->depth;
$values = [];
do {
if ($reader->nodeType !== Reader::ELEMENT) {
continue;
}
if (!is_null($namespace) && $namespace === $reader->namespaceURI) {
$values[] = $reader->localName;
} else {
$values[] = $reader->getClark();
}
} while ($reader->depth >= $currentDepth && $reader->next());
$reader->next();
return $values;
}
/**
* The valueObject deserializer turns an xml element into a PHP object of
* a specific class.
*
* This is primarily used by the mapValueObject function from the Service
* class, but it can also easily be used for more specific situations.
*
* @param Reader $reader
* @param string $className
* @param string $namespace
* @return object
*/
function valueObject(Reader $reader, $className, $namespace) {
$valueObject = new $className();
if ($reader->isEmptyElement) {
$reader->next();
return $valueObject;
}
$defaultProperties = get_class_vars($className);
$reader->read();
do {
if ($reader->nodeType === Reader::ELEMENT && $reader->namespaceURI == $namespace) {
if (property_exists($valueObject, $reader->localName)) {
if (is_array($defaultProperties[$reader->localName])) {
$valueObject->{$reader->localName}[] = $reader->parseCurrentElement()['value'];
} else {
$valueObject->{$reader->localName} = $reader->parseCurrentElement()['value'];
}
} else {
// Ignore property
$reader->next();
}
} else {
if (!$reader->read()) {
break;
}
}
} while ($reader->nodeType !== Reader::END_ELEMENT);
$reader->read();
return $valueObject;
}
/**
* This deserializer helps you deserialize xml structures that look like
* this:
*
* <collection>
* <item>...</item>
* <item>...</item>
* <item>...</item>
* </collection>
*
* Many XML documents use patterns like that, and this deserializer
* allow you to get all the 'items' as an array.
*
* In that previous example, you would register the deserializer as such:
*
* $reader->elementMap['{}collection'] = function($reader) {
* return repeatingElements($reader, '{}item');
* }
*
* The repeatingElements deserializer simply returns everything as an array.
*
* @param Reader $reader
* @param string $childElementName Element name in clark-notation
* @return array
*/
function repeatingElements(Reader $reader, $childElementName) {
if ($childElementName[0] !== '{') {
$childElementName = '{}' . $childElementName;
}
$result = [];
foreach ($reader->parseGetElements() as $element) {
if ($element['name'] === $childElementName) {
$result[] = $element['value'];
}
}
return $result;
}