lutaml-xsd 1.2.79 → 1.2.80

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: fc71d86940b282e842650b924ddfbef4c92dbb7d39645dec6e57cf80c5edbd81
4
- data.tar.gz: e7643e86eb653d99ac2a72c39792b828994c1203a0fbbc89042856456ba59855
3
+ metadata.gz: 0aed69846e615ba0095fd9030ca3c7d870fe590c855694797393e669a57085ec
4
+ data.tar.gz: abcb15e8a7482e4589d08bbb886529f6aff6462db5c56e4626c9610dcb25777f
5
5
  SHA512:
6
- metadata.gz: 65b97ebeb74d183e3347e801b4d67fc183dec70698e501dd60fffcedcafbcc9a33f97d830aab3449a9ab72c8ca5ab0699aa41a5aafbbbd731512134408afcade
7
- data.tar.gz: a4ae99c07f652e26102f6bae1f6a3d1c241d032436e2c424f4e03f06921b6feb0ef46798febf19598ec005fbbf6757829638c543e557ab399e6c2ffb785264b2
6
+ metadata.gz: 928180700fa58c7965b7f57729b681a1580f18c5da136c6b7729a013426ca5bfeaad44b686ce0e28c2eb1247dd3e73ca1a1e34c81b61c37030165bf4f297ae62
7
+ data.tar.gz: 50441ce2cda522fed7e3df0acd8843ef3824a72b685bf663e21ade1d9c5a2366fbb2af244b4f83d839dbdb4e2c41faf3cf1eb14f3376bef8c0ef15f03de9fb98
data/TODO.conformance.md CHANGED
@@ -120,22 +120,29 @@ pinned commit) is measured via `rake conformance:w3c`:
120
120
 
121
121
  | mode | pass-rate |
122
122
  |---|---|
123
- | pipeline | 4569/4696 (97.3%) |
123
+ | pipeline | 4563/4696 (97.17%) |
124
124
 
125
- (2026-11-02, bundled-stack measurement on main with the escape,
125
+ (2026-11-02, bundled-stack measurement with the escape,
126
126
  hint-following, pattern-alternative, identity-crash, engine-set,
127
127
  engine-refresh, IC value-space, IC field-resolution, facade-silence,
128
128
  regex-semantics, redefine-refresh, gmonth-refresh, digit-vintage,
129
- stgh-refresh, group-ref, attribute-group-redefine and
130
- notation-length rounds; moxml
131
- 0.6.1 (gemspec floor `~> 0.6.0` per the owner — the 0.6 line drops
132
- the Opal surface; measured conformance-neutral), lutaml-model
133
- 0.8.155, engine leptris 1.9.401.0.) Same-stack comparison: 1.2.43
129
+ stgh-refresh, group-ref, attribute-group-redefine,
130
+ notation-length and ref-namespace rounds; moxml
131
+ 0.6.2 (gemspec floor `~> 0.6.0` per the owner — the 0.6 line drops
132
+ the Opal surface), lutaml-model
133
+ 0.8.157 (gemspec floor `~> 0.8.156` per the owner; the lockfile is
134
+ no longer committed — measurements resolve fresh), engine leptris
135
+ 1.9.402.0. The 1.9.402/0.6.2/0.8.157 ride costs 8 net: engine
136
+ fixed+EMPTY content regressions (9 cases, filed leptris-rs#25 —
137
+ empty content satisfies the fixed facet per §3.12.2/§4.3.1.2),
138
+ reU6 we-accept, MINUS two drift fixes (RegexTest_234, stE063).)
139
+ Same-stack comparison: 1.2.43
134
140
  baseline 3961 (84.3%) → 4039 (86.0%) → 4090 (87.1%) → 4360 (92.8%,
135
141
  lockfile refresh) → 4411 (93.9%) → 4430 (94.3%) → 4435 (94.4%) →
136
142
  4436 (94.5%) → 4439 (94.5%) → 4454 (94.9%) → 4495 (95.7%) → 4524
137
143
  (96.3%) → 4534 (96.6%) → 4540 (96.7%) → 4542 (96.7%) → 4546 (96.8%)
