<?php 
namespace Aws\Api\Serializer; 
 
use Aws\Api\MapShape; 
use Aws\Api\Service; 
use Aws\Api\Shape; 
use Aws\Api\StructureShape; 
use Aws\Api\ListShape; 
use Aws\Api\TimestampShape; 
use XMLWriter; 
 
/** 
 * @internal Formats the XML body of a REST-XML services. 
 */ 
class XmlBody 
{ 
    /** @var \Aws\Api\Service */ 
    private $api; 
 
    /** 
     * @param Service $api API being used to create the XML body. 
     */ 
    public function __construct(Service $api) 
    { 
        $this->api = $api; 
    } 
 
    /** 
     * Builds the XML body based on an array of arguments. 
     * 
     * @param Shape $shape Operation being constructed 
     * @param array $args  Associative array of arguments 
     * 
     * @return string 
     */ 
    public function build(Shape $shape, array $args) 
    { 
        $xml = new XMLWriter(); 
        $xml->openMemory(); 
        $xml->startDocument('1.0', 'UTF-8'); 
        $this->format($shape, $shape['locationName'], $args, $xml); 
        $xml->endDocument(); 
 
        return $xml->outputMemory(); 
    } 
 
    private function startElement(Shape $shape, $name, XMLWriter $xml) 
    { 
        $xml->startElement($name); 
 
        if ($ns = $shape['xmlNamespace']) { 
            $xml->writeAttribute( 
                isset($ns['prefix']) ? "xmlns:{$ns['prefix']}" : 'xmlns', 
                $shape['xmlNamespace']['uri'] 
            ); 
        } 
    } 
 
    private function format(Shape $shape, $name, $value, XMLWriter $xml) 
    { 
        // Any method mentioned here has a custom serialization handler. 
        static $methods = [ 
            'add_structure' => true, 
            'add_list'      => true, 
            'add_blob'      => true, 
            'add_timestamp' => true, 
            'add_boolean'   => true, 
            'add_map'       => true, 
            'add_string'    => true 
        ]; 
 
        $type = 'add_' . $shape['type']; 
        if (isset($methods[$type])) { 
            $this->{$type}($shape, $name, $value, $xml); 
        } else { 
            $this->defaultShape($shape, $name, $value, $xml); 
        } 
    } 
 
    private function defaultShape(Shape $shape, $name, $value, XMLWriter $xml) 
    { 
        $this->startElement($shape, $name, $xml); 
        $xml->writeRaw($value); 
        $xml->endElement(); 
    } 
 
    private function add_structure( 
        StructureShape $shape, 
        $name, 
        array $value, 
        \XMLWriter $xml 
    ) { 
        $this->startElement($shape, $name, $xml); 
 
        foreach ($this->getStructureMembers($shape, $value) as $k => $definition) { 
            $this->format( 
                $definition['member'], 
                $definition['member']['locationName'] ?: $k, 
                $definition['value'], 
                $xml 
            ); 
        } 
 
        $xml->endElement(); 
    } 
 
    private function getStructureMembers(StructureShape $shape, array $value) 
    { 
        $members = []; 
 
        foreach ($value as $k => $v) { 
            if ($v !== null && $shape->hasMember($k)) { 
                $definition = [ 
                    'member' => $shape->getMember($k), 
                    'value'  => $v, 
                ]; 
 
                if ($definition['member']['xmlAttribute']) { 
                    // array_unshift_associative 
                    $members = [$k => $definition] + $members; 
                } else { 
                    $members[$k] = $definition; 
                } 
            } 
        } 
 
        return $members; 
    } 
 
    private function add_list( 
        ListShape $shape, 
        $name, 
        array $value, 
        XMLWriter $xml 
    ) { 
        $items = $shape->getMember(); 
 
        if ($shape['flattened']) { 
            $elementName = $name; 
        } else { 
            $this->startElement($shape, $name, $xml); 
            $elementName = $items['locationName'] ?: 'member'; 
        } 
 
        foreach ($value as &$v) { 
            $this->format($items, $elementName, $v, $xml); 
        } 
 
        if (!$shape['flattened']) { 
            $xml->endElement(); 
        } 
    } 
 
    private function add_map( 
        MapShape $shape, 
        $name, 
        array $value, 
        XMLWriter $xml 
    ) { 
        $xmlEntry = $shape['flattened'] ? $shape['locationName'] : 'entry'; 
        $xmlKey = $shape->getKey()['locationName'] ?: 'key'; 
        $xmlValue = $shape->getValue()['locationName'] ?: 'value'; 
 
        $this->startElement($shape, $name, $xml); 
 
        foreach ($value as $key => $v) { 
            $this->startElement($shape, $xmlEntry, $xml); 
            $this->format($shape->getKey(), $xmlKey, $key, $xml); 
            $this->format($shape->getValue(), $xmlValue, $v, $xml); 
            $xml->endElement(); 
        } 
 
        $xml->endElement(); 
    } 
 
    private function add_blob(Shape $shape, $name, $value, XMLWriter $xml) 
    { 
        $this->startElement($shape, $name, $xml); 
        $xml->writeRaw(base64_encode($value)); 
        $xml->endElement(); 
    } 
 
    private function add_timestamp( 
        TimestampShape $shape, 
        $name, 
        $value, 
        XMLWriter $xml 
    ) { 
        $this->startElement($shape, $name, $xml); 
        $xml->writeRaw(TimestampShape::format($value, 'iso8601')); 
        $xml->endElement(); 
    } 
 
    private function add_boolean( 
        Shape $shape, 
        $name, 
        $value, 
        XMLWriter $xml 
    ) { 
        $this->startElement($shape, $name, $xml); 
        $xml->writeRaw($value ? 'true' : 'false'); 
        $xml->endElement(); 
    } 
 
    private function add_string( 
        Shape $shape, 
        $name, 
        $value, 
        XMLWriter $xml 
    ) { 
        if ($shape['xmlAttribute']) { 
            $xml->writeAttribute($shape['locationName'] ?: $name, $value); 
        } else { 
            $this->defaultShape($shape, $name, $value, $xml); 
        } 
    } 
} 
 
 |