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
data/lib/multi_xml.rb
CHANGED
|
@@ -1,305 +1,208 @@
|
|
|
1
|
-
require
|
|
2
|
-
require
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
1
|
+
require "bigdecimal"
|
|
2
|
+
require "date"
|
|
3
|
+
require "stringio"
|
|
4
|
+
require "time"
|
|
5
|
+
require "yaml"
|
|
6
|
+
require_relative "multi_xml/concurrency"
|
|
7
|
+
require_relative "multi_xml/constants"
|
|
8
|
+
require_relative "multi_xml/errors"
|
|
9
|
+
require_relative "multi_xml/file_like"
|
|
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"
|
|
16
|
+
|
|
17
|
+
# A generic swappable back-end for parsing XML
|
|
18
|
+
#
|
|
19
|
+
# MultiXML provides a unified interface for XML parsing across different
|
|
20
|
+
# parser libraries. It automatically selects the best available parser
|
|
21
|
+
# (Ox, LibXML, Nokogiri, Oga, or REXML) and converts XML to Ruby hashes.
|
|
22
|
+
#
|
|
23
|
+
# @api public
|
|
24
|
+
# @example Parse XML
|
|
25
|
+
# MultiXML.parse('<root><name>John</name></root>')
|
|
26
|
+
# #=> {"root"=>{"name"=>"John"}}
|
|
27
|
+
#
|
|
28
|
+
# @example Set the parser
|
|
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)
|
|
14
56
|
end
|
|
15
57
|
end
|
|
16
58
|
|
|
17
|
-
unless defined?(REQUIREMENT_MAP)
|
|
18
|
-
REQUIREMENT_MAP = [
|
|
19
|
-
['ox', :ox],
|
|
20
|
-
['libxml', :libxml],
|
|
21
|
-
['nokogiri', :nokogiri],
|
|
22
|
-
['rexml/document', :rexml],
|
|
23
|
-
['oga', :oga],
|
|
24
|
-
].freeze
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
CONTENT_ROOT = '__content__'.freeze unless defined?(CONTENT_ROOT)
|
|
28
|
-
|
|
29
|
-
unless defined?(PARSING)
|
|
30
|
-
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
|
-
|
|
33
|
-
PARSING = {
|
|
34
|
-
'symbol' => proc { |symbol| symbol.to_sym },
|
|
35
|
-
'date' => proc { |date| Date.parse(date) },
|
|
36
|
-
'datetime' => datetime_proc,
|
|
37
|
-
'dateTime' => datetime_proc,
|
|
38
|
-
'integer' => proc { |integer| integer.to_i },
|
|
39
|
-
'float' => float_proc,
|
|
40
|
-
'double' => float_proc,
|
|
41
|
-
'decimal' => proc { |number| BigDecimal(number) },
|
|
42
|
-
'boolean' => proc { |boolean| !%w(0 false).include?(boolean.strip) },
|
|
43
|
-
'string' => proc { |string| string.to_s },
|
|
44
|
-
'yaml' => proc { |yaml| YAML.load(yaml) rescue yaml }, # rubocop:disable RescueModifier
|
|
45
|
-
'base64Binary' => proc { |binary| ::Base64.decode64(binary) },
|
|
46
|
-
'binary' => proc { |binary, entity| parse_binary(binary, entity) },
|
|
47
|
-
'file' => proc { |file, entity| parse_file(file, entity) },
|
|
48
|
-
}.freeze
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
unless defined?(TYPE_NAMES)
|
|
52
|
-
TYPE_NAMES = {
|
|
53
|
-
'Symbol' => 'symbol',
|
|
54
|
-
'Integer' => 'integer',
|
|
55
|
-
'BigDecimal' => 'decimal',
|
|
56
|
-
'Float' => 'float',
|
|
57
|
-
'TrueClass' => 'boolean',
|
|
58
|
-
'FalseClass' => 'boolean',
|
|
59
|
-
'Date' => 'date',
|
|
60
|
-
'DateTime' => 'datetime',
|
|
61
|
-
'Time' => 'datetime',
|
|
62
|
-
'Array' => 'array',
|
|
63
|
-
'Hash' => 'hash',
|
|
64
|
-
}.freeze
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
DISALLOWED_XML_TYPES = %w(symbol yaml).freeze
|
|
68
|
-
|
|
69
|
-
DEFAULT_OPTIONS = {
|
|
70
|
-
:typecast_xml_value => true,
|
|
71
|
-
:disallowed_types => DISALLOWED_XML_TYPES,
|
|
72
|
-
:symbolize_keys => false,
|
|
73
|
-
}.freeze
|
|
74
|
-
|
|
75
59
|
class << self
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
self.parser = default_parser
|
|
80
|
-
@parser
|
|
81
|
-
end
|
|
60
|
+
include Helpers
|
|
61
|
+
include ParserResolution
|
|
62
|
+
include ParseSupport
|
|
82
63
|
|
|
83
|
-
#
|
|
84
|
-
# have loaded and installed. First checks to see
|
|
85
|
-
# if any parsers are already loaded, then checks
|
|
86
|
-
# to see which are installed if none are loaded.
|
|
87
|
-
def default_parser
|
|
88
|
-
return :ox if defined?(::Ox)
|
|
89
|
-
return :libxml if defined?(::LibXML)
|
|
90
|
-
return :nokogiri if defined?(::Nokogiri)
|
|
91
|
-
return :oga if defined?(::Oga)
|
|
92
|
-
|
|
93
|
-
REQUIREMENT_MAP.each do |library, parser|
|
|
94
|
-
begin
|
|
95
|
-
require library
|
|
96
|
-
return parser
|
|
97
|
-
rescue LoadError
|
|
98
|
-
next
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
raise(NoParserError.new("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
|
-
end
|
|
103
|
-
|
|
104
|
-
# Set the XML parser utilizing a symbol, string, or class.
|
|
105
|
-
# Supported by default are:
|
|
106
|
-
#
|
|
107
|
-
# * <tt>:libxml</tt>
|
|
108
|
-
# * <tt>:nokogiri</tt>
|
|
109
|
-
# * <tt>:ox</tt>
|
|
110
|
-
# * <tt>:rexml</tt>
|
|
111
|
-
# * <tt>:oga</tt>
|
|
112
|
-
def parser=(new_parser)
|
|
113
|
-
case new_parser
|
|
114
|
-
when String, Symbol
|
|
115
|
-
require "multi_xml/parsers/#{new_parser.to_s.downcase}"
|
|
116
|
-
@parser = MultiXml::Parsers.const_get(new_parser.to_s.split('_').collect(&:capitalize).join('').to_s)
|
|
117
|
-
when Class, Module
|
|
118
|
-
@parser = new_parser
|
|
119
|
-
else
|
|
120
|
-
raise('Did not recognize your parser specification. Please specify either a symbol or a class.')
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
# Parse an XML string or IO into Ruby.
|
|
125
|
-
#
|
|
126
|
-
# <b>Options</b>
|
|
64
|
+
# Get the current XML parser module
|
|
127
65
|
#
|
|
128
|
-
#
|
|
66
|
+
# Returns the currently configured parser, auto-detecting one if not set.
|
|
67
|
+
# Parsers are checked in order of performance: Ox, LibXML, Nokogiri, Oga, REXML.
|
|
129
68
|
#
|
|
130
|
-
#
|
|
69
|
+
# @api public
|
|
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.
|
|
131
74
|
#
|
|
132
|
-
#
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
xml = xml.strip if xml.respond_to?(:strip)
|
|
139
|
-
begin
|
|
140
|
-
xml = StringIO.new(xml) unless xml.respond_to?(:read)
|
|
141
|
-
|
|
142
|
-
char = xml.getc
|
|
143
|
-
return {} if char.nil?
|
|
144
|
-
xml.ungetc(char)
|
|
145
|
-
|
|
146
|
-
hash = undasherize_keys(parser.parse(xml) || {})
|
|
147
|
-
hash = options[:typecast_xml_value] ? typecast_xml_value(hash, options[:disallowed_types]) : hash
|
|
148
|
-
rescue DisallowedTypeError
|
|
149
|
-
raise
|
|
150
|
-
rescue parser.parse_error => error
|
|
151
|
-
raise(ParseError, error.message, error.backtrace) # rubocop:disable RaiseArgs
|
|
152
|
-
end
|
|
153
|
-
hash = symbolize_keys(hash) if options[:symbolize_keys]
|
|
154
|
-
hash
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
# This module decorates files with the <tt>original_filename</tt>
|
|
158
|
-
# and <tt>content_type</tt> methods.
|
|
159
|
-
module FileLike #:nodoc:
|
|
160
|
-
attr_writer :original_filename, :content_type
|
|
161
|
-
|
|
162
|
-
def original_filename
|
|
163
|
-
@original_filename || 'untitled'
|
|
164
|
-
end
|
|
75
|
+
# @example Get current parser
|
|
76
|
+
# MultiXML.parser #=> MultiXML::Parsers::Ox
|
|
77
|
+
def parser
|
|
78
|
+
override = Fiber[:multi_xml_parser]
|
|
79
|
+
return override if override
|
|
165
80
|
|
|
166
|
-
|
|
167
|
-
@content_type || 'application/octet-stream'
|
|
168
|
-
end
|
|
81
|
+
@parser ||= resolve_parser(detect_parser)
|
|
169
82
|
end
|
|
170
83
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
#
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
84
|
+
# Set the XML parser to use
|
|
85
|
+
#
|
|
86
|
+
# @api public
|
|
87
|
+
# @param new_parser [Symbol, String, Module] Parser specification
|
|
88
|
+
# - Symbol/String: :libxml, :nokogiri, :ox, :rexml, :oga
|
|
89
|
+
# - Module: Custom parser implementing parse(io) or
|
|
90
|
+
# parse(io, namespaces: ...) and parse_error
|
|
91
|
+
# @return [Module] the newly configured parser module
|
|
92
|
+
# @example Set parser by symbol
|
|
93
|
+
# MultiXML.parser = :nokogiri
|
|
94
|
+
# @example Set parser by module
|
|
95
|
+
# MultiXML.parser = MyCustomParser
|
|
96
|
+
def parser=(new_parser)
|
|
97
|
+
@parser = resolve_parser(new_parser)
|
|
181
98
|
end
|
|
182
99
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
100
|
+
# Parse XML into a Ruby Hash
|
|
101
|
+
#
|
|
102
|
+
# @api public
|
|
103
|
+
# @param xml [String, IO] XML content as a string or IO-like object
|
|
104
|
+
# @param options [Hash] Parsing options
|
|
105
|
+
# @option options [Symbol, String, Module] :parser Parser to use for this call
|
|
106
|
+
# @option options [Boolean] :symbolize_names Convert keys to symbols (default: false)
|
|
107
|
+
# @option options [Array<String>] :disallowed_types Types to reject (default: ['yaml', 'symbol'])
|
|
108
|
+
# @option options [Boolean] :typecast_xml_value Apply type conversions (default: true)
|
|
109
|
+
# @option options [Symbol] :namespaces Namespace handling mode (:strip or :preserve)
|
|
110
|
+
# @return [Hash] Parsed XML as nested hash
|
|
111
|
+
# @raise [ParseError] if XML is malformed
|
|
112
|
+
# @raise [DisallowedTypeError] if XML contains a disallowed type attribute
|
|
113
|
+
# @example Parse simple XML
|
|
114
|
+
# MultiXML.parse('<root><name>John</name></root>')
|
|
115
|
+
# #=> {"root"=>{"name"=>"John"}}
|
|
116
|
+
# @example Parse with symbolized names
|
|
117
|
+
# MultiXML.parse('<root><name>John</name></root>', symbolize_names: true)
|
|
118
|
+
# #=> {root: {name: "John"}}
|
|
119
|
+
def parse(xml, options = {})
|
|
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))
|
|
124
|
+
io = normalize_input(xml)
|
|
125
|
+
return {} if io.eof?
|
|
126
|
+
|
|
127
|
+
result = parse_with_error_handling(io, xml, resolve_parse_parser(options), namespaces)
|
|
128
|
+
apply_postprocessing(result, options)
|
|
189
129
|
end
|
|
130
|
+
end
|
|
190
131
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
|
203
153
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
154
|
+
require_relative "multi_xml/deprecated"
|
|
155
|
+
|
|
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)
|
|
216
185
|
end
|
|
186
|
+
end
|
|
217
187
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
# This attempt fails to consider the order that the detect method
|
|
237
|
-
# retrieves the entries.
|
|
238
|
-
# _, entries = value.detect {|key, _| key != 'type'}
|
|
239
|
-
|
|
240
|
-
# This approach ignores attribute entries that are not convertable
|
|
241
|
-
# to an Array which allows attributes to be ignored.
|
|
242
|
-
_, entries = value.detect { |k, v| k != 'type' && (v.is_a?(Array) || v.is_a?(Hash)) }
|
|
243
|
-
|
|
244
|
-
case entries
|
|
245
|
-
when NilClass
|
|
246
|
-
[]
|
|
247
|
-
when String
|
|
248
|
-
[] if entries.strip.empty?
|
|
249
|
-
when Array
|
|
250
|
-
entries.collect { |entry| typecast_xml_value(entry, disallowed_types) }
|
|
251
|
-
when Hash
|
|
252
|
-
[typecast_xml_value(entries, disallowed_types)]
|
|
253
|
-
else
|
|
254
|
-
raise("can't typecast #{entries.class.name}: #{entries.inspect}")
|
|
255
|
-
end
|
|
256
|
-
|
|
257
|
-
elsif value.key?(CONTENT_ROOT)
|
|
258
|
-
content = value[CONTENT_ROOT]
|
|
259
|
-
block = PARSING[value['type']]
|
|
260
|
-
if block
|
|
261
|
-
if block.arity == 1
|
|
262
|
-
value.delete('type') if PARSING[value['type']]
|
|
263
|
-
if value.keys.size > 1
|
|
264
|
-
value[CONTENT_ROOT] = block.call(content)
|
|
265
|
-
value
|
|
266
|
-
else
|
|
267
|
-
block.call(content)
|
|
268
|
-
end
|
|
269
|
-
else
|
|
270
|
-
block.call(content, value)
|
|
271
|
-
end
|
|
272
|
-
else
|
|
273
|
-
value.keys.size > 1 ? value : content
|
|
274
|
-
end
|
|
275
|
-
elsif value['type'] == 'string' && value['nil'] != 'true'
|
|
276
|
-
''
|
|
277
|
-
# blank or nil parsed values are represented by nil
|
|
278
|
-
elsif value.empty? || value['nil'] == 'true'
|
|
279
|
-
nil
|
|
280
|
-
# If the type is the only element which makes it then
|
|
281
|
-
# this still makes the value nil, except if type is
|
|
282
|
-
# a XML node(where type['value'] is a Hash)
|
|
283
|
-
elsif value['type'] && value.size == 1 && !value['type'].is_a?(Hash)
|
|
284
|
-
nil
|
|
285
|
-
else
|
|
286
|
-
xml_value = value.inject({}) do |hash, (k, v)|
|
|
287
|
-
hash[k] = typecast_xml_value(v, disallowed_types)
|
|
288
|
-
hash
|
|
289
|
-
end
|
|
290
|
-
|
|
291
|
-
# Turn {:files => {:file => #<StringIO>} into {:files => #<StringIO>} so it is compatible with
|
|
292
|
-
# how multipart uploaded files from HTML appear
|
|
293
|
-
xml_value['file'].is_a?(StringIO) ? xml_value['file'] : xml_value
|
|
294
|
-
end
|
|
295
|
-
when Array
|
|
296
|
-
value.map! { |i| typecast_xml_value(i, disallowed_types) }
|
|
297
|
-
value.length > 1 ? value : value.first
|
|
298
|
-
when String
|
|
299
|
-
value
|
|
300
|
-
else
|
|
301
|
-
raise("can't typecast #{value.class.name}: #{value.inspect}")
|
|
302
|
-
end
|
|
188
|
+
class << self
|
|
189
|
+
# Resolve missing constants to their {MultiXML} counterparts
|
|
190
|
+
#
|
|
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.
|
|
196
|
+
#
|
|
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)
|
|
303
206
|
end
|
|
304
207
|
end
|
|
305
208
|
end
|