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.
data/sig/multi_xml.rbs ADDED
@@ -0,0 +1,304 @@
1
+ # Type signatures for MultiXML
2
+
3
+ # Recursive type alias for parsed XML values
4
+ # XML parsing produces nested structures of hashes, arrays, and primitive values
5
+ type MultiXML::xmlValue = String
6
+ | Integer
7
+ | Float
8
+ | bool
9
+ | Symbol
10
+ | Time
11
+ | Date
12
+ | BigDecimal
13
+ | StringIO
14
+ | nil
15
+ | Array[MultiXML::xmlValue]
16
+ | Hash[String, MultiXML::xmlValue]
17
+ | Hash[Symbol, MultiXML::xmlValue]
18
+
19
+ # Type for hash with string keys used internally during parsing
20
+ type MultiXML::xmlHash = Hash[String, MultiXML::xmlValue]
21
+
22
+ # Interface for parser modules
23
+ interface MultiXML::_Parser
24
+ def parse: ((IO | StringIO) io) -> MultiXML::xmlHash?
25
+ | ((IO | StringIO) io, ?namespaces: Symbol) -> MultiXML::xmlHash?
26
+ def parse_error: () -> singleton(Exception)
27
+ end
28
+
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
+
34
+ VERSION: Gem::Version
35
+
36
+ TEXT_CONTENT_KEY: String
37
+
38
+ RUBY_TYPE_TO_XML: Hash[String, String]
39
+
40
+ DISALLOWED_TYPES: Array[String]
41
+
42
+ FALSE_BOOLEAN_VALUES: Set[String]
43
+
44
+ NAMESPACE_MODES: Array[Symbol]
45
+
46
+ DEFAULT_OPTIONS: Hash[Symbol, bool | Array[String] | Symbol]
47
+
48
+ # Array of [library_name, parser_symbol] pairs
49
+ PARSER_PREFERENCE: Array[Array[String | Symbol]]
50
+
51
+ PARSE_DATETIME: ^(String) -> Time
52
+
53
+ ISO_WEEK_DATE: Regexp
54
+
55
+ # Lambda for creating file-like StringIO from base64 content
56
+ # Uses untyped for content because unpack1 returns various types
57
+ # Uses untyped for entity because hash values are xmlValue but we access specific String keys
58
+ FILE_CONVERTER: ^(untyped, untyped) -> StringIO
59
+
60
+ # Type converters keyed by XML type attribute string
61
+ # Uses untyped key because hash["type"] returns xmlValue, and Hash#[] with non-String returns nil
62
+ TYPE_CONVERTERS: Hash[untyped, Proc | Method]
63
+
64
+ DEPRECATION_WARNINGS_SHOWN: Set[Symbol]
65
+
66
+ self.@parser: Module
67
+
68
+ extend Helpers
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
+
98
+ # Public API: Get the current XML parser module
99
+ def self.parser: () -> Module
100
+
101
+ # Public API: Set the XML parser to use
102
+ def self.parser=: (Symbol | String | Module new_parser) -> Module
103
+
104
+ # Public API: Parse XML into a Ruby Hash
105
+ # Uses untyped for options because values vary by key (:parser, :symbolize_keys, :disallowed_types, :typecast_xml_value)
106
+ def self.parse: (String | StringIO xml, ?Hash[Symbol, untyped] options) -> xmlHash
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
113
+ private
114
+
115
+ # Resolve a parser specification (Symbol, String, or Module) to a parser
116
+ def self.resolve_parser: (Symbol | String | Module spec) -> Module
117
+
118
+ # Validate that a parser satisfies the contract
119
+ def self.validate_parser!: (Module parser) -> Module
120
+
121
+ # Load a parser module by name
122
+ def self.load_parser: (Symbol | String name) -> Module
123
+
124
+ # Convert snake_case to CamelCase
125
+ def self.camelize: (String name) -> String
126
+
127
+ # Detect the best available parser
128
+ def self.detect_parser: () -> (Symbol | String)
129
+
130
+ # Find an already-loaded parser library
131
+ def self.find_loaded_parser: () -> Symbol?
132
+
133
+ # Try to find an available parser by requiring libraries
134
+ def self.find_available_parser: () -> (String | Symbol | nil)
135
+
136
+ # Attempt to require a library, returning success/failure
137
+ # Kernel#require accepts String; library may be Symbol from PARSER_PREFERENCE (coerced at runtime)
138
+ def self.try_require: (untyped library) -> bool
139
+
140
+ # Raise NoParserError - never returns
141
+ def self.raise_no_parser_error: () -> bot
142
+
143
+ # Convert String to StringIO, pass through IO-like objects
144
+ # Uses respond_to?(:read) duck typing - returns input unchanged if IO-like
145
+ def self.normalize_input: (String | StringIO xml) -> untyped
146
+
147
+ # Parse with error handling and key normalization
148
+ # xml_parser implements _Parser interface; original_input uses respond_to? duck typing
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
173
+
174
+ module Helpers
175
+ # Recursively convert all hash keys to symbols
176
+ # Uses case/when type dispatch - Steep can't track flow narrowing
177
+ def self?.symbolize_keys: (untyped data) -> untyped
178
+
179
+ # Recursively convert dashes in hash keys to underscores
180
+ # Uses case/when type dispatch - Steep can't track flow narrowing
181
+ def self?.undasherize_keys: (untyped data) -> untyped
182
+
183
+ # Recursively typecast XML values based on type attributes
184
+ # Uses case/when type dispatch - Steep can't track flow narrowing
185
+ def self?.typecast_xml_value: (untyped value, ?Array[String] disallowed_types) -> xmlValue
186
+
187
+ # Typecast array elements and unwrap single-element arrays
188
+ def self?.typecast_array: (Array[xmlValue] array, Array[String] disallowed_types) -> xmlValue
189
+
190
+ # Typecast a hash based on its type attribute
191
+ def self?.typecast_hash: (xmlHash hash, Array[String] disallowed_types) -> xmlValue
192
+
193
+ # Check if a type is in the disallowed list
194
+ # Uses is_a?(Hash) guard then include? - Steep can't narrow xmlValue to String
195
+ def self?.disallowed_type?: (untyped type, Array[String] disallowed_types) -> boolish
196
+
197
+ # Convert a hash based on its type and content
198
+ def self?.convert_hash: (xmlHash hash, xmlValue type, Array[String] disallowed_types) -> xmlValue
199
+
200
+ # Typecast all child values in a hash
201
+ def self?.typecast_children: (xmlHash hash, Array[String] disallowed_types) -> (xmlHash | StringIO)
202
+
203
+ # Extract array entries from element with type="array"
204
+ def self?.extract_array_entries: (xmlHash hash, Array[String] disallowed_types) -> Array[xmlValue]
205
+
206
+ # Find array or hash entries in a hash, excluding the type key
207
+ # Returns xmlValue subset (Array or Hash) - uses is_a? that Steep can't narrow
208
+ def self?.find_array_entries: (xmlHash hash) -> untyped
209
+
210
+ # Wrap hash in array if needed and typecast all entries
211
+ def self?.wrap_and_typecast: (Array[xmlValue] | xmlHash entries, Array[String] disallowed_types) -> Array[xmlValue]
212
+
213
+ # Convert text content using type converters
214
+ # hash["type"] is xmlValue, used as Hash key - Steep requires String
215
+ def self?.convert_text_content: (xmlHash hash) -> xmlValue
216
+
217
+ # Unwrap value if hash has no other significant keys
218
+ def self?.unwrap_if_simple: (xmlHash hash, xmlValue value) -> (xmlValue | xmlHash)
219
+
220
+ # Check if a hash represents an empty value
221
+ def self?.empty_value?: (xmlHash hash, xmlValue type) -> bool
222
+
223
+ private
224
+
225
+ # Recursively transform hash keys using a block
226
+ # Block receives key (String) and returns transformed key
227
+ # Uses untyped because &:to_sym Proc type narrowing not supported by Steep
228
+ def self?.transform_keys: (untyped data) { (untyped) -> untyped } -> untyped
229
+
230
+ # Unwrap a file object from the result hash if present
231
+ def self?.unwrap_file_if_present: (xmlHash result) -> (xmlHash | StringIO)
232
+
233
+ # Apply a type converter to content
234
+ # Content is xmlValue (from hash.fetch) but typically String in practice
235
+ def self?.apply_converter: (xmlHash hash, untyped content, Proc | Method converter) -> xmlValue
236
+ end
237
+
238
+ module FileLike
239
+ DEFAULT_FILENAME: String
240
+
241
+ DEFAULT_CONTENT_TYPE: String
242
+
243
+ @original_filename: String?
244
+
245
+ @content_type: String?
246
+
247
+ attr_writer original_filename: String?
248
+
249
+ attr_writer content_type: String?
250
+
251
+ def original_filename: () -> String
252
+
253
+ def content_type: () -> String
254
+ end
255
+
256
+ # Represents a StringIO that has been extended with FileLike
257
+ # Used for file type conversions in XML parsing
258
+ class FileIO < StringIO
259
+ include FileLike
260
+ end
261
+
262
+ class ParseError < StandardError
263
+ @xml: String?
264
+
265
+ @cause: Exception?
266
+
267
+ attr_reader xml: String?
268
+
269
+ attr_reader cause: Exception?
270
+
271
+ # Message can be String (normal) or Exception (from parser errors), or nil for default
272
+ def initialize: (?(String | Exception | nil) message, ?xml: String?, ?cause: Exception?) -> void
273
+ end
274
+
275
+ class NoParserError < StandardError
276
+ end
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
+
284
+ class DisallowedTypeError < StandardError
285
+ @type: String
286
+
287
+ attr_reader type: String
288
+
289
+ def initialize: (String type) -> void
290
+ end
291
+
292
+ # Parsers module - parser implementations depend on optional external gems
293
+ module Parsers
294
+ end
295
+ end
296
+
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
304
+ end
metadata CHANGED
@@ -1,54 +1,88 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: multi_xml
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.9.1
5
5
  platform: ruby
