pax_global_header 0000666 0000000 0000000 00000000064 15004466057 0014521 g ustar 00root root 0000000 0000000 52 comment=35bff56b82185983620fe60a095752b3d9a7c10f
multi_xml-0.7.2/ 0000775 0000000 0000000 00000000000 15004466057 0013541 5 ustar 00root root 0000000 0000000 multi_xml-0.7.2/.github/ 0000775 0000000 0000000 00000000000 15004466057 0015101 5 ustar 00root root 0000000 0000000 multi_xml-0.7.2/.github/FUNDING.yml 0000664 0000000 0000000 00000000021 15004466057 0016707 0 ustar 00root root 0000000 0000000 github: [sferik]
multi_xml-0.7.2/.github/workflows/ 0000775 0000000 0000000 00000000000 15004466057 0017136 5 ustar 00root root 0000000 0000000 multi_xml-0.7.2/.github/workflows/ci.yml 0000664 0000000 0000000 00000000524 15004466057 0020255 0 ustar 00root root 0000000 0000000 name: ci
on: [push, pull_request]
jobs:
build:
strategy:
matrix:
ruby: ["3.2", "3.3", "3.4", "jruby-10"]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
- run: bundle exec rake test
multi_xml-0.7.2/.gitignore 0000664 0000000 0000000 00000000130 15004466057 0015523 0 ustar 00root root 0000000 0000000 *.gem
*~
.bundle
.rvmrc
.yardoc
Gemfile.lock
coverage/*
doc/*
log/*
measurement/*
pkg/*
multi_xml-0.7.2/.rspec 0000664 0000000 0000000 00000000027 15004466057 0014655 0 ustar 00root root 0000000 0000000 --color
--order random
multi_xml-0.7.2/.rubocop.yml 0000664 0000000 0000000 00000001720 15004466057 0016013 0 ustar 00root root 0000000 0000000 require:
- standard
plugins:
- rubocop-performance
- rubocop-rake
- rubocop-rspec
- standard-performance
AllCops:
NewCops: enable
TargetRubyVersion: 3.2
Layout/ArgumentAlignment:
EnforcedStyle: with_fixed_indentation
IndentationWidth: 2
Layout/CaseIndentation:
EnforcedStyle: end
Layout/EndAlignment:
EnforcedStyleAlignWith: start_of_line
Layout/LineLength:
Max: 140
Layout/ParameterAlignment:
EnforcedStyle: with_fixed_indentation
IndentationWidth: 2
Layout/SpaceInsideHashLiteralBraces:
EnforcedStyle: no_space
Metrics/ParameterLists:
CountKeywordArgs: false
Style/Alias:
EnforcedStyle: prefer_alias_method
Style/Documentation:
Enabled: false
Style/FrozenStringLiteralComment:
EnforcedStyle: never
Style/OpenStructUse:
Enabled: false
Style/StringLiterals:
EnforcedStyle: double_quotes
Style/StringLiteralsInInterpolation:
EnforcedStyle: double_quotes
Style/TernaryParentheses:
EnforcedStyle: require_parentheses
multi_xml-0.7.2/.yardopts 0000664 0000000 0000000 00000000137 15004466057 0015410 0 ustar 00root root 0000000 0000000 --no-private
--protected
--markup markdown
-
CHANGELOG.md
CONTRIBUTING.md
LICENSE.md
README.md
multi_xml-0.7.2/CHANGELOG.md 0000664 0000000 0000000 00000012127 15004466057 0015355 0 ustar 00root root 0000000 0000000 0.7.2
-----
* [Drop support for Ruby 3.1](https://github.com/sferik/multi_xml/commit/fab6288edd36c58a2b13e0206d8bed305fcb4a4b)
0.7.1
-----
* [Relax required Ruby version constraint to allow installation on Debian stable](https://github.com/sferik/multi_xml/commit/7d18711466a15e158dc71344ca6f6e18838ecc8d)
0.7.0
-----
* [Add support for Ruby 3.3](https://github.com/sferik/multi_xml/pull/67)
* [Drop support for Ruby 3.0](https://github.com/sferik/multi_xml/commit/eec72c56307fede3a93f1a61553587cb278b0c8a) [and](https://github.com/sferik/multi_xml/commit/6a6dec80a36c30774a5525b45f71d346fb561e69) [earlier](https://github.com/sferik/multi_xml/commit/e7dad37a0a0be8383a26ffe515c575b5b4d04588)
* [Don't mutate strings](https://github.com/sferik/multi_xml/commit/71be3fff4afb0277a7e1c47c5f1f4b6106a8eb45)
0.6.0
-----
* [Duplexed Streams](https://github.com/sferik/multi_xml/pull/45)
* [Support for Oga](https://github.com/sferik/multi_xml/pull/47)
* [Integer unification for Ruby 2.4](https://github.com/sferik/multi_xml/pull/54)
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.7.2/CONTRIBUTING.md 0000664 0000000 0000000 00000003734 15004466057 0016001 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.7.2/Gemfile 0000664 0000000 0000000 00000001034 15004466057 0015032 0 ustar 00root root 0000000 0000000 source "https://rubygems.org"
gem "libxml-ruby", require: nil, platforms: :ruby
gem "nokogiri", require: nil
gem "oga", ">= 2.3", require: nil
gem "ox", require: nil, platforms: :ruby
gem "rexml", require: nil
gem "rake", ">= 13.2.1"
gem "rspec", ">= 3.12"
gem "rubocop", ">= 1.62.1"
gem "rubocop-performance", ">= 1.20.2"
gem "rubocop-rake", ">= 0.6"
gem "rubocop-rspec", ">= 2.24"
gem "simplecov", ">= 0.22"
gem "standard", ">= 1.35.1"
gem "standard-performance", ">= 1.3.1"
gem "yard", ">= 0.9.36"
gem "yardstick", ">= 0.9.9"
gemspec
multi_xml-0.7.2/LICENSE.md 0000664 0000000 0000000 00000002044 15004466057 0015145 0 ustar 00root root 0000000 0000000 Copyright (c) 2010-2025 Erik Berlin
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.7.2/README.md 0000664 0000000 0000000 00000004640 15004466057 0015024 0 ustar 00root root 0000000 0000000 # MultiXML
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
```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 use them before attempting to load
a new one. 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 the following Ruby
implementations:
* 3.2
* 3.3
* 3.4
* JRuby 10
If something doesn't work on one of these versions, 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-2025 Erik Berlin. See [LICENSE][] for details.
[license]: LICENSE.md
multi_xml-0.7.2/Rakefile 0000664 0000000 0000000 00000001304 15004466057 0015204 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: %i[spec rubocop verify_measurements]
multi_xml-0.7.2/bin/ 0000775 0000000 0000000 00000000000 15004466057 0014311 5 ustar 00root root 0000000 0000000 multi_xml-0.7.2/bin/console 0000775 0000000 0000000 00000000375 15004466057 0015706 0 ustar 00root root 0000000 0000000 #!/usr/bin/env ruby
require "bundler/setup"
require "multi_xml"
# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.
require "irb"
IRB.start(__FILE__)
multi_xml-0.7.2/bin/setup 0000775 0000000 0000000 00000000203 15004466057 0015372 0 ustar 00root root 0000000 0000000 #!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
set -vx
bundle install
# Do any other automated setup that you need to do here
multi_xml-0.7.2/lib/ 0000775 0000000 0000000 00000000000 15004466057 0014307 5 ustar 00root root 0000000 0000000 multi_xml-0.7.2/lib/multi_xml.rb 0000664 0000000 0000000 00000023335 15004466057 0016654 0 ustar 00root root 0000000 0000000 require "bigdecimal"
require "date"
require "stringio"
require "time"
require "yaml"
module MultiXml # rubocop:disable Metrics/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 Style/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 Style/RescueModifier
"base64Binary" => proc { |binary| base64_decode(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|
require library
return parser
rescue LoadError
next
end
raise(NoParserError,
"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 Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/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 = typecast_xml_value(hash, options[:disallowed_types]) if options[:typecast_xml_value]
rescue DisallowedTypeError
raise
rescue parser.parse_error => e
raise(ParseError, e.message, e.backtrace)
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_decode(binary)
else
binary
end
end
def parse_file(file, entity)
f = StringIO.new(base64_decode(file))
f.extend(FileLike)
f.original_filename = entity["name"]
f.content_type = entity["content_type"]
f
end
def base64_decode(input)
input.unpack1("m")
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.each_with_object({}) do |(key, value), hash|
hash[key.to_s.tr("-", "_")] = 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 Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/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, 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.each_with_object({}) do |(k, v), hash|
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.7.2/lib/multi_xml/ 0000775 0000000 0000000 00000000000 15004466057 0016321 5 ustar 00root root 0000000 0000000 multi_xml-0.7.2/lib/multi_xml/parsers/ 0000775 0000000 0000000 00000000000 15004466057 0020000 5 ustar 00root root 0000000 0000000 multi_xml-0.7.2/lib/multi_xml/parsers/libxml.rb 0000664 0000000 0000000 00000001071 15004466057 0021613 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, &)
node.each_child(&)
end
def each_attr(node, &)
node.each_attr(&)
end
def node_name(node)
node.name
end
end
end
end
multi_xml-0.7.2/lib/multi_xml/parsers/libxml2_parser.rb 0000664 0000000 0000000 00000003563 15004466057 0023261 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 Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
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
node_hash.delete(MultiXml::CONTENT_ROOT) if node_hash[MultiXml::CONTENT_ROOT].strip.empty?
# 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, "inheritor should define #{__method__}")
end
private
def each_child(*)
raise(NotImplementedError, "inheritor should define #{__method__}")
end
def each_attr(*)
raise(NotImplementedError, "inheritor should define #{__method__}")
end
def node_name(*)
raise(NotImplementedError, "inheritor should define #{__method__}")
end
end
end
end
multi_xml-0.7.2/lib/multi_xml/parsers/nokogiri.rb 0000664 0000000 0000000 00000001231 15004466057 0022143 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, &)
node.children.each(&)
end
def each_attr(node, &)
node.attribute_nodes.each(&)
end
def node_name(node)
node.node_name
end
end
end
end
multi_xml-0.7.2/lib/multi_xml/parsers/oga.rb 0000664 0000000 0000000 00000003264 15004466057 0021100 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 Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
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
node_hash.delete(MultiXml::CONTENT_ROOT) if node_hash[MultiXml::CONTENT_ROOT].strip.empty?
# 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, &)
node.children.each(&)
end
def each_attr(node, &)
node.attributes.each(&)
end
def node_name(node)
node.name
end
end
end
end
multi_xml-0.7.2/lib/multi_xml/parsers/ox.rb 0000664 0000000 0000000 00000004144 15004466057 0020756 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:
module_function
def parse_error
Exception
end
def parse(io)
handler = Handler.new
::Ox.sax_parse(handler, io, convert_special: true, skip: :skip_return)
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(StandardError, "#{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
end
end
end
multi_xml-0.7.2/lib/multi_xml/parsers/rexml.rb 0000664 0000000 0000000 00000006140 15004466057 0021455 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, "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.map(&:value).join
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.7.2/lib/multi_xml/version.rb 0000664 0000000 0000000 00000000075 15004466057 0020335 0 ustar 00root root 0000000 0000000 module MultiXml
VERSION = Gem::Version.create("0.7.2")
end
multi_xml-0.7.2/multi_xml.gemspec 0000664 0000000 0000000 00000003266 15004466057 0017127 0 ustar 00root root 0000000 0000000 require_relative "lib/multi_xml/version"
Gem::Specification.new do |spec|
spec.name = "multi_xml"
spec.version = MultiXml::VERSION
spec.authors = ["Erik Berlin"]
spec.email = ["sferik@gmail.com"]
spec.summary = "Provides swappable XML backends utilizing LibXML, Nokogiri, Ox, or REXML."
spec.homepage = "https://github.com/sferik/multi_xml"
spec.license = "MIT"
spec.required_ruby_version = ">= 3.2"
spec.metadata["allowed_push_host"] = "https://rubygems.org"
spec.metadata = {
"allowed_push_host" => "https://rubygems.org",
"bug_tracker_uri" => "https://github.com/sferik/multi_xml/issues",
"changelog_uri" => "https://github.com/sferik/multi_xml/blob/master/CHANGELOG.md",
"documentation_uri" => "https://rubydoc.info/gems/multi_xml/",
"funding_uri" => "https://github.com/sponsors/sferik",
"homepage_uri" => spec.homepage,
"rubygems_mfa_required" => "true",
"source_code_uri" => "https://github.com/sferik/multi_xml"
}
# Specify which files should be added to the gem when it is released.
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
spec.files = Dir.chdir(__dir__) do
`git ls-files -z`.split("\x0").reject do |f|
(File.expand_path(f) == __FILE__) || f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor])
end
end
spec.bindir = "exe"
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]
# For more information and examples about making a new gem, check out our
# guide at: https://bundler.io/guides/creating_gem.html
spec.metadata["rubygems_mfa_required"] = "true"
spec.add_dependency("bigdecimal", "~> 3.1")
end
multi_xml-0.7.2/spec/ 0000775 0000000 0000000 00000000000 15004466057 0014473 5 ustar 00root root 0000000 0000000 multi_xml-0.7.2/spec/helper.rb 0000664 0000000 0000000 00000000462 15004466057 0016301 0 ustar 00root root 0000000 0000000 def jruby?
RUBY_PLATFORM == "java"
end
require "simplecov"
SimpleCov.start do
add_filter "/spec"
percent = (jruby?) ? 91.38 : 93.16
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.7.2/spec/multi_xml_spec.rb 0000664 0000000 0000000 00000002605 15004466057 0020047 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_a(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|
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
multi_xml-0.7.2/spec/parser_shared_example.rb 0000664 0000000 0000000 00000052640 15004466057 0021364 0 ustar 00root root 0000000 0000000 shared_examples_for "a parser" do |parser|
before do
MultiXml.parser = parser
LibXML::XML::Error.set_handler(&LibXML::XML::Error::QUIET_HANDLER) if parser == "LibXML"
rescue LoadError
pending "Parser #{parser} couldn't be loaded"
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 = " "
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 Berlin")
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 Berlin"
end
it "returns the correct content" do
expect(MultiXml.parse(@xml)["user"]).to eq("Erik Berlin")
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 Berlin")
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 Berlin")
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 Berlin"},
{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 BerlinWynn 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 Berlin", "Wynn Netherland"])
end
end
context 'with an attribute type="array" in addition to other attributes' do
before do
@xml = 'Erik BerlinWynn 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 Berlin", "Wynn Netherland"])
end
end
context 'with an attribute type="array" containing only one item' do
before do
@xml = 'Erik Berlin'
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 Berlin"])
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 Berlin")
end
end
context "with text" do
before do
@xml = "Erik Berlin"
end
it "returns the correct text" do
expect(MultiXml.parse(@xml)["user"]["name"]).to eq("Erik Berlin")
end
end
context "with an unrecognized attribute type" do
before do
@xml = 'Erik Berlin'
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 Berlin
XML
end
it "parses correctly" do
expect(MultiXml.parse(@xml)).to eq("user" => {"name" => "Erik Berlin"})
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 Berlin",
"status" => {"text" => "Hello"}}})
end
end
end
context "with sibling children" do
before do
@xml = "Erik BerlinWynn 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 Berlin", "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.7.2/spec/speed.rb 0000775 0000000 0000000 00000002430 15004466057 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|
require library
rescue LoadError
next
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") do
puts opts
Process.exit!(0)
end
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