require'base64'require'bigdecimal'require'date'require'multi_xml/core_extensions'require'time'require'yaml'moduleMultiXmlclassParseError<StandardError;endclass<<selfREQUIREMENT_MAP=[['libxml',:libxml],['nokogiri',:nokogiri],['rexml/document',:rexml]]unlessdefined?(REQUIREMENT_MAP)CONTENT_ROOT='__content__'.freezeunlessdefined?(CONTENT_ROOT)# TODO: use Time.xmlschema instead of Time.parse;# use regexp instead of Date.parseunlessdefined?(PARSING)PARSING={'symbol'=>Proc.new{|symbol|symbol.to_sym},'date'=>Proc.new{|date|Date.parse(date)},'datetime'=>Proc.new{|time|Time.parse(time).utcrescueDateTime.parse(time).utc},'integer'=>Proc.new{|integer|integer.to_i},'float'=>Proc.new{|float|float.to_f},'decimal'=>Proc.new{|number|BigDecimal(number)},'boolean'=>Proc.new{|boolean|!%w(0 false).include?(boolean.strip)},'string'=>Proc.new{|string|string.to_s},'yaml'=>Proc.new{|yaml|YAML::load(yaml)rescueyaml},'base64Binary'=>Proc.new{|binary|binary.unpack('m').first},'binary'=>Proc.new{|binary,entity|parse_binary(binary,entity)},'file'=>Proc.new{|file,entity|parse_file(file,entity)}}PARSING.update('double'=>PARSING['float'],'dateTime'=>PARSING['datetime'])end# Get the current parser class.defparserreturn@parserif@parserself.parser=self.default_parser@parserend# The default parser based on what you currently# have loaded and installed. First checks to see# if any parsers are already loaded, then checks# to see which are installed if none are loaded.defdefault_parserreturn:libxmlifdefined?(::LibXML)return:nokogiriifdefined?(::Nokogiri)REQUIREMENT_MAP.eachdo|(library,parser)|beginrequirelibraryreturnparserrescueLoadErrornextendendend# Set the XML parser utilizing a symbol, string, or class.# Supported by default are:## * <tt>:libxml</tt># * <tt>:nokogiri</tt># * <tt>:rexml</tt>defparser=(new_parser)casenew_parserwhenString,Symbolrequire"multi_xml/parsers/#{new_parser.to_s.downcase}"@parser=MultiXml::Parsers.const_get("#{new_parser.to_s.split('_').map{|s|s.capitalize}.join('')}")whenClass,Module@parser=new_parserelseraise"Did not recognize your parser specification. Please specify either a symbol or a class."endend# Parse an XML string into Ruby.## <b>Options</b>## <tt>:symbolize_keys</tt> :: If true, will use symbols instead of strings for the keys.defparse(xml,options={})xml.strip!beginhash=typecast_xml_value(undasherize_keys(parser.parse(xml)))||{}rescueparser.parse_error=>errorraiseParseError,error.to_s,error.backtraceendhash=symbolize_keys(hash)ifoptions[:symbolize_keys]hashend# This module decorates files with the <tt>original_filename</tt># and <tt>content_type</tt> methods.moduleFileLike#:nodoc:attr_writer:original_filename,:content_typedeforiginal_filename@original_filename||'untitled'enddefcontent_type@content_type||'application/octet-stream'endendprivate# TODO: Add support for other encodingsdefself.parse_binary(binary,entity)#:nodoc:caseentity['encoding']when'base64'Base64.decode64(binary)elsebinaryendenddefself.parse_file(file,entity)f=StringIO.new(Base64.decode64(file))f.extend(FileLike)f.original_filename=entity['name']f.content_type=entity['content_type']fenddefsymbolize_keys(hash)hash.inject({})do|result,(key,value)|new_key=casekeywhenStringkey.to_symelsekeyendnew_value=casevaluewhenHashsymbolize_keys(value)elsevalueendresult[new_key]=new_valueresultendenddefundasherize_keys(params)caseparamswhenHashparams.inject({})do|hash,(key,value)|hash[key.to_s.tr('-','_')]=undasherize_keys(value)hashendwhenArrayparams.map{|value|undasherize_keys(value)}elseparamsendenddeftypecast_xml_value(value)casevaluewhenHashifvalue['type']=='array'_,entries=Array.wrap(value.detect{|key,value|key!='type'})ifentries.blank?||(value.is_a?(Hash)&&c=value[CONTENT_ROOT]&&c.blank?)[]elsecaseentrieswhenArrayentries.map{|value|typecast_xml_value(value)}whenHash[typecast_xml_value(entries)]elseraise"can't typecast #{entries.class.name}: #{entries.inspect}"endendelsifvalue.has_key?(CONTENT_ROOT)content=value[CONTENT_ROOT]ifblock=PARSING[value['type']]block.arity==1?block.call(content):block.call(content,value)elsecontentendelsifvalue['type']=='string'&&value['nil']!='true'''# blank or nil parsed values are represented by nilelsifvalue.blank?||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)elsifvalue['type']&&value.size==1&&!value['type'].is_a?(Hash)nilelsexml_value=value.inject({})do|hash,(key,value)|hash[key]=typecast_xml_value(value)hashend# Turn {:files => {:file => #<StringIO>} into {:files => #<StringIO>} so it is compatible with# how multipart uploaded files from HTML appearxml_value['file'].is_a?(StringIO)?xml_value['file']:xml_valueendwhenArrayvalue.map!{|i|typecast_xml_value(i)}value.length>1?value:value.firstwhenStringvalueelseraise"can't typecast #{value.class.name}: #{value.inspect}"endendendend