nori-2.7.1/0000755000004100000410000000000014705312601012517 5ustar www-datawww-datanori-2.7.1/nori.gemspec0000644000004100000410000000167514705312601015044 0ustar www-datawww-data# -*- 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 = '>= 3.0' s.license = "MIT" s.add_dependency "bigdecimal" s.add_development_dependency "rake", "~> 12.3.3" s.add_development_dependency "nokogiri", ">= 1.4.0" s.add_development_dependency "rspec", "~> 3.11.0" s.metadata["rubygems_mfa_required"] = "true" 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.7.1/.gitignore0000644000004100000410000000007414705312601014510 0ustar www-datawww-data.DS_Store doc coverage *~ *.gem .bundle Gemfile.lock /.idea nori-2.7.1/.github/0000755000004100000410000000000014705312601014057 5ustar www-datawww-datanori-2.7.1/.github/dependabot.yml0000644000004100000410000000072314705312601016711 0ustar www-datawww-data# To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for more information: # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates # https://containers.dev/guide/dependabot version: 2 updates: - package-ecosystem: "devcontainers" directory: "/" schedule: interval: weekly nori-2.7.1/.github/workflows/0000755000004100000410000000000014705312601016114 5ustar www-datawww-datanori-2.7.1/.github/workflows/test.yml0000644000004100000410000000111214705312601017611 0ustar www-datawww-dataname: CI on: [push, pull_request] jobs: build: runs-on: ubuntu-latest strategy: matrix: ruby-version: - '3.0' - '3.1' - '3.2' - '3.3' - 'head' - jruby-9.4.5.0 steps: - uses: actions/checkout@v4 - name: Set up Ruby ${{ matrix.ruby-version }} uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby-version }} bundler-cache: true # runs 'bundle install' and caches installed gems automatically - name: Run tests run: bundle exec rake nori-2.7.1/lib/0000755000004100000410000000000014705312601013265 5ustar www-datawww-datanori-2.7.1/lib/nori.rb0000644000004100000410000000514314705312601014564 0ustar www-datawww-datarequire "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, :scrub_xml => 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 = scrub_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 def scrub_xml(string) if @options[:scrub_xml] if string.respond_to? :scrub string.scrub else if string.valid_encoding? string else enc = string.encoding mid_enc = (["UTF-8", "UTF-16BE"].map { |e| Encoding.find(e) } - [enc]).first string.encode(mid_enc, undef: :replace, invalid: :replace).encode(enc) end end else string end end end nori-2.7.1/lib/nori/0000755000004100000410000000000014705312601014234 5ustar www-datawww-datanori-2.7.1/lib/nori/parser/0000755000004100000410000000000014705312601015530 5ustar www-datawww-datanori-2.7.1/lib/nori/parser/rexml.rb0000644000004100000410000000260514705312601017207 0ustar www-datawww-datarequire "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 raw_data = parser.pull event = unnormalize(raw_data) 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 stack.last.add_node(event[1]) unless event[1].strip.length == 0 || stack.empty? when :cdata stack.last.add_node(raw_data[1]) unless raw_data[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.7.1/lib/nori/parser/nokogiri.rb0000644000004100000410000000303214705312601017674 0ustar www-datawww-datarequire "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.7.1/lib/nori/string_with_attributes.rb0000644000004100000410000000013314705312601021365 0ustar www-datawww-dataclass Nori class StringWithAttributes < String attr_accessor :attributes end end nori-2.7.1/lib/nori/string_io_file.rb0000644000004100000410000000015314705312601017554 0ustar www-datawww-dataclass Nori class StringIOFile < StringIO attr_accessor :original_filename, :content_type end end nori-2.7.1/lib/nori/string_utils.rb0000644000004100000410000000077514705312601017320 0ustar www-datawww-dataclass Nori module StringUtils # Converts a string to snake case. # # @param inputstring [String] The string to be converted to snake case. # @return [String] A copy of the input string converted to snake case. def self.snakecase(inputstring) str = inputstring.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 end end nori-2.7.1/lib/nori/core_ext.rb0000644000004100000410000000007114705312601016367 0ustar www-datawww-datarequire "nori/string_utils" require "nori/core_ext/hash" nori-2.7.1/lib/nori/core_ext/0000755000004100000410000000000014705312601016044 5ustar www-datawww-datanori-2.7.1/lib/nori/core_ext/hash.rb0000644000004100000410000000303514705312601017315 0ustar www-datawww-datarequire "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| %{#{StringUtils.snakecase(k.to_s).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.7.1/lib/nori/version.rb0000644000004100000410000000004314705312601016243 0ustar www-datawww-dataclass Nori VERSION = '2.7.1' end nori-2.7.1/lib/nori/xml_utility_node.rb0000644000004100000410000002115414705312601020154 0ustar www-datawww-datarequire "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.7.1/spec/0000755000004100000410000000000014705312601013451 5ustar www-datawww-datanori-2.7.1/spec/spec_helper.rb0000644000004100000410000000007114705312601016265 0ustar www-datawww-datarequire "bundler" Bundler.require :default, :development nori-2.7.1/spec/nori/0000755000004100000410000000000014705312601014420 5ustar www-datawww-datanori-2.7.1/spec/nori/string_utils_spec.rb0000644000004100000410000000206014705312601020503 0ustar www-datawww-datarequire "spec_helper" describe Nori::StringUtils do describe ".snakecase" do it "lowercases one word CamelCase" do expect(Nori::StringUtils.snakecase("Merb")).to eq("merb") end it "makes one underscore snakecase two word CamelCase" do expect(Nori::StringUtils.snakecase("MerbCore")).to eq("merb_core") end it "handles CamelCase with more than 2 words" do expect(Nori::StringUtils.snakecase("SoYouWantContributeToMerbCore")).to eq("so_you_want_contribute_to_merb_core") end it "handles CamelCase with more than 2 capital letter in a row" do expect(Nori::StringUtils.snakecase("CNN")).to eq("cnn") expect(Nori::StringUtils.snakecase("CNNNews")).to eq("cnn_news") expect(Nori::StringUtils.snakecase("HeadlineCNNNews")).to eq("headline_cnn_news") end it "does NOT change one word lowercase" do expect(Nori::StringUtils.snakecase("merb")).to eq("merb") end it "leaves snake_case as is" do expect(Nori::StringUtils.snakecase("merb_core")).to eq("merb_core") end end end nori-2.7.1/spec/nori/nori_spec.rb0000644000004100000410000005576314705312601016746 0ustar www-datawww-datarequire "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 < test") end it "should scrub bad characters" do xml = "a\xfbc".force_encoding('UTF-8') expect(parse(xml)["tag"]).to eq("a\uFFFDc") 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.7.1/spec/nori/core_ext/0000755000004100000410000000000014705312601016230 5ustar www-datawww-datanori-2.7.1/spec/nori/core_ext/hash_spec.rb0000644000004100000410000000137714705312601020522 0ustar www-datawww-datarequire "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.7.1/spec/nori/api_spec.rb0000644000004100000410000001442014705312601016531 0ustar www-datawww-datarequire "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| Nori::StringUtils.snakecase(tag).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 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 underscore conversion" do xml = 'foo bar' hash = nori(:convert_dashes_to_underscores => 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.7.1/.rspec0000644000004100000410000000001114705312601013624 0ustar www-datawww-data--colour nori-2.7.1/Rakefile0000644000004100000410000000033014705312601014160 0ustar www-datawww-datarequire "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.7.1/Gemfile0000644000004100000410000000013214705312601014006 0ustar www-datawww-datasource 'https://rubygems.org' gemspec if RUBY_VERSION >= "3" gem "rexml", "~> 3.2" end nori-2.7.1/LICENSE0000644000004100000410000000204514705312601013525 0ustar www-datawww-dataCopyright (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.7.1/benchmark/0000755000004100000410000000000014705312601014451 5ustar www-datawww-datanori-2.7.1/benchmark/soap_response.xml0000644000004100000410000003043114705312601020054 0ustar www-datawww-data 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.7.1/benchmark/benchmark.rb0000644000004100000410000000057314705312601016735 0ustar www-datawww-data$:.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.7.1/README.md0000644000004100000410000000647414705312601014011 0ustar www-datawww-dataNori ==== [![CI](https://github.com/savonrb/nori/actions/workflows/test.yml/badge.svg)](https://github.com/savonrb/nori/actions/workflows/test.yml) [![Gem Version](https://badge.fury.io/rb/nori.svg)](http://badge.fury.io/rb/nori) [![Code Climate](https://codeclimate.com/github/savonrb/nori.svg)](https://codeclimate.com/github/savonrb/nori) Really simple XML parsing ripped from Crack, which ripped it from Merb. 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. # Examples ```ruby Nori.new.parse("This is the content") # => {"tag"=>"This is the content"} Nori.new.parse('') #=> {"foo"=>nil} Nori.new.parse('') #=> {} Nori.new.parse('') #=> {"foo"=>{"@bar"=>"baz"}} Nori.new.parse('Content') #=> {"foo"=>"Content"} ``` ## Nori::StringWithAttributes You can access a string node's attributes via `attributes`. ```ruby result = Nori.new.parse('Content') #=> {"foo"=>"Content"} result["foo"].class # => Nori::StringWithAttributes result["foo"].attributes # => {"bar"=>"baz"} ``` ## advanced_typecasting Nori can automatically convert string values to `TrueClass`, `FalseClass`, `Time`, `Date`, and `DateTime`: ```ruby # "true" and "false" String values are converted to `TrueClass` and `FalseClass`. Nori.new.parse("true") # => {"value"=>true} # String values matching xs:time, xs:date and xs:dateTime are converted to `Time`, `Date` and `DateTime` objects. Nori.new.parse("09:33:55.7Z") # => {"value"=>2022-09-29 09:33:55.7 UTC # disable with advanced_typecasting: false Nori.new(advanced_typecasting: false).parse("true") # => {"value"=>"true"} ``` ## strip_namespaces Nori can strip the namespaces from your XML tags. This feature is disabled by default. ``` ruby Nori.new.parse('') # => {"soap:Envelope"=>{"@xmlns:soap"=>"http://schemas.xmlsoap.org/soap/envelope/"}} Nori.new(:strip_namespaces => true).parse('') # => {"Envelope"=>{"@xmlns:soap"=>"http://schemas.xmlsoap.org/soap/envelope/"}} ``` ## convert_tags_to Nori lets you specify a custom formula to convert XML tags to Hash keys using `convert_tags_to`. ``` ruby Nori.new.parse('active') # => {"userResponse"=>{"accountStatus"=>"active"}} parser = Nori.new(:convert_tags_to => lambda { |tag| Nori::StringUtils.snakecase(tag).to_sym }) parser.parse('active') # => {:user_response=>{:account_status=>"active"}} ``` ## convert_dashes_to_underscores By default, Nori will automatically convert dashes in tag names to underscores. ```ruby Nori.new.parse('foo bar') # => {"any_tag"=>"foo bar"} # disable with convert_dashes_to_underscores parser = Nori.new(:convert_dashes_to_underscores => false) parser.parse('foo bar') # => {"any-tag"=>"foo bar"} ``` nori-2.7.1/CHANGELOG.md0000644000004100000410000001742614705312601014342 0ustar www-datawww-data# 2.7.1 (2024-07-28) * Stop monkey-patching String with #snakecase by @mchu in https://github.com/savonrb/nori/pull/102 # 2.7.0 (2024-02-13) * Added support for ruby 3.1, 3.2, 3.3. Dropped support for ruby 2.7 and below. * Feature: `Nori::Parser` has a new option, `:scrub_xml`, which defaults to true, for scrubbing invalid characters ([#72](https://github.com/savonrb/nori/pull/72)). This should allow documents containing invalid characters to still be parsed. * Fix: REXML parser changes `<` inside CDATA to `<` ([#94](https://github.com/savonrb/nori/pull/94)) * Change: `Object#blank?` is no longer patched in. # 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. * BREAKING CHANGE: Newlines are now preserved when present in the value of inner text nodes. See the example below: before: ``` Nori.new.parse("\n<embedded>\n<one></one>\n<two></two>\n<embedded>\n") => {"outer"=>""} ``` after: ``` Nori.new.parse("\n<embedded>\n<one></one>\n<two></two>\n<embedded>\n") => {"outer"=>"\n\n\n\n"} ``` # 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.7.1/.devcontainer/0000755000004100000410000000000014705312601015256 5ustar www-datawww-datanori-2.7.1/.devcontainer/devcontainer.json0000644000004100000410000000136014705312601020632 0ustar www-datawww-data// For format details, see https://aka.ms/devcontainer.json. For config options, see the // README at: https://github.com/devcontainers/templates/tree/main/src/ruby { "name": "Ruby", // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile "image": "mcr.microsoft.com/devcontainers/ruby:0-3-bullseye" // Features to add to the dev container. More info: https://containers.dev/features. // "features": {}, // Use 'forwardPorts' to make a list of ports inside the container available locally. // "forwardPorts": [], // Configure tool-specific properties. // "customizations": {}, // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. // "remoteUser": "root" }