nori-2.6.0/0000755000175000017500000000000012551223667012047 5ustar globusglobusnori-2.6.0/lib/0000755000175000017500000000000012551223667012615 5ustar globusglobusnori-2.6.0/lib/nori/0000755000175000017500000000000012551223667013564 5ustar globusglobusnori-2.6.0/lib/nori/core_ext.rb0000644000175000017500000000013312551223667015716 0ustar globusglobusrequire "nori/core_ext/object" require "nori/core_ext/string" require "nori/core_ext/hash" nori-2.6.0/lib/nori/core_ext/0000755000175000017500000000000012551223667015374 5ustar globusglobusnori-2.6.0/lib/nori/core_ext/object.rb0000644000175000017500000000033012551223667017163 0ustar globusglobusclass Nori module CoreExt module Object def blank? respond_to?(:empty?) ? empty? : !self end unless method_defined?(:blank?) end end end Object.send :include, Nori::CoreExt::Object nori-2.6.0/lib/nori/core_ext/string.rb0000644000175000017500000000067712551223667017241 0ustar globusglobusclass Nori module CoreExt module String # Returns the String in snake_case. def snakecase str = dup str.gsub!(/::/, '/') str.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2') str.gsub!(/([a-z\d])([A-Z])/, '\1_\2') str.tr! ".", "_" str.tr! "-", "_" str.downcase! str end unless method_defined?(:snakecase) end end end String.send :include, Nori::CoreExt::String nori-2.6.0/lib/nori/core_ext/hash.rb0000644000175000017500000000302012551223667016637 0ustar globusglobusrequire "uri" class Nori module CoreExt module Hash # @param key The key for the param. # @param value The value for the param. # # @return This key value pair as a param # # @example normalize_param(:name, "Bob Jones") #=> "name=Bob%20Jones" def normalize_param(key, value) if value.is_a?(Array) normalize_array_params(key, value) elsif value.is_a?(Hash) normalize_hash_params(key, value) else normalize_simple_type_params(key, value) end end # @return The hash as attributes for an XML tag. # # @example # { :one => 1, "two"=>"TWO" }.to_xml_attributes # #=> 'one="1" two="TWO"' def to_xml_attributes map do |k, v| %{#{k.to_s.snakecase.sub(/^(.{1,1})/) { |m| m.downcase }}="#{v}"} end.join(' ') end private def normalize_simple_type_params(key, value) ["#{key}=#{encode_simple_value(value)}"] end def normalize_array_params(key, array) array.map do |element| normalize_param("#{key}[]", element) end end def normalize_hash_params(key, hash) hash.map do |nested_key, element| normalize_param("#{key}[#{nested_key}]", element) end end def encode_simple_value(value) URI.encode(value.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]")) end end end end Hash.send :include, Nori::CoreExt::Hash nori-2.6.0/lib/nori/string_with_attributes.rb0000644000175000017500000000013312551223667020715 0ustar globusglobusclass Nori class StringWithAttributes < String attr_accessor :attributes end end nori-2.6.0/lib/nori/version.rb0000644000175000017500000000004312551223667015573 0ustar globusglobusclass Nori VERSION = '2.6.0' end nori-2.6.0/lib/nori/xml_utility_node.rb0000644000175000017500000002115412551223667017504 0ustar globusglobusrequire "date" require "time" require "yaml" require "bigdecimal" require "nori/string_with_attributes" require "nori/string_io_file" class Nori # This is a slighly modified version of the XMLUtilityNode from # http://merb.devjavu.com/projects/merb/ticket/95 (has.sox@gmail.com) # # John Nunemaker: # It's mainly just adding vowels, as I ht cd wth n vwls :) # This represents the hard part of the work, all I did was change the # underlying parser. class XMLUtilityNode # Simple xs:time Regexp. # Valid xs:time formats # 13:20:00 1:20 PM # 13:20:30.5555 1:20 PM and 30.5555 seconds # 13:20:00-05:00 1:20 PM, US Eastern Standard Time # 13:20:00+02:00 1:20 PM, Central European Standard Time # 13:20:00Z 1:20 PM, Coordinated Universal Time (UTC) # 13:20:30.5555Z 1:20 PM and 30.5555 seconds, Coordinated Universal Time (UTC) # 00:00:00 midnight # 24:00:00 midnight XS_TIME = /^\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?$/ # Simple xs:date Regexp. # Valid xs:date formats # 2004-04-12 April 12, 2004 # -0045-01-01 January 1, 45 BC # 12004-04-12 April 12, 12004 # 2004-04-12-05:00 April 12, 2004, US Eastern Standard Time, which is 5 hours behind Coordinated Universal Time (UTC) # 2004-04-12+02:00 April 12, 2004, Central European Summer Time, which is 2 hours ahead of Coordinated Universal Time (UTC) # 2004-04-12Z April 12, 2004, Coordinated Universal Time (UTC) XS_DATE = /^-?\d{4}-\d{2}-\d{2}(?:Z|[+-]\d{2}:?\d{2})?$/ # Simple xs:dateTime Regexp. # Valid xs:dateTime formats # 2004-04-12T13:20:00 1:20 pm on April 12, 2004 # 2004-04-12T13:20:15.5 1:20 pm and 15.5 seconds on April 12, 2004 # 2004-04-12T13:20:00-05:00 1:20 pm on April 12, 2004, US Eastern Standard Time # 2004-04-12T13:20:00+02:00 1:20 pm on April 12, 2004, Central European Summer Time # 2004-04-12T13:20:15.5-05:00 1:20 pm and 15.5 seconds on April 12, 2004, US Eastern Standard Time # 2004-04-12T13:20:00Z 1:20 pm on April 12, 2004, Coordinated Universal Time (UTC) # 2004-04-12T13:20:15.5Z 1:20 pm and 15.5 seconds on April 12, 2004, Coordinated Universal Time (UTC) XS_DATE_TIME = /^-?\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?$/ def self.typecasts @@typecasts end def self.typecasts=(obj) @@typecasts = obj end def self.available_typecasts @@available_typecasts end def self.available_typecasts=(obj) @@available_typecasts = obj end self.typecasts = {} self.typecasts["integer"] = lambda { |v| v.nil? ? nil : v.to_i } self.typecasts["boolean"] = lambda { |v| v.nil? ? nil : (v.strip != "false") } self.typecasts["datetime"] = lambda { |v| v.nil? ? nil : Time.parse(v).utc } self.typecasts["date"] = lambda { |v| v.nil? ? nil : Date.parse(v) } self.typecasts["dateTime"] = lambda { |v| v.nil? ? nil : Time.parse(v).utc } self.typecasts["decimal"] = lambda { |v| v.nil? ? nil : BigDecimal(v.to_s) } self.typecasts["double"] = lambda { |v| v.nil? ? nil : v.to_f } self.typecasts["float"] = lambda { |v| v.nil? ? nil : v.to_f } self.typecasts["string"] = lambda { |v| v.to_s } self.typecasts["base64Binary"] = lambda { |v| v.unpack('m').first } self.available_typecasts = self.typecasts.keys def initialize(options, name, attributes = {}) @options = options @name = Nori.hash_key(name, options) if converter = options[:convert_attributes_to] intermediate = attributes.map {|k, v| converter.call(k, v) }.flatten attributes = Hash[*intermediate] end # leave the type alone if we don't know what it is @type = self.class.available_typecasts.include?(attributes["type"]) ? attributes.delete("type") : attributes["type"] @nil_element = false attributes.keys.each do |key| if result = /^((.*):)?nil$/.match(key) @nil_element = attributes.delete(key) == "true" attributes.delete("xmlns:#{result[2]}") if result[1] end attributes.delete(key) if @options[:delete_namespace_attributes] && key[/^(xmlns|xsi)/] end @attributes = undasherize_keys(attributes) @children = [] @text = false end attr_accessor :name, :attributes, :children, :type def prefixed_attributes attributes.inject({}) do |memo, (key, value)| memo[prefixed_attribute_name("@#{key}")] = value memo end end def prefixed_attribute_name(attribute) return attribute unless @options[:convert_tags_to].respond_to? :call @options[:convert_tags_to].call(attribute) end def add_node(node) @text = true if node.is_a? String @children << node end def to_hash if @type == "file" f = StringIOFile.new((@children.first || '').unpack('m').first) f.original_filename = attributes['name'] || 'untitled' f.content_type = attributes['content_type'] || 'application/octet-stream' return { name => f } end if @text t = typecast_value(inner_html) t = advanced_typecasting(t) if t.is_a?(String) && @options[:advanced_typecasting] if t.is_a?(String) t = StringWithAttributes.new(t) t.attributes = attributes end return { name => t } else #change repeating groups into an array groups = @children.inject({}) { |s,e| (s[e.name] ||= []) << e; s } out = nil if @type == "array" out = [] groups.each do |k, v| if v.size == 1 out << v.first.to_hash.entries.first.last else out << v.map{|e| e.to_hash[k]} end end out = out.flatten else # If Hash out = {} groups.each do |k,v| if v.size == 1 out.merge!(v.first) else out.merge!( k => v.map{|e| e.to_hash[k]}) end end out.merge! prefixed_attributes unless attributes.empty? out = out.empty? ? @options[:empty_tag_value] : out end if @type && out.nil? { name => typecast_value(out) } else { name => out } end end end # Typecasts a value based upon its type. For instance, if # +node+ has #type == "integer", # {{[node.typecast_value("12") #=> 12]}} # # @param value The value that is being typecast. # # @details [:type options] # "integer":: # converts +value+ to an integer with #to_i # "boolean":: # checks whether +value+, after removing spaces, is the literal # "true" # "datetime":: # Parses +value+ using Time.parse, and returns a UTC Time # "date":: # Parses +value+ using Date.parse # # @return # The result of typecasting +value+. # # @note # If +self+ does not have a "type" key, or if it's not one of the # options specified above, the raw +value+ will be returned. def typecast_value(value) return value unless @type proc = self.class.typecasts[@type] proc.nil? ? value : proc.call(value) end def advanced_typecasting(value) split = value.split return value if split.size > 1 case split.first when "true" then true when "false" then false when XS_DATE_TIME then try_to_convert(value) {|x| DateTime.parse(x)} when XS_DATE then try_to_convert(value) {|x| Date.parse(x)} when XS_TIME then try_to_convert(value) {|x| Time.parse(x)} else value end end # Take keys of the form foo-bar and convert them to foo_bar def undasherize_keys(params) params.keys.each do |key, value| params[key.tr("-", "_")] = params.delete(key) end params end # Get the inner_html of the REXML node. def inner_html @children.join end # Converts the node into a readable HTML node. # # @return The HTML node in text form. def to_html attributes.merge!(:type => @type ) if @type "<#{name}#{attributes.to_xml_attributes}>#{@nil_element ? '' : inner_html}" end alias to_s to_html private def try_to_convert(value, &block) block.call(value) rescue ArgumentError value end def strip_namespace(string) string.split(":").last end end end nori-2.6.0/lib/nori/string_io_file.rb0000644000175000017500000000015312551223667017104 0ustar globusglobusclass Nori class StringIOFile < StringIO attr_accessor :original_filename, :content_type end end nori-2.6.0/lib/nori/parser/0000755000175000017500000000000012551223667015060 5ustar globusglobusnori-2.6.0/lib/nori/parser/rexml.rb0000644000175000017500000000237012551223667016536 0ustar globusglobusrequire "rexml/parsers/baseparser" require "rexml/text" require "rexml/document" class Nori module Parser # = Nori::Parser::REXML # # REXML pull parser. module REXML def self.parse(xml, options) stack = [] parser = ::REXML::Parsers::BaseParser.new(xml) while true event = unnormalize(parser.pull) case event[0] when :end_document break when :end_doctype, :start_doctype # do nothing when :start_element stack.push Nori::XMLUtilityNode.new(options, event[1], event[2]) when :end_element if stack.size > 1 temp = stack.pop stack.last.add_node(temp) end when :text, :cdata stack.last.add_node(event[1]) unless event[1].strip.length == 0 || stack.empty? end end stack.length > 0 ? stack.pop.to_hash : {} end def self.unnormalize(event) event.map! do |el| if el.is_a?(String) ::REXML::Text.unnormalize(el) elsif el.is_a?(Hash) el.each {|k,v| el[k] = ::REXML::Text.unnormalize(v)} else el end end end end end end nori-2.6.0/lib/nori/parser/nokogiri.rb0000644000175000017500000000303212551223667017224 0ustar globusglobusrequire "nokogiri" class Nori module Parser # = Nori::Parser::Nokogiri # # Nokogiri SAX parser. module Nokogiri class Document < ::Nokogiri::XML::SAX::Document attr_accessor :options def stack @stack ||= [] end def start_element(name, attrs = []) stack.push Nori::XMLUtilityNode.new(options, name, Hash[*attrs.flatten]) end # To keep backward behaviour compatibility # delete last child if it is a space-only text node def end_element(name) if stack.size > 1 last = stack.pop maybe_string = last.children.last if maybe_string.is_a?(String) and maybe_string.strip.empty? last.children.pop end stack.last.add_node last end end # If this node is a successive character then add it as is. # First child being a space-only text node will not be added # because there is no previous characters. def characters(string) last = stack.last if last and last.children.last.is_a?(String) or string.strip.size > 0 last.add_node(string) end end alias cdata_block characters end def self.parse(xml, options) document = Document.new document.options = options parser = ::Nokogiri::XML::SAX::Parser.new document parser.parse xml document.stack.length > 0 ? document.stack.pop.to_hash : {} end end end end nori-2.6.0/lib/nori.rb0000644000175000017500000000415712551223667014120 0ustar globusglobusrequire "nori/version" require "nori/core_ext" require "nori/xml_utility_node" class Nori def self.hash_key(name, options = {}) name = name.tr("-", "_") if options[:convert_dashes_to_underscores] name = name.split(":").last if options[:strip_namespaces] name = options[:convert_tags_to].call(name) if options[:convert_tags_to].respond_to? :call name end PARSERS = { :rexml => "REXML", :nokogiri => "Nokogiri" } def initialize(options = {}) defaults = { :strip_namespaces => false, :delete_namespace_attributes => false, :convert_tags_to => nil, :convert_attributes_to => nil, :empty_tag_value => nil, :advanced_typecasting => true, :convert_dashes_to_underscores => true, :parser => :nokogiri } validate_options! defaults.keys, options.keys @options = defaults.merge(options) end def find(hash, *path) return hash if path.empty? key = path.shift key = self.class.hash_key(key, @options) value = find_value(hash, key) find(value, *path) if value end def parse(xml) cleaned_xml = xml.strip return {} if cleaned_xml.empty? parser = load_parser @options[:parser] parser.parse(cleaned_xml, @options) end private def load_parser(parser) require "nori/parser/#{parser}" Parser.const_get PARSERS[parser] end # Expects a +block+ which receives a tag to convert. # Accepts +nil+ for a reset to the default behavior of not converting tags. def convert_tags_to(reset = nil, &block) @convert_tag = reset || block end def validate_options!(available_options, options) spurious_options = options - available_options unless spurious_options.empty? raise ArgumentError, "Spurious options: #{spurious_options.inspect}\n" \ "Available options are: #{available_options.inspect}" end end def find_value(hash, key) hash.each do |k, v| key_without_namespace = k.to_s.split(':').last return v if key_without_namespace == key.to_s end nil end end nori-2.6.0/.gitignore0000644000175000017500000000007412551223667014040 0ustar globusglobus.DS_Store doc coverage *~ *.gem .bundle Gemfile.lock /.idea nori-2.6.0/Rakefile0000644000175000017500000000033012551223667013510 0ustar globusglobusrequire "bundler/gem_tasks" desc "Benchmark Nori parsers" task :benchmark do require "benchmark/benchmark" end require "rspec/core/rake_task" RSpec::Core::RakeTask.new task :default => :spec task :test => :spec nori-2.6.0/benchmark/0000755000175000017500000000000012551223667014001 5ustar globusglobusnori-2.6.0/benchmark/soap_response.xml0000644000175000017500000003043112551223667017404 0ustar globusglobus PALMA DE MALLORCA Spain Schengen Son San Juan 11 km 08.02.10 08.02.10 Passengers 1 Disembarking: -1 2 0 Baby buggies are delivered at aircraft door or stairs. 2008-09-01T00:00:00+02:00 -1 0 PMI PALMA DE MALLORCA Spain Schengen Son San Juan 11 km 08.02.10 08.02.10 Aircraft 2 Turnaround: -1 5.30 0 Station applies EST process. 2009-05-05T00:00:00+02:00 -1 -1 PMI PALMA DE MALLORCA Spain Schengen Son San Juan 11 km 08.02.10 08.02.10 General 4 Addresses: -1 8.50 1 Station Manager: YYYYYYYY XXXXXXX PMIKXXX Tel.:+34 971 xxx xxx Mobile:+ 34 600 46 xx xx 2010-02-08T00:00:00+01:00 -1 -1 PMI PALMA DE MALLORCA Spain Schengen Son San Juan 11 km 08.02.10 08.02.10 General 4 Addresses: -1 8.50 2 Handling Agent: xxxxxxx Airport Services Operations PMIIxxx Tel +34 971 xxx xxx Passenger Services PMIPXXX Tel : +34 971 xxx xxx 2010-02-08T00:00:00+01:00 -1 -1 PMI PALMA DE MALLORCA Spain Schengen Son San Juan 11 km 08.02.10 08.02.10 General 4 Announcements: -1 11 1 Prerecorded Spanish announcements available. 2009-12-30T00:00:00+01:00 -1 0 PMI PALMA DE MALLORCA Spain Schengen Son San Juan 11 km 08.02.10 08.02.10 General 4 Life jackets / DEMO: -1 12 0 Infant life jackets to be distributed. DEMO with life jackets. 2002-07-24T00:00:00+02:00 -1 0 PMI PALMA DE MALLORCA Spain Schengen Son San Juan 11 km 08.02.10 08.02.10 Catering 5 General: 0 1 1 LSG XXX XXXX Tel.: +34 971 xxx xxx or xxx xxx Sita: PMIAXXX 2005-06-01T00:00:00+02:00 -1 0 PMI nori-2.6.0/benchmark/benchmark.rb0000644000175000017500000000057312551223667016265 0ustar globusglobus$:.push File.expand_path("../../lib", __FILE__) require "nori" require "benchmark" Benchmark.bm 30 do |x| num = 500 xml = File.read File.expand_path("../soap_response.xml", __FILE__) x.report "rexml parser" do num.times { Nori.new(parser: :rexml).parse xml } end x.report "nokogiri parser" do num.times { Nori.new(parser: :nokogiri).parse xml } end end nori-2.6.0/spec/0000755000175000017500000000000012551223667013001 5ustar globusglobusnori-2.6.0/spec/nori/0000755000175000017500000000000012551223667013750 5ustar globusglobusnori-2.6.0/spec/nori/core_ext/0000755000175000017500000000000012551223667015560 5ustar globusglobusnori-2.6.0/spec/nori/core_ext/object_spec.rb0000644000175000017500000000064612551223667020373 0ustar globusglobusrequire "spec_helper" describe Object do describe "#blank?" do [nil, false, [], {}].each do |object| it "should return true for: #{object.inspect}" do expect(object.blank?).to be_true end end [true, [nil], 1, "string", { :key => "value" }].each do |object| it "should return false for: #{object.inspect}" do expect(object.blank?).to be_false end end end end nori-2.6.0/spec/nori/core_ext/hash_spec.rb0000644000175000017500000000137712551223667020052 0ustar globusglobusrequire "spec_helper" describe Hash do describe "#normalize_param" do it "should have specs" end describe "#to_xml_attributes" do it "should turn the hash into xml attributes" do attrs = { :one => "ONE", "two" => "TWO" }.to_xml_attributes expect(attrs).to match(/one="ONE"/m) expect(attrs).to match(/two="TWO"/m) end it "should preserve _ in hash keys" do attrs = { :some_long_attribute => "with short value", :crash => :burn, :merb => "uses extlib" }.to_xml_attributes expect(attrs).to match(/some_long_attribute="with short value"/) expect(attrs).to match(/merb="uses extlib"/) expect(attrs).to match(/crash="burn"/) end end end nori-2.6.0/spec/nori/core_ext/string_spec.rb0000644000175000017500000000161512551223667020430 0ustar globusglobusrequire "spec_helper" describe String do describe "#snakecase" do it "lowercases one word CamelCase" do expect("Merb".snakecase).to eq("merb") end it "makes one underscore snakecase two word CamelCase" do expect("MerbCore".snakecase).to eq("merb_core") end it "handles CamelCase with more than 2 words" do expect("SoYouWantContributeToMerbCore".snakecase).to eq("so_you_want_contribute_to_merb_core") end it "handles CamelCase with more than 2 capital letter in a row" do expect("CNN".snakecase).to eq("cnn") expect("CNNNews".snakecase).to eq("cnn_news") expect("HeadlineCNNNews".snakecase).to eq("headline_cnn_news") end it "does NOT change one word lowercase" do expect("merb".snakecase).to eq("merb") end it "leaves snake_case as is" do expect("merb_core".snakecase).to eq("merb_core") end end end nori-2.6.0/spec/nori/nori_spec.rb0000644000175000017500000005547412551223667016275 0ustar globusglobusrequire "spec_helper" describe Nori do Nori::PARSERS.each do |parser, class_name| context "using the :#{parser} parser" do let(:parser) { parser } it "should work with unnormalized characters" do xml = '&' expect(parse(xml)).to eq({ 'root' => "&" }) end it "should transform a simple tag with content" do xml = "This is the contents" expect(parse(xml)).to eq({ 'tag' => 'This is the contents' }) end it "should work with cdata tags" do xml = <<-END END expect(parse(xml)["tag"].strip).to eq("text inside cdata") end it "should transform a simple tag with attributes" do xml = "" hash = { 'tag' => { '@attr1' => '1', '@attr2' => '2' } } expect(parse(xml)).to eq(hash) end it "should transform repeating siblings into an array" do xml =<<-XML XML expect(parse(xml)['opt']['user'].class).to eq(Array) hash = { 'opt' => { 'user' => [{ '@login' => 'grep', '@fullname' => 'Gary R Epstein' },{ '@login' => 'stty', '@fullname' => 'Simon T Tyson' }] } } expect(parse(xml)).to eq(hash) end it "should not transform non-repeating siblings into an array" do xml =<<-XML XML expect(parse(xml)['opt']['user'].class).to eq(Hash) hash = { 'opt' => { 'user' => { '@login' => 'grep', '@fullname' => 'Gary R Epstein' } } } expect(parse(xml)).to eq(hash) end it "should prefix attributes with an @-sign to avoid problems with overwritten values" do xml =<<-XML grep 76737 XML expect(parse(xml)["multiRef"]).to eq({ "login" => "grep", "@id" => "id1", "id" => "76737" }) end context "without advanced typecasting" do it "should not transform 'true'" do hash = parse("true", :advanced_typecasting => false) expect(hash["value"]).to eq("true") end it "should not transform 'false'" do hash = parse("false", :advanced_typecasting => false) expect(hash["value"]).to eq("false") end it "should not transform Strings matching the xs:time format" do hash = parse("09:33:55Z", :advanced_typecasting => false) expect(hash["value"]).to eq("09:33:55Z") end it "should not transform Strings matching the xs:date format" do hash = parse("1955-04-18-05:00", :advanced_typecasting => false) expect(hash["value"]).to eq("1955-04-18-05:00") end it "should not transform Strings matching the xs:dateTime format" do hash = parse("1955-04-18T11:22:33-05:00", :advanced_typecasting => false) expect(hash["value"]).to eq("1955-04-18T11:22:33-05:00") end end context "with advanced typecasting" do it "should transform 'true' to TrueClass" do expect(parse("true")["value"]).to eq(true) end it "should transform 'false' to FalseClass" do expect(parse("false")["value"]).to eq(false) end it "should transform Strings matching the xs:time format to Time objects" do expect(parse("09:33:55.7Z")["value"]).to eq(Time.parse("09:33:55.7Z")) end it "should transform Strings matching the xs:time format ahead of utc to Time objects" do expect(parse("09:33:55+02:00")["value"]).to eq(Time.parse("09:33:55+02:00")) end it "should transform Strings matching the xs:date format to Date objects" do expect(parse("1955-04-18-05:00")["value"]).to eq(Date.parse("1955-04-18-05:00")) end it "should transform Strings matching the xs:dateTime format ahead of utc to Date objects" do expect(parse("1955-04-18+02:00")["value"]).to eq(Date.parse("1955-04-18+02:00")) end it "should transform Strings matching the xs:dateTime format to DateTime objects" do expect(parse("1955-04-18T11:22:33.5Z")["value"]).to eq( DateTime.parse("1955-04-18T11:22:33.5Z") ) end it "should transform Strings matching the xs:dateTime format ahead of utc to DateTime objects" do expect(parse("1955-04-18T11:22:33+02:00")["value"]).to eq( DateTime.parse("1955-04-18T11:22:33+02:00") ) end it "should transform Strings matching the xs:dateTime format with seconds and an offset to DateTime objects" do expect(parse("2004-04-12T13:20:15.5-05:00")["value"]).to eq( DateTime.parse("2004-04-12T13:20:15.5-05:00") ) end it "should not transform Strings containing an xs:time String and more" do expect(parse("09:33:55Z is a time")["value"]).to eq("09:33:55Z is a time") expect(parse("09:33:55Z_is_a_file_name")["value"]).to eq("09:33:55Z_is_a_file_name") end it "should not transform Strings containing an xs:date String and more" do expect(parse("1955-04-18-05:00 is a date")["value"]).to eq("1955-04-18-05:00 is a date") expect(parse("1955-04-18-05:00_is_a_file_name")["value"]).to eq("1955-04-18-05:00_is_a_file_name") end it "should not transform Strings containing an xs:dateTime String and more" do expect(parse("1955-04-18T11:22:33-05:00 is a dateTime")["value"]).to eq( "1955-04-18T11:22:33-05:00 is a dateTime" ) expect(parse("1955-04-18T11:22:33-05:00_is_a_file_name")["value"]).to eq( "1955-04-18T11:22:33-05:00_is_a_file_name" ) end ["00-00-00", "0000-00-00", "0000-00-00T00:00:00", "0569-23-0141", "DS2001-19-1312654773", "e6:53:01:00:ce:b4:06"].each do |date_string| it "should not transform a String like '#{date_string}' to date or time" do expect(parse("#{date_string}")["value"]).to eq(date_string) end end end context "Parsing xml with text and attributes" do before do xml =<<-XML Gary R Epstein Simon T Tyson XML @data = parse(xml) end it "correctly parse text nodes" do expect(@data).to eq({ 'opt' => { 'user' => [ 'Gary R Epstein', 'Simon T Tyson' ] } }) end it "parses attributes for text node if present" do expect(@data['opt']['user'][0].attributes).to eq({'login' => 'grep'}) end it "default attributes to empty hash if not present" do expect(@data['opt']['user'][1].attributes).to eq({}) end it "add 'attributes' accessor methods to parsed instances of String" do expect(@data['opt']['user'][0]).to respond_to(:attributes) expect(@data['opt']['user'][0]).to respond_to(:attributes=) end it "not add 'attributes' accessor methods to all instances of String" do expect("some-string").not_to respond_to(:attributes) expect("some-string").not_to respond_to(:attributes=) end end it "should typecast an integer" do xml = "10" expect(parse(xml)['tag']).to eq(10) end it "should typecast a true boolean" do xml = "true" expect(parse(xml)['tag']).to be(true) end it "should typecast a false boolean" do ["false"].each do |w| expect(parse("#{w}")['tag']).to be(false) end end it "should typecast a datetime" do xml = "2007-12-31 10:32" expect(parse(xml)['tag']).to eq(Time.parse( '2007-12-31 10:32' ).utc) end it "should typecast a date" do xml = "2007-12-31" expect(parse(xml)['tag']).to eq(Date.parse('2007-12-31')) end xml_entities = { "<" => "<", ">" => ">", '"' => """, "'" => "'", "&" => "&" } it "should unescape html entities" do xml_entities.each do |k,v| xml = "Some content #{v}" expect(parse(xml)['tag']).to match(Regexp.new(k)) end end it "should unescape XML entities in attributes" do xml_entities.each do |key, value| xml = "" expect(parse(xml)['tag']['@attr']).to match(Regexp.new(key)) end end it "should undasherize keys as tags" do xml = "Stuff" expect(parse(xml).keys).to include('tag_1') end it "should undasherize keys as attributes" do xml = "" expect(parse(xml)['tag1'].keys).to include('@attr_1') end it "should undasherize keys as tags and attributes" do xml = "" expect(parse(xml).keys).to include('tag_1') expect(parse(xml)['tag_1'].keys).to include('@attr_1') end it "should render nested content correctly" do xml = "Tag1 Content This is strong" expect(parse(xml)['root']['tag1']).to eq("Tag1 Content This is strong") end it "should render nested content with text nodes correctly" do xml = "Tag1 ContentStuff Hi There" expect(parse(xml)['root']).to eq("Tag1 ContentStuff Hi There") end it "should ignore attributes when a child is a text node" do xml = "Stuff" expect(parse(xml)).to eq({ "root" => "Stuff" }) end it "should ignore attributes when any child is a text node" do xml = "Stuff in italics" expect(parse(xml)).to eq({ "root" => "Stuff in italics" }) end it "should correctly transform multiple children" do xml = <<-XML 35 Home Simpson 1988-01-01 2000-04-28 23:01 true XML hash = { "user" => { "@gender" => "m", "age" => 35, "name" => "Home Simpson", "dob" => Date.parse('1988-01-01'), "joined_at" => Time.parse("2000-04-28 23:01"), "is_cool" => true } } expect(parse(xml)).to eq(hash) end it "should properly handle nil values (ActiveSupport Compatible)" do topic_xml = <<-EOT EOT expected_topic_hash = { 'title' => nil, 'id' => nil, 'approved' => nil, 'written_on' => nil, 'viewed_at' => nil, # don't execute arbitary YAML code 'content' => { "@type" => "yaml" }, 'parent_id' => nil, 'nil_true' => nil, 'namespaced' => nil } expect(parse(topic_xml)["topic"]).to eq(expected_topic_hash) end it "should handle a single record from xml (ActiveSupport Compatible)" do topic_xml = <<-EOT The First Topic David 1 true 0 2592000000 2003-07-16 2003-07-16T09:28:00+0000 --- \n1: should be an integer\n:message: Have a nice day\narray: \n- should-have-dashes: true\n should_have_underscores: true david@loudthinking.com 1.5 135 yes EOT expected_topic_hash = { 'title' => "The First Topic", 'author_name' => "David", 'id' => 1, 'approved' => true, 'replies_count' => 0, 'replies_close_in' => 2592000000, 'written_on' => Date.new(2003, 7, 16), 'viewed_at' => Time.utc(2003, 7, 16, 9, 28), # Changed this line where the key is :message. The yaml specifies this as a symbol, and who am I to change what you specify # The line in ActiveSupport is # 'content' => { 'message' => "Have a nice day", 1 => "should be an integer", "array" => [{ "should-have-dashes" => true, "should_have_underscores" => true }] }, 'content' => "--- \n1: should be an integer\n:message: Have a nice day\narray: \n- should-have-dashes: true\n should_have_underscores: true", 'author_email_address' => "david@loudthinking.com", 'parent_id' => nil, 'ad_revenue' => BigDecimal("1.50"), 'optimum_viewing_angle' => 135.0, # don't create symbols from arbitary remote code 'resident' => "yes" } parse(topic_xml)["topic"].each do |k,v| expect(v).to eq(expected_topic_hash[k]) end end it "should handle multiple records (ActiveSupport Compatible)" do topics_xml = <<-EOT The First Topic David 1 false 0 2592000000 2003-07-16 2003-07-16T09:28:00+0000 Have a nice day david@loudthinking.com The Second Topic Jason 1 false 0 2592000000 2003-07-16 2003-07-16T09:28:00+0000 Have a nice day david@loudthinking.com EOT expected_topic_hash = { 'title' => "The First Topic", 'author_name' => "David", 'id' => 1, 'approved' => false, 'replies_count' => 0, 'replies_close_in' => 2592000000, 'written_on' => Date.new(2003, 7, 16), 'viewed_at' => Time.utc(2003, 7, 16, 9, 28), 'content' => "Have a nice day", 'author_email_address' => "david@loudthinking.com", 'parent_id' => nil } # puts Nori.parse(topics_xml)['topics'].first.inspect parse(topics_xml)["topics"].first.each do |k,v| expect(v).to eq(expected_topic_hash[k]) end end context "with convert_attributes_to set to a custom formula" do it "alters attributes and values" do converter = lambda {|key, value| ["#{key}_k", "#{value}_v"] } xml = <<-XML 21 XML expect(parse(xml, :convert_attributes_to => converter)).to eq({'user' => {'@name_k' => 'value_v', 'age' => '21'}}) end end it "should handle a single record from_xml with attributes other than type (ActiveSupport Compatible)" do topic_xml = <<-EOT EOT expected_topic_hash = { '@id' => "175756086", '@owner' => "55569174@N00", '@secret' => "0279bf37a1", '@server' => "76", '@title' => "Colored Pencil PhotoBooth Fun", '@ispublic' => "1", '@isfriend' => "0", '@isfamily' => "0", } parse(topic_xml)["rsp"]["photos"]["photo"].each do |k, v| expect(v).to eq(expected_topic_hash[k]) end end it "should handle an emtpy array (ActiveSupport Compatible)" do blog_xml = <<-XML XML expected_blog_hash = {"blog" => {"posts" => []}} expect(parse(blog_xml)).to eq(expected_blog_hash) end it "should handle empty array with whitespace from xml (ActiveSupport Compatible)" do blog_xml = <<-XML XML expected_blog_hash = {"blog" => {"posts" => []}} expect(parse(blog_xml)).to eq(expected_blog_hash) end it "should handle array with one entry from_xml (ActiveSupport Compatible)" do blog_xml = <<-XML a post XML expected_blog_hash = {"blog" => {"posts" => ["a post"]}} expect(parse(blog_xml)).to eq(expected_blog_hash) end it "should handle array with multiple entries from xml (ActiveSupport Compatible)" do blog_xml = <<-XML a post another post XML expected_blog_hash = {"blog" => {"posts" => ["a post", "another post"]}} expect(parse(blog_xml)).to eq(expected_blog_hash) end it "should handle file types (ActiveSupport Compatible)" do blog_xml = <<-XML XML hash = parse(blog_xml) expect(hash.keys).to include('blog') expect(hash['blog'].keys).to include('logo') file = hash['blog']['logo'] expect(file.original_filename).to eq('logo.png') expect(file.content_type).to eq('image/png') end it "should handle file from xml with defaults (ActiveSupport Compatible)" do blog_xml = <<-XML XML file = parse(blog_xml)['blog']['logo'] expect(file.original_filename).to eq('untitled') expect(file.content_type).to eq('application/octet-stream') end it "should handle xsd like types from xml (ActiveSupport Compatible)" do bacon_xml = <<-EOT 0.5 12.50 1 2007-12-25T12:34:56+0000 YmFiZS5wbmc= EOT expected_bacon_hash = { 'weight' => 0.5, 'chunky' => true, 'price' => BigDecimal("12.50"), 'expires_at' => Time.utc(2007,12,25,12,34,56), 'notes' => "", 'illustration' => "babe.png" } expect(parse(bacon_xml)["bacon"]).to eq(expected_bacon_hash) end it "should let type trickle through when unknown (ActiveSupport Compatible)" do product_xml = <<-EOT 0.5 image.gif EOT expected_product_hash = { 'weight' => 0.5, 'image' => {'@type' => 'ProductImage', 'filename' => 'image.gif' }, } expect(parse(product_xml)["product"]).to eq(expected_product_hash) end it "should handle unescaping from xml (ActiveResource Compatible)" do xml_string = 'First & Last NameFirst &amp; Last Name' expected_hash = { 'bare_string' => 'First & Last Name', 'pre_escaped_string' => 'First & Last Name' } expect(parse(xml_string)['person']).to eq(expected_hash) end it "handle an empty xml string" do expect(parse('')).to eq({}) end # As returned in the response body by the unfuddle XML API when creating objects it "handle an xml string containing a single space" do expect(parse(' ')).to eq({}) end end end def parse(xml, options = {}) defaults = {:parser => parser} Nori.new(defaults.merge(options)).parse(xml) end end nori-2.6.0/spec/nori/api_spec.rb0000644000175000017500000001467112551223667016071 0ustar globusglobusrequire "spec_helper" describe Nori do describe "PARSERS" do it "should return a Hash of parser details" do expect(Nori::PARSERS).to eq({ :rexml => "REXML", :nokogiri => "Nokogiri" }) end end context ".new" do it "defaults to not strip any namespace identifiers" do xml = <<-XML a_case XML expect(nori.parse(xml)["history"]["ns10:case"]).to eq("a_case") end it "defaults to not change XML tags" do xml = 'active' expect(nori.parse(xml)).to eq({ "userResponse" => { "@id" => "1", "accountStatus" => "active" } }) end it "raises when passed unknown global options" do expect { Nori.new(:invalid => true) }. to raise_error(ArgumentError, /Spurious options: \[:invalid\]/) end end context ".new with :strip_namespaces" do it "strips the namespace identifiers when set to true" do xml = '' expect(nori(:strip_namespaces => true).parse(xml)).to have_key("Envelope") end it "still converts namespaced entries to array elements" do xml = <<-XML a_name another_name XML expected = [{ "name" => "a_name" }, { "name" => "another_name" }] expect(nori(:strip_namespaces => true).parse(xml)["history"]["case"]).to eq(expected) end end context ".new with :convert_tags_to" do it "converts all tags by a given formula" do xml = 'active' snakecase_symbols = lambda { |tag| tag.snakecase.to_sym } nori = nori(:convert_tags_to => snakecase_symbols) expect(nori.parse(xml)).to eq({ :user_response => { :@id => "1", :account_status => "active" } }) end end context '#find' do before do upcase = lambda { |tag| tag.upcase } @nori = nori(:convert_tags_to => upcase) xml = 'active' @hash = @nori.parse(xml) end it 'returns the Hash when the path is empty' do result = @nori.find(@hash) expect(result).to eq("USERRESPONSE" => { "ACCOUNTSTATUS" => "active", "@ID" => "1" }) end it 'returns the result for a single key' do result = @nori.find(@hash, 'userResponse') expect(result).to eq("ACCOUNTSTATUS" => "active", "@ID" => "1") end it 'returns the result for nested keys' do result = @nori.find(@hash, 'userResponse', 'accountStatus') expect(result).to eq("active") end it 'strips the namespaces from Hash keys' do xml = 'active' hash = @nori.parse(xml) result = @nori.find(hash, 'userResponse', 'accountStatus') expect(result).to eq("active") end end context "#parse" do it "defaults to use advanced typecasting" do hash = nori.parse("true") expect(hash["value"]).to eq(true) end it "defaults to use the Nokogiri parser" do # parsers are loaded lazily by default require "nori/parser/nokogiri" expect(Nori::Parser::Nokogiri).to receive(:parse).and_return({}) nori.parse("thing") end it "strips the XML" do xml = double("xml") expect(xml).to receive(:strip).and_return("thing") expect(nori.parse(xml)).to eq({ "any" => "thing" }) end end context "#parse without :advanced_typecasting" do it "can be changed to not typecast too much" do hash = nori(:advanced_typecasting => false).parse("true") expect(hash["value"]).to eq("true") end end context "#parse with :parser" do it "can be configured to use the REXML parser" do # parsers are loaded lazily by default require "nori/parser/rexml" expect(Nori::Parser::REXML).to receive(:parse).and_return({}) nori(:parser => :rexml).parse("thing") end end context "#parse without :delete_namespace_attributes" do it "can be changed to not delete xmlns attributes" do xml = 'active' hash = nori(:delete_namespace_attributes => false).parse(xml) expect(hash).to eq({"userResponse" => {"@xmlns" => "http://schema.company.com/some/path/to/namespace/v1", "accountStatus" => "active"}}) end it "can be changed to not delete xsi attributes" do xml = 'active' hash = nori(:delete_namespace_attributes => false).parse(xml) expect(hash).to eq({"userResponse" => {"@xsi" => "abc:myType", "accountStatus" => "active"}}) end end context "#parse with :delete_namespace_attributes" do it "can be changed to delete xmlns attributes" do xml = 'active' hash = nori(:delete_namespace_attributes => true).parse(xml) expect(hash).to eq({"userResponse" => {"accountStatus" => "active"}}) end it "can be changed to delete xsi attributes" do xml = 'active' hash = nori(:delete_namespace_attributes => true).parse(xml) expect(hash).to eq({"userResponse" => {"accountStatus" => "active"}}) end end context "#parse with :convert_dashes_to_underscores" do it "can be configured to skip dash to underscope conversion" do xml = 'foo bar false).parse(xml) expect(hash).to eq({'any-tag' => 'foo bar'}) end end context "#parse with :empty_tag_value set to empty string" do it "can be configured to convert empty tags to given value" do xml = "" hash = nori(:empty_tag_value => "").parse(xml) expect(hash).to eq("parentTag" => { "tag" => "" }) end end def nori(options = {}) Nori.new(options) end end nori-2.6.0/spec/spec_helper.rb0000644000175000017500000000007112551223667015615 0ustar globusglobusrequire "bundler" Bundler.require :default, :development nori-2.6.0/nori.gemspec0000644000175000017500000000161112551223667014362 0ustar globusglobus# -*- encoding: utf-8 -*- $:.push File.expand_path("../lib", __FILE__) require "nori/version" Gem::Specification.new do |s| s.name = "nori" s.version = Nori::VERSION s.authors = ["Daniel Harrington", "John Nunemaker", "Wynn Netherland"] s.email = "me@rubiii.com" s.homepage = "https://github.com/savonrb/nori" s.summary = "XML to Hash translator" s.description = s.summary s.required_ruby_version = '>= 1.9.2' s.rubyforge_project = "nori" s.license = "MIT" s.add_development_dependency "rake", "~> 10.0" s.add_development_dependency "nokogiri", ">= 1.4.0" s.add_development_dependency "rspec", "~> 2.12" s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ["lib"] end nori-2.6.0/LICENSE0000644000175000017500000000204512551223667013055 0ustar globusglobusCopyright (c) 2011 Daniel Harrington Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. nori-2.6.0/CHANGELOG.md0000644000175000017500000001523712551223667013670 0ustar globusglobus# 2.6.0 (2015-05-06) * Feature: [#69](https://github.com/savonrb/nori/pull/69) Add option to convert empty tags to a value other than nil. # 2.5.0 (2015-03-31) * Formally drop support for ruby 1.8.7. Installing Nori from rubygems for that version should no longer attempt to install versions that will not work. # 2.4.0 (2014-04-19) * Change: Dropped support for ruby 1.8, rubinius and ree * Feature: Added `:convert_attributes` feature similar to `:convert_tags_to` * Feature: Added `:convert_dashes_to_underscore` option # 2.3.0 (2013-07-26) * Change: `Nori#find` now ignores namespace prefixes in Hash keys it is searching through. * Fix: Limited Nokogiri to < 1.6, because v1.6 dropped support for Ruby 1.8. # 2.2.0 (2013-04-25) * Feature: [#42](https://github.com/savonrb/nori/pull/42) adds the `:delete_namespace_attributes` option to remove namespace attributes like `xmlns:*` or `xsi:*`. # 2.1.0 (2013-04-21) * Feature: Added `Nori.hash_key` and `Nori#find` to work with Hash keys generated by Nori. Original issue: [savonrb/savon#393](https://github.com/savonrb/savon/pull/393) # 2.0.4 (2013-02-26) * Fix: [#37](https://github.com/savonrb/nori/issues/37) special characters problem on Ruby 1.9.3-p392. # 2.0.3 (2013-01-10) * Fix for remote code execution bug. For more in-depth information, read about the recent [Rails hotfix](https://groups.google.com/forum/?fromgroups=#!topic/rubyonrails-security/61bkgvnSGTQ). Please make sure to upgrade now! # 2.0.2 (YANKED) * Yanked because of a problem with XML that starts with an instruction tag. # 2.0.1 (YANKED) * Yanked because of a problem with XML that starts with an instruction tag. # 2.0.0 (2012-12-12) Please make sure to read the updated README for how to use the new version. * Change: Nori now defaults to use the Nokogiri parser. * Refactoring: Changed the `Nori` module to a class. This might cause problems if you included the `Nori` module somewhere in your application. This use case was removed for overall simplicity. * Refactoring: Changed the interface to remove any global state. The global configuration is gone and replaced with simple options to be passed to `Nori.new`. ``` ruby parser = Nori.new(strip_namespaces: true) parser.parse(xml) ``` * Refactoring: Removed the `Nori::Parser` module methods. After refactoring the rest, there was only a single method left for this module and that was moved to `Nori`. * Fix: [#16](https://github.com/savonrb/nori/issues/16) strip XML passed to Nori. ## 1.1.5 (2013-03-03) * Fix: [#37](https://github.com/savonrb/nori/issues/37) special characters problem on Ruby 1.9.3-p392. ## 1.1.4 (2013-01-10) * Fix for remote code execution bug. For more in-depth information, read about the recent [Rails hotfix](https://groups.google.com/forum/?fromgroups=#!topic/rubyonrails-security/61bkgvnSGTQ). Please make sure to upgrade now! ## 1.1.3 (2012-07-12) * Fix: Merged [pull request 21](https://github.com/savonrb/nori/pull/21) to fix an issue with date/time/datetime regexes not matching positive time zone offsets and datetime strings with seconds. ## 1.1.2 (2012-06-30) * Fix: Reverted `Object#xml_attributes` feature which is planned for version 2.0. ## 1.1.1 (2012-06-29) - yanked * Fix: Merged [pull request 17](https://github.com/savonrb/nori/pull/17) for improved xs:time/xs:date/xs:dateTime regular expression matchers. ## 1.1.0 (2012-02-17) * Improvement: Merged [pull request 9](https://github.com/savonrb/nori/pull/9) to allow multiple configurations of Nori. * Fix: Merged [pull request 10](https://github.com/savonrb/nori/pull/10) to handle date/time parsing errors. Fixes a couple of similar error reports. ## 1.0.2 (2011-07-04) * Fix: When specifying a custom formula to convert tags, XML attributes were ignored. Now, a formula is applied to both XML tags and attributes. ## 1.0.1 (2011-06-21) * Fix: Make sure to always load both StringWithAttributes and StringIOFile to prevent NameError's. ## 1.0.0 (2011-06-20) * Notice: As of v1.0.0, Nori will follow [Semantic Versioning](http://semver.org). * Feature: Added somewhat advanced typecasting: What this means: * "true" and "false" are converted to TrueClass and FalseClass * Strings matching an xs:time, xs:date and xs:dateTime are converted to Time, Date and DateTime objects. You can disable this feature via: Nori.advanced_typecasting = false * Feature: Added an option to strip the namespaces from every tag. This feature might raise problems and is therefore disabled by default. Nori.strip_namespaces = true * Feature: Added an option to specify a custom formula to convert tags. Here's an example: Nori.configure do |config| config.convert_tags_to { |tag| tag.snake_case.to_sym } end xml = 'active' parse(xml).should ## { :user_response => { :account_status => "active" } ## 0.2.4 (2011-06-21) * Fix: backported fixes from v1.0.1 ## 0.2.3 (2011-05-26) * Fix: Use extended core classes StringWithAttributes and StringIOFile instead of creating singletons to prevent serialization problems. ## 0.2.2 (2011-05-16) * Fix: namespaced xs:nil values should be nil objects. ## 0.2.1 (2011-05-15) * Fix: Changed XML attributes converted to Hash keys to be prefixed with an @-sign. This avoids problems with attributes and child nodes having the same name. true 76737 is now translated to: { "multiRef" => { "@id" => "id1", "id" => "76737", "approved" => "true" } } ## 0.2.0 (2011-04-30) * Removed JSON from the original Crack basis * Fixed a problem with Object#blank? * Added swappable parsers * Added a Nokogiri parser with you can switch to via: Nori.parser = :nokogiri ## 0.1.7 2010-02-19 * 1 minor patch * Added patch from @purp for ISO 8601 date/time format ## 0.1.6 2010-01-31 * 1 minor patch * Added Crack::VERSION constant - http://weblog.rubyonrails.org/2009/9/1/gem-packaging-best-practices ## 0.1.5 2010-01-27 * 1 minor patch * Strings that begin with dates shouldn't be parsed as such (sandro) ## 0.1.3 2009-06-22 * 1 minor patch * Parsing a text node with attributes stores them in the attributes method (tamalw) ## 0.1.2 2009-04-21 * 2 minor patches * Correct unnormalization of attribute values (der-flo) * Fix error in parsing YAML in the case where a hash value ends with backslashes, and there are subsequent values in the hash (deadprogrammer) ## 0.1.1 2009-03-31 * 1 minor patch * Parsing empty or blank xml now returns empty hash instead of raising error. ## 0.1.0 2009-03-28 * Initial release. nori-2.6.0/README.md0000644000175000017500000000474312551223667013336 0ustar globusglobusNori ==== [![Build Status](https://secure.travis-ci.org/savonrb/nori.png)](http://travis-ci.org/savonrb/nori) [![Gem Version](https://badge.fury.io/rb/nori.png)](http://badge.fury.io/rb/nori) [![Code Climate](https://codeclimate.com/github/savonrb/nori.png)](https://codeclimate.com/github/savonrb/nori) [![Coverage Status](https://coveralls.io/repos/savonrb/nori/badge.png?branch=master)](https://coveralls.io/r/savonrb/nori) Really simple XML parsing ripped from Crack which ripped it from Merb. Nori was created to bypass the stale development of Crack, improve its XML parser and fix certain issues. ``` ruby parser = Nori.new parser.parse("This is the contents") # => { 'tag' => 'This is the contents' } ``` Nori supports pluggable parsers and ships with both REXML and Nokogiri implementations. It defaults to Nokogiri since v2.0.0, but you can change it to use REXML via: ``` ruby Nori.new(:parser => :rexml) # or :nokogiri ``` Make sure Nokogiri is in your LOAD_PATH when parsing XML, because Nori tries to load it when it's needed. Typecasting ----------- Besides regular typecasting, Nori features somewhat "advanced" typecasting: * "true" and "false" String values are converted to `TrueClass` and `FalseClass`. * String values matching xs:time, xs:date and xs:dateTime are converted to `Time`, `Date` and `DateTime` objects. You can disable this feature via: ``` ruby Nori.new(:advanced_typecasting => false) ``` Namespaces ---------- Nori can strip the namespaces from your XML tags. This feature might raise problems and is therefore disabled by default. Enable it via: ``` ruby Nori.new(:strip_namespaces => true) ``` XML tags -> Hash keys --------------------- Nori lets you specify a custom formula to convert XML tags to Hash keys. Let me give you an example: ``` ruby parser = Nori.new(:convert_tags_to => lambda { |tag| tag.snakecase.to_sym }) xml = 'active' parser.parse(xml) # => { :user_response => { :account_status => "active" } ``` Dashes and underscores ---------------------- Nori will automatically convert dashes in tag names to underscores. For example: ```ruby parser = Nori.new parser.parse('foo bar') # => { "any_tag" => "foo bar" } ``` You can control this behavior with the `:convert_dashes_to_underscores` option: ```ruby parser = Nori.new(:convert_dashes_to_underscores => false) parser.parse('foo bar') # => { "any-tag" => "foo bar" } ``` nori-2.6.0/metadata.yml0000644000175000017500000000544112551223667014356 0ustar globusglobus--- !ruby/object:Gem::Specification name: nori version: !ruby/object:Gem::Version version: 2.6.0 platform: ruby authors: - Daniel Harrington - John Nunemaker - Wynn Netherland autorequire: bindir: bin cert_chain: [] date: 2015-05-07 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: rake requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '10.0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '10.0' - !ruby/object:Gem::Dependency name: nokogiri requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 1.4.0 type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 1.4.0 - !ruby/object:Gem::Dependency name: rspec requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '2.12' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '2.12' description: XML to Hash translator email: me@rubiii.com executables: [] extensions: [] extra_rdoc_files: [] files: - ".gitignore" - ".rspec" - ".travis.yml" - CHANGELOG.md - Gemfile - LICENSE - README.md - Rakefile - benchmark/benchmark.rb - benchmark/soap_response.xml - lib/nori.rb - lib/nori/core_ext.rb - lib/nori/core_ext/hash.rb - lib/nori/core_ext/object.rb - lib/nori/core_ext/string.rb - lib/nori/parser/nokogiri.rb - lib/nori/parser/rexml.rb - lib/nori/string_io_file.rb - lib/nori/string_with_attributes.rb - lib/nori/version.rb - lib/nori/xml_utility_node.rb - nori.gemspec - spec/nori/api_spec.rb - spec/nori/core_ext/hash_spec.rb - spec/nori/core_ext/object_spec.rb - spec/nori/core_ext/string_spec.rb - spec/nori/nori_spec.rb - spec/spec_helper.rb homepage: https://github.com/savonrb/nori licenses: - MIT metadata: {} post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 1.9.2 required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: nori rubygems_version: 2.2.2 signing_key: specification_version: 4 summary: XML to Hash translator test_files: - spec/nori/api_spec.rb - spec/nori/core_ext/hash_spec.rb - spec/nori/core_ext/object_spec.rb - spec/nori/core_ext/string_spec.rb - spec/nori/nori_spec.rb - spec/spec_helper.rb nori-2.6.0/.rspec0000644000175000017500000000001112551223667013154 0ustar globusglobus--colour nori-2.6.0/.travis.yml0000644000175000017500000000027212551223667014161 0ustar globusglobus# https://github.com/travis-ci/travis-ci/wiki/.travis.yml-options language: "ruby" script: "bundle exec rake" rvm: - 1.9.2 - 1.9.3 - 2.0 - 2.1 - 2.2 - jruby-19mode - rbx-2 nori-2.6.0/Gemfile0000644000175000017500000000017712551223667013347 0ustar globusglobussource 'https://rubygems.org' gemspec platform :rbx do gem 'json' gem 'racc' gem 'rubysl' gem 'rubinius-coverage' end