multi_xml 0.8.0 → 0.9.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 +4 -4
- data/.mutant.yml +6 -1
- data/CHANGELOG.md +30 -0
- data/Gemfile +5 -3
- data/README.md +183 -39
- data/Rakefile +8 -1
- 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 +3 -3
- 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 +137 -134
- 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,116 @@ 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
|
|
127
|
+
result = parse_with_error_handling(io, xml, resolve_parse_parser(options), namespaces)
|
|
128
|
+
apply_postprocessing(result, options)
|
|
130
129
|
end
|
|
130
|
+
end
|
|
131
131
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
|
142
153
|
|
|
143
|
-
|
|
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
|
|
152
|
-
end
|
|
154
|
+
require_relative "multi_xml/deprecated"
|
|
153
155
|
|
|
154
|
-
|
|
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. The module forwards every
|
|
161
|
+
# method call to {MultiXML} via {.method_missing} and resolves constant
|
|
162
|
+
# access via {.const_missing}, so both dotted calls and :: constant
|
|
163
|
+
# lookups (including rescue clauses) route through the canonical module.
|
|
164
|
+
#
|
|
165
|
+
# @api public
|
|
166
|
+
# @deprecated Use {MultiXML} (all-caps) instead. Will be removed in v1.0.
|
|
167
|
+
module MultiXml
|
|
168
|
+
class << self
|
|
169
|
+
# Forward method calls to {MultiXML}, emitting a one-time warning
|
|
155
170
|
#
|
|
156
|
-
# @api
|
|
157
|
-
# @return [
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
171
|
+
# @api public
|
|
172
|
+
# @return [Object] the delegated call's return value
|
|
173
|
+
# @example
|
|
174
|
+
# MultiXml.parse("<a>1</a>") # delegates to MultiXML.parse
|
|
175
|
+
# rubocop:disable Naming/BlockForwarding, Style/ArgumentsForwarding
|
|
176
|
+
def method_missing(name, *args, **kwargs, &block)
|
|
177
|
+
::MultiXML.warn_deprecation_once(:multi_xml_constant,
|
|
178
|
+
"The MultiXml constant is deprecated and will be removed in v1.0. Use MultiXML instead.")
|
|
179
|
+
if ::MultiXML.respond_to?(name)
|
|
180
|
+
::MultiXML.public_send(name, *args, **kwargs, &block)
|
|
181
|
+
else
|
|
182
|
+
super
|
|
161
183
|
end
|
|
162
|
-
nil
|
|
163
184
|
end
|
|
185
|
+
# rubocop:enable Naming/BlockForwarding, Style/ArgumentsForwarding
|
|
164
186
|
|
|
165
|
-
#
|
|
187
|
+
# Respond to any method {MultiXML} responds to
|
|
166
188
|
#
|
|
167
|
-
# @api
|
|
168
|
-
# @param
|
|
169
|
-
# @
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
# Raise an error indicating no parser is available
|
|
178
|
-
#
|
|
179
|
-
# @api private
|
|
180
|
-
# @return [void]
|
|
181
|
-
# @raise [NoParserError] always
|
|
182
|
-
def raise_no_parser_error
|
|
183
|
-
raise NoParserError, <<~MSG.chomp
|
|
184
|
-
No XML parser detected. Install one of: ox, nokogiri, libxml-ruby, or oga.
|
|
185
|
-
See https://github.com/sferik/multi_xml for more information.
|
|
186
|
-
MSG
|
|
189
|
+
# @api public
|
|
190
|
+
# @param name [Symbol] method name
|
|
191
|
+
# @param include_private [Boolean] include private methods
|
|
192
|
+
# @return [Boolean] true if {MultiXML} responds to the method
|
|
193
|
+
# @example
|
|
194
|
+
# MultiXml.respond_to?(:parse) #=> true
|
|
195
|
+
def respond_to_missing?(name, include_private)
|
|
196
|
+
::MultiXML.respond_to?(name, include_private)
|
|
187
197
|
end
|
|
188
198
|
|
|
189
|
-
#
|
|
199
|
+
# Resolve missing constants to their {MultiXML} counterparts
|
|
190
200
|
#
|
|
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
|
|
201
|
+
# The lookup is performed with ``inherit: false`` so a stray
|
|
202
|
+
# top-level ``::ParseError`` constant in the host process (Racc
|
|
203
|
+
# defines one when Nokogiri is loaded) is correctly ignored. Enables
|
|
204
|
+
# rescue MultiXml::ParseError and MultiXml::Parsers::Ox to keep
|
|
205
|
+
# working during the deprecation cycle.
|
|
201
206
|
#
|
|
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))
|
|
207
|
+
# @api public
|
|
208
|
+
# @param name [Symbol] constant name
|
|
209
|
+
# @return [Object] the resolved constant from {MultiXML}
|
|
210
|
+
# @example
|
|
211
|
+
# MultiXml::Parsers::Ox # returns MultiXML::Parsers::Ox
|
|
212
|
+
def const_missing(name)
|
|
213
|
+
::MultiXML.warn_deprecation_once(:multi_xml_constant,
|
|
214
|
+
"The MultiXml constant is deprecated and will be removed in v1.0. Use MultiXML instead.")
|
|
215
|
+
::MultiXML.const_get(name, false)
|
|
213
216
|
end
|
|
214
217
|
end
|
|
215
218
|
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.0
|
|
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: []
|