138
- → 4557 (97.0%) → 4565 (97.2%) → 4569 (97.3%), +608 net over the 1.2.43 baseline. The escapes round (PR #197 — \d→\p{Nd},
144
+ → 4557 (97.0%) → 4565 (97.2%) → 4569 (97.3%) → 4562 (drift raw) →
145
+ 4563 (97.17%, ref-namespace round), +602 net over the 1.2.43 baseline. The escapes round (PR #197 — \d→\p{Nd},
139
146
  \w/\W/\s to their XSD value spaces) collapses Regex 120→69;
140
147
  same-step pattern facets now behave as alternatives and
141
148
  whitespace-only text reaches the facets intact (PR #200), Regex
@@ -230,6 +237,8 @@ corners.
230
237
  | Built-in lexical tables reject valid forms (char-reference shapes; gMonth FIXED 1.9.398, \d legacy-Nd FIXED 1.9.399, stG/stH list lexicals FIXED 1.9.400/401) | ~5 | leptris#1936 |
231
238
  | Automaton size cap surfaces as an instance error instead of engine-inert silence | 0 — FIXED in leptris 1.9.398 | moxml#514 (follow-up to #473) |
232
239
  | Regex dialect remainder (engine `\d` vintage, `\p{Is...}` blocks, char-reference shapes) | ~30 | engine-side of leptris#1936 |
240
+ | Empty content against a fixed element value rejected (1.9.402.0 regression — isDefault001/002/076, ctZ013a, elemQ019, stE065/066/096, particlesZ040) | 9 | leptris-rs#25 |
241
+ | Unresolvable include/import/redefine schemaLocation raises at parse instead of warning (anyURI_a001/a002, schD7, schG8) | 4 | lutaml-model#1087 |
233
242
  | Strict wildcard/attribute admission on further compositions | ~5 | moxml#510 thread |
234
243
  | Ruby-side under-detections (Group 16, DataTypes 7, IC 6, Attribute 5, Additional 4, Particles 4, SimpleType 4, Element 1, Wildcards 1 — minus what the regex round banked) | ~60 | OURS — the remaining Ruby work queue |
235
244
 
@@ -388,6 +397,31 @@ tuple keys, node-set field violations and absent-field rows handled, cross-eleme
388
397
  duration/dateTime/gMonthDay/gYear corners (engine-bound and
389
398
  contested-highlight), gMonth_pattern001 (bugzilla 6901 — do not
390
399
  chase), double018 (disputed reading).
400
+ - REF-NAMESPACE ROUND (2026-11-02): +2 Particles (13→11, particlesDb001/
401
+ Db006). A ref particle names a declaration in ONE namespace, but the
402
+ match path resolved it against the CHILD's namespace first — a global
403
+ declaration duplicated across an imported namespace satisfied the ref
404
+ and foreign substitutes were accepted. The ref branch now prefers the
405
+ declaration in the content-model OWNER schema's target namespace
406
+ (threaded through the match chain), then the child's, then the
407
+ unique-name fallback; parsed particles carry no prefix map
408
+ (lutaml-model discards xmlns), so the owner namespace is the
409
+ strongest signal available. The engine — fed documents one at a
410
+ time — admits the substitute, so a targeted scan on the engine-accept
411
+ path reports the foreign child (scan_cross_namespace_refs). NIL
412
+ owner namespace means UNKNOWN, not "no namespace": resolve(local,
413
+ nil) only matches no-namespace declarations and a hint-loaded
414
+ document can supply a foreign namesake (idH021 regression caught
415
+ and fixed in-round). Zero regressions on the drifted stack.
416
+ - STACK DRIFT (2026-11-02, same day): the lockfile is no longer
417
+ committed (owner chore) — measurements resolve fresh and rode
418
+ moxml 0.6.1→0.6.2, leptris 1.9.401.0→1.9.402.0, lutaml-model
419
+ 0.8.155→0.8.157 at once. TWO fixes rode in (RegexTest_234,
420
+ stE063); the fixed+EMPTY engine regression cost 9 (leptris-rs#25)
421
+ and reU6 flipped to we-accept (regex class, untriaged). Suite
422
+ 4569→4562 raw; 4563 with this round. NET -6 documented, not
423
+ chased — the fixed-empty leg reverses the moment leptris-rs#25
424
+ lands.
391
425
  - REGEX SEMANTICS ROUND (2026-10-31, PR #208): \w/\W now carry their
392
426
  Appendix F complements (\w = [^\p{P}\p{Z}\p{C}], class interiors
393
427
  via the exact positive partition L/M/N/S), \I/\C complements added
@@ -94,8 +94,9 @@ module Lutaml
94
94
 
95
95
  # The engine does not check abstract-element usage; scan for
96
96
  # it regardless of whether the engine answered.
97
+ owner_ns = repository&.target_namespace_for(schema_def)
97
98
  scan_abstract_usage(xml_element, particles, collector,
98
- repository)
99
+ repository, owner_ns)
99
100
 
100
101
  handled = if occurrence_guard(repository).particles_safe?(particles)
101
102
  engine_content_check(xml_element,
@@ -107,10 +108,14 @@ module Lutaml
107
108
  # (particlesZ034_b); the Ruby matcher decides those.
108
109
  false
109
110
  end
110
- return if handled
111
+ if handled
112
+ scan_cross_namespace_refs(xml_element, particles, collector,
113
+ repository, owner_ns)
114
+ return
115
+ end
111
116
 
112
117
  validate_particle_sequence(xml_element, particles, collector,
113
- repository)
118
+ repository, owner_ns)
114
119
  end
115
120
 
116
121
  private
@@ -133,14 +138,14 @@ module Lutaml
133
138
  #
134
139
  # @return [void]
135
140
  def validate_particle_sequence(xml_element, particles, collector,
136
- repository)
141
+ repository, owner_ns = nil)
137
142
  return if particles.empty?
138
143
 
139
144
  children = Array(xml_element.children)
140
145
  matcher = ContentModelMatcher.new(
141
146
  particles,
142
147
  element_match: ->(child, particle) do
143
- element_matches?(child, particle, repository)
148
+ element_matches?(child, particle, repository, owner_ns)
144
149
  end,
145
150
  wildcard_match: ->(child, wildcard) do
146
151
  wildcard_permits?(wildcard, xml_element, child, repository)
@@ -155,13 +160,13 @@ module Lutaml
155
160
  result = matcher.match(children)
156
161
  if result.matched
157
162
  scan_wildcard_strict_usage(xml_element, particles, collector,
158
- repository)
163
+ repository, owner_ns)
159
164
  return
160
165
  end
161
166
 
162
167
  known = collector.errors.size
163
168
  walk_sequence_diagnostics(xml_element, particles, collector,
164
- repository)
169
+ repository, owner_ns)
165
170
  return if collector.errors.size > known
166
171
 
167
172
  report_matcher_failure(result, children, xml_element, collector)
@@ -317,15 +322,16 @@ module Lutaml
317
322
  # instances. Containers flatten to their leaf declarations —
318
323
  # the walk keeps them whole, the scan needs the leaves.
319
324
  def scan_abstract_usage(xml_element, particles, collector,
320
- repository)
325
+ repository, owner_ns = nil)
321
326
  leaves = leaf_particles(particles, repository)