6
6
  authors:
7
- - Erik Michaels-Ober
8
- autorequire:
9
- bindir: bin
7
+ - Erik Berlin
8
+ bindir: exe
10
9
  cert_chain: []
11
- date: 2016-12-06 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
- name: bundler
13
+ name: bigdecimal
15
14
  requirement: !ruby/object:Gem::Requirement
16
15
  requirements:
17
- - - "~>"
16
+ - - ">="
18
17
  - !ruby/object:Gem::Version
19
- version: '1.0'
20
- type: :development
18
+ version: '3.1'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '5'
22
+ type: :runtime
21
23
  prerelease: false
22
24
  version_requirements: !ruby/object:Gem::Requirement
23
25
  requirements:
24
- - - "~>"
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '3.1'
29
+ - - "<"
25
30
  - !ruby/object:Gem::Version
26
- version: '1.0'
27
- description: Provides swappable XML backends utilizing LibXML, Nokogiri, Ox, or REXML.
28
- email: sferik@gmail.com
31
+ version: '5'
32
+ email:
33
+ - sferik@gmail.com
29
34
  executables: []
30
35
  extensions: []
31
36
  extra_rdoc_files: []
32
37
  files:
38
+ - ".mutant.yml"
39
+ - ".rspec"
40
+ - ".rubocop.yml"
33
41
  - ".yardopts"
