xsd 2.9.1 → 2.9.3
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/.rubocop.yml +1 -1
- data/CHANGELOG.md +29 -0
- data/Gemfile +4 -4
- data/README.md +10 -0
- data/lib/xsd/base_object.rb +47 -6
- data/lib/xsd/objects/attribute.rb +2 -0
- data/lib/xsd/objects/complex_type.rb +1 -0
- data/lib/xsd/objects/element.rb +2 -0
- data/lib/xsd/objects/import.rb +13 -4
- data/lib/xsd/objects/restriction.rb +2 -2
- data/lib/xsd/objects/schema.rb +4 -1
- data/lib/xsd/objects/simple_type.rb +1 -0
- data/lib/xsd/objects/union.rb +1 -1
- data/lib/xsd/shared/cached_data_type.rb +13 -0
- data/lib/xsd/version.rb +1 -1
- data/lib/xsd/xml.rb +10 -2
- data/lib/xsd.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 178b66562e4da9833513bd00a12c528231551bac936eb7b72c611c9d3fc0a2bb
|
|
4
|
+
data.tar.gz: e58a38469f83f232583eb23f6fbefda81a86c56edf7cbfce06b305d81d3ce622
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7c34d9b9ee21284c3bc3d3b20a6f29de7ff94ac4dae1a85aaf2998dad74a35c043b612bac4691880ddefcddda42bf0734faef1fa64a63a984c301d020f3103ea
|
|
7
|
+
data.tar.gz: 9d846b4eaeff11247851889a133bcb62de24dad598764778be74a81d97d50b251f0257d95801c6aae8e09be588e7676c6aae2ad24e0806f8e8895ccb055f0c2b
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,32 @@
|
|
|
1
|
+
## [2.9.3] - 2026-10-24
|
|
2
|
+
|
|
3
|
+
### Fixed
|
|
4
|
+
|
|
5
|
+
- A schema with no target namespace that imports another namespace (an XMLDSig `Signature` referenced as
|
|
6
|
+
`ds:SignatureType`) now finds what it imports. `Schema#targets_namespace?` took a namespace URI for a prefix, looked
|
|
7
|
+
it up among the schema's prefixes, found nothing, and matched that nothing against the absent target namespace:
|
|
8
|
+
every reference by a prefixed name was looked for in the schema itself.
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Every import of the absent namespace is read from its own location. Schemas with no target namespace are each a
|
|
13
|
+
schema of their own (a provider's documents, every one shipping its own copies of the common types, which may
|
|
14
|
+
differ), so a second import of the absent namespace was not the same schema as the first one; before, it was
|
|
15
|
+
answered with whichever such schema had been loaded first. An import whose location is loaded already is answered
|
|
16
|
+
with that schema in any namespace (`XML#loaded_schema`); an import of a named namespace is still answered with a
|
|
17
|
+
schema known for it before its location is read.
|
|
18
|
+
|
|
19
|
+
## [2.9.2] - 2026-10-21
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- A property, link or child of a schema object (`name`, `type`, `max_occurs`, `complex_type`, …) is read through a
|
|
24
|
+
method of its own, defined when the class declares it, instead of `method_missing`, which costs far more per call.
|
|
25
|
+
The lookup order is the same: a method a class or an ancestor defines itself still answers first, and
|
|
26
|
+
`respond_to?` answers as before. Reading XML against a large schema runs about 40% faster under YJIT.
|
|
27
|
+
- `data_type` of an element, attribute, simple and complex type is worked out once and kept: the schema does not
|
|
28
|
+
change once read, and converting a document asked for it once per value.
|
|
29
|
+
|
|
1
30
|
## [2.9.1] - 2026-10-20
|
|
2
31
|
|
|
3
32
|
### Changed
|
data/Gemfile
CHANGED
|
@@ -8,8 +8,8 @@ gemspec
|
|
|
8
8
|
group :development do
|
|
9
9
|
gem 'logger', '~> 1.5'
|
|
10
10
|
gem 'rspec', '~> 3.12'
|
|
11
|
-
gem 'rubocop', '~> 1.
|
|
12
|
-
gem 'rubocop-performance', '~> 1.
|
|
13
|
-
gem 'rubocop-rake', '~> 0.
|
|
14
|
-
gem 'rubocop-rspec', '~>
|
|
11
|
+
gem 'rubocop', '~> 1.92'
|
|
12
|
+
gem 'rubocop-performance', '~> 1.27'
|
|
13
|
+
gem 'rubocop-rake', '~> 0.7'
|
|
14
|
+
gem 'rubocop-rspec', '~> 3.8'
|
|
15
15
|
end
|
data/README.md
CHANGED
|
@@ -61,6 +61,16 @@ element.multiple_allowed? # => true
|
|
|
61
61
|
element.required? # => false
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
+
### Imports and the absent namespace
|
|
65
|
+
|
|
66
|
+
XML Schema leaves it to the processor whether every `<xs:import>` of a namespace is read or only the first (a
|
|
67
|
+
`schemaLocation` is a hint); libxml2, and so Nokogiri's validator, reads the first. This reader reads them all, and
|
|
68
|
+
resolves a name within the include tree of the schema the name is written in. That is what a set of schemas with no
|
|
69
|
+
target namespace needs when one schema imports them all: every document a schema of its own, with its own copies of
|
|
70
|
+
common types that may differ from one copy to another. Put together into one schema they break the rule that names
|
|
71
|
+
are unique within a namespace, so a validator assembling one schema of them (`Schema#validate_xml`, which validates
|
|
72
|
+
through Nokogiri) sees only the first of them.
|
|
73
|
+
|
|
64
74
|
## Development
|
|
65
75
|
|
|
66
76
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can
|
data/lib/xsd/base_object.rb
CHANGED
|
@@ -73,7 +73,7 @@ module XSD
|
|
|
73
73
|
# @param [Boolean] deep
|
|
74
74
|
# @return Nokogiri::XML::NodeSet
|
|
75
75
|
def nodes(name = :*, deep = false)
|
|
76
|
-
node.xpath("./#{
|
|
76
|
+
node.xpath("./#{'/' if deep}xs:#{name}", XML_SCHEMA_NS)
|
|
77
77
|
end
|
|
78
78
|
|
|
79
79
|
# Get schemas by namespace or prefix
|
|
@@ -279,12 +279,14 @@ module XSD
|
|
|
279
279
|
# @param [Symbol] type
|
|
280
280
|
# @param [Hash] options
|
|
281
281
|
def self.property(name, type, options = {}, &block)
|
|
282
|
-
|
|
282
|
+
method = to_underscore(name)
|
|
283
|
+
properties[method] = {
|
|
283
284
|
name: name,
|
|
284
285
|
type: type,
|
|
285
286
|
resolve: block,
|
|
286
287
|
**options
|
|
287
288
|
}
|
|
289
|
+
define_member_reader(method)
|
|
288
290
|
end
|
|
289
291
|
|
|
290
292
|
# Define new object child
|
|
@@ -292,10 +294,12 @@ module XSD
|
|
|
292
294
|
# @param [Symbol, Array<Symbol>] type
|
|
293
295
|
# @param [Hash] options
|
|
294
296
|
def self.child(name, type, options = {})
|
|
295
|
-
|
|
297
|
+
method = to_underscore(name)
|
|
298
|
+
children[method] = {
|
|
296
299
|
type: type,
|
|
297
300
|
**options
|
|
298
301
|
}
|
|
302
|
+
define_member_reader(method)
|
|
299
303
|
end
|
|
300
304
|
|
|
301
305
|
# Define new object child
|
|
@@ -303,16 +307,53 @@ module XSD
|
|
|
303
307
|
# @param [Symbol] type
|
|
304
308
|
# @param [Hash] options
|
|
305
309
|
def self.link(name, type, options = {})
|
|
306
|
-
|
|
310
|
+
method = to_underscore(name)
|
|
311
|
+
links[method] = {
|
|
307
312
|
type: type,
|
|
308
313
|
**options
|
|
309
314
|
}
|
|
315
|
+
define_member_reader(method)
|
|
310
316
|
end
|
|
311
317
|
|
|
318
|
+
# A property, link or child is read through a method of its own: reaching it through method_missing costs far
|
|
319
|
+
# more per call, and an object is asked for its members all the time. The method goes in a module of the class's
|
|
320
|
+
# own, so that a method the class defines itself still wins, and is not defined where a method of that name
|
|
321
|
+
# exists already — an ancestor's own keeps answering, as it did when the name went to method_missing.
|
|
322
|
+
# @param [Symbol] method
|
|
323
|
+
def self.define_member_reader(method)
|
|
324
|
+
return if method_defined?(method) || private_method_defined?(method)
|
|
325
|
+
|
|
326
|
+
member_readers.define_method(method) do |*args|
|
|
327
|
+
value = read_member(method, args)
|
|
328
|
+
raise NoMethodError.new("undefined method '#{method}' for an instance of #{self.class}", method) if value.equal?(NOT_A_MEMBER)
|
|
329
|
+
|
|
330
|
+
value
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# The module a class's member readers are defined in
|
|
335
|
+
# @return Module
|
|
336
|
+
def self.member_readers
|
|
337
|
+
@member_readers ||= Module.new.tap { |readers| include(readers) }
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# What #read_member answers when the name is none of the object's members
|
|
341
|
+
NOT_A_MEMBER = Object.new.freeze
|
|
342
|
+
|
|
312
343
|
# Lookup for properties
|
|
313
344
|
# @param [Symbol] method
|
|
314
345
|
# @param [Array] args
|
|
315
346
|
def method_missing(method, *args)
|
|
347
|
+
value = read_member(method, args)
|
|
348
|
+
value.equal?(NOT_A_MEMBER) ? super : value
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
# The value of a property, link or child, cached: from the node, through a reference, or by its name in the
|
|
352
|
+
# schemas. A member reader and method_missing both come here.
|
|
353
|
+
# @param [Symbol] method
|
|
354
|
+
# @param [Array] args
|
|
355
|
+
# @return Object, NOT_A_MEMBER
|
|
356
|
+
def read_member(method, args)
|
|
316
357
|
# check cache first, nil and false results are cached as well
|
|
317
358
|
return @cache[method] if @cache.key?(method)
|
|
318
359
|
|
|
@@ -343,7 +384,7 @@ module XSD
|
|
|
343
384
|
return @cache[method] = result
|
|
344
385
|
end
|
|
345
386
|
|
|
346
|
-
|
|
387
|
+
NOT_A_MEMBER
|
|
347
388
|
end
|
|
348
389
|
|
|
349
390
|
# Does object has property/link/child?
|
|
@@ -368,7 +409,7 @@ module XSD
|
|
|
368
409
|
# @param [String, nil] string
|
|
369
410
|
# @return String, nil
|
|
370
411
|
def nil_if_empty(string)
|
|
371
|
-
string
|
|
412
|
+
string && string.empty? ? nil : string
|
|
372
413
|
end
|
|
373
414
|
|
|
374
415
|
private
|
data/lib/xsd/objects/element.rb
CHANGED
data/lib/xsd/objects/import.rb
CHANGED
|
@@ -15,12 +15,21 @@ module XSD
|
|
|
15
15
|
# @return String, nil
|
|
16
16
|
property :schemaLocation, :string
|
|
17
17
|
|
|
18
|
-
# Get imported schema
|
|
18
|
+
# Get imported schema: the one at the import's location when that one is loaded already; otherwise one known for
|
|
19
|
+
# the namespace, added manually or by another import. Not for the absent namespace: schemas with no target
|
|
20
|
+
# namespace are each a schema of its own (a provider's documents, every one with its own copies of the common
|
|
21
|
+
# types), so every import of it is read from its own location.
|
|
19
22
|
# @return Schema
|
|
20
23
|
def imported_schema
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
if schema_location
|
|
25
|
+
located = reader.loaded_schema(schema_location, base: schema.location)
|
|
26
|
+
return located if located
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
unless namespace.nil?
|
|
30
|
+
known_schema = reader.schemas_for_namespace(namespace).find { |s| !s.equal?(schema) }
|
|
31
|
+
return known_schema if known_schema
|
|
32
|
+
end
|
|
24
33
|
|
|
25
34
|
unless schema_location
|
|
26
35
|
raise ImportError,
|
|
@@ -62,14 +62,14 @@ module XSD
|
|
|
62
62
|
def collect_elements(include_base = false)
|
|
63
63
|
# Don't include elements from base by default, that will lead to element duplicates because
|
|
64
64
|
# elements in complex restriction MUST be all listen in restricting type
|
|
65
|
-
super
|
|
65
|
+
super
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
# Get all available attributes on the current stack level, optionally including base type attributes
|
|
69
69
|
# @param [Boolean] include_base
|
|
70
70
|
# @return Array<Attribute>
|
|
71
71
|
def collect_attributes(include_base = true)
|
|
72
|
-
result = super
|
|
72
|
+
result = super
|
|
73
73
|
# Filter restricted attributes to avoid duplicates from restricting and restricted type
|
|
74
74
|
include_base ? result.to_h { |item| [item.name, item] }.values : result
|
|
75
75
|
end
|
data/lib/xsd/objects/schema.rb
CHANGED
|
@@ -190,7 +190,10 @@ module XSD
|
|
|
190
190
|
# @param [String, nil] namespace
|
|
191
191
|
# @return Boolean
|
|
192
192
|
def targets_namespace?(namespace)
|
|
193
|
-
|
|
193
|
+
key = namespace.nil? ? 'xmlns' : "xmlns:#{namespace}"
|
|
194
|
+
# a prefix names a namespace only when the schema declares it: looking up a namespace URI as a prefix finds
|
|
195
|
+
# nothing, and nothing must not pass for the absent target namespace of a schema that has none
|
|
196
|
+
namespace == target_namespace || (namespaces.key?(key) && namespaces[key] == target_namespace) ||
|
|
194
197
|
# an unqualified name in a chameleon schema names something of the namespace it was included into
|
|
195
198
|
(chameleon? && namespace.nil? && !namespaces.key?('xmlns'))
|
|
196
199
|
end
|
|
@@ -7,6 +7,7 @@ module XSD
|
|
|
7
7
|
# https://www.w3schools.com/xml/el_simpletype.asp
|
|
8
8
|
class SimpleType < BaseObject
|
|
9
9
|
include Named
|
|
10
|
+
prepend CachedDataType
|
|
10
11
|
|
|
11
12
|
# Specifies a name for the element. This attribute is required if the simpleType element is a child of the
|
|
12
13
|
# schema element, otherwise it is not allowed
|
data/lib/xsd/objects/union.rb
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module XSD
|
|
4
|
+
# A definition's data type follows from the schema, which does not change once read: it is worked out on first use
|
|
5
|
+
# and kept. Reading XML casts every value by it, so it is asked for once per value otherwise.
|
|
6
|
+
module CachedDataType
|
|
7
|
+
def data_type
|
|
8
|
+
return @data_type if defined?(@data_type)
|
|
9
|
+
|
|
10
|
+
@data_type = super
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
data/lib/xsd/version.rb
CHANGED
data/lib/xsd/xml.rb
CHANGED
|
@@ -21,7 +21,7 @@ module XSD
|
|
|
21
21
|
elsif Pathname.new(location).absolute?
|
|
22
22
|
File.read(location)
|
|
23
23
|
else
|
|
24
|
-
raise ImportError, "Failed to locate import '#{location}'#{
|
|
24
|
+
raise ImportError, "Failed to locate import '#{location}'#{" for namespace '#{namespace}'" if namespace}"
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
27
|
|
|
@@ -158,6 +158,14 @@ module XSD
|
|
|
158
158
|
@schemas_by_location[expanded] = add_schema_xml(xml, location: expanded)
|
|
159
159
|
end
|
|
160
160
|
|
|
161
|
+
# The schema already read from a location, relative to a base as an import or an include names it
|
|
162
|
+
# @param [String] location
|
|
163
|
+
# @param [String, nil] base Location of the referencing schema
|
|
164
|
+
# @return Schema, nil
|
|
165
|
+
def loaded_schema(location, base: nil)
|
|
166
|
+
@schemas_by_location[expand_location(location.tr('\\', '/'), base)]
|
|
167
|
+
end
|
|
168
|
+
|
|
161
169
|
# Get first added (considered primary) schema
|
|
162
170
|
# @return Schema, nil
|
|
163
171
|
def schema
|
|
@@ -256,7 +264,7 @@ module XSD
|
|
|
256
264
|
elsif File.file?(location)
|
|
257
265
|
File.read(location)
|
|
258
266
|
else
|
|
259
|
-
raise ImportError, "Failed to locate import '#{location}'#{
|
|
267
|
+
raise ImportError, "Failed to locate import '#{location}'#{" for namespace '#{namespace}'" if namespace}"
|
|
260
268
|
end
|
|
261
269
|
end
|
|
262
270
|
|
data/lib/xsd.rb
CHANGED
|
@@ -10,6 +10,7 @@ require 'xsd/shared/element_container'
|
|
|
10
10
|
require 'xsd/shared/based'
|
|
11
11
|
require 'xsd/shared/simple_typed'
|
|
12
12
|
require 'xsd/shared/complex_typed'
|
|
13
|
+
require 'xsd/shared/cached_data_type'
|
|
13
14
|
require 'xsd/objects/facet'
|
|
14
15
|
require 'xsd/objects/attribute'
|
|
15
16
|
require 'xsd/objects/attribute_group'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: xsd
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.9.
|
|
4
|
+
version: 2.9.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- d.arkhipov
|
|
@@ -102,6 +102,7 @@ files:
|
|
|
102
102
|
- lib/xsd/objects/unique.rb
|
|
103
103
|
- lib/xsd/shared/attribute_container.rb
|
|
104
104
|
- lib/xsd/shared/based.rb
|
|
105
|
+
- lib/xsd/shared/cached_data_type.rb
|
|
105
106
|
- lib/xsd/shared/complex_typed.rb
|
|
106
107
|
- lib/xsd/shared/element_container.rb
|
|
107
108
|
- lib/xsd/shared/min_max_occurs.rb
|