322
327
  xml_element.children.each do |child|
323
328
  leaves.each do |particle|
324
329
  next unless particle.is_a?(Lutaml::Xsd::Element)
325
330
  next unless particle_matches?(child, particle, repository,
326
- parent: xml_element)
331
+ parent: xml_element,
332
+ owner_ns: owner_ns)
327
333
  next unless element_declaration_abstract?(particle, child,
328
- repository)
334
+ repository, owner_ns)
329
335
 
330
336
  report_error(
331
337
  collector,
@@ -360,7 +366,7 @@ module Lutaml
360
366
  #
361
367
  # @return [void]
362
368
  def scan_wildcard_strict_usage(xml_element, particles, collector,
363
- repository)
369
+ repository, owner_ns = nil)
364
370
  leaves = leaf_particles(particles, repository)
365
371
  wildcards = leaves.select { |p| p.is_a?(Lutaml::Xsd::Any) }
366
372
  return if wildcards.empty?
@@ -370,7 +376,7 @@ module Lutaml
370
376
  end
371
377
  xml_element.children.each do |child|
372
378
  next if element_leaves.any? do |p|
373
- element_matches?(child, p, repository)
379
+ element_matches?(child, p, repository, owner_ns)
374
380
  end
375
381
 
376
382
  wildcards.each do |wildcard|
@@ -391,7 +397,7 @@ module Lutaml
391
397
  #
392
398
  # @return [void]
393
399
  def walk_sequence_diagnostics(xml_element, particles, collector,
394
- repository)
400
+ repository, owner_ns = nil)
395
401
  return if particles.empty?
396
402
 
397
403
  children = xml_element.children
@@ -418,13 +424,15 @@ module Lutaml
418
424
  child = children[child_index]
419
425
 