34
42
  - CHANGELOG.md
35
43
  - CONTRIBUTING.md
44
+ - Gemfile
36
45
  - LICENSE.md
37
46
  - README.md
47
+ - Rakefile
48
+ - Steepfile
49
+ - benchmark.rb
50
+ - benchmark/overall_parser_benchmark.rb
38
51
  - lib/multi_xml.rb
52
+ - lib/multi_xml/concurrency.rb
53
+ - lib/multi_xml/constants.rb
54
+ - lib/multi_xml/deprecated.rb
55
+ - lib/multi_xml/errors.rb
56
+ - lib/multi_xml/file_like.rb
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
63
+ - lib/multi_xml/parsers/dom_parser.rb
39
64
  - lib/multi_xml/parsers/libxml.rb
40
- - lib/multi_xml/parsers/libxml2_parser.rb
65
+ - lib/multi_xml/parsers/libxml_sax.rb
41
66
  - lib/multi_xml/parsers/nokogiri.rb
67
+ - lib/multi_xml/parsers/nokogiri_sax.rb
42
68
  - lib/multi_xml/parsers/oga.rb
43
69
  - lib/multi_xml/parsers/ox.rb
44
70
  - lib/multi_xml/parsers/rexml.rb
71
+ - lib/multi_xml/parsers/sax_handler.rb
45
72
  - lib/multi_xml/version.rb
