multi_xml 0.8.1 → 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 +4 -4
- data/.mutant.yml +6 -1
- data/CHANGELOG.md +30 -0
- data/Gemfile +2 -1
- data/README.md +183 -39
- data/Rakefile +7 -0
- data/Steepfile +8 -1
- 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 +65 -20
- data/lib/multi_xml/deprecated.rb +35 -0
- data/lib/multi_xml/errors.rb +62 -8
- data/lib/multi_xml/file_like.rb +2 -2
- data/lib/multi_xml/helpers.rb +2 -2
- 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 +107 -14
- data/lib/multi_xml/parsers/libxml.rb +36 -13
- data/lib/multi_xml/parsers/libxml_sax.rb +104 -19
- data/lib/multi_xml/parsers/nokogiri.rb +36 -13
- data/lib/multi_xml/parsers/nokogiri_sax.rb +47 -19
- data/lib/multi_xml/parsers/oga.rb +87 -15
- data/lib/multi_xml/parsers/ox.rb +120 -37
- data/lib/multi_xml/parsers/rexml.rb +104 -16
- data/lib/multi_xml/parsers/sax_handler.rb +84 -32
- data/lib/multi_xml/version.rb +3 -3
- data/lib/multi_xml.rb +132 -139
- data/sig/multi_xml.rbs +93 -16
- metadata +11 -2
data/lib/multi_xml.rb
CHANGED
|
@@ -3,27 +3,63 @@ require "date"
|
|
|
3
3
|
require "stringio"
|
|
4
4
|
require "time"
|
|
5
5
|
require "yaml"
|
|
6
|
+
require_relative "multi_xml/concurrency"
|
|
6
7
|
require_relative "multi_xml/constants"
|
|
7
8
|
require_relative "multi_xml/errors"
|
|
8
9
|
require_relative "multi_xml/file_like"
|
|
9
10
|
require_relative "multi_xml/helpers"
|
|
11
|
+
require_relative "multi_xml/options"
|
|
12
|
+
require_relative "multi_xml/options_normalization"
|
|
13
|
+
require_relative "multi_xml/parser"
|
|
14
|
+
require_relative "multi_xml/parser_resolution"
|
|
15
|
+
require_relative "multi_xml/parse_support"
|
|
10
16
|
|
|
11
17
|
# A generic swappable back-end for parsing XML
|
|
12
18
|
#
|
|
13
|
-
#
|
|
19
|
+
# MultiXML provides a unified interface for XML parsing across different
|
|
14
20
|
# parser libraries. It automatically selects the best available parser
|
|
15
21
|
# (Ox, LibXML, Nokogiri, Oga, or REXML) and converts XML to Ruby hashes.
|
|
16
22
|
#
|
|
17
23
|
# @api public
|
|
18
24
|
# @example Parse XML
|
|
19
|
-
#
|
|
25
|
+
# MultiXML.parse('<root><name>John</name></root>')
|
|
20
26
|
# #=> {"root"=>{"name"=>"John"}}
|
|
21
27
|
#
|
|
22
28
|
# @example Set the parser
|
|
23
|
-
#
|
|
24
|
-
module
|
|
29
|
+
# MultiXML.parser = :nokogiri
|
|
30
|
+
module MultiXML
|
|
31
|
+
extend Options
|
|
32
|
+
|
|
33
|
+
# Tracks which deprecation warnings have already been emitted so each
|
|
34
|
+
# one fires at most once per process. Stored as a Set rather than a
|
|
35
|
+
# Hash so presence checks have unambiguous semantics for mutation tests.
|
|
36
|
+
DEPRECATION_WARNINGS_SHOWN = Set.new
|
|
37
|
+
private_constant :DEPRECATION_WARNINGS_SHOWN
|
|
38
|
+
|
|
39
|
+
# Emit a deprecation warning at most once per process for the given key
|
|
40
|
+
#
|
|
41
|
+
# The warning is tagged with the :deprecated category so callers can
|
|
42
|
+
# silence the whole set with Warning[:deprecated] = false or surface
|
|
43
|
+
# it via ruby -W:deprecated — the standard Ruby idiom for library
|
|
44
|
+
# deprecations since 2.7.
|
|
45
|
+
#
|
|
46
|
+
# @api private
|
|
47
|
+
# @param key [Symbol] identifier for the deprecation (typically the method name)
|
|
48
|
+
# @param message [String] warning message to emit on first call
|
|
49
|
+
# @return [void]
|
|
50
|
+
def self.warn_deprecation_once(key, message)
|
|
51
|
+
Concurrency.synchronize(:deprecation_warnings) do
|
|
52
|
+
return if DEPRECATION_WARNINGS_SHOWN.include?(key)
|
|
53
|
+
|
|
54
|
+
Kernel.warn(message, category: :deprecated)
|
|
55
|
+
DEPRECATION_WARNINGS_SHOWN.add(key)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
25
59
|
class << self
|
|
26
60
|
include Helpers
|
|
61
|
+
include ParserResolution
|
|
62
|
+
include ParseSupport
|
|
27
63
|
|
|
28
64
|
# Get the current XML parser module
|
|
29
65
|
#
|
|
@@ -32,9 +68,16 @@ module MultiXml
|
|
|
32
68
|
#
|
|
33
69
|
# @api public
|
|
34
70
|
# @return [Module] the current parser module
|
|
71
|
+
# Honors a fiber-local override set by {.with_parser} so concurrent
|
|
72
|
+
# blocks observe their own parser without clobbering the process-wide
|
|
73
|
+
# default. Falls back to the process default when no override is set.
|
|
74
|
+
#
|
|
35
75
|
# @example Get current parser
|
|
36
|
-
#
|
|
76
|
+
# MultiXML.parser #=> MultiXML::Parsers::Ox
|
|
37
77
|
def parser
|
|
78
|
+
override = Fiber[:multi_xml_parser]
|
|
79
|
+
return override if override
|
|
80
|
+
|
|
38
81
|
@parser ||= resolve_parser(detect_parser)
|
|
39
82
|
end
|
|
40
83
|
|
|
@@ -43,12 +86,13 @@ module MultiXml
|
|
|
43
86
|
# @api public
|
|
44
87
|
# @param new_parser [Symbol, String, Module] Parser specification
|
|
45
88
|
# - Symbol/String: :libxml, :nokogiri, :ox, :rexml, :oga
|
|
46
|
-
# - Module: Custom parser implementing parse(io)
|
|
89
|
+
# - Module: Custom parser implementing parse(io) or
|
|
90
|
+
# parse(io, namespaces: ...) and parse_error
|
|
47
91
|
# @return [Module] the newly configured parser module
|
|
48
92
|
# @example Set parser by symbol
|
|
49
|
-
#
|
|
93
|
+
# MultiXML.parser = :nokogiri
|
|
50
94
|
# @example Set parser by module
|
|
51
|
-
#
|
|
95
|
+
# MultiXML.parser = MyCustomParser
|
|
52
96
|
def parser=(new_parser)
|
|
53
97
|
@parser = resolve_parser(new_parser)
|
|
54
98
|
end
|
|
@@ -59,157 +103,106 @@ module MultiXml
|
|
|
59
103
|
# @param xml [String, IO] XML content as a string or IO-like object
|
|
60
104
|
# @param options [Hash] Parsing options
|
|
61
105
|
# @option options [Symbol, String, Module] :parser Parser to use for this call
|
|
62
|
-
# @option options [Boolean] :
|
|
106
|
+
# @option options [Boolean] :symbolize_names Convert keys to symbols (default: false)
|
|
63
107
|
# @option options [Array<String>] :disallowed_types Types to reject (default: ['yaml', 'symbol'])
|
|
64
108
|
# @option options [Boolean] :typecast_xml_value Apply type conversions (default: true)
|
|
109
|
+
# @option options [Symbol] :namespaces Namespace handling mode (:strip or :preserve)
|
|
65
110
|
# @return [Hash] Parsed XML as nested hash
|
|
66
111
|
# @raise [ParseError] if XML is malformed
|
|
67
112
|
# @raise [DisallowedTypeError] if XML contains a disallowed type attribute
|
|
68
113
|
# @example Parse simple XML
|
|
69
|
-
#
|
|
114
|
+
# MultiXML.parse('<root><name>John</name></root>')
|
|
70
115
|
# #=> {"root"=>{"name"=>"John"}}
|
|
71
|
-
# @example Parse with symbolized
|
|
72
|
-
#
|
|
116
|
+
# @example Parse with symbolized names
|
|
117
|
+
# MultiXML.parse('<root><name>John</name></root>', symbolize_names: true)
|
|
73
118
|
# #=> {root: {name: "John"}}
|
|
74
119
|
def parse(xml, options = {})
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
120
|
+
call_site = OptionsNormalization.normalize_symbolize_option(options)
|
|
121
|
+
global = OptionsNormalization.normalize_symbolize_option(parse_options(call_site))
|
|
122
|
+
options = DEFAULT_OPTIONS.merge(global, call_site)
|
|
123
|
+
namespaces = validate_namespaces_mode(options.fetch(:namespaces))
|
|
78
124
|
io = normalize_input(xml)
|
|
79
125
|
return {} if io.eof?
|
|
80
126
|
|
|
81
|
-
result = parse_with_error_handling(io, xml,
|
|
82
|
-
|
|
83
|
-
result = symbolize_keys(result) if options.fetch(:symbolize_keys)
|
|
84
|
-
result
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
private
|
|
88
|
-
|
|
89
|
-
# Resolve a parser specification to a module
|
|
90
|
-
#
|
|
91
|
-
# @api private
|
|
92
|
-
# @param spec [Symbol, String, Class, Module] Parser specification
|
|
93
|
-
# @return [Module] Resolved parser module
|
|
94
|
-
# @raise [RuntimeError] if spec is invalid
|
|
95
|
-
def resolve_parser(spec)
|
|
96
|
-
case spec
|
|
97
|
-
when String, Symbol then load_parser(spec)
|
|
98
|
-
when Module then spec
|
|
99
|
-
else raise "Invalid parser specification: expected Symbol, String, or Module"
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
# Load a parser by name
|
|
104
|
-
#
|
|
105
|
-
# @api private
|
|
106
|
-
# @param name [Symbol, String] Parser name
|
|
107
|
-
# @return [Module] Loaded parser module
|
|
108
|
-
def load_parser(name)
|
|
109
|
-
name = name.to_s.downcase
|
|
110
|
-
require "multi_xml/parsers/#{name}"
|
|
111
|
-
Parsers.const_get(camelize(name))
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
# Convert underscored string to CamelCase
|
|
115
|
-
#
|
|
116
|
-
# @api private
|
|
117
|
-
# @param name [String] Underscored string
|
|
118
|
-
# @return [String] CamelCased string
|
|
119
|
-
def camelize(name)
|
|
120
|
-
name.split("_").map(&:capitalize).join
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
# Detect the best available parser
|
|
124
|
-
#
|
|
125
|
-
# @api private
|
|
126
|
-
# @return [Symbol] Parser name
|
|
127
|
-
# @raise [NoParserError] if no parser is available
|
|
128
|
-
def detect_parser
|
|
129
|
-
find_loaded_parser || find_available_parser || raise_no_parser_error
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
# Parser constant names mapped to their symbols, in preference order
|
|
133
|
-
#
|
|
134
|
-
# @api private
|
|
135
|
-
LOADED_PARSER_CHECKS = {
|
|
136
|
-
Ox: :ox,
|
|
137
|
-
LibXML: :libxml,
|
|
138
|
-
Nokogiri: :nokogiri,
|
|
139
|
-
Oga: :oga
|
|
140
|
-
}.freeze
|
|
141
|
-
private_constant :LOADED_PARSER_CHECKS
|
|
142
|
-
|
|
143
|
-
# Find an already-loaded parser library
|
|
144
|
-
#
|
|
145
|
-
# @api private
|
|
146
|
-
# @return [Symbol, nil] Parser name or nil if none loaded
|
|
147
|
-
def find_loaded_parser
|
|
148
|
-
LOADED_PARSER_CHECKS.each do |const_name, parser_name|
|
|
149
|
-
return parser_name if const_defined?(const_name)
|
|
150
|
-
end
|
|
151
|
-
nil
|
|
127
|
+
result = parse_with_error_handling(io, xml, resolve_parse_parser(options), namespaces)
|
|
128
|
+
apply_postprocessing(result, options)
|
|
152
129
|
end
|
|
130
|
+
end
|
|
153
131
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
132
|
+
# Execute a block with a temporarily-swapped parser
|
|
133
|
+
#
|
|
134
|
+
# The override is stored in fiber-local storage so concurrent fibers
|
|
135
|
+
# and threads each see their own parser without racing on a shared
|
|
136
|
+
# module variable; nested calls save and restore the previous
|
|
137
|
+
# fiber-local value. Matches {MultiJSON.with_adapter}.
|
|
138
|
+
#
|
|
139
|
+
# @api public
|
|
140
|
+
# @param new_parser [Symbol, String, Module] parser to use
|
|
141
|
+
# @yield block to execute with the temporary parser
|
|
142
|
+
# @return [Object] result of the block
|
|
143
|
+
# @example
|
|
144
|
+
# MultiXML.with_parser(:rexml) { MultiXML.parse("<a>1</a>") }
|
|
145
|
+
def self.with_parser(new_parser)
|
|
146
|
+
previous_override = Fiber[:multi_xml_parser]
|
|
147
|
+
Fiber[:multi_xml_parser] = resolve_parser(new_parser)
|
|
148
|
+
yield
|
|
149
|
+
ensure
|
|
150
|
+
Fiber[:multi_xml_parser] = previous_override
|
|
151
|
+
end
|
|
152
|
+
end
|
|
164
153
|
|
|
165
|
-
|
|
166
|
-
#
|
|
167
|
-
# @api private
|
|
168
|
-
# @param library [String] Library to require
|
|
169
|
-
# @return [Boolean] true if successful, false if LoadError
|
|
170
|
-
def try_require(library)
|
|
171
|
-
require library
|
|
172
|
-
true
|
|
173
|
-
rescue LoadError
|
|
174
|
-
false
|
|
175
|
-
end
|
|
154
|
+
require_relative "multi_xml/deprecated"
|
|
176
155
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
156
|
+
# Backward-compatible alias for the legacy MultiXml constant name
|
|
157
|
+
#
|
|
158
|
+
# Downstream code that still writes MultiXml.parse(...) or
|
|
159
|
+
# rescue MultiXml::ParseError continues to work, but emits a one-time
|
|
160
|
+
# deprecation warning pointing at MultiXML. Each public method on
|
|
161
|
+
# {MultiXML} gets an explicit forwarder defined on this module, and
|
|
162
|
+
# constant access resolves via {.const_missing}, so both dotted calls
|
|
163
|
+
# and :: constant lookups (including rescue clauses) route through
|
|
164
|
+
# the canonical module.
|
|
165
|
+
#
|
|
166
|
+
# @api public
|
|
167
|
+
# @deprecated Use {MultiXML} (all-caps) instead. Will be removed in v1.0.
|
|
168
|
+
module MultiXml
|
|
169
|
+
# Forward every public method MultiXML exposes through an explicit
|
|
170
|
+
# singleton method on the legacy MultiXml module, so callers that
|
|
171
|
+
# capture the method as a Method object (``MultiXml.method(:load)``)
|
|
172
|
+
# find this forwarder instead of falling back to inherited methods like
|
|
173
|
+
# ``Kernel#load``. The earlier ``method_missing``-based shim left
|
|
174
|
+
# ``MultiXml.method(:load)`` resolving to ``Kernel#load`` (because
|
|
175
|
+
# ``Module#method`` doesn't consult ``method_missing``) so a captured
|
|
176
|
+
# ``MultiXml.method(:load)`` would interpret the XML payload as a file
|
|
177
|
+
# path and crash with ``LoadError``. Forwarding eagerly fixes the
|
|
178
|
+
# capture path while preserving the one-time deprecation warning each
|
|
179
|
+
# call emits.
|
|
180
|
+
(::MultiXML.public_methods - ::Module.public_methods).each do |forwarded|
|
|
181
|
+
define_singleton_method(forwarded) do |*args, **kwargs, &block|
|
|
182
|
+
::MultiXML.warn_deprecation_once(:multi_xml_constant,
|
|
183
|
+
"The MultiXml constant is deprecated and will be removed in v1.0. Use MultiXML instead.")
|
|
184
|
+
::MultiXML.public_send(forwarded, *args, **kwargs, &block)
|
|
187
185
|
end
|
|
186
|
+
end
|
|
188
187
|
|
|
189
|
-
|
|
188
|
+
class << self
|
|
189
|
+
# Resolve missing constants to their {MultiXML} counterparts
|
|
190
190
|
#
|
|
191
|
-
#
|
|
192
|
-
#
|
|
193
|
-
#
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
StringIO.new(xml.to_s.strip)
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
# Parse XML with error handling and key normalization
|
|
191
|
+
# The lookup is performed with ``inherit: false`` so a stray
|
|
192
|
+
# top-level ``::ParseError`` constant in the host process (Racc
|
|
193
|
+
# defines one when Nokogiri is loaded) is correctly ignored. Enables
|
|
194
|
+
# rescue MultiXml::ParseError and MultiXml::Parsers::Ox to keep
|
|
195
|
+
# working during the deprecation cycle.
|
|
201
196
|
#
|
|
202
|
-
# @api
|
|
203
|
-
# @param
|
|
204
|
-
# @
|
|
205
|
-
# @
|
|
206
|
-
#
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
xml_string = original_input.respond_to?(:read) ? original_input.tap(&:rewind).read : original_input.to_s
|
|
212
|
-
raise(ParseError.new(e, xml: xml_string, cause: e))
|
|
197
|
+
# @api public
|
|
198
|
+
# @param name [Symbol] constant name
|
|
199
|
+
# @return [Object] the resolved constant from {MultiXML}
|
|
200
|
+
# @example
|
|
201
|
+
# MultiXml::Parsers::Ox # returns MultiXML::Parsers::Ox
|
|
202
|
+
def const_missing(name)
|
|
203
|
+
::MultiXML.warn_deprecation_once(:multi_xml_constant,
|
|
204
|
+
"The MultiXml constant is deprecated and will be removed in v1.0. Use MultiXML instead.")
|
|
205
|
+
::MultiXML.const_get(name, false)
|
|
213
206
|
end
|
|
214
207
|
end
|
|
215
208
|
end
|
data/sig/multi_xml.rbs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# Type signatures for
|
|
1
|
+
# Type signatures for MultiXML
|
|
2
2
|
|
|
3
3
|
# Recursive type alias for parsed XML values
|
|
4
4
|
# XML parsing produces nested structures of hashes, arrays, and primitive values
|
|
5
|
-
type
|
|
5
|
+
type MultiXML::xmlValue = String
|
|
6
6
|
| Integer
|
|
7
7
|
| Float
|
|
8
8
|
| bool
|
|
@@ -12,20 +12,25 @@ type MultiXml::xmlValue = String
|
|
|
12
12
|
| BigDecimal
|
|
13
13
|
| StringIO
|
|
14
14
|
| nil
|
|
15
|
-
| Array[
|
|
16
|
-
| Hash[String,
|
|
17
|
-
| Hash[Symbol,
|
|
15
|
+
| Array[MultiXML::xmlValue]
|
|
16
|
+
| Hash[String, MultiXML::xmlValue]
|
|
17
|
+
| Hash[Symbol, MultiXML::xmlValue]
|
|
18
18
|
|
|
19
19
|
# Type for hash with string keys used internally during parsing
|
|
20
|
-
type
|
|
20
|
+
type MultiXML::xmlHash = Hash[String, MultiXML::xmlValue]
|
|
21
21
|
|
|
22
22
|
# Interface for parser modules
|
|
23
|
-
interface
|
|
24
|
-
def parse: (StringIO io) ->
|
|
23
|
+
interface MultiXML::_Parser
|
|
24
|
+
def parse: ((IO | StringIO) io) -> MultiXML::xmlHash?
|
|
25
|
+
| ((IO | StringIO) io, ?namespaces: Symbol) -> MultiXML::xmlHash?
|
|
25
26
|
def parse_error: () -> singleton(Exception)
|
|
26
27
|
end
|
|
27
28
|
|
|
28
|
-
module
|
|
29
|
+
module MultiXML
|
|
30
|
+
extend Options
|
|
31
|
+
|
|
32
|
+
type parse_options_input = Hash[Symbol, untyped] | ^() -> Hash[Symbol, untyped] | ^(Hash[Symbol, untyped]) -> Hash[Symbol, untyped]
|
|
33
|
+
|
|
29
34
|
VERSION: Gem::Version
|
|
30
35
|
|
|
31
36
|
TEXT_CONTENT_KEY: String
|
|
@@ -36,13 +41,17 @@ module MultiXml
|
|
|
36
41
|
|
|
37
42
|
FALSE_BOOLEAN_VALUES: Set[String]
|
|
38
43
|
|
|
39
|
-
|
|
44
|
+
NAMESPACE_MODES: Array[Symbol]
|
|
45
|
+
|
|
46
|
+
DEFAULT_OPTIONS: Hash[Symbol, bool | Array[String] | Symbol]
|
|
40
47
|
|
|
41
48
|
# Array of [library_name, parser_symbol] pairs
|
|
42
49
|
PARSER_PREFERENCE: Array[Array[String | Symbol]]
|
|
43
50
|
|
|
44
51
|
PARSE_DATETIME: ^(String) -> Time
|
|
45
52
|
|
|
53
|
+
ISO_WEEK_DATE: Regexp
|
|
54
|
+
|
|
46
55
|
# Lambda for creating file-like StringIO from base64 content
|
|
47
56
|
# Uses untyped for content because unpack1 returns various types
|
|
48
57
|
# Uses untyped for entity because hash values are xmlValue but we access specific String keys
|
|
@@ -52,12 +61,40 @@ module MultiXml
|
|
|
52
61
|
# Uses untyped key because hash["type"] returns xmlValue, and Hash#[] with non-String returns nil
|
|
53
62
|
TYPE_CONVERTERS: Hash[untyped, Proc | Method]
|
|
54
63
|
|
|
55
|
-
|
|
64
|
+
DEPRECATION_WARNINGS_SHOWN: Set[Symbol]
|
|
56
65
|
|
|
57
66
|
self.@parser: Module
|
|
58
67
|
|
|
59
68
|
extend Helpers
|
|
60
69
|
|
|
70
|
+
module Concurrency
|
|
71
|
+
MUTEXES: Hash[Symbol, Thread::Mutex]
|
|
72
|
+
|
|
73
|
+
def self.synchronize: [T] (Symbol name) { () -> T } -> T
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
module Options
|
|
77
|
+
EMPTY_OPTIONS: Hash[Symbol, untyped]
|
|
78
|
+
|
|
79
|
+
def parse_options=: (parse_options_input options) -> parse_options_input
|
|
80
|
+
|
|
81
|
+
def parse_options: (*untyped args) -> Hash[Symbol, untyped]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
module Parser
|
|
85
|
+
def parse_error: () -> Class
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
module ParserResolution
|
|
89
|
+
LOADED_PARSER_CHECKS: Hash[Symbol, Symbol]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
module ParseSupport
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Emit a deprecation warning at most once per process
|
|
96
|
+
def self.warn_deprecation_once: (Symbol key, String message) -> void
|
|
97
|
+
|
|
61
98
|
# Public API: Get the current XML parser module
|
|
62
99
|
def self.parser: () -> Module
|
|
63
100
|
|
|
@@ -68,11 +105,19 @@ module MultiXml
|
|
|
68
105
|
# Uses untyped for options because values vary by key (:parser, :symbolize_keys, :disallowed_types, :typecast_xml_value)
|
|
69
106
|
def self.parse: (String | StringIO xml, ?Hash[Symbol, untyped] options) -> xmlHash
|
|
70
107
|
|
|
108
|
+
def self.parse_iso_week_datetime: (String string) -> Time
|
|
109
|
+
# Public API: Execute a block with a temporarily-swapped parser
|
|
110
|
+
def self.with_parser: [T] (Symbol | String | Module new_parser) { () -> T } -> T
|
|
111
|
+
# Deprecated alias for `parse`
|
|
112
|
+
def self.load: (String | StringIO xml, ?Hash[Symbol, untyped] options) -> xmlHash
|
|
71
113
|
private
|
|
72
114
|
|
|
73
115
|
# Resolve a parser specification (Symbol, String, or Module) to a parser
|
|
74
116
|
def self.resolve_parser: (Symbol | String | Module spec) -> Module
|
|
75
117
|
|
|
118
|
+
# Validate that a parser satisfies the contract
|
|
119
|
+
def self.validate_parser!: (Module parser) -> Module
|
|
120
|
+
|
|
76
121
|
# Load a parser module by name
|
|
77
122
|
def self.load_parser: (Symbol | String name) -> Module
|
|
78
123
|
|
|
@@ -101,7 +146,30 @@ module MultiXml
|
|
|
101
146
|
|
|
102
147
|
# Parse with error handling and key normalization
|
|
103
148
|
# xml_parser implements _Parser interface; original_input uses respond_to? duck typing
|
|
104
|
-
def self.parse_with_error_handling: (StringIO io, untyped original_input, untyped xml_parser) -> xmlHash
|
|
149
|
+
def self.parse_with_error_handling: ((IO | StringIO) io, untyped original_input, untyped xml_parser, Symbol namespaces) -> xmlHash
|
|
150
|
+
|
|
151
|
+
# Dispatch to parsers that may or may not accept `namespaces:`
|
|
152
|
+
def self.parse_with_namespaces_compatibility: ((IO | StringIO) io, untyped xml_parser, Symbol namespaces) -> xmlHash?
|
|
153
|
+
|
|
154
|
+
# Validate the :namespaces option value
|
|
155
|
+
def self.validate_namespaces_mode: (untyped mode) -> Symbol
|
|
156
|
+
|
|
157
|
+
# Resolve which parser this parse call should use, honoring the :parser option
|
|
158
|
+
def self.resolve_parse_parser: (Hash[Symbol, untyped] options) -> Module
|
|
159
|
+
|
|
160
|
+
# Check whether a parser accepts the `namespaces:` keyword
|
|
161
|
+
def self.parser_supports_namespaces_keyword?: (untyped xml_parser) -> bool
|
|
162
|
+
|
|
163
|
+
# Apply typecasting and key-symbolization as configured
|
|
164
|
+
def self.apply_postprocessing: (xmlHash result, Hash[Symbol, untyped] options) -> xmlHash
|
|
165
|
+
|
|
166
|
+
module OptionsNormalization
|
|
167
|
+
# Translate the deprecated :symbolize_keys option to :symbolize_names
|
|
168
|
+
def self.normalize_symbolize_option: (Hash[Symbol, untyped] options) -> Hash[Symbol, untyped]
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Define a deprecated alias that delegates to a new method name
|
|
172
|
+
def self.deprecate_alias: (Symbol name, Symbol replacement) -> Symbol
|
|
105
173
|
|
|
106
174
|
module Helpers
|
|
107
175
|
# Recursively convert all hash keys to symbols
|
|
@@ -207,6 +275,12 @@ module MultiXml
|
|
|
207
275
|
class NoParserError < StandardError
|
|
208
276
|
end
|
|
209
277
|
|
|
278
|
+
class ParserLoadError < ArgumentError
|
|
279
|
+
def initialize: (?(String | nil) message, ?cause: Exception?) -> void
|
|
280
|
+
|
|
281
|
+
def self.build: (Exception original_exception) -> ParserLoadError
|
|
282
|
+
end
|
|
283
|
+
|
|
210
284
|
class DisallowedTypeError < StandardError
|
|
211
285
|
@type: String
|
|
212
286
|
|
|
@@ -220,8 +294,11 @@ module MultiXml
|
|
|
220
294
|
end
|
|
221
295
|
end
|
|
222
296
|
|
|
223
|
-
#
|
|
224
|
-
module
|
|
225
|
-
|
|
226
|
-
|
|
297
|
+
# Backward-compatible alias for the legacy MultiXml constant name
|
|
298
|
+
module MultiXml
|
|
299
|
+
def self.method_missing: (Symbol name, *untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
|
|
300
|
+
|
|
301
|
+
def self.respond_to_missing?: ((Symbol | String) name, bool include_private) -> bool
|
|
302
|
+
|
|
303
|
+
def self.const_missing: (Symbol name) -> untyped
|
|
227
304
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: multi_xml
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.9.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Erik Berlin
|
|
@@ -46,11 +46,20 @@ files:
|
|
|
46
46
|
- README.md
|
|
47
47
|
- Rakefile
|
|
48
48
|
- Steepfile
|
|
49
|
+
- benchmark.rb
|
|
50
|
+
- benchmark/overall_parser_benchmark.rb
|
|
49
51
|
- lib/multi_xml.rb
|
|
52
|
+
- lib/multi_xml/concurrency.rb
|
|
50
53
|
- lib/multi_xml/constants.rb
|
|
54
|
+
- lib/multi_xml/deprecated.rb
|
|
51
55
|
- lib/multi_xml/errors.rb
|
|
52
56
|
- lib/multi_xml/file_like.rb
|
|
53
57
|
- lib/multi_xml/helpers.rb
|
|
58
|
+
- lib/multi_xml/options.rb
|
|
59
|
+
- lib/multi_xml/options_normalization.rb
|
|
60
|
+
- lib/multi_xml/parse_support.rb
|
|
61
|
+
- lib/multi_xml/parser.rb
|
|
62
|
+
- lib/multi_xml/parser_resolution.rb
|
|
54
63
|
- lib/multi_xml/parsers/dom_parser.rb
|
|
55
64
|
- lib/multi_xml/parsers/libxml.rb
|
|
56
65
|
- lib/multi_xml/parsers/libxml_sax.rb
|
|
@@ -88,7 +97,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
88
97
|
- !ruby/object:Gem::Version
|
|
89
98
|
version: '0'
|
|
90
99
|
requirements: []
|
|
91
|
-
rubygems_version: 4.0.
|
|
100
|
+
rubygems_version: 4.0.11
|
|
92
101
|
specification_version: 4
|
|
93
102
|
summary: Provides swappable XML backends utilizing LibXML, Nokogiri, Ox, or REXML.
|
|
94
103
|
test_files: []
|