xsd 2.9.0 → 2.9.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6dafeffb7f25c0134087002cfa7a33a2ae26bfb5d7aefc83dd998f73ce6551ce
4
- data.tar.gz: 309a398a467318eb24d8912556010c11cba92f72419088b709540775dc270e00
3
+ metadata.gz: 81b3091d973a5f08501a01ba6d6b746b4863bc28b5392ec634c4d226b7775a98
4
+ data.tar.gz: 5bad6e4209bd42086a25d892a5c8a0845012ad91a57a97c7b80cf13f0656624b
5
5
  SHA512:
6
- metadata.gz: 292b2948f27d88c4363149e41aabca8858cd3c9105b804c5e85231135f1deb9d1a2bab10a6868839721dfd866b75a6579aca43d9243ca115dd752597cbaa7d81
7
- data.tar.gz: cdc5245afa7c4fd48064b8c6ddac6966d8e0010d8a98e23fc59b43eb3df98e2159f4912db2e4ca3c5610e7713f0af1d525b6137e1c5c74edd38b8d87896c0717
6
+ metadata.gz: fd1216b66f6d84f386850e5eb8160eadac25677d739a5c140eb64a974f300c7e0eca26f7c0d5a1fcd9fd8bb665c16d06c91776ea57d13b322e1b925dce35f765
7
+ data.tar.gz: 4a0145cebcb96eaf27b8a00fc7caf3be761a2d83b2809853f51c74c81702ae377168308a652ba716afd68fb2a7001b4a97ba713ea341c4983f11d14f8e007453
data/CHANGELOG.md CHANGED
@@ -1,3 +1,25 @@
1
+ ## [2.9.2] - 2026-10-21
2
+
3
+ ### Changed
4
+
5
+ - A property, link or child of a schema object (`name`, `type`, `max_occurs`, `complex_type`, …) is read through a
6
+ method of its own, defined when the class declares it, instead of `method_missing`, which costs far more per call.
7
+ The lookup order is the same: a method a class or an ancestor defines itself still answers first, and
8
+ `respond_to?` answers as before. Reading XML against a large schema runs about 40% faster under YJIT.
9
+ - `data_type` of an element, attribute, simple and complex type is worked out once and kept: the schema does not
10
+ change once read, and converting a document asked for it once per value.
11
+
12
+ ## [2.9.1] - 2026-10-20
13
+
14
+ ### Changed
15
+
16
+ - A reference by name (a `type`, `ref`, `base`, …) is looked up in an index of the schema's top-level declarations
17
+ (`Schema#declaration_node`), built on the first lookup, instead of an XPath query that scanned every child of the
18
+ schema again for each reference. Large schemas referenced thousands of times are read several times faster
19
+ there; what is found is the same.
20
+ - The specs no longer load `wasabi`, which only a commented-out example used and which does not load on Ruby 3.4
21
+ (it requires `kconv`): the suite runs on Ruby 3.4.
22
+
1
23
  ## [2.9.0] - 2026-10-18
2
24
 
3
25
  ### Changed
data/Gemfile CHANGED
@@ -12,5 +12,4 @@ group :development do
12
12
  gem 'rubocop-performance', '~> 1.17'
13
13
  gem 'rubocop-rake', '~> 0.6'
14
14
  gem 'rubocop-rspec', '~> 2.10'
15
- gem 'wasabi', '~> 4.0'
16
15
  end
@@ -129,9 +129,9 @@ module XSD
129
129
  # determine schema for namespace
130
130
  search_schemas = schemas_for_namespace(name_prefix)
131
131
 
132
- # find element in target schema, name is passed as an xpath variable to stay safe with any name contents
132
+ # find the declaration in the schemas of the namespace
133
133
  search_schemas.each do |s|
134
- node = s.node.at_xpath("./xs:#{node_name}[@name=$name]", XML_SCHEMA_NS, name: name_local)
134
+ node = s.declaration_node(node_name, name_local)
135
135
  return s.node_to_object(node) if node
136
136
  end
137
137
 