420
426
  break unless particle_matches?(child, particle, repository,
421
- parent: xml_element)
427
+ parent: xml_element,
428
+ owner_ns: owner_ns)
422
429
 
423
430
  if particle.is_a?(Lutaml::Xsd::Any)
424
431
  check_strict_wildcard_child(child, particle, collector,
425
432
  repository)
426
433
  elsif particle.is_a?(Lutaml::Xsd::Element) &&
427
- element_declaration_abstract?(particle, child, repository)
434
+ element_declaration_abstract?(particle, child, repository,
435
+ owner_ns)
428
436
  report_error(
429
437
  collector,
430
438
  code: "abstract_element_used",
@@ -489,19 +497,74 @@ module Lutaml
489
497
  # declarations cannot appear directly in instances (substitution
490
498
  # members remain legal). Direct declarations carry their own
491
499
  # abstract flag; references resolve to the global declaration.
492
- def element_declaration_abstract?(particle, child, repository)
493
- declaration = declaration_for(particle, child, repository)
500
+ def element_declaration_abstract?(particle, child, repository,
501
+ owner_ns = nil)
502
+ declaration = declaration_for(particle, child, repository,
503
+ owner_ns)
494
504
  return false unless declaration
495
505
 
496
506
  truthy?(declaration.abstract) && child.name == declaration.name
497
507
  end
498
508
 
499
- def declaration_for(particle, child, repository)
509
+ # A qualified ref names a declaration in ONE namespace.
510
+ # Global names duplicated across namespaces (import + main)
511
+ # resolve against the child's namespace today; when the owner
512
+ # schema's target namespace declares the same local name, a
513
+ # foreign child is an unexpected element (the engine — fed
514
+ # the documents one at a time — admits the substitute, so
515
+ # this runs on the engine-accept path). Wildcard territory
516
+ # is exempt.
517
+ #
518
+ # @return [void]
519
+ def scan_cross_namespace_refs(xml_element, particles, collector,
520
+ repository, owner_ns)
521
+ return if owner_ns.nil?
522
+
523
+ refs = leaf_particles(particles, repository).select do |p|
524
+ p.is_a?(Lutaml::Xml::Schema::Xsd::Element) &&
525
+ p.ref.to_s.match?(/\S/)
526
+ end
527
+ return if refs.empty?
528
+
529
+ xml_element.children.each do |child|
530
+ next if child.namespace_uri.to_s == owner_ns
531
+ next unless refs.any? do |r|
532
+ r.ref.to_s.split(":").last == child.name
533
+ end
534
+ next unless repository.resolve(child.name, owner_ns)
535
+ next if wildcard_covers_child?(xml_element, particles, child,
536
+ repository)
537
+
538
+ report_error(
539
+ collector,
540
+ code: "unexpected_element_in_sequence",
541
+ message: "Unexpected element '#{child.qualified_name}' " \
542
+ "in sequence — the reference names the same " \
543
+ "declaration in #{owner_ns}",
544
+ location: child.xpath,
545
+ context: { element: child.qualified_name },
546
+ suggestion: "Use the declaration's namespace or rename " \
547
+ "the duplicate",
548
+ )
549
+ end
550
+ end
551
+
552
+ def wildcard_covers_child?(xml_element, particles, child,
553
+ repository)
554
+ leaf_particles(particles, repository).any? do |p|
555
+ next false unless p.is_a?(Lutaml::Xsd::Any)
556
+
557
+ wildcard_permits?(p, xml_element, child, repository)
558
+ end
559
+ end
560
+
561
+ def declaration_for(particle, child, repository, owner_ns = nil)
500
562
  return particle if particle.name.to_s.match?(/\S/)
501
563
  return nil unless repository
502
564
 
503
565
  local = particle.ref.to_s.split(":").last
504
- repository.resolve(local, child.namespace_uri) ||
566
+ (owner_ns && repository.resolve(local, owner_ns)) ||
567
+ repository.resolve(local, child.namespace_uri) ||
505
568
  repository.resolve(local, nil)
506
569
  end
507
570
 
@@ -649,10 +712,11 @@ module Lutaml
649
712
  # wildcard namespace constraints scoped to the target namespace
650
713
  # @return [Boolean]
651
714
  def particle_matches?(xml_element, particle, repository = nil,
652
- parent: nil, group_depth: 0)
715
+ parent: nil, group_depth: 0, owner_ns: nil)
653
716
  particle_matches_child?(xml_element, particle, repository,
654
717
  parent: parent,
655
- group_depth: group_depth)
718
+ group_depth: group_depth,
719
+ owner_ns: owner_ns)
656
720
  end
