lutaml-xsd 1.2.101 → 1.2.103

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: 6105bb86be0d4abbb820b3362454d8b05d2323366b35b6d1e6225feafc12dd57
4
- data.tar.gz: 639b6495bc87a3d3b6a8ec3edac0dfc4457da4af3dba1bef20e4d2c464628c54
3
+ metadata.gz: 0406cd7f1ca030db5b4c6db63f060fdaae3981f61d2913f3552c510129f80b8c
4
+ data.tar.gz: 067f44365c98c7a9b3fd221c74d35965d5e628b3cb58f788e7210bd8a8cc6964
5
5
  SHA512:
6
- metadata.gz: 7da7d36525dd07c46f9b81e9e1219f1faf5882c871cca810026b76b65f712cf8387e92ecee363a8f0a4ad23b75b42e7b5a1bcd41cf6a8b1c2793812b58dfaf1d
7
- data.tar.gz: 6475b579cbaa25e35cfcdb497e60c501425337e65676406aec619fbc83a386fb66cf5f94b5e7c4d77a96e79b0ae13bd1818517ca2e10191ae9a31976b5f0fb44
6
+ metadata.gz: d7b9a1f01944bfe42b257e750226f2e9b200fbe3a30fe14ac834f3b1b7fdd675a12afb77764eaf821e09662674788ecf764064516477e26df394da4ba8b66ac2
7
+ data.tar.gz: 8dc0ac9729456a8903c6a2f70c4db8d1d2c7704076654ac173c4450aa089c76d3369a48f61d1638c210e26cd5b94584cebeff2af4700bdf3964b587136082183
data/TODO.conformance.md CHANGED
@@ -519,7 +519,50 @@ tuple keys, node-set field violations and absent-field rows handled, cross-eleme
519
519
  0.6.3 + lutaml-model 0.8.166 train carried fixes (the FULL #45
520
520
  stE legs, the #522 attgC four + attgD mostly + attJ018 +
521
521
  schF1/P2, stE065/066/096) AND a WHOLESALE 407 regex regression:
