lutaml-xsd 1.2.82 → 1.2.84

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: 8c3da66790f16686077f84ff27947241c3fd9290e4a36f92d5b26ee70d340af4
4
- data.tar.gz: 205af1e6a170523b054c34ba0cf43b86203877784bfe471ef88e47bcdbf60ae9
3
+ metadata.gz: 0ba83c4ddd3e8f05926b71bd53ed35e0aba700381e1ca5ed13c803ac2164412b
4
+ data.tar.gz: ae5e50d04a1a0230afbbdf904484601800b5772b2348c2b374feef2fa75403d7
5
5
  SHA512:
6
- metadata.gz: 6901b1b6a2fc09e00325fe3e24df146097e6bf6967493e46e04d7eabe8b7a9183cdccc2cc8ccf39cfd9d0f3cf90157b0647c772f61512a68085c041fbf1329be
7
- data.tar.gz: 221ce2f9ce160b0524a99cb9dfb029ccc10040adbe8282e8cb37ecbc94cc9f689e1a6b2e28a6ca27cd069ef2c6547a5d728f37a3254b5fc501b9cce50d27118c
6
+ metadata.gz: 6d52035a236b15bc9a063269dd5ee14389ec12d36619eb22cfcc85bbb9e98e17b2aedd55e41b6dbcbf7eb9e2f1797aaf83423b58d657e6e281befd52ab61c928
7
+ data.tar.gz: 6b35f257dbc8cadcd929212015294b407c2e5836c375e568a24bf71f05e41eb9823fbb97bf167f7bc289792e9a0f1d8f060088a7931594bc1b5d96aa99992a11
data/TODO.conformance.md CHANGED
@@ -120,7 +120,7 @@ pinned commit) is measured via `rake conformance:w3c`:
120
120
 
121
121
  | mode | pass-rate |
122
122
  |---|---|
123
- | pipeline | 4578/4696 (97.49%) |
123
+ | pipeline | 4588/4696 (97.7%) |
124
124
 
125
125
  (2026-11-02, bundled-stack measurement with the escape,
126
126
  hint-following, pattern-alternative, identity-crash, engine-set,
@@ -143,7 +143,8 @@ lockfile refresh) → 4411 (93.9%) → 4430 (94.3%) → 4435 (94.4%) →
143
143
  (96.3%) → 4534 (96.6%) → 4540 (96.7%) → 4542 (96.7%) → 4546 (96.8%)
144
144
  → 4557 (97.0%) → 4565 (97.2%) → 4569 (97.3%) → 4562 (drift raw) →
145
145
  4563 (97.17%, ref-namespace round) → 4573 (97.38%, group-content
146
- round) → 4578 (97.49%, attribute-prohibition round), +617 net over the 1.2.43 baseline. The escapes round (PR #197 — \d→\p{Nd},
146
+ round) → 4578 (97.49%, attribute-prohibition round) → 4585 (97.64%,
147
+ multi-document round) → 4588 (97.7%, type-binding round), +627 net over the 1.2.43 baseline. The escapes round (PR #197 — \d→\p{Nd},
147
148
  \w/\W/\s to their XSD value spaces) collapses Regex 120→69;
148
149
  same-step pattern facets now behave as alternatives and