46
- - multi_xml.gemspec
73
+ - sig/multi_xml.rbs
47
74
  homepage: https://github.com/sferik/multi_xml
48
75
  licenses:
49
76
  - MIT
50
- metadata: {}
51
- post_install_message:
77
+ metadata:
78
+ allowed_push_host: https://rubygems.org
79
+ bug_tracker_uri: https://github.com/sferik/multi_xml/issues
80
+ changelog_uri: https://github.com/sferik/multi_xml/blob/master/CHANGELOG.md
81
+ documentation_uri: https://rubydoc.info/gems/multi_xml/
82
+ funding_uri: https://github.com/sponsors/sferik
83
+ homepage_uri: https://github.com/sferik/multi_xml
84
+ rubygems_mfa_required: 'true'
85
+ source_code_uri: https://github.com/sferik/multi_xml
52
86
  rdoc_options: []
53
87
  require_paths:
54
88
  - lib
@@ -56,16 +90,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
56
90
  requirements:
57
91
  - - ">="
58
92
  - !ruby/object:Gem::Version
59
- version: '0'
93
+ version: '3.2'
60
94
  required_rubygems_version: !ruby/object:Gem::Requirement
61
95
  requirements:
62
96
  - - ">="
63
97
  - !ruby/object:Gem::Version
64
- version: 1.3.5
98
+ version: '0'
65
99
  requirements: []
66
- rubyforge_project:
67
- rubygems_version: 2.5.2
68
- signing_key:
100
+ rubygems_version: 4.0.11
69
101
  specification_version: 4
70
- summary: A generic swappable back-end for XML parsing
102
+ summary: Provides swappable XML backends utilizing LibXML, Nokogiri, Ox, or REXML.
71
103
  test_files: []
@@ -1,72 +0,0 @@
1
- module MultiXml
2
- module Parsers
3
- module Libxml2Parser #:nodoc:
4
- # Convert XML document to hash
5
- #
6
- # node::
7
- # The XML node object to convert to a hash.
8
- #
9
- # hash::
10
- # Hash to merge the converted element into.
11
- def node_to_hash(node, hash = {}) # rubocop:disable AbcSize, CyclomaticComplexity, MethodLength, PerceivedComplexity
12
- node_hash = {MultiXml::CONTENT_ROOT => ''}
13
-
14
- name = node_name(node)
15
-
16
- # Insert node hash into parent hash correctly.
17
- case hash[name]
18
- when Array
19
- hash[name] << node_hash
20
- when Hash
21
- hash[name] = [hash[name], node_hash]
22
- when NilClass
23
- hash[name] = node_hash
24
- end
25
-
26
- # Handle child elements
27
- each_child(node) do |c|
28
- if c.element?
29
- node_to_hash(c, node_hash)
30
- elsif c.text? || c.cdata?
31
- node_hash[MultiXml::CONTENT_ROOT] << c.content
32
- end
33
- end
34
-
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
39
-
40
- # Handle attributes
41
- each_attr(node) do |a|
42
- key = node_name(a)
43
- v = node_hash[key]
44
- node_hash[key] = (v ? [a.value, v] : a.value)
45
- end
46
-
47
- hash
48
- end
49
-
50
- # Parse an XML Document IO into a simple hash.
51
- # xml::
52
- # XML Document IO to parse
53
- def parse(_)
54
- raise(NotImplementedError.new("inheritor should define #{__method__}"))
55
- end
56
-
57
- private
58
-
59
- def each_child(*)
60
- raise(NotImplementedError.new("inheritor should define #{__method__}"))
61
- end
62
-
63
- def each_attr(*)
64
- raise(NotImplementedError.new("inheritor should define #{__method__}"))
65
- end
66
-
67
- def node_name(*)
68
- raise(NotImplementedError.new("inheritor should define #{__method__}"))
69
- end
70
- end
71
- end
72
- end
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 = 'sferik@gmail.com'
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