522
+ - PARENT-CONTEXT ROUND B (2026-11-08): +1 Additional (isDefault079;
523
+ suite 4629->4630 = 98.59%, zero regressions, rspec green).
524
+ POSITIONAL BINDING for duplicate same-named locals: a model with
525
+ two regvalueop locals carrying DIFFERENT fixed values never bound
526
+ the second child to the second particle — the name-only dispatch
527
+ always hit the first. The engine computes each child's same-name
528
+ sibling index (on the RAW moxml nodes — wrapper objects are not
529
+ identity-stable across accessor calls, the IdUniquenessRule
530
+ lesson) and dispatches the positionally-bound declaration when
531
+ (a) the parent's model holds MORE THAN ONE same-named particle,
532
+ (b) every particle is INDEXED (attempt-3's starvation guard: raw
533
+ parent-model particles carry none of the form/ns metadata the
534
+ rules assume — resolver positional_children returns [] otherwise),
535
+ and (c) the candidate's expected namespace AGREES with the child's
536
+ (particlesQ016's global/local namesake guard). The ordered walk
537
+ keeps a PER-CALL visited set — the resolver's
538
+ each_particle_element memoizes on the instance and would poison
539
+ repeats. LESSON: scripted line-surgery on lib files dropped a
540
+ class end and leaked methods to top level (runtime-only
541
+ uninitialized-constant failures, caught by sentinel probes) —
542
+ structural edits go through the Edit tool only. Remaining parked
543
+ (matcher-assignment + root-assessment shapes): QFE1700c1,
544
+ addB065, addB188, schA1/D10 + the lax-wildcard six.
545
+
522
546
  ~177 cases — the ENTIRE Unicode-block escape family (~80
547
+ - PARENT-CONTEXT ROUND A (2026-11-08): +1 Errata10 (errC007; suite
548
+ 4628->4629 = 98.57%, zero regressions, rspec green). The
549
+ parent-context redesign opens with an ENGINE-LEVEL win: anyType
550
+ content is LAX — a child validates only against a declaration in
551
+ its OWN namespace, so a strictly-unresolvable child of an anyType
552
+ element (including one the namespace fallback would have served
553
+ from a targeted schema) is lax-undeclared and its subtree escapes
554
+ assessment (the suppression rides the existing skip-wildcard walk,
555
+ hereditary like QFE1700a1). The resolver gains resolve_strict
556
+ (exact-namespace only) and the shared anyType reference check.
557
+ errC007's contract is now enforced WITHOUT the namespace-fallback
558
+ narrowing that regressed five substitution/import shapes — those
559
+ lookups never touch this path. Round B (next): positional binding
560
+ for duplicate same-named particles (isDefault079) — ordered
561
+ per-call walk (the resolver's visited_particle? memoizes on the
562
+ instance and would poison repeats), gated on >1 same-named
563
+ particles AND all indexed (@namespaces.key? — the attempt-3
564
+ starvation guard).
565
+
523
566
  \p{Is...} per-block tests) plus the reL/reP/reQ/reR/reS/reT
524
567
  - ANYSIMPLETYPE-WHITESPACE ROUND (2026-11-07): +1 Additional
525
568
  (test93160; suite 4627->4628 = 98.55%, pending measure confirm).
@@ -58,12 +58,32 @@ module Lutaml
58
58
  # empty xs:int child escapes assessment too).
59
59
  next true if subtree_suppressed
60
60
 
61
- skip_here = skip_wildcard_subtree?(parent, xml_element, resolver)
61
+ skip_here = skip_wildcard_subtree?(parent, xml_element, resolver) ||
62
+ lax_any_type_child?(parent, xml_element, resolver)
62
63
  next true if skip_here
63
64
 
64
65
  schema_def = resolver.resolve(
65
66
  xml_element.name, xml_element.namespace_uri
66
67
  )
68
+ if parent
69
+ parent_declaration =
70
+ resolver.resolve(parent.name, parent.namespace_uri)
71
+ candidates = resolver.positional_children(parent_declaration,
72
+ xml_element.name)
73
+ positional = candidates[same_name_sibling_index(xml_element,
74
+ parent)]
75
+ # The binding must agree with the child's OWN namespace:
76
+ # an unqualified ref's global namesake never serves a
77
+ # qualified local namesake (particlesQ016).
78
+ child_ns = xml_element.namespace_uri.to_s
79
+ expected_ns =
80
+ resolver.expected_namespace_for(positional).to_s
81
+ if positional &&
82
+ ((child_ns.empty? && expected_ns.empty?) ||
83
+ child_ns == expected_ns)
84
+ schema_def = positional
85
+ end
86
+ end
67
87
  rules.each do |rule|
68
88
  argument = rule.schema_argument(schema_def, repository: resolver)
69
89
  next if argument.nil? && !rule.runs_without_schema?
@@ -203,6 +223,23 @@ module Lutaml
203
223
  end
204
224
  end
205
225
 
226
+ # The child's position among its same-named siblings — the index
227
+ # the positional binding (duplicate same-named declarations,
228
+ # isDefault079) binds to. Computed on the RAW moxml nodes: the
229
+ # wrapper objects are not identity-stable across accessor calls
230
+ # (the IdUniquenessRule lesson).
231
+ #
232
+ # @return [Integer]
233
+ def same_name_sibling_index(element, parent)
234
+ raw_children = Array(parent.moxml_element.elements)
235
+ position = raw_children.index do |node|
236
+ node.equal?(element.moxml_element)
237
+ end
238
+ return 0 if position.nil?
239
+
240
+ raw_children.take(position).count { |node| node.name == element.name }
241
+ end
242
+
206
243
  # Whether the parent's content model holds a processContents
207
244
  # skip wildcard whose namespace range admits the child — the
208
245
  # child subtree then escapes assessment entirely (addB125).
@@ -223,6 +260,40 @@ module Lutaml
223
260
  end
224
261
  end
225
262
 
263
+ # A child of an ANYTYPE element validates only against a
264
+ # declaration in its OWN namespace (anyType content is lax —
265
+ # errC007's contract); a child with no exact-namespace
266
+ # declaration is lax-undeclared and its subtree escapes
267
+ # assessment. The namespace fallback never serves this
268
+ # decision: a targeted schema's global is a DIFFERENT element
269
+ # than the unqualified child.
270
+ #
271
+ # @return [Boolean]
272
+ def lax_any_type_child?(parent, child, resolver)
273
+ return false unless parent
274
+
275
+ declaration = resolver.resolve(parent.name, parent.namespace_uri)
276
+ return false unless any_type_declaration?(declaration, resolver)
277
+
278
+ resolver.resolve_strict(child.name, child.namespace_uri).nil?
279
+ end
280
+
281
+ # Whether the declaration's effective type is xs:anyType:
282
+ # declared by reference, or the implicit default for a typeless
283
+ # declaration.
284
+ #
285
+ # @return [Boolean]
286
+ def any_type_declaration?(declaration, resolver)
287
+ return false unless
288
+ declaration.is_a?(Lutaml::Xml::Schema::Xsd::Element)
289
+
290
+ return true if resolver.builtin_any_type?(declaration.type.to_s)
291
+
292
+ declaration.type.to_s.empty? &&
293
+ declaration.complex_type.nil? &&
294
+ declaration.simple_type.nil?
295
+ end
296
+
226
297
  # Handle errors that occur during rule execution
227
298
  #
228
299
  # @param rule [ValidationRule] The rule that failed
@@ -159,10 +159,14 @@ module Lutaml
159
159
  @expected_namespaces[element]
160
160
  end
161
161
 
162
+ # The namespace-EXACT resolution — no unique-name fallback. The
163
+ # lax anyType-content decision (errC007) and other
164
+ # namespace-strict consumers use it.
165
+ #
162
166
  # @param name [String] local element name
163
167
  # @param namespace_uri [String, nil] instance element namespace
164
168
  # @return [Lutaml::Xml::Schema::Xsd::Element, nil]
165
- def resolve(name, namespace_uri)
169
+ def resolve_strict(name, namespace_uri)
166
170
  return nil if name.nil? || name.empty?
167
171
 
168
172
  candidates = @elements[name]
@@ -171,13 +175,33 @@ module Lutaml
171
175
  exact = candidates.find do |_element, namespace, _kind|
172
176
  namespace_matches?(namespace, namespace_uri)
173
177
  end
174
- return exact&.first if exact
178
+ exact&.first
179
+ end
180
+
181
+ # Whether the type reference names xs:anyType.
182
+ #
183
+ # @param reference [String]
184
+ # @return [Boolean]
185
+ def builtin_any_type?(reference)
186
+ parts = reference.to_s.split(":")
187
+ %w[xs xsd].include?(parts.first) && parts.last == "anyType"
188
+ end
189
+
190
+ # @param name [String] local element name
191
+ # @param namespace_uri [String, nil] instance element namespace
192
+ # @return [Lutaml::Xml::Schema::Xsd::Element, nil]
193
+ def resolve(name, namespace_uri)
194
+ exact = resolve_strict(name, namespace_uri)
195
+ return exact if exact
175
196
 
176
197
  # Unique-name fallback for instances that omit namespaces even
177
198
  # though the schema declares one. A foreign namespace never
178
199
  # falls back.
179
200
  return nil unless namespace_uri.to_s.empty?
180
201
 
202
+ candidates = @elements[name]
203
+ return nil if candidates.empty?
204
+
181
205
  candidates.length == 1 ? candidates.first.first : nil
182
206
  end
183
207
 
@@ -563,8 +587,92 @@ module Lutaml
563
587
  element_declaration_simple?(field_decl)
564
588
  end
565
589
 
590
+ # The ordered same-named inline particles of the parent's
591
+ # content model — the POSITIONAL binding for duplicate
592
+ # declarations (isDefault079: two regvalueop locals with
593
+ # different fixed values; the second child binds to the second
594
+ # particle). Returns [] unless the name appears MORE THAN ONCE
595
+ # and every particle is INDEXED (attempt-3's starvation: raw
596
+ # particles carry none of the form/ns metadata the rules
597
+ # assume). The walk keeps its own cycle guard — the resolver's
598
+ # each_particle_element memoizes visited nodes on the instance
599
+ # and would poison repeated walks.
600
+ #
601
+ # @param parent_declaration [Lutaml::Xml::Schema::Xsd::Element, nil]
602
+ # @param local_name [String]
603
+ # @return [Array<Lutaml::Xml::Schema::Xsd::Element>]
604
+ def positional_children(parent_declaration, local_name)
605
+ type = parent_declaration&.complex_type ||
606
+ resolve_type(parent_declaration)
607
+ return [] unless type.is_a?(Lutaml::Xml::Schema::Xsd::ComplexType)
608
+
609
+ particles = []
610
+ collect_named_particles(particle_for_type(type), local_name,
611
+ particles, {}.compare_by_identity, 0)
612
+ return [] if particles.length < 2
613
+ return [] unless particles.all? { |p| @namespaces.key?(p) }
614
+
615
+ particles
616
+ end
617
+
566
618
  private
567
619
 
620
+ # Depth-first ordered collection of same-named element
621
+ # particles; per-call visited set (identity-keyed), group refs
622
+ # expanded.
623
+ #
624
+ # @return [void]
625
+ def collect_named_particles(node, local_name, out, visited, depth)
626
+ return if node.nil? || depth > 24
627
+
628
+ if node.is_a?(Array)
629
+ node.each do |child|
630
+ collect_named_particles(child, local_name, out, visited,
631
+ depth + 1)
632
+ end
633
+ return
634
+ end
635
+ return if visited[node]
636
+
637
+ visited[node] = true
638
+
639
+ if node.is_a?(Lutaml::Xml::Schema::Xsd::Element)
640
+ if node.name.to_s.split(":").last == local_name
641
+ out << node
642
+ elsif node.ref.to_s.match?(/\S/) &&
643
+ node.ref.to_s.split(":").last == local_name
644
+ global = resolve_global(node.ref.to_s.split(":").last, nil)
645
+ out << global if global
646
+ end
647
+ return
648
+ end
649
+
650
+ nested = CONTAINER_NESTED_KINDS[node.class]
651
+ nested&.each do |kind|
652
+ collect_named_particles(Array(node.public_send(kind)),
653
+ local_name, out, visited, depth + 1)
654
+ end
655
+ (CONTAINER_LEAF_KINDS[node.class] || []).each do |kind|
656
+ Array(node.public_send(kind)).each do |leaf|
657
+ next unless leaf
658
+
659
+ if leaf.is_a?(Lutaml::Xml::Schema::Xsd::Group)
660
+ resolved = if leaf.ref
661
+ resolve_group(leaf.ref.to_s
662
+ .split(":").last)
663
+ else
664
+ leaf
665
+ end
666
+ collect_named_particles(resolved, local_name, out, visited,
667
+ depth + 1)
668
+ elsif leaf.is_a?(Lutaml::Xml::Schema::Xsd::Element)
669
+ collect_named_particles(leaf, local_name, out, visited,
670
+ depth + 1)
671
+ end
672
+ end
673
+ end
674
+ end
675
+
568
676
  # Whether a TYPE carries an identity value for a field bound to
569
677
  # the selector node itself (a "." field): a simple type or a
570
678
  # complex type with simple content does; complex content does not.
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Xsd
5
- VERSION = "1.2.101"
5
+ VERSION = "1.2.103"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lutaml-xsd
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.101
4
+ version: 1.2.103
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-11-07 00:00:00.000000000 Z
11
+ date: 2026-11-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: liquid