module MultiXml
def base64_decode(input)
def base64_decode(input) input.unpack1("m") end
def default_parser
if any parsers are already loaded, then checks
have loaded and installed. First checks to see
The default parser based on what you currently
def default_parser return :ox if defined?(::Ox) return :libxml if defined?(::LibXML) return :nokogiri if defined?(::Nokogiri) return :oga if defined?(::Oga) REQUIREMENT_MAP.each do |library, parser| require library return parser rescue LoadError next end raise(NoParserError, "No XML parser detected. If you're using Rubinius and Bundler, try adding an XML parser to your Gemfile (e.g. libxml-ruby, nokogiri, or rubysl-rexml). For more information, see https://github.com/sferik/multi_xml/issues/42.") end
def parse(xml, options = {}) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
:typecast_xml_value :: If true, won't typecast values for parsed document
:disallowed_types :: Types to disallow from being typecasted. Defaults to `['yaml', 'symbol']`. Use `[]` to allow all types.
:symbolize_keys :: If true, will use symbols instead of strings for the keys.
Options
Parse an XML string or IO into Ruby.
def parse(xml, options = {}) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity xml ||= "" options = DEFAULT_OPTIONS.merge(options) xml = xml.strip if xml.respond_to?(:strip) begin xml = StringIO.new(xml) unless xml.respond_to?(:read) char = xml.getc return {} if char.nil? xml.ungetc(char) hash = undasherize_keys(parser.parse(xml) || {}) hash = typecast_xml_value(hash, options[:disallowed_types]) if options[:typecast_xml_value] rescue DisallowedTypeError raise rescue parser.parse_error => e raise(ParseError, e.message, e.backtrace) end hash = symbolize_keys(hash) if options[:symbolize_keys] hash end
def parse_binary(binary, entity) # :nodoc:
TODO: Add support for other encodings
def parse_binary(binary, entity) # :nodoc: case entity["encoding"] when "base64" base64_decode(binary) else binary end end
def parse_file(file, entity)
def parse_file(file, entity) f = StringIO.new(base64_decode(file)) f.extend(FileLike) f.original_filename = entity["name"] f.content_type = entity["content_type"] f end
def parser
def parser return @parser if defined?(@parser) self.parser = default_parser @parser end
def parser=(new_parser)
* :rexml
* :ox
* :nokogiri
* :libxml
Supported by default are:
Set the XML parser utilizing a symbol, string, or class.
def parser=(new_parser) case new_parser when String, Symbol require "multi_xml/parsers/#{new_parser.to_s.downcase}" @parser = MultiXml::Parsers.const_get(new_parser.to_s.split("_").collect(&:capitalize).join.to_s) when Class, Module @parser = new_parser else raise("Did not recognize your parser specification. Please specify either a symbol or a class.") end end
def symbolize_keys(params)
def symbolize_keys(params) case params when Hash params.inject({}) do |result, (key, value)| result.merge(key.to_sym => symbolize_keys(value)) end when Array params.collect { |value| symbolize_keys(value) } else params end end
def typecast_xml_value(value, disallowed_types = nil) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
def typecast_xml_value(value, disallowed_types = nil) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity disallowed_types ||= DISALLOWED_XML_TYPES case value when Hash if value.include?("type") && !value["type"].is_a?(Hash) && disallowed_types.include?(value["type"]) raise(DisallowedTypeError, value["type"]) end if value["type"] == "array" # this commented-out suggestion helps to avoid the multiple attribute # problem, but it breaks when there is only one item in the array. # # from: https://github.com/jnunemaker/httparty/issues/102 # # _, entries = value.detect { |k, v| k != 'type' && v.is_a?(Array) } # This attempt fails to consider the order that the detect method # retrieves the entries. # _, entries = value.detect {|key, _| key != 'type'} # This approach ignores attribute entries that are not convertable # to an Array which allows attributes to be ignored. _, entries = value.detect { |k, v| k != "type" && (v.is_a?(Array) || v.is_a?(Hash)) } case entries when NilClass [] when String [] if entries.strip.empty? when Array entries.collect { |entry| typecast_xml_value(entry, disallowed_types) } when Hash [typecast_xml_value(entries, disallowed_types)] else raise("can't typecast #{entries.class.name}: #{entries.inspect}") end elsif value.key?(CONTENT_ROOT) content = value[CONTENT_ROOT] block = PARSING[value["type"]] if block if block.arity == 1 value.delete("type") if PARSING[value["type"]] if value.keys.size > 1 value[CONTENT_ROOT] = block.call(content) value else block.call(content) end else block.call(content, value) end else (value.keys.size > 1) ? value : content end elsif value["type"] == "string" && value["nil"] != "true" "" # blank or nil parsed values are represented by nil elsif value.empty? || value["nil"] == "true" nil # If the type is the only element which makes it then # this still makes the value nil, except if type is # a XML node(where type['value'] is a Hash) elsif value["type"] && value.size == 1 && !value["type"].is_a?(Hash) nil else xml_value = value.each_with_object({}) do |(k, v), hash| hash[k] = typecast_xml_value(v, disallowed_types) hash end # Turn {:files => {:file => #<StringIO>} into {:files => #<StringIO>} so it is compatible with # how multipart uploaded files from HTML appear (xml_value["file"].is_a?(StringIO)) ? xml_value["file"] : xml_value end when Array value.map! { |i| typecast_xml_value(i, disallowed_types) } (value.length > 1) ? value : value.first when String value else raise("can't typecast #{value.class.name}: #{value.inspect}") end end
def undasherize_keys(params)
def undasherize_keys(params) case params when Hash params.each_with_object({}) do |(key, value), hash| hash[key.to_s.tr("-", "_")] = undasherize_keys(value) hash end when Array params.collect { |value| undasherize_keys(value) } else params end end