303 lines
9.3 KiB
PHP
303 lines
9.3 KiB
PHP
<?php
|
|
|
|
namespace GuzzleHttp\Command\Guzzle\ResponseLocation;
|
|
|
|
use GuzzleHttp\Command\Guzzle\Parameter;
|
|
use GuzzleHttp\Command\Result;
|
|
use GuzzleHttp\Command\ResultInterface;
|
|
use Psr\Http\Message\ResponseInterface;
|
|
|
|
/**
|
|
* Extracts elements from an XML document
|
|
*/
|
|
class XmlLocation extends AbstractLocation
|
|
{
|
|
/** @var \SimpleXMLElement XML document being visited */
|
|
private $xml;
|
|
|
|
/**
|
|
* Set the name of the location
|
|
*
|
|
* @param string $locationName
|
|
*/
|
|
public function __construct($locationName = 'xml')
|
|
{
|
|
parent::__construct($locationName);
|
|
}
|
|
|
|
/**
|
|
* @return ResultInterface
|
|
*/
|
|
public function before(
|
|
ResultInterface $result,
|
|
ResponseInterface $response,
|
|
Parameter $model
|
|
) {
|
|
$this->xml = simplexml_load_string((string) $response->getBody());
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @return Result|ResultInterface
|
|
*/
|
|
public function after(
|
|
ResultInterface $result,
|
|
ResponseInterface $response,
|
|
Parameter $model
|
|
) {
|
|
// Handle additional, undefined properties
|
|
$additional = $model->getAdditionalProperties();
|
|
if ($additional instanceof Parameter
|
|
&& $additional->getLocation() == $this->locationName
|
|
) {
|
|
$result = new Result(array_merge(
|
|
$result->toArray(),
|
|
self::xmlToArray($this->xml)
|
|
));
|
|
}
|
|
|
|
$this->xml = null;
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @return ResultInterface
|
|
*/
|
|
public function visit(
|
|
ResultInterface $result,
|
|
ResponseInterface $response,
|
|
Parameter $param
|
|
) {
|
|
$sentAs = $param->getWireName();
|
|
$ns = null;
|
|
if (null !== $sentAs && strstr($sentAs, ':')) {
|
|
list($ns, $sentAs) = explode(':', $sentAs);
|
|
}
|
|
|
|
// Process the primary property
|
|
if (count($this->xml->children($ns, true)->{$sentAs})) {
|
|
$result[$param->getName()] = $this->recursiveProcess(
|
|
$param,
|
|
$this->xml->children($ns, true)->{$sentAs}
|
|
);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Recursively process a parameter while applying filters
|
|
*
|
|
* @param Parameter $param API parameter being processed
|
|
* @param \SimpleXMLElement $node Node being processed
|
|
*
|
|
* @return array
|
|
*/
|
|
private function recursiveProcess(
|
|
Parameter $param,
|
|
\SimpleXMLElement $node
|
|
) {
|
|
$result = [];
|
|
$type = $param->getType();
|
|
|
|
if ($type == 'object') {
|
|
$result = $this->processObject($param, $node);
|
|
} elseif ($type == 'array') {
|
|
$result = $this->processArray($param, $node);
|
|
} else {
|
|
// We are probably handling a flat data node (i.e. string or
|
|
// integer), so let's check if it's childless, which indicates a
|
|
// node containing plain text.
|
|
if ($node->children()->count() == 0) {
|
|
// Retrieve text from node
|
|
$result = (string) $node;
|
|
}
|
|
}
|
|
|
|
// Filter out the value
|
|
if (isset($result)) {
|
|
$result = $param->filter($result);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @return array
|
|
*/
|
|
private function processArray(Parameter $param, \SimpleXMLElement $node)
|
|
{
|
|
// Cast to an array if the value was a string, but should be an array
|
|
$items = $param->getItems();
|
|
$sentAs = $items->getWireName();
|
|
$result = [];
|
|
$ns = null;
|
|
|
|
if (null !== $sentAs && strstr($sentAs, ':')) {
|
|
// Get namespace from the wire name
|
|
list($ns, $sentAs) = explode(':', $sentAs);
|
|
} else {
|
|
// Get namespace from data
|
|
$ns = $items->getData('xmlNs');
|
|
}
|
|
|
|
if ($sentAs === null) {
|
|
// A general collection of nodes
|
|
foreach ($node as $child) {
|
|
$result[] = $this->recursiveProcess($items, $child);
|
|
}
|
|
} else {
|
|
// A collection of named, repeating nodes
|
|
// (i.e. <collection><foo></foo><foo></foo></collection>)
|
|
$children = $node->children($ns, true)->{$sentAs};
|
|
foreach ($children as $child) {
|
|
$result[] = $this->recursiveProcess($items, $child);
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Process an object
|
|
*
|
|
* @param Parameter $param API parameter being parsed
|
|
* @param \SimpleXMLElement $node Value to process
|
|
*
|
|
* @return array
|
|
*/
|
|
private function processObject(Parameter $param, \SimpleXMLElement $node)
|
|
{
|
|
$result = $knownProps = $knownAttributes = [];
|
|
|
|
// Handle known properties
|
|
if ($properties = $param->getProperties()) {
|
|
foreach ($properties as $property) {
|
|
$name = $property->getName();
|
|
$sentAs = $property->getWireName();
|
|
$knownProps[$sentAs] = 1;
|
|
if (strpos($sentAs, ':')) {
|
|
list($ns, $sentAs) = explode(':', $sentAs);
|
|
} else {
|
|
$ns = $property->getData('xmlNs');
|
|
}
|
|
|
|
if ($property->getData('xmlAttribute')) {
|
|
// Handle XML attributes
|
|
$result[$name] = (string) $node->attributes($ns, true)->{$sentAs};
|
|
$knownAttributes[$sentAs] = 1;
|
|
} elseif (count($node->children($ns, true)->{$sentAs})) {
|
|
// Found a child node matching wire name
|
|
$childNode = $node->children($ns, true)->{$sentAs};
|
|
$result[$name] = $this->recursiveProcess(
|
|
$property,
|
|
$childNode
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle additional, undefined properties
|
|
$additional = $param->getAdditionalProperties();
|
|
if ($additional instanceof Parameter) {
|
|
// Process all child elements according to the given schema
|
|
foreach ($node->children($additional->getData('xmlNs'), true) as $childNode) {
|
|
$sentAs = $childNode->getName();
|
|
if (!isset($knownProps[$sentAs])) {
|
|
$result[$sentAs] = $this->recursiveProcess(
|
|
$additional,
|
|
$childNode
|
|
);
|
|
}
|
|
}
|
|
} elseif ($additional === null || $additional === true) {
|
|
// Blindly transform the XML into an array preserving as much data
|
|
// as possible. Remove processed, aliased properties.
|
|
$array = array_diff_key(self::xmlToArray($node), $knownProps);
|
|
// Remove @attributes that were explicitly plucked from the
|
|
// attributes list.
|
|
if (isset($array['@attributes']) && $knownAttributes) {
|
|
$array['@attributes'] = array_diff_key($array['@attributes'], $knownProps);
|
|
if (!$array['@attributes']) {
|
|
unset($array['@attributes']);
|
|
}
|
|
}
|
|
|
|
// Merge it together with the original result
|
|
$result = array_merge($array, $result);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Convert an XML document to an array.
|
|
*
|
|
* @param int $nesting
|
|
* @param null $ns
|
|
*
|
|
* @return array
|
|
*/
|
|
private static function xmlToArray(
|
|
\SimpleXMLElement $xml,
|
|
$ns = null,
|
|
$nesting = 0
|
|
) {
|
|
$result = [];
|
|
$children = $xml->children($ns, true);
|
|
|
|
foreach ($children as $name => $child) {
|
|
$attributes = (array) $child->attributes($ns, true);
|
|
if (!isset($result[$name])) {
|
|
$childArray = self::xmlToArray($child, $ns, $nesting + 1);
|
|
$result[$name] = $attributes
|
|
? array_merge($attributes, $childArray)
|
|
: $childArray;
|
|
continue;
|
|
}
|
|
// A child element with this name exists so we're assuming
|
|
// that the node contains a list of elements
|
|
if (!is_array($result[$name])) {
|
|
$result[$name] = [$result[$name]];
|
|
} elseif (!isset($result[$name][0])) {
|
|
// Convert the first child into the first element of a numerically indexed array
|
|
$firstResult = $result[$name];
|
|
$result[$name] = [];
|
|
$result[$name][] = $firstResult;
|
|
}
|
|
$childArray = self::xmlToArray($child, $ns, $nesting + 1);
|
|
if ($attributes) {
|
|
$result[$name][] = array_merge($attributes, $childArray);
|
|
} else {
|
|
$result[$name][] = $childArray;
|
|
}
|
|
}
|
|
|
|
// Extract text from node
|
|
$text = trim((string) $xml);
|
|
if ($text === '') {
|
|
$text = null;
|
|
}
|
|
|
|
// Process attributes
|
|
$attributes = (array) $xml->attributes($ns, true);
|
|
if ($attributes) {
|
|
if ($text !== null) {
|
|
$result['value'] = $text;
|
|
}
|
|
$result = array_merge($attributes, $result);
|
|
} elseif ($text !== null) {
|
|
$result = $text;
|
|
}
|
|
|
|
// Make sure we're always returning an array
|
|
if ($nesting == 0 && !is_array($result)) {
|
|
$result = [$result];
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
}
|