multi_xml 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.rspec +2 -0
- data/.rubocop.yml +54 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile +21 -0
- data/LICENSE.md +1 -1
- data/README.md +8 -29
- data/Rakefile +33 -0
- data/lib/multi_xml/parsers/libxml.rb +8 -8
- data/lib/multi_xml/parsers/libxml2_parser.rb +11 -13
- data/lib/multi_xml/parsers/nokogiri.rb +9 -8
- data/lib/multi_xml/parsers/oga.rb +16 -18
- data/lib/multi_xml/parsers/ox.rb +9 -9
- data/lib/multi_xml/parsers/rexml.rb +6 -6
- data/lib/multi_xml/version.rb +1 -43
- data/lib/multi_xml.rb +93 -87
- metadata +28 -20
- data/multi_xml.gemspec +0 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7bebe7a08139024835279d7ffc78a7d86d0cb9a5ff59787068e044f7c75dc6f9
|
4
|
+
data.tar.gz: a2f579d2e18a266fe9573174de87ac960fcf867081705a2a6feb458bfa573225
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: be4ffea0dd836f1d1d1baf73ffded7de5a79fdc53a5b039b5e7fd0a615a786ca08dc0a7bbf5561266cdade44a9cb3c2382fe6e35a54599876f77ec74ec937d42
|
7
|
+
data.tar.gz: d579ab46f0122a1011704e397062b5d9e648eeaae85a8baa30ac8eda7f3d1cdaa9e57fe1922e6e3e7ac608ef9607033c1547773dbb2eb1fe6785176a3471764d
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require:
|
2
|
+
- rubocop-performance
|
3
|
+
- rubocop-rake
|
4
|
+
- rubocop-rspec
|
5
|
+
- standard
|
6
|
+
- standard-performance
|
7
|
+
|
8
|
+
AllCops:
|
9
|
+
NewCops: enable
|
10
|
+
TargetRubyVersion: 3.1
|
11
|
+
|
12
|
+
Layout/ArgumentAlignment:
|
13
|
+
EnforcedStyle: with_fixed_indentation
|
14
|
+
IndentationWidth: 2
|
15
|
+
|
16
|
+
Layout/CaseIndentation:
|
17
|
+
EnforcedStyle: end
|
18
|
+
|
19
|
+
Layout/EndAlignment:
|
20
|
+
EnforcedStyleAlignWith: start_of_line
|
21
|
+
|
22
|
+
Layout/LineLength:
|
23
|
+
Max: 140
|
24
|
+
|
25
|
+
Layout/ParameterAlignment:
|
26
|
+
EnforcedStyle: with_fixed_indentation
|
27
|
+
IndentationWidth: 2
|
28
|
+
|
29
|
+
Layout/SpaceInsideHashLiteralBraces:
|
30
|
+
EnforcedStyle: no_space
|
31
|
+
|
32
|
+
Metrics/ParameterLists:
|
33
|
+
CountKeywordArgs: false
|
34
|
+
|
35
|
+
Style/Alias:
|
36
|
+
EnforcedStyle: prefer_alias_method
|
37
|
+
|
38
|
+
Style/Documentation:
|
39
|
+
Enabled: false
|
40
|
+
|
41
|
+
Style/FrozenStringLiteralComment:
|
42
|
+
EnforcedStyle: never
|
43
|
+
|
44
|
+
Style/OpenStructUse:
|
45
|
+
Enabled: false
|
46
|
+
|
47
|
+
Style/StringLiterals:
|
48
|
+
EnforcedStyle: double_quotes
|
49
|
+
|
50
|
+
Style/StringLiteralsInInterpolation:
|
51
|
+
EnforcedStyle: double_quotes
|
52
|
+
|
53
|
+
Style/TernaryParentheses:
|
54
|
+
EnforcedStyle: require_parentheses
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
0.7.0
|
2
|
+
-----
|
3
|
+
* [Add support for Ruby 3.3](https://github.com/sferik/multi_xml/pull/67)
|
4
|
+
* [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)
|
5
|
+
* [Don't mutate strings](https://github.com/sferik/multi_xml/commit/71be3fff4afb0277a7e1c47c5f1f4b6106a8eb45)
|
6
|
+
|
7
|
+
0.6.0
|
8
|
+
-----
|
9
|
+
* [Duplexed Streams](https://github.com/sferik/multi_xml/pull/45)
|
10
|
+
* [Support for Oga](https://github.com/sferik/multi_xml/pull/47)
|
11
|
+
* [Integer unification for Ruby 2.4](https://github.com/sferik/multi_xml/pull/54)
|
12
|
+
|
1
13
|
0.5.5
|
2
14
|
-----
|
3
15
|
* [Fix symbolize_keys function](https://github.com/sferik/multi_xml/commit/a4cae3aeb690999287cd30206399abaa5ce1ae81)
|
data/Gemfile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
source "https://rubygems.org"
|
2
|
+
|
3
|
+
gem "libxml-ruby", require: nil, platforms: :ruby
|
4
|
+
gem "nokogiri", require: nil
|
5
|
+
gem "oga", ">= 2.3", require: nil
|
6
|
+
gem "ox", require: nil, platforms: :ruby
|
7
|
+
gem "rexml", require: nil
|
8
|
+
|
9
|
+
gem "rake", ">= 13.2.1"
|
10
|
+
gem "rspec", ">= 3.12"
|
11
|
+
gem "rubocop", ">= 1.62.1"
|
12
|
+
gem "rubocop-performance", ">= 1.20.2"
|
13
|
+
gem "rubocop-rake", ">= 0.6"
|
14
|
+
gem "rubocop-rspec", ">= 2.24"
|
15
|
+
gem "simplecov", ">= 0.22"
|
16
|
+
gem "standard", ">= 1.35.1"
|
17
|
+
gem "standard-performance", ">= 1.3.1"
|
18
|
+
gem "yard", ">= 0.9.36"
|
19
|
+
gem "yardstick", ">= 0.9.9"
|
20
|
+
|
21
|
+
gemspec
|
data/LICENSE.md
CHANGED
data/README.md
CHANGED
@@ -1,17 +1,5 @@
|
|
1
1
|
# MultiXML
|
2
2
|
|
3
|
-
[][gem]
|
4
|
-
[][travis]
|
5
|
-
[][gemnasium]
|
6
|
-
[][codeclimate]
|
7
|
-
[][coveralls]
|
8
|
-
|
9
|
-
[gem]: https://rubygems.org/gems/multi_xml
|
10
|
-
[travis]: http://travis-ci.org/sferik/multi_xml
|
11
|
-
[gemnasium]: https://gemnasium.com/sferik/multi_xml
|
12
|
-
[codeclimate]: https://codeclimate.com/github/sferik/multi_xml
|
13
|
-
[coveralls]: https://coveralls.io/r/sferik/multi_xml
|
14
|
-
|
15
3
|
A generic swappable back-end for XML parsing
|
16
4
|
|
17
5
|
## Installation
|
@@ -23,10 +11,6 @@ A generic swappable back-end for XML parsing
|
|
23
11
|
[documentation]: http://rdoc.info/gems/multi_xml
|
24
12
|
|
25
13
|
## Usage Examples
|
26
|
-
Lots of Ruby libraries utilize XML parsing in some form, and everyone has their
|
27
|
-
favorite XML library. In order to best support multiple XML parsers and
|
28
|
-
libraries, `multi_xml` is a general-purpose swappable XML backend library. You
|
29
|
-
use it like so:
|
30
14
|
```ruby
|
31
15
|
require 'multi_xml'
|
32
16
|
|
@@ -54,24 +38,19 @@ The `parser` setter takes either a symbol or a class (to allow for custom XML
|
|
54
38
|
parsers) that responds to `.parse` at the class level.
|
55
39
|
|
56
40
|
MultiXML tries to have intelligent defaulting. That is, if you have any of the
|
57
|
-
supported parsers already loaded, it will
|
58
|
-
|
41
|
+
supported parsers already loaded, it will use them before attempting to load
|
42
|
+
a new one. When loading, libraries are ordered by speed: first Ox, then LibXML,
|
59
43
|
then Nokogiri, and finally REXML.
|
60
44
|
|
61
45
|
## Supported Ruby Versions
|
62
|
-
This library aims to support and is
|
46
|
+
This library aims to support and is tested against the following Ruby
|
63
47
|
implementations:
|
64
48
|
|
65
|
-
*
|
66
|
-
*
|
67
|
-
*
|
68
|
-
* Ruby 2.2
|
69
|
-
* Ruby 2.3
|
70
|
-
* [JRuby 9000][jruby]
|
71
|
-
|
72
|
-
[jruby]: http://jruby.org/
|
49
|
+
* 3.1
|
50
|
+
* 3.2
|
51
|
+
* 3.3
|
73
52
|
|
74
|
-
If something doesn't work on one of these
|
53
|
+
If something doesn't work on one of these versions, it's a bug.
|
75
54
|
|
76
55
|
This library may inadvertently work (or seem to work) on other Ruby
|
77
56
|
implementations, however support will only be provided for the versions listed
|
@@ -90,6 +69,6 @@ MultiXML was inspired by [MultiJSON][].
|
|
90
69
|
[multijson]: https://github.com/intridea/multi_json/
|
91
70
|
|
92
71
|
## Copyright
|
93
|
-
Copyright (c) 2010-
|
72
|
+
Copyright (c) 2010-2024 Erik Berlin. See [LICENSE][] for details.
|
94
73
|
|
95
74
|
[license]: LICENSE.md
|
data/Rakefile
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require "bundler"
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
require "rspec/core/rake_task"
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
6
|
+
|
7
|
+
task test: :spec
|
8
|
+
|
9
|
+
require "rubocop/rake_task"
|
10
|
+
RuboCop::RakeTask.new
|
11
|
+
|
12
|
+
require "yard"
|
13
|
+
YARD::Rake::YardocTask.new do |task|
|
14
|
+
task.files = ["lib/**/*.rb", "-", "LICENSE.md"]
|
15
|
+
task.options = [
|
16
|
+
"--no-private",
|
17
|
+
"--protected",
|
18
|
+
"--output-dir", "doc/yard",
|
19
|
+
"--markup", "markdown"
|
20
|
+
]
|
21
|
+
end
|
22
|
+
|
23
|
+
require "yardstick/rake/measurement"
|
24
|
+
Yardstick::Rake::Measurement.new do |measurement|
|
25
|
+
measurement.output = "measurement/report.txt"
|
26
|
+
end
|
27
|
+
|
28
|
+
require "yardstick/rake/verify"
|
29
|
+
Yardstick::Rake::Verify.new do |verify|
|
30
|
+
verify.threshold = 48.8
|
31
|
+
end
|
32
|
+
|
33
|
+
task default: %i[spec rubocop verify_measurements]
|
@@ -1,9 +1,9 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "libxml" unless defined?(LibXML)
|
2
|
+
require "multi_xml/parsers/libxml2_parser"
|
3
3
|
|
4
4
|
module MultiXml
|
5
5
|
module Parsers
|
6
|
-
module Libxml
|
6
|
+
module Libxml # :nodoc:
|
7
7
|
include Libxml2Parser
|
8
8
|
extend self
|
9
9
|
|
@@ -15,14 +15,14 @@ module MultiXml
|
|
15
15
|
node_to_hash(LibXML::XML::Parser.io(xml).parse.root)
|
16
16
|
end
|
17
17
|
|
18
|
-
|
18
|
+
private
|
19
19
|
|
20
|
-
def each_child(node, &
|
21
|
-
node.each_child(&
|
20
|
+
def each_child(node, &)
|
21
|
+
node.each_child(&)
|
22
22
|
end
|
23
23
|
|
24
|
-
def each_attr(node, &
|
25
|
-
node.each_attr(&
|
24
|
+
def each_attr(node, &)
|
25
|
+
node.each_attr(&)
|
26
26
|
end
|
27
27
|
|
28
28
|
def node_name(node)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module MultiXml
|
2
2
|
module Parsers
|
3
|
-
module Libxml2Parser
|
3
|
+
module Libxml2Parser # :nodoc:
|
4
4
|
# Convert XML document to hash
|
5
5
|
#
|
6
6
|
# node::
|
@@ -8,8 +8,8 @@ module MultiXml
|
|
8
8
|
#
|
9
9
|
# hash::
|
10
10
|
# Hash to merge the converted element into.
|
11
|
-
def node_to_hash(node, hash = {}) # rubocop:disable AbcSize, CyclomaticComplexity, MethodLength
|
12
|
-
node_hash = {MultiXml::CONTENT_ROOT =>
|
11
|
+
def node_to_hash(node, hash = {}) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
|
12
|
+
node_hash = {MultiXml::CONTENT_ROOT => ""}
|
13
13
|
|
14
14
|
name = node_name(node)
|
15
15
|
|
@@ -28,20 +28,18 @@ module MultiXml
|
|
28
28
|
if c.element?
|
29
29
|
node_to_hash(c, node_hash)
|
30
30
|
elsif c.text? || c.cdata?
|
31
|
-
node_hash[MultiXml::CONTENT_ROOT]
|
31
|
+
node_hash[MultiXml::CONTENT_ROOT] += c.content
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
35
|
# Remove content node if it is empty
|
36
|
-
if node_hash[MultiXml::CONTENT_ROOT].strip.empty?
|
37
|
-
node_hash.delete(MultiXml::CONTENT_ROOT)
|
38
|
-
end
|
36
|
+
node_hash.delete(MultiXml::CONTENT_ROOT) if node_hash[MultiXml::CONTENT_ROOT].strip.empty?
|
39
37
|
|
40
38
|
# Handle attributes
|
41
39
|
each_attr(node) do |a|
|
42
40
|
key = node_name(a)
|
43
41
|
v = node_hash[key]
|
44
|
-
node_hash[key] = (v ? [a.value, v] : a.value)
|
42
|
+
node_hash[key] = ((v) ? [a.value, v] : a.value)
|
45
43
|
end
|
46
44
|
|
47
45
|
hash
|
@@ -51,21 +49,21 @@ module MultiXml
|
|
51
49
|
# xml::
|
52
50
|
# XML Document IO to parse
|
53
51
|
def parse(_)
|
54
|
-
raise(NotImplementedError
|
52
|
+
raise(NotImplementedError, "inheritor should define #{__method__}")
|
55
53
|
end
|
56
54
|
|
57
|
-
|
55
|
+
private
|
58
56
|
|
59
57
|
def each_child(*)
|
60
|
-
raise(NotImplementedError
|
58
|
+
raise(NotImplementedError, "inheritor should define #{__method__}")
|
61
59
|
end
|
62
60
|
|
63
61
|
def each_attr(*)
|
64
|
-
raise(NotImplementedError
|
62
|
+
raise(NotImplementedError, "inheritor should define #{__method__}")
|
65
63
|
end
|
66
64
|
|
67
65
|
def node_name(*)
|
68
|
-
raise(NotImplementedError
|
66
|
+
raise(NotImplementedError, "inheritor should define #{__method__}")
|
69
67
|
end
|
70
68
|
end
|
71
69
|
end
|
@@ -1,9 +1,9 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "nokogiri" unless defined?(Nokogiri)
|
2
|
+
require "multi_xml/parsers/libxml2_parser"
|
3
3
|
|
4
4
|
module MultiXml
|
5
5
|
module Parsers
|
6
|
-
module Nokogiri
|
6
|
+
module Nokogiri # :nodoc:
|
7
7
|
include Libxml2Parser
|
8
8
|
extend self
|
9
9
|
|
@@ -14,17 +14,18 @@ module MultiXml
|
|
14
14
|
def parse(xml)
|
15
15
|
doc = ::Nokogiri::XML(xml)
|
16
16
|
raise(doc.errors.first) unless doc.errors.empty?
|
17
|
+
|
17
18
|
node_to_hash(doc.root)
|
18
19
|
end
|
19
20
|
|
20
|
-
|
21
|
+
private
|
21
22
|
|
22
|
-
def each_child(node, &
|
23
|
-
node.children.each(&
|
23
|
+
def each_child(node, &)
|
24
|
+
node.children.each(&)
|
24
25
|
end
|
25
26
|
|
26
|
-
def each_attr(node, &
|
27
|
-
node.attribute_nodes.each(&
|
27
|
+
def each_attr(node, &)
|
28
|
+
node.attribute_nodes.each(&)
|
28
29
|
end
|
29
30
|
|
30
31
|
def node_name(node)
|
@@ -1,9 +1,9 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "oga" unless defined?(Oga)
|
2
|
+
require "multi_xml/parsers/libxml2_parser"
|
3
3
|
|
4
4
|
module MultiXml
|
5
5
|
module Parsers
|
6
|
-
module Oga
|
6
|
+
module Oga # :nodoc:
|
7
7
|
include Libxml2Parser
|
8
8
|
extend self
|
9
9
|
|
@@ -16,8 +16,8 @@ module MultiXml
|
|
16
16
|
node_to_hash(document.children[0])
|
17
17
|
end
|
18
18
|
|
19
|
-
def node_to_hash(node, hash = {}) # rubocop:disable AbcSize, CyclomaticComplexity, MethodLength
|
20
|
-
node_hash = {MultiXml::CONTENT_ROOT =>
|
19
|
+
def node_to_hash(node, hash = {}) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
|
20
|
+
node_hash = {MultiXml::CONTENT_ROOT => ""}
|
21
21
|
|
22
22
|
name = node_name(node)
|
23
23
|
|
@@ -36,38 +36,36 @@ module MultiXml
|
|
36
36
|
if c.is_a?(::Oga::XML::Element)
|
37
37
|
node_to_hash(c, node_hash)
|
38
38
|
elsif c.is_a?(::Oga::XML::Text) || c.is_a?(::Oga::XML::Cdata)
|
39
|
-
node_hash[MultiXml::CONTENT_ROOT]
|
39
|
+
node_hash[MultiXml::CONTENT_ROOT] += c.text
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
43
|
# Remove content node if it is empty
|
44
|
-
if node_hash[MultiXml::CONTENT_ROOT].strip.empty?
|
45
|
-
node_hash.delete(MultiXml::CONTENT_ROOT)
|
46
|
-
end
|
44
|
+
node_hash.delete(MultiXml::CONTENT_ROOT) if node_hash[MultiXml::CONTENT_ROOT].strip.empty?
|
47
45
|
|
48
46
|
# Handle attributes
|
49
47
|
each_attr(node) do |a|
|
50
48
|
key = node_name(a)
|
51
49
|
v = node_hash[key]
|
52
|
-
node_hash[key] = (v ? [a.value, v] : a.value)
|
50
|
+
node_hash[key] = ((v) ? [a.value, v] : a.value)
|
53
51
|
end
|
54
52
|
|
55
53
|
hash
|
56
54
|
end
|
57
55
|
|
58
|
-
|
56
|
+
private
|
59
57
|
|
60
|
-
def each_child(node, &
|
61
|
-
node.children.each(&
|
58
|
+
def each_child(node, &)
|
59
|
+
node.children.each(&)
|
62
60
|
end
|
63
61
|
|
64
|
-
def each_attr(node, &
|
65
|
-
node.attributes.each(&
|
62
|
+
def each_attr(node, &)
|
63
|
+
node.attributes.each(&)
|
66
64
|
end
|
67
65
|
|
68
66
|
def node_name(node)
|
69
67
|
node.name
|
70
68
|
end
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/multi_xml/parsers/ox.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "ox" unless defined?(Ox)
|
2
2
|
|
3
3
|
# Each MultiXml parser is expected to parse an XML document into a Hash. The
|
4
4
|
# conversion rules are:
|
@@ -20,8 +20,8 @@ require 'ox' unless defined?(Ox)
|
|
20
20
|
|
21
21
|
module MultiXml
|
22
22
|
module Parsers
|
23
|
-
module Ox
|
24
|
-
|
23
|
+
module Ox # :nodoc:
|
24
|
+
module_function
|
25
25
|
|
26
26
|
def parse_error
|
27
27
|
Exception
|
@@ -29,7 +29,7 @@ module MultiXml
|
|
29
29
|
|
30
30
|
def parse(io)
|
31
31
|
handler = Handler.new
|
32
|
-
::Ox.sax_parse(handler, io, :
|
32
|
+
::Ox.sax_parse(handler, io, convert_special: true, skip: :skip_return)
|
33
33
|
handler.doc
|
34
34
|
end
|
35
35
|
|
@@ -68,7 +68,7 @@ module MultiXml
|
|
68
68
|
end
|
69
69
|
|
70
70
|
def error(message, line, column)
|
71
|
-
raise(
|
71
|
+
raise(StandardError, "#{message} at #{line}:#{column}")
|
72
72
|
end
|
73
73
|
|
74
74
|
def append(key, value)
|
@@ -85,7 +85,7 @@ module MultiXml
|
|
85
85
|
h[key] = value
|
86
86
|
end
|
87
87
|
end
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -1,8 +1,8 @@
|
|
1
|
-
require
|
1
|
+
require "rexml/document" unless defined?(REXML::Document)
|
2
2
|
|
3
3
|
module MultiXml
|
4
4
|
module Parsers
|
5
|
-
module Rexml
|
5
|
+
module Rexml # :nodoc:
|
6
6
|
extend self
|
7
7
|
|
8
8
|
def parse_error
|
@@ -15,11 +15,12 @@ module MultiXml
|
|
15
15
|
# XML Document IO to parse
|
16
16
|
def parse(xml)
|
17
17
|
doc = REXML::Document.new(xml)
|
18
|
-
raise(REXML::ParseException
|
18
|
+
raise(REXML::ParseException, "The document #{doc.to_s.inspect} does not have a valid root") unless doc.root
|
19
|
+
|
19
20
|
merge_element!({}, doc.root)
|
20
21
|
end
|
21
22
|
|
22
|
-
|
23
|
+
private
|
23
24
|
|
24
25
|
# Convert an XML element and merge into the hash
|
25
26
|
#
|
@@ -56,8 +57,7 @@ module MultiXml
|
|
56
57
|
def merge_texts!(hash, element)
|
57
58
|
if element.has_text?
|
58
59
|
# must use value to prevent double-escaping
|
59
|
-
texts =
|
60
|
-
element.texts.each { |t| texts << t.value }
|
60
|
+
texts = element.texts.map(&:value).join
|
61
61
|
merge!(hash, MultiXml::CONTENT_ROOT, texts)
|
62
62
|
else
|
63
63
|
hash
|
data/lib/multi_xml/version.rb
CHANGED
@@ -1,45 +1,3 @@
|
|
1
1
|
module MultiXml
|
2
|
-
|
3
|
-
module_function
|
4
|
-
|
5
|
-
# @return [Integer]
|
6
|
-
def major
|
7
|
-
0
|
8
|
-
end
|
9
|
-
|
10
|
-
# @return [Integer]
|
11
|
-
def minor
|
12
|
-
6
|
13
|
-
end
|
14
|
-
|
15
|
-
# @return [Integer]
|
16
|
-
def patch
|
17
|
-
0
|
18
|
-
end
|
19
|
-
|
20
|
-
# @return [Integer, NilClass]
|
21
|
-
def pre
|
22
|
-
nil
|
23
|
-
end
|
24
|
-
|
25
|
-
# @return [Hash]
|
26
|
-
def to_h
|
27
|
-
{
|
28
|
-
:major => major,
|
29
|
-
:minor => minor,
|
30
|
-
:patch => patch,
|
31
|
-
:pre => pre,
|
32
|
-
}
|
33
|
-
end
|
34
|
-
|
35
|
-
# @return [Array]
|
36
|
-
def to_a
|
37
|
-
[major, minor, patch, pre].compact
|
38
|
-
end
|
39
|
-
|
40
|
-
# @return [String]
|
41
|
-
def to_s
|
42
|
-
to_a.join('.')
|
43
|
-
end
|
44
|
-
end
|
2
|
+
VERSION = Gem::Version.create("0.7.0")
|
45
3
|
end
|
data/lib/multi_xml.rb
CHANGED
@@ -1,81 +1,83 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
|
7
|
-
|
8
|
-
module MultiXml # rubocop:disable ModuleLength
|
1
|
+
require "bigdecimal"
|
2
|
+
require "date"
|
3
|
+
require "stringio"
|
4
|
+
require "time"
|
5
|
+
require "yaml"
|
6
|
+
|
7
|
+
module MultiXml # rubocop:disable Metrics/ModuleLength
|
9
8
|
class ParseError < StandardError; end
|
9
|
+
|
10
10
|
class NoParserError < StandardError; end
|
11
|
+
|
11
12
|
class DisallowedTypeError < StandardError
|
12
13
|
def initialize(type)
|
13
|
-
super
|
14
|
+
super("Disallowed type attribute: #{type.inspect}")
|
14
15
|
end
|
15
16
|
end
|
16
17
|
|
17
18
|
unless defined?(REQUIREMENT_MAP)
|
18
19
|
REQUIREMENT_MAP = [
|
19
|
-
[
|
20
|
-
[
|
21
|
-
[
|
22
|
-
[
|
23
|
-
[
|
20
|
+
["ox", :ox],
|
21
|
+
["libxml", :libxml],
|
22
|
+
["nokogiri", :nokogiri],
|
23
|
+
["rexml/document", :rexml],
|
24
|
+
["oga", :oga]
|
24
25
|
].freeze
|
25
26
|
end
|
26
27
|
|
27
|
-
CONTENT_ROOT =
|
28
|
+
CONTENT_ROOT = "__content__".freeze unless defined?(CONTENT_ROOT)
|
28
29
|
|
29
30
|
unless defined?(PARSING)
|
30
31
|
float_proc = proc { |float| float.to_f }
|
31
|
-
datetime_proc = proc { |time| Time.parse(time).utc rescue DateTime.parse(time).utc } # rubocop:disable RescueModifier
|
32
|
+
datetime_proc = proc { |time| Time.parse(time).utc rescue DateTime.parse(time).utc } # rubocop:disable Style/RescueModifier
|
32
33
|
|
33
34
|
PARSING = {
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
35
|
+
"symbol" => proc { |symbol| symbol.to_sym },
|
36
|
+
"date" => proc { |date| Date.parse(date) },
|
37
|
+
"datetime" => datetime_proc,
|
38
|
+
"dateTime" => datetime_proc,
|
39
|
+
"integer" => proc { |integer| integer.to_i },
|
40
|
+
"float" => float_proc,
|
41
|
+
"double" => float_proc,
|
42
|
+
"decimal" => proc { |number| BigDecimal(number) },
|
43
|
+
"boolean" => proc { |boolean| !%w[0 false].include?(boolean.strip) },
|
44
|
+
"string" => proc { |string| string.to_s },
|
45
|
+
"yaml" => proc { |yaml| YAML.load(yaml) rescue yaml }, # rubocop:disable Style/RescueModifier
|
46
|
+
"base64Binary" => proc { |binary| base64_decode(binary) },
|
47
|
+
"binary" => proc { |binary, entity| parse_binary(binary, entity) },
|
48
|
+
"file" => proc { |file, entity| parse_file(file, entity) }
|
48
49
|
}.freeze
|
49
50
|
end
|
50
51
|
|
51
52
|
unless defined?(TYPE_NAMES)
|
52
53
|
TYPE_NAMES = {
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
54
|
+
"Symbol" => "symbol",
|
55
|
+
"Integer" => "integer",
|
56
|
+
"BigDecimal" => "decimal",
|
57
|
+
"Float" => "float",
|
58
|
+
"TrueClass" => "boolean",
|
59
|
+
"FalseClass" => "boolean",
|
60
|
+
"Date" => "date",
|
61
|
+
"DateTime" => "datetime",
|
62
|
+
"Time" => "datetime",
|
63
|
+
"Array" => "array",
|
64
|
+
"Hash" => "hash"
|
64
65
|
}.freeze
|
65
66
|
end
|
66
67
|
|
67
|
-
DISALLOWED_XML_TYPES = %w
|
68
|
+
DISALLOWED_XML_TYPES = %w[symbol yaml].freeze
|
68
69
|
|
69
70
|
DEFAULT_OPTIONS = {
|
70
|
-
:
|
71
|
-
:
|
72
|
-
:
|
71
|
+
typecast_xml_value: true,
|
72
|
+
disallowed_types: DISALLOWED_XML_TYPES,
|
73
|
+
symbolize_keys: false
|
73
74
|
}.freeze
|
74
75
|
|
75
76
|
class << self
|
76
77
|
# Get the current parser class.
|
77
78
|
def parser
|
78
79
|
return @parser if defined?(@parser)
|
80
|
+
|
79
81
|
self.parser = default_parser
|
80
82
|
@parser
|
81
83
|
end
|
@@ -91,14 +93,13 @@ module MultiXml # rubocop:disable ModuleLength
|
|
91
93
|
return :oga if defined?(::Oga)
|
92
94
|
|
93
95
|
REQUIREMENT_MAP.each do |library, parser|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
next
|
99
|
-
end
|
96
|
+
require library
|
97
|
+
return parser
|
98
|
+
rescue LoadError
|
99
|
+
next
|
100
100
|
end
|
101
|
-
raise(NoParserError
|
101
|
+
raise(NoParserError,
|
102
|
+
"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.")
|
102
103
|
end
|
103
104
|
|
104
105
|
# Set the XML parser utilizing a symbol, string, or class.
|
@@ -113,11 +114,11 @@ module MultiXml # rubocop:disable ModuleLength
|
|
113
114
|
case new_parser
|
114
115
|
when String, Symbol
|
115
116
|
require "multi_xml/parsers/#{new_parser.to_s.downcase}"
|
116
|
-
@parser = MultiXml::Parsers.const_get(new_parser.to_s.split(
|
117
|
+
@parser = MultiXml::Parsers.const_get(new_parser.to_s.split("_").collect(&:capitalize).join.to_s)
|
117
118
|
when Class, Module
|
118
119
|
@parser = new_parser
|
119
120
|
else
|
120
|
-
raise(
|
121
|
+
raise("Did not recognize your parser specification. Please specify either a symbol or a class.")
|
121
122
|
end
|
122
123
|
end
|
123
124
|
|
@@ -130,8 +131,8 @@ module MultiXml # rubocop:disable ModuleLength
|
|
130
131
|
# <tt>:disallowed_types</tt> :: Types to disallow from being typecasted. Defaults to `['yaml', 'symbol']`. Use `[]` to allow all types.
|
131
132
|
#
|
132
133
|
# <tt>:typecast_xml_value</tt> :: If true, won't typecast values for parsed document
|
133
|
-
def parse(xml, options = {}) # rubocop:disable AbcSize, CyclomaticComplexity, MethodLength, PerceivedComplexity
|
134
|
-
xml ||=
|
134
|
+
def parse(xml, options = {}) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
135
|
+
xml ||= ""
|
135
136
|
|
136
137
|
options = DEFAULT_OPTIONS.merge(options)
|
137
138
|
|
@@ -141,14 +142,15 @@ module MultiXml # rubocop:disable ModuleLength
|
|
141
142
|
|
142
143
|
char = xml.getc
|
143
144
|
return {} if char.nil?
|
145
|
+
|
144
146
|
xml.ungetc(char)
|
145
147
|
|
146
148
|
hash = undasherize_keys(parser.parse(xml) || {})
|
147
|
-
hash =
|
149
|
+
hash = typecast_xml_value(hash, options[:disallowed_types]) if options[:typecast_xml_value]
|
148
150
|
rescue DisallowedTypeError
|
149
151
|
raise
|
150
|
-
rescue parser.parse_error =>
|
151
|
-
raise(ParseError,
|
152
|
+
rescue parser.parse_error => e
|
153
|
+
raise(ParseError, e.message, e.backtrace)
|
152
154
|
end
|
153
155
|
hash = symbolize_keys(hash) if options[:symbolize_keys]
|
154
156
|
hash
|
@@ -156,38 +158,42 @@ module MultiXml # rubocop:disable ModuleLength
|
|
156
158
|
|
157
159
|
# This module decorates files with the <tt>original_filename</tt>
|
158
160
|
# and <tt>content_type</tt> methods.
|
159
|
-
module FileLike
|
161
|
+
module FileLike # :nodoc:
|
160
162
|
attr_writer :original_filename, :content_type
|
161
163
|
|
162
164
|
def original_filename
|
163
|
-
@original_filename ||
|
165
|
+
@original_filename || "untitled"
|
164
166
|
end
|
165
167
|
|
166
168
|
def content_type
|
167
|
-
@content_type ||
|
169
|
+
@content_type || "application/octet-stream"
|
168
170
|
end
|
169
171
|
end
|
170
172
|
|
171
|
-
|
173
|
+
private
|
172
174
|
|
173
175
|
# TODO: Add support for other encodings
|
174
|
-
def parse_binary(binary, entity)
|
175
|
-
case entity[
|
176
|
-
when
|
177
|
-
|
176
|
+
def parse_binary(binary, entity) # :nodoc:
|
177
|
+
case entity["encoding"]
|
178
|
+
when "base64"
|
179
|
+
base64_decode(binary)
|
178
180
|
else
|
179
181
|
binary
|
180
182
|
end
|
181
183
|
end
|
182
184
|
|
183
185
|
def parse_file(file, entity)
|
184
|
-
f = StringIO.new(
|
186
|
+
f = StringIO.new(base64_decode(file))
|
185
187
|
f.extend(FileLike)
|
186
|
-
f.original_filename = entity[
|
187
|
-
f.content_type = entity[
|
188
|
+
f.original_filename = entity["name"]
|
189
|
+
f.content_type = entity["content_type"]
|
188
190
|
f
|
189
191
|
end
|
190
192
|
|
193
|
+
def base64_decode(input)
|
194
|
+
input.unpack1("m")
|
195
|
+
end
|
196
|
+
|
191
197
|
def symbolize_keys(params)
|
192
198
|
case params
|
193
199
|
when Hash
|
@@ -204,8 +210,8 @@ module MultiXml # rubocop:disable ModuleLength
|
|
204
210
|
def undasherize_keys(params)
|
205
211
|
case params
|
206
212
|
when Hash
|
207
|
-
params.
|
208
|
-
hash[key.to_s.tr(
|
213
|
+
params.each_with_object({}) do |(key, value), hash|
|
214
|
+
hash[key.to_s.tr("-", "_")] = undasherize_keys(value)
|
209
215
|
hash
|
210
216
|
end
|
211
217
|
when Array
|
@@ -215,16 +221,16 @@ module MultiXml # rubocop:disable ModuleLength
|
|
215
221
|
end
|
216
222
|
end
|
217
223
|
|
218
|
-
def typecast_xml_value(value, disallowed_types = nil) # rubocop:disable AbcSize, CyclomaticComplexity, MethodLength, PerceivedComplexity
|
224
|
+
def typecast_xml_value(value, disallowed_types = nil) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
219
225
|
disallowed_types ||= DISALLOWED_XML_TYPES
|
220
226
|
|
221
227
|
case value
|
222
228
|
when Hash
|
223
|
-
if value.include?(
|
224
|
-
raise(DisallowedTypeError
|
229
|
+
if value.include?("type") && !value["type"].is_a?(Hash) && disallowed_types.include?(value["type"])
|
230
|
+
raise(DisallowedTypeError, value["type"])
|
225
231
|
end
|
226
232
|
|
227
|
-
if value[
|
233
|
+
if value["type"] == "array"
|
228
234
|
|
229
235
|
# this commented-out suggestion helps to avoid the multiple attribute
|
230
236
|
# problem, but it breaks when there is only one item in the array.
|
@@ -239,7 +245,7 @@ module MultiXml # rubocop:disable ModuleLength
|
|
239
245
|
|
240
246
|
# This approach ignores attribute entries that are not convertable
|
241
247
|
# to an Array which allows attributes to be ignored.
|
242
|
-
_, entries = value.detect { |k, v| k !=
|
248
|
+
_, entries = value.detect { |k, v| k != "type" && (v.is_a?(Array) || v.is_a?(Hash)) }
|
243
249
|
|
244
250
|
case entries
|
245
251
|
when NilClass
|
@@ -256,10 +262,10 @@ module MultiXml # rubocop:disable ModuleLength
|
|
256
262
|
|
257
263
|
elsif value.key?(CONTENT_ROOT)
|
258
264
|
content = value[CONTENT_ROOT]
|
259
|
-
block = PARSING[value[
|
265
|
+
block = PARSING[value["type"]]
|
260
266
|
if block
|
261
267
|
if block.arity == 1
|
262
|
-
value.delete(
|
268
|
+
value.delete("type") if PARSING[value["type"]]
|
263
269
|
if value.keys.size > 1
|
264
270
|
value[CONTENT_ROOT] = block.call(content)
|
265
271
|
value
|
@@ -270,31 +276,31 @@ module MultiXml # rubocop:disable ModuleLength
|
|
270
276
|
block.call(content, value)
|
271
277
|
end
|
272
278
|
else
|
273
|
-
value.keys.size > 1 ? value : content
|
279
|
+
(value.keys.size > 1) ? value : content
|
274
280
|
end
|
275
|
-
elsif value[
|
276
|
-
|
281
|
+
elsif value["type"] == "string" && value["nil"] != "true"
|
282
|
+
""
|
277
283
|
# blank or nil parsed values are represented by nil
|
278
|
-
elsif value.empty? || value[
|
284
|
+
elsif value.empty? || value["nil"] == "true"
|
279
285
|
nil
|
280
286
|
# If the type is the only element which makes it then
|
281
287
|
# this still makes the value nil, except if type is
|
282
288
|
# a XML node(where type['value'] is a Hash)
|
283
|
-
elsif value[
|
289
|
+
elsif value["type"] && value.size == 1 && !value["type"].is_a?(Hash)
|
284
290
|
nil
|
285
291
|
else
|
286
|
-
xml_value = value.
|
292
|
+
xml_value = value.each_with_object({}) do |(k, v), hash|
|
287
293
|
hash[k] = typecast_xml_value(v, disallowed_types)
|
288
294
|
hash
|
289
295
|
end
|
290
296
|
|
291
297
|
# Turn {:files => {:file => #<StringIO>} into {:files => #<StringIO>} so it is compatible with
|
292
298
|
# how multipart uploaded files from HTML appear
|
293
|
-
xml_value[
|
299
|
+
(xml_value["file"].is_a?(StringIO)) ? xml_value["file"] : xml_value
|
294
300
|
end
|
295
301
|
when Array
|
296
302
|
value.map! { |i| typecast_xml_value(i, disallowed_types) }
|
297
|
-
value.length > 1 ? value : value.first
|
303
|
+
(value.length > 1) ? value : value.first
|
298
304
|
when String
|
299
305
|
value
|
300
306
|
else
|
metadata
CHANGED
@@ -1,40 +1,45 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: multi_xml
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
- Erik
|
8
|
-
autorequire:
|
9
|
-
bindir:
|
7
|
+
- Erik Berlin
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-04-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: bigdecimal
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '1
|
20
|
-
type: :
|
19
|
+
version: '3.1'
|
20
|
+
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '1
|
27
|
-
description:
|
28
|
-
email:
|
26
|
+
version: '3.1'
|
27
|
+
description:
|
28
|
+
email:
|
29
|
|
29
30
|
executables: []
|
30
31
|
extensions: []
|
31
32
|
extra_rdoc_files: []
|
32
33
|
files:
|
34
|
+
- ".rspec"
|
35
|
+
- ".rubocop.yml"
|
33
36
|
- ".yardopts"
|
34
37
|
- CHANGELOG.md
|
35
38
|
- CONTRIBUTING.md
|
39
|
+
- Gemfile
|
36
40
|
- LICENSE.md
|
37
41
|
- README.md
|
42
|
+
- Rakefile
|
38
43
|
- lib/multi_xml.rb
|
39
44
|
- lib/multi_xml/parsers/libxml.rb
|
40
45
|
- lib/multi_xml/parsers/libxml2_parser.rb
|
@@ -43,12 +48,16 @@ files:
|
|
43
48
|
- lib/multi_xml/parsers/ox.rb
|
44
49
|
- lib/multi_xml/parsers/rexml.rb
|
45
50
|
- lib/multi_xml/version.rb
|
46
|
-
- multi_xml.gemspec
|
47
51
|
homepage: https://github.com/sferik/multi_xml
|
48
52
|
licenses:
|
49
53
|
- MIT
|
50
|
-
metadata:
|
51
|
-
|
54
|
+
metadata:
|
55
|
+
allowed_push_host: https://rubygems.org
|
56
|
+
homepage_uri: https://github.com/sferik/multi_xml
|
57
|
+
source_code_uri: https://github.com/sferik/multi_xml
|
58
|
+
changelog_uri: https://github.com/sferik/multi_xml/blob/master/CHANGELOG.md
|
59
|
+
rubygems_mfa_required: 'true'
|
60
|
+
post_install_message:
|
52
61
|
rdoc_options: []
|
53
62
|
require_paths:
|
54
63
|
- lib
|
@@ -56,16 +65,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
56
65
|
requirements:
|
57
66
|
- - ">="
|
58
67
|
- !ruby/object:Gem::Version
|
59
|
-
version:
|
68
|
+
version: 3.1.4
|
60
69
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
70
|
requirements:
|
62
71
|
- - ">="
|
63
72
|
- !ruby/object:Gem::Version
|
64
|
-
version:
|
73
|
+
version: '0'
|
65
74
|
requirements: []
|
66
|
-
|
67
|
-
|
68
|
-
signing_key:
|
75
|
+
rubygems_version: 3.5.9
|
76
|
+
signing_key:
|
69
77
|
specification_version: 4
|
70
|
-
summary:
|
78
|
+
summary: Provides swappable XML backends utilizing LibXML, Nokogiri, Ox, or REXML.
|
71
79
|
test_files: []
|
data/multi_xml.gemspec
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
3
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require 'multi_xml/version'
|
5
|
-
|
6
|
-
Gem::Specification.new do |spec|
|
7
|
-
spec.add_development_dependency 'bundler', '~> 1.0'
|
8
|
-
spec.author = 'Erik Michaels-Ober'
|
9
|
-
spec.description = 'Provides swappable XML backends utilizing LibXML, Nokogiri, Ox, or REXML.'
|
10
|
-
spec.email = '[email protected]'
|
11
|
-
spec.files = %w(.yardopts CHANGELOG.md CONTRIBUTING.md LICENSE.md README.md multi_xml.gemspec) + Dir['lib/**/*.rb']
|
12
|
-
spec.homepage = 'https://github.com/sferik/multi_xml'
|
13
|
-
spec.licenses = ['MIT']
|
14
|
-
spec.name = 'multi_xml'
|
15
|
-
spec.require_paths = ['lib']
|
16
|
-
spec.required_rubygems_version = '>= 1.3.5'
|
17
|
-
spec.summary = 'A generic swappable back-end for XML parsing'
|
18
|
-
spec.version = MultiXml::Version
|
19
|
-
end
|