multi_xml 0.6.0 → 0.9.1
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/.mutant.yml +21 -0
- data/.rspec +2 -0
- data/.rubocop.yml +65 -0
- data/CHANGELOG.md +62 -0
- data/Gemfile +26 -0
- data/LICENSE.md +1 -1
- data/README.md +181 -57
- data/Rakefile +68 -0
- data/Steepfile +29 -0
- data/benchmark/overall_parser_benchmark.rb +5 -0
- data/benchmark.rb +1002 -0
- data/lib/multi_xml/concurrency.rb +31 -0
- data/lib/multi_xml/constants.rb +179 -0
- data/lib/multi_xml/deprecated.rb +35 -0
- data/lib/multi_xml/errors.rb +147 -0
- data/lib/multi_xml/file_like.rb +62 -0
- data/lib/multi_xml/helpers.rb +228 -0
- data/lib/multi_xml/options.rb +63 -0
- data/lib/multi_xml/options_normalization.rb +40 -0
- data/lib/multi_xml/parse_support.rb +113 -0
- data/lib/multi_xml/parser.rb +47 -0
- data/lib/multi_xml/parser_resolution.rb +150 -0
- data/lib/multi_xml/parsers/dom_parser.rb +190 -0
- data/lib/multi_xml/parsers/libxml.rb +57 -17
- data/lib/multi_xml/parsers/libxml_sax.rb +188 -0
- data/lib/multi_xml/parsers/nokogiri.rb +60 -19
- data/lib/multi_xml/parsers/nokogiri_sax.rb +130 -0
- data/lib/multi_xml/parsers/oga.rb +116 -49
- data/lib/multi_xml/parsers/ox.rb +191 -66
- data/lib/multi_xml/parsers/rexml.rb +169 -75
- data/lib/multi_xml/parsers/sax_handler.rb +169 -0
- data/lib/multi_xml/version.rb +6 -44
- data/lib/multi_xml.rb +187 -284
- data/sig/multi_xml.rbs +304 -0
- metadata +55 -23
- data/lib/multi_xml/parsers/libxml2_parser.rb +0 -72
- data/multi_xml.gemspec +0 -19
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
require "cgi/escape"
|
|
2
|
+
|
|
3
|
+
module MultiXML
|
|
4
|
+
module Parsers
|
|
5
|
+
# Shared SAX handler logic for building hash trees from XML events.
|
|
6
|
+
#
|
|
7
|
+
# Provides a stack machine used by both NokogiriSax and LibxmlSax
|
|
8
|
+
# handlers. Parser-specific subclasses translate their native callbacks
|
|
9
|
+
# into calls on this entrypoint:
|
|
10
|
+
#
|
|
11
|
+
# - handle_start_element_ns(local, prefix, attr_tuples, ns_decls)
|
|
12
|
+
# where attr_tuples = [[attr_prefix_or_nil, local, value], ...]
|
|
13
|
+
# ns_decls = [[prefix_or_nil, uri], ...]
|
|
14
|
+
#
|
|
15
|
+
# @api private
|
|
16
|
+
module SaxHandler
|
|
17
|
+
# Initialize the handler state
|
|
18
|
+
#
|
|
19
|
+
# @api private
|
|
20
|
+
# @param mode [Symbol] Namespace handling mode
|
|
21
|
+
# @return [void]
|
|
22
|
+
def initialize_handler(mode)
|
|
23
|
+
@mode = mode
|
|
24
|
+
@result = {}
|
|
25
|
+
@stack = [@result]
|
|
26
|
+
@pending = []
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Get the parsed result
|
|
30
|
+
#
|
|
31
|
+
# @api private
|
|
32
|
+
# @return [Hash] the parsed hash
|
|
33
|
+
attr_reader :result
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
# Get the current element hash on top of the stack
|
|
38
|
+
#
|
|
39
|
+
# @api private
|
|
40
|
+
# @return [Hash] current hash being built
|
|
41
|
+
def current = @stack.last
|
|
42
|
+
|
|
43
|
+
# Entry point for namespace-aware start events
|
|
44
|
+
#
|
|
45
|
+
# @api private
|
|
46
|
+
# @param local [String] Local element name
|
|
47
|
+
# @param prefix [String, nil] Element namespace prefix
|
|
48
|
+
# @param attr_tuples [Array] Attributes as [prefix, local, value]
|
|
49
|
+
# @param ns_decls [Array] xmlns declarations as [prefix, uri] pairs
|
|
50
|
+
# @return [void]
|
|
51
|
+
def handle_start_element_ns(local, prefix, attr_tuples, ns_decls)
|
|
52
|
+
child = {TEXT_CONTENT_KEY => +""}
|
|
53
|
+
add_child_to_current(format_name(prefix, local), child)
|
|
54
|
+
@stack << child
|
|
55
|
+
|
|
56
|
+
@pending << build_pending_attrs(ns_decls, attr_tuples)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Apply attributes and pop the current element from the stack
|
|
60
|
+
#
|
|
61
|
+
# @api private
|
|
62
|
+
# @return [void]
|
|
63
|
+
def handle_end_element
|
|
64
|
+
@pending.pop.each { |key, value| add_attr_to_current(key, value) }
|
|
65
|
+
strip_whitespace_content
|
|
66
|
+
@stack.pop
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Append text to the current element's content
|
|
70
|
+
#
|
|
71
|
+
# @api private
|
|
72
|
+
# @param text [String] Text to append
|
|
73
|
+
# @return [void]
|
|
74
|
+
def append_text(text)
|
|
75
|
+
current[TEXT_CONTENT_KEY] << text
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Build the list of attributes to apply at element-end
|
|
79
|
+
#
|
|
80
|
+
# @api private
|
|
81
|
+
# @param ns_decls [Array] xmlns declarations
|
|
82
|
+
# @param attr_tuples [Array] Attribute [prefix, local, value] tuples
|
|
83
|
+
# @return [Array<Array>] list of [key, value] pairs
|
|
84
|
+
def build_pending_attrs(ns_decls, attr_tuples)
|
|
85
|
+
preserved_ns_decls(ns_decls) + attr_tuples.map do |prefix, local, value|
|
|
86
|
+
[format_name(prefix, local), CGI.unescapeHTML(value)]
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Transform xmlns declarations into attribute pairs for :preserve mode
|
|
91
|
+
#
|
|
92
|
+
# @api private
|
|
93
|
+
# @param ns_decls [Array] Declarations as [prefix, uri]
|
|
94
|
+
# @return [Array<Array>] [xmlns key, uri] pairs (empty outside :preserve)
|
|
95
|
+
def preserved_ns_decls(ns_decls)
|
|
96
|
+
return [] unless @mode == :preserve
|
|
97
|
+
|
|
98
|
+
ns_decls.map { |prefix, uri| [prefix ? "xmlns:#{prefix}" : "xmlns", uri] }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Produce a name string for a [prefix, local] tuple
|
|
102
|
+
#
|
|
103
|
+
# @api private
|
|
104
|
+
# @param prefix [String, nil] Namespace prefix
|
|
105
|
+
# @param local [String] Local part of the name
|
|
106
|
+
# @return [String] formatted name
|
|
107
|
+
def format_name(prefix, local)
|
|
108
|
+
(@mode == :preserve && prefix) ? "#{prefix}:#{local}" : local
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Add a child element to the current hash, folding on collision
|
|
112
|
+
#
|
|
113
|
+
# @api private
|
|
114
|
+
# @param name [String] Child element name
|
|
115
|
+
# @param child [Hash] Child hash to add
|
|
116
|
+
# @return [void]
|
|
117
|
+
def add_child_to_current(name, child)
|
|
118
|
+
existing = current[name]
|
|
119
|
+
current[name] = case existing
|
|
120
|
+
when Array then existing << child
|
|
121
|
+
when Hash then [existing, child]
|
|
122
|
+
else child
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Add an attribute value to the current hash (attr wins on collision)
|
|
127
|
+
#
|
|
128
|
+
# Attributes are applied at end_element, after children have already
|
|
129
|
+
# populated the hash. When an attribute collides with a child of the
|
|
130
|
+
# same local name, the attribute is placed first in the resulting
|
|
131
|
+
# array (matching DomParser / REXML behavior and existing tests).
|
|
132
|
+
#
|
|
133
|
+
# @api private
|
|
134
|
+
# @param key [String] Attribute key
|
|
135
|
+
# @param value [String] Attribute value
|
|
136
|
+
# @return [void]
|
|
137
|
+
def add_attr_to_current(key, value)
|
|
138
|
+
existing = current[key]
|
|
139
|
+
current[key] = case existing
|
|
140
|
+
when nil then value
|
|
141
|
+
when Array then insert_attribute_before_children(existing, value)
|
|
142
|
+
when Hash then [value, existing]
|
|
143
|
+
else [existing, value]
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Insert a later attribute before any child-element entries
|
|
148
|
+
#
|
|
149
|
+
# @api private
|
|
150
|
+
# @param values [Array] Existing colliding values
|
|
151
|
+
# @param value [String] Attribute value to insert
|
|
152
|
+
# @return [Array] Updated value list
|
|
153
|
+
def insert_attribute_before_children(values, value)
|
|
154
|
+
child_index = values.index { |entry| entry.is_a?(Hash) } || values.length
|
|
155
|
+
values.dup.insert(child_index, value)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Remove empty or whitespace-only text content from the current hash
|
|
159
|
+
#
|
|
160
|
+
# @api private
|
|
161
|
+
# @return [void]
|
|
162
|
+
def strip_whitespace_content
|
|
163
|
+
content = current[TEXT_CONTENT_KEY]
|
|
164
|
+
should_remove = content.empty? || (current.size > 1 && content.strip.empty?)
|
|
165
|
+
current.delete(TEXT_CONTENT_KEY) if should_remove
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
data/lib/multi_xml/version.rb
CHANGED
|
@@ -1,45 +1,7 @@
|
|
|
1
|
-
module
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
1
|
+
module MultiXML
|
|
2
|
+
# The current version of MultiXML
|
|
3
|
+
#
|
|
4
|
+
# @api public
|
|
5
|
+
# @return [Gem::Version] the gem version
|
|
6
|
+
VERSION = Gem::Version.create("0.9.1")
|
|
45
7
|
end
|