pax_global_header 0000666 0000000 0000000 00000000064 13021465425 0014514 g ustar 00root root 0000000 0000000 52 comment=a1bbeaedb92cb6e0ff0692afd1e4e670318ccf78
multi_xml-0.6.0/ 0000775 0000000 0000000 00000000000 13021465425 0013531 5 ustar 00root root 0000000 0000000 multi_xml-0.6.0/.gitignore 0000664 0000000 0000000 00000000130 13021465425 0015513 0 ustar 00root root 0000000 0000000 *.gem
*~
.bundle
.rvmrc
.yardoc
Gemfile.lock
coverage/*
doc/*
log/*
measurement/*
pkg/*
multi_xml-0.6.0/.rspec 0000664 0000000 0000000 00000000027 13021465425 0014645 0 ustar 00root root 0000000 0000000 --color
--order random
multi_xml-0.6.0/.rubocop.yml 0000664 0000000 0000000 00000002016 13021465425 0016002 0 ustar 00root root 0000000 0000000 AllCops:
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.yml 0000664 0000000 0000000 00000000460 13021465425 0015642 0 ustar 00root root 0000000 0000000 bundler_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/.yardopts 0000664 0000000 0000000 00000000137 13021465425 0015400 0 ustar 00root root 0000000 0000000 --no-private
--protected
--markup markdown
-
CHANGELOG.md
CONTRIBUTING.md
LICENSE.md
README.md
multi_xml-0.6.0/CHANGELOG.md 0000664 0000000 0000000 00000010117 13021465425 0015342 0 ustar 00root root 0000000 0000000 0.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.md 0000664 0000000 0000000 00000003734 13021465425 0015771 0 ustar 00root root 0000000 0000000 ## 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/Gemfile 0000664 0000000 0000000 00000001106 13021465425 0015022 0 ustar 00root root 0000000 0000000 source '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.md 0000664 0000000 0000000 00000002053 13021465425 0015135 0 ustar 00root root 0000000 0000000 Copyright (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.md 0000664 0000000 0000000 00000006631 13021465425 0015016 0 ustar 00root root 0000000 0000000 # MultiXML
[][gem]
[][travis]
[][gemnasium]
[][codeclimate]
[][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/Rakefile 0000664 0000000 0000000 00000001317 13021465425 0015200 0 ustar 00root root 0000000 0000000 require '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/ 0000775 0000000 0000000 00000000000 13021465425 0014277 5 ustar 00root root 0000000 0000000 multi_xml-0.6.0/lib/multi_xml.rb 0000664 0000000 0000000 00000023443 13021465425 0016644 0 ustar 00root root 0000000 0000000 require '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/ 0000775 0000000 0000000 00000000000 13021465425 0016311 5 ustar 00root root 0000000 0000000 multi_xml-0.6.0/lib/multi_xml/parsers/ 0000775 0000000 0000000 00000000000 13021465425 0017770 5 ustar 00root root 0000000 0000000 multi_xml-0.6.0/lib/multi_xml/parsers/libxml.rb 0000664 0000000 0000000 00000001112 13021465425 0021577 0 ustar 00root root 0000000 0000000 require '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.rb 0000664 0000000 0000000 00000003621 13021465425 0023244 0 ustar 00root root 0000000 0000000 module 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.rb 0000664 0000000 0000000 00000001251 13021465425 0022135 0 ustar 00root root 0000000 0000000 require '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.rb 0000664 0000000 0000000 00000003361 13021465425 0021066 0 ustar 00root root 0000000 0000000 require '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.rb 0000664 0000000 0000000 00000004162 13021465425 0020746 0 ustar 00root root 0000000 0000000 require '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.rb 0000664 0000000 0000000 00000006171 13021465425 0021451 0 ustar 00root root 0000000 0000000 require '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.rb 0000664 0000000 0000000 00000001125 13021465425 0020322 0 ustar 00root root 0000000 0000000 module 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.gemspec 0000664 0000000 0000000 00000001445 13021465425 0017114 0 ustar 00root root 0000000 0000000 # 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/ 0000775 0000000 0000000 00000000000 13021465425 0014463 5 ustar 00root root 0000000 0000000 multi_xml-0.6.0/spec/helper.rb 0000664 0000000 0000000 00000000643 13021465425 0016272 0 ustar 00root root 0000000 0000000 def 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.rb 0000664 0000000 0000000 00000002651 13021465425 0020040 0 ustar 00root root 0000000 0000000 require '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.rb 0000664 0000000 0000000 00000053010 13021465425 0021344 0 ustar 00root root 0000000 0000000 shared_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.rb 0000775 0000000 0000000 00000002537 13021465425 0016122 0 ustar 00root root 0000000 0000000 #!/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