xsd 2.9.1 → 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: 90d2e1e3950f8ada667ad7a2960940c3a4857918425c6fb54ef6c6a92702f1bc
4
- data.tar.gz: 713e112c94582f982c01708d6ca735fd667006b476cb6ef0125383c55e39644b
3
+ metadata.gz: 81b3091d973a5f08501a01ba6d6b746b4863bc28b5392ec634c4d226b7775a98
4
+ data.tar.gz: 5bad6e4209bd42086a25d892a5c8a0845012ad91a57a97c7b80cf13f0656624b
5
5
  SHA512:
6
- metadata.gz: a804d3750423fe3c04c5d99614dac7a49679941498dacb9e9f72ef319c99da2c21ab70e94df58e38e7d0bae59561c66836c81db7df651ca68030baf363c96eab
7
- data.tar.gz: 7ed63b4341d8a27a6fc1b3df4d3c6aebd30a2a5e101bf91ccaa32b54997b47f475d98c329c406eb5802706d4361b9ef6dc119a6422922726234391d124a202b2
6
+ metadata.gz: fd1216b66f6d84f386850e5eb8160eadac25677d739a5c140eb64a974f300c7e0eca26f7c0d5a1fcd9fd8bb665c16d06c91776ea57d13b322e1b925dce35f765
7
+ data.tar.gz: 4a0145cebcb96eaf27b8a00fc7caf3be761a2d83b2809853f51c74c81702ae377168308a652ba716afd68fb2a7001b4a97ba713ea341c4983f11d14f8e007453
data/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
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
+
1
12
  ## [2.9.1] - 2026-10-20
2
13
 
3
14
  ### Changed
@@ -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
@@ -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.1'
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,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xsd
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.9.1
4
+ version: 2.9.2
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