@@ -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
- properties[to_underscore(name)] = {
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
- children[to_underscore(name)] = {
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
- links[to_underscore(name)] = {
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
- super
387
+ NOT_A_MEMBER
347
388
  end
348
389
 
349
390
  # Does object has property/link/child?
@@ -6,6 +6,7 @@ module XSD
6
6
  # extension (both simpleContent and complexContent)
7
7
  # https://www.w3schools.com/xml/el_attribute.asp
8
8
  class Attribute < BaseObject
9
+ prepend CachedDataType
9
10
  TYPE_PROPERTY = :type
10
11
 
11
12
  include SimpleTyped
@@ -6,6 +6,7 @@ module XSD
6
6
  # Parent elements: element, redefine, schema
7
7
  # https://www.w3schools.com/xml/el_complextype.asp
8
8
  class ComplexType < BaseObject
9
+ prepend CachedDataType
9
10
  include AttributeContainer
10
11
  include Named
11
12
 
@@ -5,6 +5,7 @@ module XSD
5
5
  # Parent elements: schema, choice, all, sequence, group
6
6
  # https://www.w3schools.com/xml/el_element.asp
7
7
  class Element < BaseObject
8
+ prepend CachedDataType
8
9
  TYPE_PROPERTY = :type
9
10
 
10
11
  include MinMaxOccurs
@@ -127,6 +127,22 @@ module XSD
127
127
  chameleon? ? (@object_cache ||= {}.compare_by_identity) : reader.object_cache
128
128
  end
129
129
 
130
+ # The node of a top-level declaration of this schema, by its kind and name — what a reference by name looks up
131
+ # (BaseObject#object_by_name). The children are indexed in one pass on the first lookup: a query per lookup
132
+ # scans all of them again, and a large schema is looked up in once for every reference to it.
133
+ # @param [Symbol, String] node_name kind of declaration: complexType, simpleType, element, group, ...
134
+ # @param [String] name local name
135
+ # @return Nokogiri::XML::Element, nil
136
+ def declaration_node(node_name, name)
137
+ @declaration_nodes ||= node.element_children.each_with_object({}) do |child, index|
138
+ next unless child.namespace&.href == XML_SCHEMA
139
+
140
+ # the first one, as a query would find it
141
+ index[[child.name, child['name']]] ||= child
142
+ end
143
+ @declaration_nodes[[node_name.to_s, name]]
144
+ end
145
+
130
146
  # This schema and every schema it includes, those included in turn too
131
147
  # @return Array<Schema>
132
148
  def with_includes
@@ -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
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module XSD
4
- VERSION = '2.9.0'
4
+ VERSION = '2.9.2'
5
5
  end
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,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xsd
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.9.0
4
+ version: 2.9.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - d.arkhipov
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2026-10-19 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
13
  name: builder
@@ -103,6 +102,7 @@ files:
103
102
  - lib/xsd/objects/unique.rb
104
103
  - lib/xsd/shared/attribute_container.rb
105
104
  - lib/xsd/shared/based.rb
105
+ - lib/xsd/shared/cached_data_type.rb
106
106
  - lib/xsd/shared/complex_typed.rb
107
107
  - lib/xsd/shared/element_container.rb
108
108
  - lib/xsd/shared/min_max_occurs.rb
@@ -119,7 +119,6 @@ metadata:
119
119
  homepage_uri: https://github.com/ekzo-dev/ruby-xsd
120
120
  source_code_uri: https://github.com/ekzo-dev/ruby-xsd
121
121
  changelog_uri: https://github.com/ekzo-dev/ruby-xsd/blob/main/CHANGELOG.md
122
- post_install_message:
123
122
  rdoc_options: []
124
123
  require_paths:
125
124
  - lib
@@ -134,8 +133,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
134
133
  - !ruby/object:Gem::Version
135
134
  version: '0'
136
135
  requirements: []
137
- rubygems_version: 3.5.5
138
- signing_key:
136
+ rubygems_version: 3.6.9
139
137
  specification_version: 4
140
138
  summary: The Ruby XSD library is an XML Schema implementation for Ruby.
141
139
  test_files: []