# frozen_string_literal: true
require 'base64'
module Aws
module Xml
class Builder
include Seahorse::Model::Shapes
def initialize(rules, options = {})
@rules = rules
@xml = options[:target] || []
indent = options[:indent] || ''
pad = options[:pad] || ''
@builder = DocBuilder.new(target: @xml, indent: indent, pad: pad)
end
def to_xml(params)
structure(@rules.location_name, @rules, params)
@xml.join
end
alias serialize to_xml
private
def structure(name, ref, values)
if values.empty?
node(name, ref)
else
node(name, ref, structure_attrs(ref, values)) do
ref.shape.members.each do |member_name, member_ref|
next if values[member_name].nil?
next if xml_attribute?(member_ref)
member(member_ref.location_name, member_ref, values[member_name])
end
end
end
end
def structure_attrs(ref, values)
ref.shape.members.inject({}) do |attrs, (member_name, member_ref)|
if xml_attribute?(member_ref) && values.key?(member_name)
attrs[member_ref.location_name] = values[member_name]
end
attrs
end
end
def list(name, ref, values)
if ref[:flattened] || ref.shape.flattened
values.each do |value|
member(ref.shape.member.location_name || name, ref.shape.member, value)
end
else
node(name, ref) do
values.each do |value|
mname = ref.shape.member.location_name || 'member'
member(mname, ref.shape.member, value)
end
end
end
end
def map(name, ref, hash)
key_ref = ref.shape.key
value_ref = ref.shape.value
if ref.shape.flattened
hash.each do |key, value|
node(name, ref) do
member(key_ref.location_name || 'key', key_ref, key)
member(value_ref.location_name || 'value', value_ref, value)
end
end
else
node(name, ref) do
hash.each do |key, value|
node('entry', ref) do
member(key_ref.location_name || 'key', key_ref, key)
member(value_ref.location_name || 'value', value_ref, value)
end
end
end
end
end
def member(name, ref, value)
case ref.shape
when StructureShape then structure(name, ref, value)
when ListShape then list(name, ref, value)
when MapShape then map(name, ref, value)
when TimestampShape then node(name, ref, timestamp(ref, value))
when BlobShape then node(name, ref, blob(value))
else
node(name, ref, value.to_s)
end
end
def blob(value)
value = value.read unless String === value
Base64.strict_encode64(value)
end
def timestamp(ref, value)
case ref['timestampFormat'] || ref.shape['timestampFormat']
when 'unixTimestamp' then value.to_i
when 'rfc822' then value.utc.httpdate
else
# xml defaults to iso8601
value.utc.iso8601
end
end
# The `args` list may contain:
#
# * [] - empty, no value or attributes
# * [value] - inline element, no attributes
# * [value, attributes_hash] - inline element with attributes
# * [attributes_hash] - self closing element with attributes
#
# Pass a block if you want to nest XML nodes inside. When doing this,
# you may *not* pass a value to the `args` list.
#
def node(name, ref, *args, &block)
attrs = args.last.is_a?(Hash) ? args.pop : {}
attrs = shape_attrs(ref).merge(attrs)
args << attrs
@builder.node(name, *args, &block)
end
def shape_attrs(ref)
if xmlns = ref['xmlNamespace']
if prefix = xmlns['prefix']
{ 'xmlns:' + prefix => xmlns['uri'] }
else
{ 'xmlns' => xmlns['uri'] }
end
else
{}
end
end
def xml_attribute?(ref)
!!ref['xmlAttribute']
end
end
end
end