149
150
  whitespace-only text reaches the facets intact (PR #200), Regex
@@ -470,6 +471,43 @@ tuple keys, node-set field violations and absent-field rows handled, cross-eleme
470
471
  use-site fixed overlays the global declaration in the value check
471
472
  (attLa005). Attribute remainder (4): attJ018 + attO009/011
472
473
  (engine-silent — queue), attO006 (leptris-rs#25 attribute leg).
474
+ - MULTI-DOCUMENT ROUND (2026-11-03): +7 (Schema 12→9 — the schT3/T9/
475
+ T10 redefine trio; ComplexType 4→1, Particles 12→10, Additional
476
+ 10→9, IC 7→6 — same-namespace collision pairs; suite 4578→4585 =
477
+ 97.64%, zero regressions, rspec green). TWO defects: (a) the
478
+ conformance repository keyed documents by TARGET NAMESPACE, so two
479
+ schemaDocuments sharing a namespace silently overwrote each other
480
+ and only one survived (schT3's redefine pair, both ns-a); a
481
+ same-namespace collision now suffixes the key instead of dropping
482
+ the document — every other case keeps the production one-doc-per-
483
+ namespace semantics (a naive path-key for everything cost 25
484
+ regressions via the unique-name fallback and first-match type
485
+ lookup, caught and reverted in-round). (b) resolve_named_type now
486
+ falls back to the parsed xs:redefine target documents: types living
487
+ only in the target (schT3 b-ct/b-st) resolve for plain references;
488
+ names a redefine BODY redefines stay OFF that path (the body is the
489
+ effective type and is not resolvable here — idF/idG/idH keep the
490
+ lenient path). KNOWN GAPS left open: schA1/schQ1/schD10/schF2
491
+ (hint-composition with same-local-name ct-A across namespaces —
492
+ per-namespace type binding), full type-redefine body semantics
493
+ (the body as effective type with the self-base hook — a
494
+ lutaml-model Redefine model shape study). Remaining Schema 9:
495
+ schA1/Q1/D10/F2 (ours), schD7/G8 (lutaml-model#1087),
496
+ schF1/G2/G7/P2 (moxml#510 composed bindings).
497
+ - TYPE-BINDING ROUND (2026-11-03): +3 (Schema 9→8 — schQ1/schF2
498
+ flip; Particles 12→8 cumulatively with the collision round; suite
499
+ 4585→4588 = 97.7%, zero regressions, rspec green). Same-local-name
500
+ types across a composition (three documents each declaring ct-A in
501
+ ns-a/ns-b/no-namespace) resolved to the FIRST candidate; the
502
+ resolver now keeps every candidate in document order and prefers
503
+ the one declared in the OWNING declaration's namespace (type
504
+ namespaces indexed at index time), with the no-namespace owner
505
+ preferring the no-namespace candidate; xsi:type overrides and
506
+ extension-base walks thread the deriving type namespace through.
507
+ STILL OPEN: schA1/schD10 (the hinted composition's CHILD value
508
+ checks bind the no-ns int ct-A to the ns-b boolean children —
509
+ 'true' vs xsd:int — the per-child type plumbing under hint
510
+ layering), full type-redefine body semantics.
473
511
  - REGEX SEMANTICS ROUND (2026-10-31, PR #208): \w/\W now carry their
474
512
  Appendix F complements (\w = [^\p{P}\p{Z}\p{C}], class interiors
475
513
  via the exact positive partition L/M/N/S), \I/\C complements added
@@ -154,8 +154,17 @@ module Lutaml
154
154
  test_case.schema_paths.each do |path|
155
155
  schema = Lutaml::Xml::Schema::Xsd.parse(File.read(path),
156
156
  location: path)
157
- key = schema.target_namespace if
158
- schema.target_namespace.to_s.match?(/\S/)
157
+ # Keyed by target namespace like production, EXCEPT that a
158
+ # same-namespace collision suffixes the key instead of
159
+ # dropping the document (schT3's redefine pair both declare
160
+ # ns-a). The resolver iterates values; keys only need to
161
+ # keep every document present.
162
+ key = if schema.target_namespace.to_s.match?(/\S/)
163
+ schema.target_namespace
164
+ else
165
+ path
166
+ end
167
+ key = "#{key}##{path}" while schemas.key?(key)
159
168
  schemas[key] = schema
160
169
  locations[path] = schema
161
170
  end
@@ -222,7 +222,11 @@ module Lutaml
222
222
  # Base attributes flow through both derivations — a
223
223
  # restriction narrows them but cannot drop them (XSD 1.0
224
224
  # §3.4.2).
225
- attributes.concat(inherited_attributes(part.base, repository)) if part
225
+ if part
226
+ attributes.concat(
227
+ inherited_attributes(part, repository),
228
+ )
229
+ end
226
230
  end
227
231
 
228
232
  # Handle simple content
@@ -234,7 +238,11 @@ module Lutaml
234
238
  )
235
239
  part = Array(schema_def.simple_content.extension).first ||
236
240
  Array(schema_def.simple_content.restriction).first
237
- attributes.concat(inherited_attributes(part.base, repository)) if part
241
+ if part
242
+ attributes.concat(
243
+ inherited_attributes(part, repository),
244
+ )
245
+ end
238
246
  end
239
247
 
240
248
  override_base_prohibitions(schema_def, attributes)
@@ -267,11 +275,13 @@ module Lutaml
267
275
  end
268
276
 
269
277
  # The base type's attribute set flows into the derived type
270
- # through extension and restriction alike.
278
+ # through extension and restriction alike. The base resolves
279
+ # through the derivation so a redefine body's own base hooks
280
+ # to the original definition.
271
281
  #
272
282
  # @return [Array<Lutaml::Xml::Schema::Xsd::Attribute>]
273
- def inherited_attributes(base_name, repository)
274
- local = base_name.to_s.split(":").last
283
+ def inherited_attributes(part, repository)
284
+ local = part.base.to_s.split(":").last
275
285
  return [] if local.to_s.empty?
276
286
 
277
287
  base = repository&.resolve_named_type(local)
@@ -247,7 +247,9 @@ module Lutaml
247
247
  xsi = repository && xsi_attribute(xml_element, "type")
248
248
  if xsi&.value.to_s.match?(/\S/)
249
249
  local = xsi.value.to_s.split(":").last
250
- named = repository.resolve_named_type(local)
250
+ named = repository.resolve_named_type(
251
+ local, repository.target_namespace_for(schema_def)
252
+ )
251
253
  return nil unless named
252
254
 
253
255
  return type_particles(named, repository, {}.compare_by_identity)
@@ -302,7 +304,10 @@ module Lutaml
302
304
  restriction = Array(content.restriction).first
303
305
  if extension
304
306
  base_local = extension.base.to_s.split(":").last
305
- base_type = base_local && repository&.resolve_named_type(base_local)
307
+ base_type = base_local &&
308
+ repository&.resolve_named_type(
309
+ base_local, repository&.target_namespace_for(type)
310
+ )
306
311
  particles = type_particles(base_type, repository, seen) +
307
312
  particles
308
313
  COMPLEX_PARTICLE_KINDS.each do |kind|
@@ -50,6 +50,14 @@ module Lutaml
50
50
  @attributes = Hash.new { |hash, key| hash[key] = [] }
51
51
  @attribute_groups = {}
52
52
  @redefined_originals = {}
53
+ # Parsed xs:redefine target documents: their named types back
54
+ # the redefined bodies (schT3: b-ct/b-st live in the target,
55
+ # which the repository does not hold). Names a redefine BODY
56
+ # redefines are NOT served from the target: the body is the
57
+ # effective type and is not resolvable here, so those keep
58
+ # the lenient unresolvable path (idF/idG/idH class).
59
+ @redefined_targets = []
60
+ @redefined_body_names = []
53
61
  @elements = index_elements
54
62
  end
55
63
 
@@ -178,6 +186,9 @@ module Lutaml
178
186
  end
179
187
 
180
188
  # Named type for an element's `type="prefix:Name"` reference.
189
+ # Same-local-name types across composed documents resolve by
190
+ # the OWNING declaration's namespace (schA1: ct-A exists in
191
+ # ns-a/ns-b/no-namespace; the reference binds its own).
181
192
  #
182
193
  # @param schema_element [Lutaml::Xml::Schema::Xsd::Element, nil]
183
194
  # @return [Object, nil]
@@ -187,18 +198,43 @@ module Lutaml
187
198
  reference = schema_element.type
188
199
  return nil if reference.nil? || reference.to_s.empty?
189
200
 
190
- resolve_named_type(reference.to_s.split(":").last)
201
+ resolve_named_type(reference.to_s.split(":").last,
202
+ target_namespace_for(schema_element))
191
203
  end
192
204
 
193
- # Named complex or simple type by local name.
205
+ # Named complex or simple type by local name, preferring a
206
+ # candidate declared in owner_ns.
194
207
  #
195
208
  # @param local_name [String]
209
+ # @param owner_ns [String, nil] the referencing declaration's
210
+ # target namespace
196
211
  # @return [Object, nil]
197
- def resolve_named_type(local_name)
212
+ def resolve_named_type(local_name, owner_ns = nil)
198
213
  return nil if local_name.nil? || local_name.empty?
199
- return @named_types[local_name] if @named_types.key?(local_name)
200
214
 
201
- result = nil
215
+ list = named_type_candidates(local_name)
216
+ return nil if list.empty?
217
+ # A nil owner means the referencing declaration lives in NO
218
+ # namespace (schA1_c): prefer the no-namespace candidate, not
219
+ # just the first.
220
+ if owner_ns.nil?
221
+ return list.find { |t| @namespaces[t].to_s.empty? } ||
222
+ list.first
223
+ end
224
+
225
+ list.find { |t| @namespaces[t] == owner_ns } || list.first
226
+ end
227
+
228
+ # Every top-level and nested declaration of the name, in
229
+ # document order: schemas first, then parsed redefine targets
230
+ # (names a redefine body redefines stay off the targets).
231
+ #
232
+ # @return [Array<Object>]
233
+ def named_type_candidates(local_name)
234
+ cache = @named_type_lists ||= {}
235
+ return cache[local_name] if cache.key?(local_name)
236
+
237
+ list = []
202
238
  schemas.each do |schema|
203
239
  found = Array(schema.complex_type).find do |ct|
204
240
  ct.name == local_name
@@ -206,10 +242,23 @@ module Lutaml
206
242
  found ||= Array(schema.simple_type).find do |st|
207
243
  st.name == local_name
208
244
  end
209
- result = found || find_nested_named_type(local_name, schema)
210
- break if result
245
+ list << found if found
246
+
247
+ nested = find_nested_named_type(local_name, schema)
248
+ list << nested if nested
249
+ end
250
+ unless @redefined_body_names.include?(local_name)
251
+ @redefined_targets.each do |schema|
252
+ found = Array(schema.complex_type).find do |ct|
253
+ ct.name == local_name
254
+ end
255
+ found ||= Array(schema.simple_type).find do |st|
256
+ st.name == local_name
257
+ end
258
+ list << found if found
259
+ end
211
260
  end
212
- @named_types[local_name] = result
261
+ cache[local_name] = list.uniq
213
262
  end
214
263
 
215
264
  # Named types declared in documents reached through resolved
@@ -602,35 +651,60 @@ module Lutaml
602
651
  # indexed under the name (attgC007/attgD036). Runs after the
603
652
  # nested descent so included declarations are already in.
604
653
  Array(schema.redefine).each do |red|
605
- target_path = nil
606
- base = begin
607
- File.dirname(@repository.source_path_for(schema))
654
+ index_redefine(index, schema, red, held_signatures, namespace,
655
+ defaults_schema)
656
+ end
657
+ end
658
+
659
+ # xs:redefine replaces an attributeGroup with a body whose
660
+ # self-reference is the §4.2.2 hook to the ORIGINAL
661
+ # definition. lutaml-model does NOT chase the redefine
662
+ # target, so the original document is parsed here (relative
663
+ # to the defining file) and indexed; the original
664
+ # declaration is kept for that hook and the redefining body
665
+ # indexed under the name (attgC007/attgD036). Runs after the
666
+ # nested descent so included declarations are already in.
667
+ def index_redefine(index, schema, red, held_signatures, namespace,
668
+ defaults_schema)
669
+ target_path = nil
670
+ base = begin
671
+ File.dirname(@repository.source_path_for(schema))
672
+ rescue StandardError
673
+ nil
674
+ end
675
+ if base && red.schema_path.to_s.match?(/\S/)
676
+ candidate = File.expand_path(red.schema_path.to_s, base)
677
+ target_path = candidate if File.exist?(candidate)
678
+ end
679
+ if target_path
680
+ begin
681
+ target = Lutaml::Xml::Schema::Xsd.parse(
682
+ File.read(target_path), location: target_path
683
+ )
684
+ @redefined_targets << target
685
+ index_schema(index, target, held_signatures, namespace,
686
+ defaults_schema)
608
687
  rescue StandardError
609
688
  nil
610
689
  end
611
- if base && red.schema_path.to_s.match?(/\S/)
612
- candidate = File.expand_path(red.schema_path.to_s, base)
613
- target_path = candidate if File.exist?(candidate)
614
- end
615
- if target_path
616
- begin
617
- target = Lutaml::Xml::Schema::Xsd.parse(
618
- File.read(target_path), location: target_path
619
- )
620
- index_schema(index, target, held_signatures, namespace,
621
- defaults_schema)
622
- rescue StandardError
623
- nil
624
- end
625
- end
626
- Array(red.attribute_group).each do |group|
627
- name = group.name.to_s
628
- next if name.empty?
690
+ end
691
+ # Names the body redefines: served by the body (not
692
+ # resolvable here), never by the target originals.
693
+ Array(red.complex_type).each do |body|
694
+ name = body.name.to_s
695
+ @redefined_body_names << name unless name.empty?
696
+ end
697
+ Array(red.simple_type).each do |body|
698
+ name = body.name.to_s
699
+ @redefined_body_names << name unless name.empty?
700
+ end
701
+ Array(red.attribute_group).each do |group|
702
+ name = group.name.to_s
703
+ next if name.empty?
629
704
 
630
- original = @attribute_groups[name]
631
- @redefined_originals[name] = original if original
632
- @attribute_groups[name] = group
633
- end
705
+ original = @attribute_groups[name]
706
+ @redefined_originals[name] = original if original
707
+ @attribute_groups[name] = group
634
708
  end
635
709
  end
636
710
 
@@ -723,6 +797,7 @@ module Lutaml
723
797
  def index_type(index, complex_type, namespace, schema)
724
798
  return unless complex_type
725
799
 
800
+ @namespaces[complex_type] = namespace
726
801
  each_particle_element(complex_type, schema) do |element|
727
802
  index_element(index, element, namespace, :inline, schema)
728
803
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Xsd
5
- VERSION = "1.2.82"
5
+ VERSION = "1.2.84"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lutaml-xsd
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.82
4
+ version: 1.2.84
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.