class AWS::Core::XML::Frame

def add_text chars

def add_text chars
  @text ||= ''
  @text << chars
end

def build_child_frame element_name

def build_child_frame element_name
  # if element_name should be wrapped
  #   build a frame for the wrapper
  #   build a child frame from the wrapper
  # else
  Frame.new(root_frame, self, element_name, rules_for(element_name))
end

def close

def close
  # some xml elements should be indexed at the root level
  # The :index rule determines the name of this index
  # and what keys the data should be indexed as (one element
  # can be indexed under multiple keys).  The index value
  # is always the element itself.
  if index = @rules[:index]
    index_keys_for(index) do |key|
      root_frame.add_to_index(index[:name], key, data)
    end
  end
end

def consume_child_frame child_frame

def consume_child_frame child_frame
  child_frame.close
  return if child_frame.ignored?
  ruby_name = child_frame.ruby_name
  value = child_frame.value
  context = data_context_for(child_frame)
  if child_frame.list?
    context[ruby_name] << value
  elsif map = child_frame.map?
    context[ruby_name][child_frame.map_key] = child_frame.map_value
  else
    context[ruby_name] = value
  end
end

def data

def data
  ignored? ? parent_frame.data : @data
end

def data_context_for child_frame

def data_context_for child_frame
  if child_frame.wrapped?
    data[child_frame.wrapper] ||= {}
    data[child_frame.wrapper]
  else
    data
  end
end

def datetime_like_value klass, parts_constructor

def datetime_like_value klass, parts_constructor
  # it's way faster to parse this specific format manually
  # vs. DateTime#parse, and this happens to be the format
  # that AWS uses almost (??) everywhere.
  if @text.tr(*TRANSLATE_DIGITS) == EASY_FORMAT
    parts = @text.tr(*DATE_PUNCTUATION).chop.split.map {|p| p.to_i }
    klass.send(parts_constructor, *parts)
  else
    # fallback in case we have to handle another date format
    klass.parse(@text)
  end
end

def forced?

def forced?
  @rules[:force]
end

def ignored?

def ignored?
  @rules[:ignore]
end

def index_keys_for index_opts, &block

def index_keys_for index_opts, &block
  # simple (single) key
  if key = index_opts[:key]
    yield(data[key])
    return
  end
  # composite key, joined by ":"
  if parts = index_opts[:keys]
    composite_key = parts.map{|part| data[part] }.join(":")
    yield(composite_key)
    return
  end
  # multiple keys, collected from the given path
  if path = index_opts[:key_path]
    keys_from_path(data, path.dup, &block)
    return
  end
  raise "missing require index rule option, :key, :keys or :key_path"
end

def initialize root_frame, parent_frame, element_name, rules

def initialize root_frame, parent_frame, element_name, rules
  @root_frame = root_frame
  @parent_frame = parent_frame
  @element_name = element_name
  @rules = rules
  @rules[:children] ||= {}
  @data = {}.merge(rules[:defaults] || {})
  @text = nil
  # initialize values for child frames of special types (e.g.
  # lists, maps, and forced elements)
  known_child_frames.each do |child_frame|
    context = data_context_for(child_frame)
    if child_frame.list?
      context[child_frame.ruby_name] = []
    elsif child_frame.map?
      context[child_frame.ruby_name] = {}
    elsif child_frame.forced?
      context[child_frame.ruby_name] = child_frame.value
    end
  end
end

def keys_from_path data, path, &block

def keys_from_path data, path, &block
  step = path.shift
  value = data[step]
  if path.empty?
    yield(value)
    return
  end
  if value.is_a?(Array)
    value.each do |v|
      keys_from_path(v, path.dup, &block)
    end
  else
    keys_from_path(value, path.dup, &block)
  end
end

def known_child_frames

other children will be parsed using standard rules
The list of child frames that have customizations (rules), all
def known_child_frames
  rules[:children].keys.map {|name| build_child_frame(name) }
end

def list?

def list?
  @rules[:list]
end

def map?

def map?
  @rules[:map]
end

def map_key

def map_key
  data[root_frame.inflect(@rules[:map].first)]
end

def map_value

def map_value
  data[root_frame.inflect(@rules[:map].last)]
end

def ruby_name

def ruby_name
  rules[:rename] || root_frame.inflect(element_name)
end

def rules_for child_element_name

def rules_for child_element_name
  rules[:children][child_element_name] || {}
end

def value

def value
  if !data.empty?
    data[:encoding] == 'base64' ?  Base64.decode64(@text.strip) : data
  elsif @text.nil?
    rules[:type] == :boolean ? false : nil
  else
    case rules[:type]
    when nil, :string then @text
    when :datetime    then datetime_like_value(DateTime, :civil)
    when :time        then datetime_like_value(Time, :utc)
    when :integer     then @text.to_i
    when :float       then @text.to_f
    when :boolean     then @text == 'true'
    when :blob        then Base64.decode64(@text)
    when :symbol      then Core::Inflection.ruby_name(@text).to_sym
    else raise "unhandled type"
    end
  end
end

def wrapped?

def wrapped?
  @rules[:wrap]
end