657
721
 
658
722
  # Check if XML element matches schema element.
@@ -663,9 +727,10 @@ module Lutaml
663
727
  # @param schema_element [Lutaml::Xsd::Element] The schema element
664
728
  # @param repository [SchemaDefinitionResolver, nil]
665
729
  # @return [Boolean]
666
- def element_matches?(xml_element, schema_element, repository = nil)
730
+ def element_matches?(xml_element, schema_element, repository = nil,
731
+ owner_ns = nil)
667
732
  particle_matches_declaration?(xml_element, schema_element,
668
- repository) ||
733
+ repository, owner_ns) ||
669
734
  substitution_member?(xml_element, schema_element, repository)
670
735
  end
671
736
 
@@ -227,14 +227,18 @@ module Lutaml
227
227
 
228
228
  # Whether an instance child matches a content-model particle.
229
229
  # Direct declarations compare name and expected namespace; ref
230
- # particles take the name and namespace of the global declaration
231
- # they reference, resolved against the child's namespace.
230
+ # particles name a qualified declaration — the owner schema's
231
+ # target namespace is preferred, then the child's, then the
232
+ # unique-name fallback (parsed particles carry no prefix map).
232
233
  #
233
234
  # @param child [XmlElement]
234
235
  # @param particle [Lutaml::Xsd::Element]
235
236
  # @param repository [SchemaDefinitionResolver, nil]
237
+ # @param owner_ns [String, nil] target namespace of the schema
238
+ # owning the content model
236
239
  # @return [Boolean]
237
- def particle_matches_declaration?(child, particle, repository)
240
+ def particle_matches_declaration?(child, particle, repository,
241
+ owner_ns = nil)
238
242
  ref = particle.ref.to_s
239
243
  if ref.empty?
240
244
  name = particle.name.to_s
@@ -245,7 +249,13 @@ module Lutaml
245
249
  repository))
246
250
  else
247
251
  local = ref.split(":").last
248
- declaration = repository&.resolve(local, child.namespace_uri) ||
252
+ # owner_ns nil means UNKNOWN (the caller has no owner
253
+ # context), not "no namespace" — resolve(local, nil) only
254
+ # matches no-namespace declarations, and a hint-loaded
255
+ # document can supply a foreign namesake.
256
+ declaration =
257
+ (owner_ns && repository&.resolve(local, owner_ns)) ||
258
+ repository&.resolve(local, child.namespace_uri) ||
249
259
  repository&.resolve(local, nil)
250
260
  return false unless declaration
251
261
 
@@ -413,10 +423,12 @@ module Lutaml
413
423
  # @param group_depth [Integer] recursion guard for group refs
414
424
  # @return [Boolean]
415
425
  def particle_matches_child?(child, particle, repository,
416
- parent: nil, group_depth: 0)
426
+ parent: nil, group_depth: 0,
427
+ owner_ns: nil)
417
428
  case particle
418
429
  when Lutaml::Xsd::Element
419
- particle_matches_declaration?(child, particle, repository) ||
430
+ particle_matches_declaration?(child, particle, repository,
431
+ owner_ns) ||
420
432
  substitution_member?(child, particle, repository)
421
433
  when Lutaml::Xsd::Any
422
434
  wildcard_permits?(particle, parent || child, child, repository)
@@ -425,13 +437,15 @@ module Lutaml
425
437
  group_depth).any? do |container|
426
438
  particle_matches_child?(child, container, repository,
427
439
  parent: parent,
428
- group_depth: group_depth + 1)
440
+ group_depth: group_depth + 1,
441
+ owner_ns: owner_ns)
429
442
  end
430
443
  when Lutaml::Xsd::Sequence, Lutaml::Xsd::Choice, Lutaml::Xsd::All
431
444
  collect_content_particles(particle).any? do |leaf|
432
445
  particle_matches_child?(child, leaf, repository,
433
446
  parent: parent,
434
- group_depth: group_depth)
447
+ group_depth: group_depth,
448
+ owner_ns: owner_ns)
435
449
  end
436
450
  else
437
451
  false
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Xsd
5
- VERSION = "1.2.79"
5
+ VERSION = "1.2.80"
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.79
4
+ version: 1.2.80
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.