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
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