pax_global_header00006660000000000000000000000064130214654250014514gustar00rootroot0000000000000052 comment=a1bbeaedb92cb6e0ff0692afd1e4e670318ccf78 multi_xml-0.6.0/000077500000000000000000000000001302146542500135315ustar00rootroot00000000000000multi_xml-0.6.0/.gitignore000066400000000000000000000001301302146542500155130ustar00rootroot00000000000000*.gem *~ .bundle .rvmrc .yardoc Gemfile.lock coverage/* doc/* log/* measurement/* pkg/* multi_xml-0.6.0/.rspec000066400000000000000000000000271302146542500146450ustar00rootroot00000000000000--color --order random multi_xml-0.6.0/.rubocop.yml000066400000000000000000000020161302146542500160020ustar00rootroot00000000000000AllCops: Exclude: - 'spec/speed.rb' Metrics/BlockNesting: Max: 5 Metrics/ClassLength: Max: 50 Metrics/LineLength: AllowURI: true Enabled: false Metrics/MethodLength: CountComments: false Max: 13 Metrics/ParameterLists: Max: 3 CountKeywordArgs: true Style/AccessModifierIndentation: EnforcedStyle: outdent Style/CollectionMethods: PreferredMethods: map: 'collect' reduce: 'inject' find: 'detect' find_all: 'select' Style/Documentation: Enabled: false Style/DotPosition: EnforcedStyle: trailing Style/DoubleNegation: Enabled: false Style/EachWithObject: Enabled: false Style/Encoding: Enabled: false Style/HashSyntax: EnforcedStyle: hash_rockets Style/Lambda: Enabled: false Style/ModuleFunction: Enabled: false Style/RaiseArgs: EnforcedStyle: compact Style/SpaceInsideHashLiteralBraces: EnforcedStyle: no_space Style/TrailingCommaInArguments: EnforcedStyleForMultiline: 'comma' Style/TrailingCommaInLiteral: EnforcedStyleForMultiline: 'comma' multi_xml-0.6.0/.travis.yml000066400000000000000000000004601302146542500156420ustar00rootroot00000000000000bundler_args: --without development env: global: - JRUBY_OPTS="$JRUBY_OPTS --debug" language: ruby rvm: - 1.9.3 - 2.0.0 - 2.1 - 2.2 - 2.3.3 - jruby-9.1.6.0 - jruby-head - ruby-head matrix: allow_failures: - rvm: jruby-head - rvm: ruby-head fast_finish: true sudo: false multi_xml-0.6.0/.yardopts000066400000000000000000000001371302146542500154000ustar00rootroot00000000000000--no-private --protected --markup markdown - CHANGELOG.md CONTRIBUTING.md LICENSE.md README.md multi_xml-0.6.0/CHANGELOG.md000066400000000000000000000101171302146542500153420ustar00rootroot000000000000000.5.5 ----- * [Fix symbolize_keys function](https://github.com/sferik/multi_xml/commit/a4cae3aeb690999287cd30206399abaa5ce1ae81) * [Fix Nokogiri parser for the same attr and inner element name](https://github.com/sferik/multi_xml/commit/a28ed86e2d7826b2edeed98552736b4c7ca52726) 0.5.4 ----- * [Add option to not cast parsed values](https://github.com/sferik/multi_xml/commit/44fc05fbcfd60cc8b555b75212471fab29fa8cd0) * [Use message instead of to_s](https://github.com/sferik/multi_xml/commit/b06f0114434ffe1957dd7bc2712cb5b76c1b45fe) 0.5.3 ----- * [Add cryptographic signature](https://github.com/sferik/multi_xml/commit/f39f0c74308090737816c622dbb7d7aa28c646c0) 0.5.2 ----- * [Remove ability to parse symbols and YAML](https://github.com/sferik/multi_xml/pull/34) 0.5.1 ----- * [Revert "Reset @@parser in between specs"](https://github.com/sferik/multi_xml/issues/28) 0.5.0 ----- * [Reset @@parser in between specs](https://github.com/sferik/multi_xml/commit/b562bed265918b43ac1c4c638ae3a7ffe95ecd83) * [Add attributes being passed through on content nodes](https://github.com/sferik/multi_xml/commit/631a8bb3c2253db0024f77f47c16d5a53b8128fd) 0.4.4 ----- * [Fix regression in MultiXml.parse](https://github.com/sferik/multi_xml/commit/45ae597d9a35cbd89cc7f5518c85bac30199fc06) 0.4.3 ----- * [Make parser a class variable](https://github.com/sferik/multi_xml/commit/6804ffc8680ed6466c66f2472f5e016c412c2c24) * [Add TYPE_NAMES constant](https://github.com/sferik/multi_xml/commit/72a21f2e86c8e3ac9689cee5f3a62102cfb98028) 0.4.2 ----- * [Fix bug in dealing with xml element attributes for both REXML and Ox](https://github.com/sferik/multi_xml/commit/ba3c1ac427ff0268abaf8186fb4bd81100c99559) * [Make Ox the preferred XML parser](https://github.com/sferik/multi_xml/commit/0a718d740c30fba426f300a929cda9ee8250d238) 0.4.1 ----- * [Use the SAX like parser with Ox](https://github.com/sferik/multi_xml/commit/d289d42817a32e48483c00d5361c76fbea62a166) 0.4.0 ----- * [Add support for Ox](https://github.com/sferik/multi_xml/pull/14) 0.3.0 ----- * [Remove core class monkeypatches](https://github.com/sferik/multi_xml/commit/f7cc3ce4d2924c0e0adc6935d1fba5ec79282938) * [Sort out some class / singleton class issues](https://github.com/sferik/multi_xml/commit/a5dac06bcf658facaaf7afa295f1291c7be15a44) * [Have parsers refer to toplevel CONTENT_ROOT instead of defining it](https://github.com/sferik/multi_xml/commit/94e6fa49e69b2a2467a0e6d3558f7d9815cae47e) * [Move redundant input sanitizing to top-level](https://github.com/sferik/multi_xml/commit/4874148214dbbd2e5a4b877734e2519af42d6132) * [Refactor libxml and nokogiri parsers to inherit from a common ancestor](https://github.com/sferik/multi_xml/commit/e0fdffcbfe641b6aaa3952ffa0570a893de325c2) 0.2.2 ----- * [Respect the global load path](https://github.com/sferik/multi_xml/commit/68eb3011b37f0e0222bb842abd2a78e1285a97c1) 0.2.1 ----- * [Add BlueCloth gem as development dependency for Markdown formatting](https://github.com/sferik/multi_xml/commit/18195cd1789176709f68f0d7f8df7fc944fe4d24) * [Replace BlueCloth with Maruku for JRuby compatibility](https://github.com/sferik/multi_xml/commit/bad5516a5ec5e7ef7fc5a35c411721522357fa19) 0.2.0 ----- * [Do not automatically load all library files](https://github.com/sferik/multi_xml/commit/dbd0447e062e8930118573c5453150e9371e5955) 0.1.4 ----- * [Preserve backtrace when catching/throwing exceptions](https://github.com/sferik/multi_xml/commit/7475ee90201c2701fddd524082832d16ca62552d) 0.1.3 ----- * [Common error handling for all parsers](https://github.com/sferik/multi_xml/commit/5357c28eddc14e921fd1be1f445db602a8dddaf2) 0.1.2 ----- * [Make wrap an Array class method](https://github.com/sferik/multi_xml/commit/28307b69bd1d9460353c861466e425c2afadcf56) 0.1.1 ----- * [Fix parsing for strings that contain newlines](https://github.com/sferik/multi_xml/commit/68087a4ce50b5d63cfa60d6f1fcbc2f6d689e43f) 0.1.0 ----- * [Add support for LibXML and Nokogiri](https://github.com/sferik/multi_xml/commit/856bb17fce66601e0b3d3eb3b64dbeb25aed3bca) 0.0.1 ----- * [REXML support](https://github.com/sferik/multi_xml/commit/2a848384a7b90fb3e26b5a8d4dc3fa3e3f2db5fc) multi_xml-0.6.0/CONTRIBUTING.md000066400000000000000000000037341302146542500157710ustar00rootroot00000000000000## Contributing In the spirit of [free software][free-sw] , **everyone** is encouraged to help improve this project. [free-sw]: http://www.fsf.org/licensing/essays/free-sw.html Here are some ways *you* can contribute: * by using alpha, beta, and prerelease versions * by reporting bugs * by suggesting new features * by writing or editing documentation * by writing specifications * by writing code (**no patch is too small**: fix typos, add comments, clean up inconsistent whitespace) * by refactoring code * by resolving [issues][] * by reviewing patches * [financially][gittip] [issues]: https://github.com/sferik/multi_xml/issues [gittip]: https://www.gittip.com/sferik/ ## Submitting an Issue We use the [GitHub issue tracker][issues] to track bugs and features. Before submitting a bug report or feature request, check to make sure it hasn't already been submitted. When submitting a bug report, please include a [Gist][] that includes a stack trace and any details that may be necessary to reproduce the bug, including your gem version, Ruby version, and operating system. Ideally, a bug report should include a pull request with failing specs. [gist]: https://gist.github.com/ ## Submitting a Pull Request 1. [Fork the repository.][fork] 2. [Create a topic branch.][branch] 3. Add specs for your unimplemented feature or bug fix. 4. Run `bundle exec rake spec`. If your specs pass, return to step 3. 5. Implement your feature or bug fix. 6. Run `bundle exec rake`. If your specs fail, return to step 5. 7. Run `open coverage/index.html`. If your changes are not completely covered by your tests, return to step 3. 8. Add documentation for your feature or bug fix. 9. Run `bundle exec rake verify_measurements`. If your changes are not 100% documented, go back to step 8. 10. Add, commit, and push your changes. 11. [Submit a pull request.][pr] [fork]: http://help.github.com/fork-a-repo/ [branch]: http://learn.github.com/p/branching.html [pr]: http://help.github.com/send-pull-requests/ multi_xml-0.6.0/Gemfile000066400000000000000000000011061302146542500150220ustar00rootroot00000000000000source 'https://rubygems.org' gem 'rake' gem 'yard' gem 'libxml-ruby', :require => nil, :platforms => :ruby gem 'nokogiri', :require => nil gem 'oga', '>= 2.3', :require => nil gem 'ox', :require => nil, :platforms => :ruby platforms :ruby_19 do gem 'json', '~> 1.8' gem 'mime-types', '~> 2.99' gem 'rest-client', '~> 1.8' end group :development do gem 'kramdown' gem 'pry' end group :test do gem 'backports' gem 'coveralls' gem 'rspec', '>= 2.14' gem 'rubocop', '~> 0.41.1' gem 'simplecov', '>= 0.9' gem 'tins', '~> 1.6.0' gem 'yardstick' end gemspec multi_xml-0.6.0/LICENSE.md000066400000000000000000000020531302146542500151350ustar00rootroot00000000000000Copyright (c) 2010-2013 Erik Michaels-Ober 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. multi_xml-0.6.0/README.md000066400000000000000000000066311302146542500150160ustar00rootroot00000000000000# MultiXML [![Gem Version](http://img.shields.io/gem/v/multi_xml.svg)][gem] [![Build Status](http://img.shields.io/travis/sferik/multi_xml.svg)][travis] [![Dependency Status](http://img.shields.io/gemnasium/sferik/multi_xml.svg)][gemnasium] [![Code Climate](http://img.shields.io/codeclimate/github/sferik/multi_xml.svg)][codeclimate] [![Coverage Status](http://img.shields.io/coveralls/sferik/multi_xml.svg)][coveralls] [gem]: https://rubygems.org/gems/multi_xml [travis]: http://travis-ci.org/sferik/multi_xml [gemnasium]: https://gemnasium.com/sferik/multi_xml [codeclimate]: https://codeclimate.com/github/sferik/multi_xml [coveralls]: https://coveralls.io/r/sferik/multi_xml A generic swappable back-end for XML parsing ## Installation gem install multi_xml ## Documentation [http://rdoc.info/gems/multi_xml][documentation] [documentation]: http://rdoc.info/gems/multi_xml ## Usage Examples Lots of Ruby libraries utilize XML parsing in some form, and everyone has their favorite XML library. In order to best support multiple XML parsers and libraries, `multi_xml` is a general-purpose swappable XML backend library. You use it like so: ```ruby require 'multi_xml' MultiXml.parser = :ox MultiXml.parser = MultiXml::Parsers::Ox # Same as above MultiXml.parse('This is the contents') # Parsed using Ox MultiXml.parser = :libxml MultiXml.parser = MultiXml::Parsers::Libxml # Same as above MultiXml.parse('This is the contents') # Parsed using LibXML MultiXml.parser = :nokogiri MultiXml.parser = MultiXml::Parsers::Nokogiri # Same as above MultiXml.parse('This is the contents') # Parsed using Nokogiri MultiXml.parser = :rexml MultiXml.parser = MultiXml::Parsers::Rexml # Same as above MultiXml.parse('This is the contents') # Parsed using REXML MultiXml.parser = :oga MultiXml.parser = MultiXml::Parsers::Oga # Same as above MultiXml.parse('This is the contents') # Parsed using Oga ``` The `parser` setter takes either a symbol or a class (to allow for custom XML parsers) that responds to `.parse` at the class level. MultiXML tries to have intelligent defaulting. That is, if you have any of the supported parsers already loaded, it will utilize them before attempting to load any. When loading, libraries are ordered by speed: first Ox, then LibXML, then Nokogiri, and finally REXML. ## Supported Ruby Versions This library aims to support and is [tested against][travis] the following Ruby implementations: * Ruby 1.9.3 * Ruby 2.0.0 * Ruby 2.1 * Ruby 2.2 * Ruby 2.3 * [JRuby 9000][jruby] [jruby]: http://jruby.org/ If something doesn't work on one of these interpreters, it's a bug. This library may inadvertently work (or seem to work) on other Ruby implementations, however support will only be provided for the versions listed above. If you would like this library to support another Ruby version, you may volunteer to be a maintainer. Being a maintainer entails making sure all tests run and pass on that implementation. When something breaks on your implementation, you will be responsible for providing patches in a timely fashion. If critical issues for a particular implementation exist at the time of a major release, support for that Ruby version may be dropped. ## Inspiration MultiXML was inspired by [MultiJSON][]. [multijson]: https://github.com/intridea/multi_json/ ## Copyright Copyright (c) 2010-2013 Erik Michaels-Ober. See [LICENSE][] for details. [license]: LICENSE.md multi_xml-0.6.0/Rakefile000066400000000000000000000013171302146542500152000ustar00rootroot00000000000000require 'bundler' Bundler::GemHelper.install_tasks require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) task :test => :spec require 'rubocop/rake_task' RuboCop::RakeTask.new require 'yard' YARD::Rake::YardocTask.new do |task| task.files = ['lib/**/*.rb', '-', 'LICENSE.md'] task.options = [ '--no-private', '--protected', '--output-dir', 'doc/yard', '--markup', 'markdown' ] end require 'yardstick/rake/measurement' Yardstick::Rake::Measurement.new do |measurement| measurement.output = 'measurement/report.txt' end require 'yardstick/rake/verify' Yardstick::Rake::Verify.new do |verify| verify.threshold = 48.8 end task :default => [:spec, :rubocop, :verify_measurements] multi_xml-0.6.0/lib/000077500000000000000000000000001302146542500142775ustar00rootroot00000000000000multi_xml-0.6.0/lib/multi_xml.rb000066400000000000000000000234431302146542500166440ustar00rootroot00000000000000require 'base64' require 'bigdecimal' require 'date' require 'stringio' require 'time' require 'yaml' module MultiXml # rubocop:disable ModuleLength class ParseError < StandardError; end class NoParserError < StandardError; end class DisallowedTypeError < StandardError def initialize(type) super "Disallowed type attribute: #{type.inspect}" end end unless defined?(REQUIREMENT_MAP) REQUIREMENT_MAP = [ ['ox', :ox], ['libxml', :libxml], ['nokogiri', :nokogiri], ['rexml/document', :rexml], ['oga', :oga], ].freeze end CONTENT_ROOT = '__content__'.freeze unless defined?(CONTENT_ROOT) unless defined?(PARSING) float_proc = proc { |float| float.to_f } datetime_proc = proc { |time| Time.parse(time).utc rescue DateTime.parse(time).utc } # rubocop:disable RescueModifier PARSING = { 'symbol' => proc { |symbol| symbol.to_sym }, 'date' => proc { |date| Date.parse(date) }, 'datetime' => datetime_proc, 'dateTime' => datetime_proc, 'integer' => proc { |integer| integer.to_i }, 'float' => float_proc, 'double' => float_proc, 'decimal' => proc { |number| BigDecimal(number) }, 'boolean' => proc { |boolean| !%w(0 false).include?(boolean.strip) }, 'string' => proc { |string| string.to_s }, 'yaml' => proc { |yaml| YAML.load(yaml) rescue yaml }, # rubocop:disable RescueModifier 'base64Binary' => proc { |binary| ::Base64.decode64(binary) }, 'binary' => proc { |binary, entity| parse_binary(binary, entity) }, 'file' => proc { |file, entity| parse_file(file, entity) }, }.freeze end unless defined?(TYPE_NAMES) TYPE_NAMES = { 'Symbol' => 'symbol', 'Integer' => 'integer', 'BigDecimal' => 'decimal', 'Float' => 'float', 'TrueClass' => 'boolean', 'FalseClass' => 'boolean', 'Date' => 'date', 'DateTime' => 'datetime', 'Time' => 'datetime', 'Array' => 'array', 'Hash' => 'hash', }.freeze end DISALLOWED_XML_TYPES = %w(symbol yaml).freeze DEFAULT_OPTIONS = { :typecast_xml_value => true, :disallowed_types => DISALLOWED_XML_TYPES, :symbolize_keys => false, }.freeze class << self # Get the current parser class. def parser return @parser if defined?(@parser) self.parser = default_parser @parser end # The default parser based on what you currently # have loaded and installed. First checks to see # if any parsers are already loaded, then checks # to see which are installed if none are loaded. def default_parser return :ox if defined?(::Ox) return :libxml if defined?(::LibXML) return :nokogiri if defined?(::Nokogiri) return :oga if defined?(::Oga) REQUIREMENT_MAP.each do |library, parser| begin require library return parser rescue LoadError next end end raise(NoParserError.new("No XML parser detected. If you're using Rubinius and Bundler, try adding an XML parser to your Gemfile (e.g. libxml-ruby, nokogiri, or rubysl-rexml). For more information, see https://github.com/sferik/multi_xml/issues/42.")) end # Set the XML parser utilizing a symbol, string, or class. # Supported by default are: # # * :libxml # * :nokogiri # * :ox # * :rexml # * :oga def parser=(new_parser) case new_parser when String, Symbol require "multi_xml/parsers/#{new_parser.to_s.downcase}" @parser = MultiXml::Parsers.const_get(new_parser.to_s.split('_').collect(&:capitalize).join('').to_s) when Class, Module @parser = new_parser else raise('Did not recognize your parser specification. Please specify either a symbol or a class.') end end # Parse an XML string or IO into Ruby. # # Options # # :symbolize_keys :: If true, will use symbols instead of strings for the keys. # # :disallowed_types :: Types to disallow from being typecasted. Defaults to `['yaml', 'symbol']`. Use `[]` to allow all types. # # :typecast_xml_value :: If true, won't typecast values for parsed document def parse(xml, options = {}) # rubocop:disable AbcSize, CyclomaticComplexity, MethodLength, PerceivedComplexity xml ||= '' options = DEFAULT_OPTIONS.merge(options) xml = xml.strip if xml.respond_to?(:strip) begin xml = StringIO.new(xml) unless xml.respond_to?(:read) char = xml.getc return {} if char.nil? xml.ungetc(char) hash = undasherize_keys(parser.parse(xml) || {}) hash = options[:typecast_xml_value] ? typecast_xml_value(hash, options[:disallowed_types]) : hash rescue DisallowedTypeError raise rescue parser.parse_error => error raise(ParseError, error.message, error.backtrace) # rubocop:disable RaiseArgs end hash = symbolize_keys(hash) if options[:symbolize_keys] hash end # This module decorates files with the original_filename # and content_type methods. module FileLike #:nodoc: attr_writer :original_filename, :content_type def original_filename @original_filename || 'untitled' end def content_type @content_type || 'application/octet-stream' end end private # TODO: Add support for other encodings def parse_binary(binary, entity) #:nodoc: case entity['encoding'] when 'base64' Base64.decode64(binary) else binary end end def parse_file(file, entity) f = StringIO.new(Base64.decode64(file)) f.extend(FileLike) f.original_filename = entity['name'] f.content_type = entity['content_type'] f end def symbolize_keys(params) case params when Hash params.inject({}) do |result, (key, value)| result.merge(key.to_sym => symbolize_keys(value)) end when Array params.collect { |value| symbolize_keys(value) } else params end end def undasherize_keys(params) case params when Hash params.inject({}) do |hash, (key, value)| hash[key.to_s.tr('-'.freeze, '_'.freeze)] = undasherize_keys(value) hash end when Array params.collect { |value| undasherize_keys(value) } else params end end def typecast_xml_value(value, disallowed_types = nil) # rubocop:disable AbcSize, CyclomaticComplexity, MethodLength, PerceivedComplexity disallowed_types ||= DISALLOWED_XML_TYPES case value when Hash if value.include?('type') && !value['type'].is_a?(Hash) && disallowed_types.include?(value['type']) raise(DisallowedTypeError.new(value['type'])) end if value['type'] == 'array' # this commented-out suggestion helps to avoid the multiple attribute # problem, but it breaks when there is only one item in the array. # # from: https://github.com/jnunemaker/httparty/issues/102 # # _, entries = value.detect { |k, v| k != 'type' && v.is_a?(Array) } # This attempt fails to consider the order that the detect method # retrieves the entries. # _, entries = value.detect {|key, _| key != 'type'} # This approach ignores attribute entries that are not convertable # to an Array which allows attributes to be ignored. _, entries = value.detect { |k, v| k != 'type' && (v.is_a?(Array) || v.is_a?(Hash)) } case entries when NilClass [] when String [] if entries.strip.empty? when Array entries.collect { |entry| typecast_xml_value(entry, disallowed_types) } when Hash [typecast_xml_value(entries, disallowed_types)] else raise("can't typecast #{entries.class.name}: #{entries.inspect}") end elsif value.key?(CONTENT_ROOT) content = value[CONTENT_ROOT] block = PARSING[value['type']] if block if block.arity == 1 value.delete('type') if PARSING[value['type']] if value.keys.size > 1 value[CONTENT_ROOT] = block.call(content) value else block.call(content) end else block.call(content, value) end else value.keys.size > 1 ? value : content end elsif value['type'] == 'string' && value['nil'] != 'true' '' # blank or nil parsed values are represented by nil elsif value.empty? || value['nil'] == 'true' nil # If the type is the only element which makes it then # this still makes the value nil, except if type is # a XML node(where type['value'] is a Hash) elsif value['type'] && value.size == 1 && !value['type'].is_a?(Hash) nil else xml_value = value.inject({}) do |hash, (k, v)| hash[k] = typecast_xml_value(v, disallowed_types) hash end # Turn {:files => {:file => #} into {:files => #} so it is compatible with # how multipart uploaded files from HTML appear xml_value['file'].is_a?(StringIO) ? xml_value['file'] : xml_value end when Array value.map! { |i| typecast_xml_value(i, disallowed_types) } value.length > 1 ? value : value.first when String value else raise("can't typecast #{value.class.name}: #{value.inspect}") end end end end multi_xml-0.6.0/lib/multi_xml/000077500000000000000000000000001302146542500163115ustar00rootroot00000000000000multi_xml-0.6.0/lib/multi_xml/parsers/000077500000000000000000000000001302146542500177705ustar00rootroot00000000000000multi_xml-0.6.0/lib/multi_xml/parsers/libxml.rb000066400000000000000000000011121302146542500215770ustar00rootroot00000000000000require 'libxml' unless defined?(LibXML) require 'multi_xml/parsers/libxml2_parser' module MultiXml module Parsers module Libxml #:nodoc: include Libxml2Parser extend self def parse_error ::LibXML::XML::Error end def parse(xml) node_to_hash(LibXML::XML::Parser.io(xml).parse.root) end private def each_child(node, &block) node.each_child(&block) end def each_attr(node, &block) node.each_attr(&block) end def node_name(node) node.name end end end end multi_xml-0.6.0/lib/multi_xml/parsers/libxml2_parser.rb000066400000000000000000000036211302146542500232440ustar00rootroot00000000000000module MultiXml module Parsers module Libxml2Parser #:nodoc: # Convert XML document to hash # # node:: # The XML node object to convert to a hash. # # hash:: # Hash to merge the converted element into. def node_to_hash(node, hash = {}) # rubocop:disable AbcSize, CyclomaticComplexity, MethodLength, PerceivedComplexity node_hash = {MultiXml::CONTENT_ROOT => ''} name = node_name(node) # Insert node hash into parent hash correctly. case hash[name] when Array hash[name] << node_hash when Hash hash[name] = [hash[name], node_hash] when NilClass hash[name] = node_hash end # Handle child elements each_child(node) do |c| if c.element? node_to_hash(c, node_hash) elsif c.text? || c.cdata? node_hash[MultiXml::CONTENT_ROOT] << c.content end end # Remove content node if it is empty if node_hash[MultiXml::CONTENT_ROOT].strip.empty? node_hash.delete(MultiXml::CONTENT_ROOT) end # Handle attributes each_attr(node) do |a| key = node_name(a) v = node_hash[key] node_hash[key] = (v ? [a.value, v] : a.value) end hash end # Parse an XML Document IO into a simple hash. # xml:: # XML Document IO to parse def parse(_) raise(NotImplementedError.new("inheritor should define #{__method__}")) end private def each_child(*) raise(NotImplementedError.new("inheritor should define #{__method__}")) end def each_attr(*) raise(NotImplementedError.new("inheritor should define #{__method__}")) end def node_name(*) raise(NotImplementedError.new("inheritor should define #{__method__}")) end end end end multi_xml-0.6.0/lib/multi_xml/parsers/nokogiri.rb000066400000000000000000000012511302146542500221350ustar00rootroot00000000000000require 'nokogiri' unless defined?(Nokogiri) require 'multi_xml/parsers/libxml2_parser' module MultiXml module Parsers module Nokogiri #:nodoc: include Libxml2Parser extend self def parse_error ::Nokogiri::XML::SyntaxError end def parse(xml) doc = ::Nokogiri::XML(xml) raise(doc.errors.first) unless doc.errors.empty? node_to_hash(doc.root) end private def each_child(node, &block) node.children.each(&block) end def each_attr(node, &block) node.attribute_nodes.each(&block) end def node_name(node) node.node_name end end end end multi_xml-0.6.0/lib/multi_xml/parsers/oga.rb000066400000000000000000000033611302146542500210660ustar00rootroot00000000000000require 'oga' unless defined?(Oga) require 'multi_xml/parsers/libxml2_parser' module MultiXml module Parsers module Oga #:nodoc: include Libxml2Parser extend self def parse_error LL::ParserError end def parse(io) document = ::Oga.parse_xml(io) node_to_hash(document.children[0]) end def node_to_hash(node, hash = {}) # rubocop:disable AbcSize, CyclomaticComplexity, MethodLength, PerceivedComplexity node_hash = {MultiXml::CONTENT_ROOT => ''} name = node_name(node) # Insert node hash into parent hash correctly. case hash[name] when Array hash[name] << node_hash when Hash hash[name] = [hash[name], node_hash] when NilClass hash[name] = node_hash end # Handle child elements each_child(node) do |c| if c.is_a?(::Oga::XML::Element) node_to_hash(c, node_hash) elsif c.is_a?(::Oga::XML::Text) || c.is_a?(::Oga::XML::Cdata) node_hash[MultiXml::CONTENT_ROOT] << c.text end end # Remove content node if it is empty if node_hash[MultiXml::CONTENT_ROOT].strip.empty? node_hash.delete(MultiXml::CONTENT_ROOT) end # Handle attributes each_attr(node) do |a| key = node_name(a) v = node_hash[key] node_hash[key] = (v ? [a.value, v] : a.value) end hash end private def each_child(node, &block) node.children.each(&block) end def each_attr(node, &block) node.attributes.each(&block) end def node_name(node) node.name end end # Oga end # Parsers end # MultiXml multi_xml-0.6.0/lib/multi_xml/parsers/ox.rb000066400000000000000000000041621302146542500207460ustar00rootroot00000000000000require 'ox' unless defined?(Ox) # Each MultiXml parser is expected to parse an XML document into a Hash. The # conversion rules are: # # - Each document starts out as an empty Hash. # # - Reading an element created an entry in the parent Hash that has a key of # the element name and a value of a Hash with attributes as key value # pairs. Children are added as described by this rule. # # - Text and CDATE is stored in the parent element Hash with a key of # MultiXml::CONTENT_ROOT and a value of the text itself. # # - If a key already exists in the Hash then the value associated with the key # is converted to an Array with the old and new value in it. # # - Other elements such as the xml prolog, doctype, and comments are ignored. # module MultiXml module Parsers module Ox #:nodoc: extend self def parse_error Exception end def parse(io) handler = Handler.new ::Ox.sax_parse(handler, io, :convert_special => true) handler.doc end class Handler attr_accessor :stack def initialize @stack = [] end def doc @stack[0] end def attr(name, value) append(name, value) unless @stack.empty? end def text(value) append(MultiXml::CONTENT_ROOT, value) end def cdata(value) append(MultiXml::CONTENT_ROOT, value) end def start_element(name) @stack.push({}) if @stack.empty? h = {} append(name, h) @stack.push(h) end def end_element(_) @stack.pop end def error(message, line, column) raise(Exception.new("#{message} at #{line}:#{column}")) end def append(key, value) key = key.to_s h = @stack.last if h.key?(key) v = h[key] if v.is_a?(Array) v << value else h[key] = [v, value] end else h[key] = value end end end # Handler end # Ox end # Parsers end # MultiXml multi_xml-0.6.0/lib/multi_xml/parsers/rexml.rb000066400000000000000000000061711302146542500214510ustar00rootroot00000000000000require 'rexml/document' unless defined?(REXML::Document) module MultiXml module Parsers module Rexml #:nodoc: extend self def parse_error ::REXML::ParseException end # Parse an XML Document IO into a simple hash using REXML # # xml:: # XML Document IO to parse def parse(xml) doc = REXML::Document.new(xml) raise(REXML::ParseException.new("The document #{doc.to_s.inspect} does not have a valid root")) unless doc.root merge_element!({}, doc.root) end private # Convert an XML element and merge into the hash # # hash:: # Hash to merge the converted element into. # element:: # XML element to merge into hash def merge_element!(hash, element) merge!(hash, element.name, collapse(element)) end # Actually converts an XML document element into a data structure. # # element:: # The document element to be collapsed. def collapse(element) hash = get_attributes(element) if element.has_elements? element.each_element { |child| merge_element!(hash, child) } merge_texts!(hash, element) unless empty_content?(element) hash else merge_texts!(hash, element) end end # Merge all the texts of an element into the hash # # hash:: # Hash to add the converted element to. # element:: # XML element whose texts are to me merged into the hash def merge_texts!(hash, element) if element.has_text? # must use value to prevent double-escaping texts = '' element.texts.each { |t| texts << t.value } merge!(hash, MultiXml::CONTENT_ROOT, texts) else hash end end # Adds a new key/value pair to an existing Hash. If the key to be added # already exists and the existing value associated with key is not # an Array, it will be wrapped in an Array. Then the new value is # appended to that Array. # # hash:: # Hash to add key/value pair to. # key:: # Key to be added. # value:: # Value to be associated with key. def merge!(hash, key, value) if hash.key?(key) if hash[key].instance_of?(Array) hash[key] << value else hash[key] = [hash[key], value] end elsif value.instance_of?(Array) hash[key] = [value] else hash[key] = value end hash end # Converts the attributes array of an XML element into a hash. # Returns an empty Hash if node has no attributes. # # element:: # XML element to extract attributes from. def get_attributes(element) attributes = {} element.attributes.each { |n, v| attributes[n] = v } attributes end # Determines if a document element has text content # # element:: # XML element to be checked. def empty_content?(element) element.texts.join.strip.empty? end end end end multi_xml-0.6.0/lib/multi_xml/version.rb000066400000000000000000000011251302146542500203220ustar00rootroot00000000000000module MultiXml module Version module_function # @return [Integer] def major 0 end # @return [Integer] def minor 6 end # @return [Integer] def patch 0 end # @return [Integer, NilClass] def pre nil end # @return [Hash] def to_h { :major => major, :minor => minor, :patch => patch, :pre => pre, } end # @return [Array] def to_a [major, minor, patch, pre].compact end # @return [String] def to_s to_a.join('.') end end end multi_xml-0.6.0/multi_xml.gemspec000066400000000000000000000014451302146542500171140ustar00rootroot00000000000000# coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'multi_xml/version' Gem::Specification.new do |spec| spec.add_development_dependency 'bundler', '~> 1.0' spec.author = 'Erik Michaels-Ober' spec.description = 'Provides swappable XML backends utilizing LibXML, Nokogiri, Ox, or REXML.' spec.email = 'sferik@gmail.com' spec.files = %w(.yardopts CHANGELOG.md CONTRIBUTING.md LICENSE.md README.md multi_xml.gemspec) + Dir['lib/**/*.rb'] spec.homepage = 'https://github.com/sferik/multi_xml' spec.licenses = ['MIT'] spec.name = 'multi_xml' spec.require_paths = ['lib'] spec.required_rubygems_version = '>= 1.3.5' spec.summary = 'A generic swappable back-end for XML parsing' spec.version = MultiXml::Version end multi_xml-0.6.0/spec/000077500000000000000000000000001302146542500144635ustar00rootroot00000000000000multi_xml-0.6.0/spec/helper.rb000066400000000000000000000006431302146542500162720ustar00rootroot00000000000000def jruby? RUBY_PLATFORM == 'java' end require 'simplecov' require 'coveralls' SimpleCov.formatters = [SimpleCov::Formatter::HTMLFormatter, Coveralls::SimpleCov::Formatter] SimpleCov.start do add_filter '/spec' percent = jruby? ? 91.29 : 92.31 minimum_coverage(percent) end require 'multi_xml' require 'rspec' RSpec.configure do |config| config.expect_with :rspec do |c| c.syntax = :expect end end multi_xml-0.6.0/spec/multi_xml_spec.rb000066400000000000000000000026511302146542500200400ustar00rootroot00000000000000require 'helper' require 'parser_shared_example' class MockDecoder def self.parse; end end describe 'MultiXml' do context 'Parsers' do it 'picks a default parser' do expect(MultiXml.parser).to be_kind_of(Module) expect(MultiXml.parser).to respond_to(:parse) end it 'defaults to the best available gem' do # Clear cache variable possibly set by previous tests MultiXml.send(:remove_instance_variable, :@parser) if MultiXml.instance_variable_defined?(:@parser) if jruby? # Ox and Libxml are not not currently available on JRuby, so Nokogiri is the best available gem expect(MultiXml.parser.name).to eq('MultiXml::Parsers::Nokogiri') else expect(MultiXml.parser.name).to eq('MultiXml::Parsers::Ox') end end it 'is settable via a symbol' do MultiXml.parser = :rexml expect(MultiXml.parser.name).to eq('MultiXml::Parsers::Rexml') end it 'is settable via a class' do MultiXml.parser = MockDecoder expect(MultiXml.parser.name).to eq('MockDecoder') end end [%w(LibXML libxml), %w(REXML rexml/document), %w(Nokogiri nokogiri), %w(Ox ox), %w(Oga oga)].each do |parser| begin require parser.last context "#{parser.first} parser" do it_behaves_like 'a parser', parser.first end rescue LoadError puts "Tests not run for #{parser.first} due to a LoadError" end end end multi_xml-0.6.0/spec/parser_shared_example.rb000066400000000000000000000530101302146542500213440ustar00rootroot00000000000000shared_examples_for 'a parser' do |parser| before do begin MultiXml.parser = parser if parser == 'LibXML' LibXML::XML::Error.set_handler(&LibXML::XML::Error::QUIET_HANDLER) end rescue LoadError pending "Parser #{parser} couldn't be loaded" end end describe '.parse' do context 'a blank string' do before do @xml = '' end it 'returns an empty Hash' do expect(MultiXml.parse(@xml)).to eq({}) end end context 'a whitespace string' do before do @xml = ' ' end it 'returns an empty Hash' do expect(MultiXml.parse(@xml)).to eq({}) end end context 'a frozen string' do before do @xml = ' '.freeze end it 'returns an empty Hash' do expect(MultiXml.parse(@xml)).to eq({}) end end unless parser == 'Oga' context 'an invalid XML document' do before do @xml = '' end it 'raises MultiXml::ParseError' do expect { MultiXml.parse(@xml) }.to raise_error(MultiXml::ParseError) end end end context 'a valid XML document' do before do @xml = '' end it 'parses correctly' do expect(MultiXml.parse(@xml)).to eq('user' => nil) end context 'with CDATA' do before do @xml = '' end it 'returns the correct CDATA' do expect(MultiXml.parse(@xml)['user']).to eq('Erik Michaels-Ober') end end context 'element with the same inner element and attribute name' do before do @xml = "Smith" end it 'returns names as Array' do expect(MultiXml.parse(@xml)['user']['name']).to eq %w(John Smith) end end context 'with content' do before do @xml = 'Erik Michaels-Ober' end it 'returns the correct content' do expect(MultiXml.parse(@xml)['user']).to eq('Erik Michaels-Ober') end end context 'with an attribute' do before do @xml = '' end it 'returns the correct attribute' do expect(MultiXml.parse(@xml)['user']['name']).to eq('Erik Michaels-Ober') end end context 'with multiple attributes' do before do @xml = '' end it 'returns the correct attributes' do expect(MultiXml.parse(@xml)['user']['name']).to eq('Erik Michaels-Ober') expect(MultiXml.parse(@xml)['user']['screen_name']).to eq('sferik') end end context 'typecast management' do before do @xml = %( Settings Test ) end context 'with :typecast_xml_value => true' do before do @setting = MultiXml.parse(@xml)['global_settings']['group']['setting'] end it { expect(@setting).to eq '' } end context 'with :typecast_xml_value => false' do before do @setting = MultiXml.parse(@xml, :typecast_xml_value => false)['global_settings']['group']['setting'] end it { expect(@setting).to eq('type' => 'string', 'description' => {'__content__' => 'Test'}) } end end context 'with :symbolize_keys => true' do before do @xml = 'Wynn Netherland' end it 'symbolizes keys' do expect(MultiXml.parse(@xml, :symbolize_keys => true)).to eq(:users => {:user => [{:name => 'Erik Michaels-Ober'}, {:name => 'Wynn Netherland'}]}) end end context 'with an attribute type="boolean"' do %w(true false).each do |boolean| context "when #{boolean}" do it "returns #{boolean}" do xml = "#{boolean}" expect(MultiXml.parse(xml)['tag']).to be instance_eval(boolean) end end end context 'when 1' do before do @xml = '1' end it 'returns true' do expect(MultiXml.parse(@xml)['tag']).to be true end end context 'when 0' do before do @xml = '0' end it 'returns false' do expect(MultiXml.parse(@xml)['tag']).to be false end end end context 'with an attribute type="integer"' do context 'with a positive integer' do before do @xml = '1' end it 'returns a Integer' do expect(MultiXml.parse(@xml)['tag']).to be_a(Integer) end it 'returns a positive number' do expect(MultiXml.parse(@xml)['tag']).to be > 0 end it 'returns the correct number' do expect(MultiXml.parse(@xml)['tag']).to eq(1) end end context 'with a negative integer' do before do @xml = '-1' end it 'returns a Integer' do expect(MultiXml.parse(@xml)['tag']).to be_a(Integer) end it 'returns a negative number' do expect(MultiXml.parse(@xml)['tag']).to be < 0 end it 'returns the correct number' do expect(MultiXml.parse(@xml)['tag']).to eq(-1) end end end context 'with an attribute type="string"' do before do @xml = '' end it 'returns a String' do expect(MultiXml.parse(@xml)['tag']).to be_a(String) end it 'returns the correct string' do expect(MultiXml.parse(@xml)['tag']).to eq('') end end context 'with an attribute type="date"' do before do @xml = '1970-01-01' end it 'returns a Date' do expect(MultiXml.parse(@xml)['tag']).to be_a(Date) end it 'returns the correct date' do expect(MultiXml.parse(@xml)['tag']).to eq(Date.parse('1970-01-01')) end end context 'with an attribute type="datetime"' do before do @xml = '1970-01-01 00:00' end it 'returns a Time' do expect(MultiXml.parse(@xml)['tag']).to be_a(Time) end it 'returns the correct time' do expect(MultiXml.parse(@xml)['tag']).to eq(Time.parse('1970-01-01 00:00')) end end context 'with an attribute type="dateTime"' do before do @xml = '1970-01-01 00:00' end it 'returns a Time' do expect(MultiXml.parse(@xml)['tag']).to be_a(Time) end it 'returns the correct time' do expect(MultiXml.parse(@xml)['tag']).to eq(Time.parse('1970-01-01 00:00')) end end context 'with an attribute type="double"' do before do @xml = '3.14159265358979' end it 'returns a Float' do expect(MultiXml.parse(@xml)['tag']).to be_a(Float) end it 'returns the correct number' do expect(MultiXml.parse(@xml)['tag']).to eq(3.14159265358979) end end context 'with an attribute type="decimal"' do before do @xml = '3.14159265358979' end it 'returns a BigDecimal' do expect(MultiXml.parse(@xml)['tag']).to be_a(BigDecimal) end it 'returns the correct number' do expect(MultiXml.parse(@xml)['tag']).to eq(3.14159265358979) end end context 'with an attribute type="base64Binary"' do before do @xml = 'aW1hZ2UucG5n' end it 'returns a String' do expect(MultiXml.parse(@xml)['tag']).to be_a(String) end it 'returns the correct string' do expect(MultiXml.parse(@xml)['tag']).to eq('image.png') end end context 'with an attribute type="yaml"' do before do @xml = "--- \n1: returns an integer\n:message: Have a nice day\narray: \n- has-dashes: true\n has_underscores: true\n" end it 'raises MultiXML::DisallowedTypeError by default' do expect { MultiXml.parse(@xml)['tag'] }.to raise_error(MultiXml::DisallowedTypeError) end it 'returns the correctly parsed YAML when the type is allowed' do expect(MultiXml.parse(@xml, :disallowed_types => [])['tag']).to eq(:message => 'Have a nice day', 1 => 'returns an integer', 'array' => [{'has-dashes' => true, 'has_underscores' => true}]) end end context 'with an attribute type="symbol"' do before do @xml = 'my_symbol' end it 'raises MultiXML::DisallowedTypeError' do expect { MultiXml.parse(@xml)['tag'] }.to raise_error(MultiXml::DisallowedTypeError) end it 'returns the correctly parsed Symbol when the type is allowed' do expect(MultiXml.parse(@xml, :disallowed_types => [])['tag']).to eq(:my_symbol) end end context 'with an attribute type="file"' do before do @xml = 'ZGF0YQ==' end it 'returns a StringIO' do expect(MultiXml.parse(@xml)['tag']).to be_a(StringIO) end it 'is decoded correctly' do expect(MultiXml.parse(@xml)['tag'].string).to eq('data') end it 'has the correct file name' do expect(MultiXml.parse(@xml)['tag'].original_filename).to eq('data.txt') end it 'has the correct content type' do expect(MultiXml.parse(@xml)['tag'].content_type).to eq('text/plain') end context 'with missing name and content type' do before do @xml = 'ZGF0YQ==' end it 'returns a StringIO' do expect(MultiXml.parse(@xml)['tag']).to be_a(StringIO) end it 'is decoded correctly' do expect(MultiXml.parse(@xml)['tag'].string).to eq('data') end it 'has the default file name' do expect(MultiXml.parse(@xml)['tag'].original_filename).to eq('untitled') end it 'has the default content type' do expect(MultiXml.parse(@xml)['tag'].content_type).to eq('application/octet-stream') end end end context 'with an attribute type="array"' do before do @xml = 'Erik Michaels-OberWynn Netherland' end it 'returns an Array' do expect(MultiXml.parse(@xml)['users']).to be_a(Array) end it 'returns the correct array' do expect(MultiXml.parse(@xml)['users']).to eq(['Erik Michaels-Ober', 'Wynn Netherland']) end end context 'with an attribute type="array" in addition to other attributes' do before do @xml = 'Erik Michaels-OberWynn Netherland' end it 'returns an Array' do expect(MultiXml.parse(@xml)['users']).to be_a(Array) end it 'returns the correct array' do expect(MultiXml.parse(@xml)['users']).to eq(['Erik Michaels-Ober', 'Wynn Netherland']) end end context 'with an attribute type="array" containing only one item' do before do @xml = 'Erik Michaels-Ober' end it 'returns an Array' do expect(MultiXml.parse(@xml)['users']).to be_a(Array) end it 'returns the correct array' do expect(MultiXml.parse(@xml)['users']).to eq(['Erik Michaels-Ober']) end end %w(integer boolean date datetime file).each do |type| context "with an empty attribute type=\"#{type}\"" do before do @xml = "" end it 'returns nil' do expect(MultiXml.parse(@xml)['tag']).to be nil end end end %w(yaml symbol).each do |type| context "with an empty attribute type=\"#{type}\"" do before do @xml = "" end it 'raises MultiXml::DisallowedTypeError by default' do expect { MultiXml.parse(@xml)['tag'] }.to raise_error(MultiXml::DisallowedTypeError) end it 'returns nil when the type is allowed' do expect(MultiXml.parse(@xml, :disallowed_types => [])['tag']).to be nil end end end context 'with an empty attribute type="array"' do before do @xml = '' end it 'returns an empty Array' do expect(MultiXml.parse(@xml)['tag']).to eq([]) end context 'with whitespace' do before do @xml = ' ' end it 'returns an empty Array' do expect(MultiXml.parse(@xml)['tag']).to eq([]) end end end context 'with XML entities' do before do @xml_entities = { '<' => '<', '>' => '>', '"' => '"', "'" => ''', '&' => '&', } end context 'in content' do it 'returns unescaped XML entities' do @xml_entities.each do |key, value| xml = "#{value}" expect(MultiXml.parse(xml)['tag']).to eq(key) end end end context 'in attribute' do it 'returns unescaped XML entities' do @xml_entities.each do |key, value| xml = "" expect(MultiXml.parse(xml)['tag']['attribute']).to eq(key) end end end end context 'with dasherized tag' do before do @xml = '' end it 'returns undasherize tag' do expect(MultiXml.parse(@xml).keys).to include('tag_1') end end context 'with dasherized attribute' do before do @xml = '' end it 'returns undasherize attribute' do expect(MultiXml.parse(@xml)['tag'].keys).to include('attribute_1') end end context 'with children' do context 'with attributes' do before do @xml = '' end it 'returns the correct attributes' do expect(MultiXml.parse(@xml)['users']['user']['name']).to eq('Erik Michaels-Ober') end end context 'with text' do before do @xml = 'Erik Michaels-Ober' end it 'returns the correct text' do expect(MultiXml.parse(@xml)['user']['name']).to eq('Erik Michaels-Ober') end end context 'with an unrecognized attribute type' do before do @xml = 'Erik Michaels-Ober' end it 'passes through the type' do expect(MultiXml.parse(@xml)['user']['type']).to eq('admin') end end context 'with attribute tags on content nodes' do context "non 'type' attributes" do before do @xml = <<-XML 123 0.123 XML @parsed_xml = MultiXml.parse(@xml) end it 'adds the attributes to the value hash' do expect(@parsed_xml['options']['value'][0]['__content__']).to eq('123') expect(@parsed_xml['options']['value'][0]['currency']).to eq('USD') expect(@parsed_xml['options']['value'][1]['__content__']).to eq('0.123') expect(@parsed_xml['options']['value'][1]['number']).to eq('percent') end end context 'unrecognized type attributes' do before do @xml = <<-XML 123 0.123 123 XML @parsed_xml = MultiXml.parse(@xml) end it 'adds the attributes to the value hash passing through the type' do expect(@parsed_xml['options']['value'][0]['__content__']).to eq('123') expect(@parsed_xml['options']['value'][0]['type']).to eq('USD') expect(@parsed_xml['options']['value'][1]['__content__']).to eq('0.123') expect(@parsed_xml['options']['value'][1]['type']).to eq('percent') expect(@parsed_xml['options']['value'][2]['__content__']).to eq('123') expect(@parsed_xml['options']['value'][2]['currency']).to eq('USD') end end context 'mixing attributes and non-attributes content nodes type attributes' do before do @xml = <<-XML 123 0.123 123 XML @parsed_xml = MultiXml.parse(@xml) end it 'adds the attributes to the value hash passing through the type' do expect(@parsed_xml['options']['value'][0]['__content__']).to eq('123') expect(@parsed_xml['options']['value'][0]['type']).to eq('USD') expect(@parsed_xml['options']['value'][1]['__content__']).to eq('0.123') expect(@parsed_xml['options']['value'][1]['type']).to eq('percent') expect(@parsed_xml['options']['value'][2]).to eq('123') end end context 'mixing recognized type attribute and non-type attributes on content nodes' do before do @xml = <<-XML 123 XML @parsed_xml = MultiXml.parse(@xml) end it 'adds the the non-type attribute and remove the recognized type attribute and do the typecast' do expect(@parsed_xml['options']['value']['__content__']).to eq(123) expect(@parsed_xml['options']['value']['number']).to eq('USD') end end context 'mixing unrecognized type attribute and non-type attributes on content nodes' do before do @xml = <<-XML 123 XML @parsed_xml = MultiXml.parse(@xml) end it 'adds the the non-type attributes and type attribute to the value hash' do expect(@parsed_xml['options']['value']['__content__']).to eq('123') expect(@parsed_xml['options']['value']['number']).to eq('USD') expect(@parsed_xml['options']['value']['type']).to eq('currency') end end end context 'with newlines and whitespace' do before do @xml = <<-XML Erik Michaels-Ober XML end it 'parses correctly' do expect(MultiXml.parse(@xml)).to eq('user' => {'name' => 'Erik Michaels-Ober'}) end end # Babies having babies context 'with children' do before do @xml = '' end it 'parses correctly' do expect(MultiXml.parse(@xml)).to eq('users' => {'user' => {'name' => 'Erik Michaels-Ober', 'status' => {'text' => 'Hello'}}}) end end end context 'with sibling children' do before do @xml = 'Erik Michaels-OberWynn Netherland' end it 'returns an Array' do expect(MultiXml.parse(@xml)['users']['user']).to be_a(Array) end it 'parses correctly' do expect(MultiXml.parse(@xml)).to eq('users' => {'user' => ['Erik Michaels-Ober', 'Wynn Netherland']}) end end end context 'a duplexed stream' do before do @xml, wr = IO.pipe Thread.new do ''.each_char do |chunk| wr << chunk end wr.close end end it 'parses correctly' do expect(MultiXml.parse(@xml)).to eq('user' => nil) end end end end multi_xml-0.6.0/spec/speed.rb000077500000000000000000000025371302146542500161220ustar00rootroot00000000000000#!/usr/bin/env ruby -wW1 $LOAD_PATH << '.' $LOAD_PATH << '../lib' if __FILE__ == $PROGRAM_NAME while (i = ARGV.index('-I')) _, path = ARGV.slice!(i, 2) $LOAD_PATH << path end end require 'optparse' require 'stringio' require 'multi_xml' %w(libxml nokogiri ox).each do |library| begin require library rescue LoadError next end end $verbose = 0 $parsers = [] $iterations = 10 opts = OptionParser.new opts.on('-v', 'increase verbosity') { $verbose += 1 } opts.on('-p', '--parser [String]', String, 'parser to test') { |parsers| $parsers = [parsers] } opts.on('-i', '--iterations [Int]', Integer, 'iterations') { |iterations| $iterations = iterations } opts.on('-h', '--help', 'Show this display') { puts opts; Process.exit!(0) } files = opts.parse(ARGV) if $parsers.empty? $parsers << 'libxml' if defined?(::LibXML) $parsers << 'nokogiri' if defined?(::Nokogiri) $parsers << 'ox' if defined?(::Ox) end files.each do |filename| times = {} xml = File.read(filename) $parsers.each do |p| MultiXml.parser = p start = Time.now $iterations.times do io = StringIO.new(xml) MultiXml.parse(io) end times[p] = Time.now - start end times.each do |p, t| puts format('%8s took %0.3f seconds to parse %s %d times.', p, t, filename, $iterations) end end