lutaml-xsd 1.2.114 → 1.2.115

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: 36f7c7c0e9d599f7400dac905f372692d9da5d253bd5d67069106d923259a4b3
4
- data.tar.gz: 6a2b5a3b8f8db1067381b3e8f924da0f06af636a152626795167b97012510e40
3
+ metadata.gz: 8361d002b4925f8e9617b1befc010cdf69eda020d9819ad6cb563d5716fd0cb3
4
+ data.tar.gz: d7ee489b009dbfe0f537b6ec11ea9e68435161619db337801a8b2fe44881b267
5
5
  SHA512:
6
- metadata.gz: c9467308b09e8249e3e5b87a1b1a2d298f95d55ec4bf466a63a0a376752e813f0f9753b821d25c000e3d39a0c65249e675df39ec1496f47ecadde12b5782d28d
7
- data.tar.gz: f147cc2f3ebb9d6ec7ca62601b72573ccc70433bdd36b50cbe46133e815b052ac5a4fec48153e3e2823ab8c6d1f3955f13f1f1714b761b4bfc1310de174b5c20
6
+ metadata.gz: a67d033744fd14e969811aed1d8b7f7e679a50b2c535a1ea649e670d5e4baf6f5fef3c5b11349cfef519dbfb0cdacdc467270ccf6c3c231d3f6f234b2c7f84b2
7
+ data.tar.gz: 02fec25f4e6c24ecf8c591e8fff9dfb245acaeacb41a09c36223ae6a1da1cdc8e35a22c215b2a11c137982b1d49c284c568e926faf49d856bcc52f68a13be1a3
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 | 4658/4696 (99.19%) — first measure above 99 |
123
+ | pipeline | 4664/4696 (99.32%) |
124
124
 
125
125
  (2026-11-02, bundled-stack measurement with the escape,
126
126
  hint-following, pattern-alternative, identity-crash, engine-set,
@@ -144,7 +144,7 @@ lockfile refresh) → 4411 (93.9%) → 4430 (94.3%) → 4435 (94.4%) →
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
146
  round) → 4578 (97.49%, attribute-prohibition round) → 4585 (97.64%,
147
- multi-document round) → 4588 (97.7%, type-binding round) → 4592 (97.79%, model-161 ride) → 4611 (98.19%, engine-406 ride) → 4613 (98.23%, temporal-enumeration round) → 4614 (98.25%, simple-type-children round) → 4617 (98.32%, ic-ancestor-scope round) → 4637 (parent-context rounds + chameleon round 1.2.109) → 4637 (nested-type-candidates round, 1.2.110) → 4637 (redefine-cycle round, 1.2.111) → 4644 measured (98.96% of measured, train-adoption round: moxml 0.6.6 + leptris 1.9.418.0) → 4649 (98.91%, quadratic-dispatch round — first FULL-denominator measure, the 5 reZ budget exclusions retired) → 4658 (99.19%, remainder-classification round — first measure above 99), +697 net over the 1.2.43 baseline. The escapes round (PR #197 — \d→\p{Nd},
147
+ multi-document round) → 4588 (97.7%, type-binding round) → 4592 (97.79%, model-161 ride) → 4611 (98.19%, engine-406 ride) → 4613 (98.23%, temporal-enumeration round) → 4614 (98.25%, simple-type-children round) → 4617 (98.32%, ic-ancestor-scope round) → 4637 (parent-context rounds + chameleon round 1.2.109) → 4637 (nested-type-candidates round, 1.2.110) → 4637 (redefine-cycle round, 1.2.111) → 4644 measured (98.96% of measured, train-adoption round: moxml 0.6.6 + leptris 1.9.418.0) → 4649 (98.91%, quadratic-dispatch round — first FULL-denominator measure, the 5 reZ budget exclusions retired) → 4658 (99.19%, remainder-classification round — first measure above 99) → 4664 (99.32%, ride-419 round), +703 net over the 1.2.43 baseline. The escapes round (PR #197 — \d→\p{Nd},
148
148
  \w/\W/\s to their XSD value spaces) collapses Regex 120→69;
149
149
  same-step pattern facets now behave as alternatives and
150
150
  whitespace-only text reaches the facets intact (PR #200), Regex
@@ -684,6 +684,38 @@ tuple keys, node-set field violations and absent-field rows handled, cross-eleme
684
684
  false, which would have flagged every schema-type override
685
685
  undervalued; caught by re-reading the -A diff (the standing
686
686
  predicate-nil lesson, now with the rename fix applied).
687
+ - RIDE-419 ROUND (2026-11-13, release pending): leptris
688
+ 1.9.419.0 closed BOTH overnight filings — leptris-rs#72
689
+ (wildZ013b/c: the namespace-less-import wildcard admission
690
+ restored) and #73 (attZ014a/b: the multi-xs:ID 1.0 reading
691
+ restored). The ride also surfaced that OUR fixed-value check
692
+ was lexical-only where the suite demands VALUE-space equality
693
+ through a union's first-accepting member (§4.1.7): stE050
694
+ ('true' == fixed '1' as boolean-true) and stE055 (float forms)
695
+ now pass via ValueSpace.union_member_value_key + the
696
+ fixed_value_space_equal? comparison in TypeValidationRule
697
+ (union members split per the established member_types.to_s.
698
+ split pattern — Array() on the attribute yields ONE
699
+ space-separated string, which silently never matches; three
700
+ call sites corrected, including a latent one from the previous
701
+ round). stE065/066/096 remain ENGINE-bound — filed
702
+ leptris-rs#74 (the engine's own fixed-value comparison is
703
+ lexical-only for unions); attgC035-038 remain FACADE-bound —
704
+ filed moxml#545 (the closed-#522 thread's uncovered legs: the
705
+ strict re-check false-reports attributeGroup-redefine §4.2.2
706
+ hook members). SUITE 4664/4696 (99.32%), zero regressions;
707
+ rspec 1663 green; rubocop clean. INCIDENT (recovered
708
+ in-round): threading repository: into validate_fixed_content
709
+ via a bulk gsub landed a call inside validate_builtin_type,
710
+ whose scope has no repository local — NameError on every
711
+ builtin-typed element, a 686-case regression wave that
712
+ briefly looked like a leptris-419 breakage; the pin-back
713
+ battery exonerated the train (the wave reproduced on 418)
714
+ and the signature fix recovered the battery. LESSON: when
715
+ threading a parameter through call sites, verify the RECEIVER
716
+ method's signature carries it — a mechanical gsub crosses
717
+ scope boundaries silently, and the crash-as-invalid
718
+ convention turns the NameError into wholesale false verdicts.
687
719
  - TRAIN ADOPTION REGRESSION — CHARACTER REFERENCES (2026-11-11):
688
720
  the 2026-11-10 adoption verdict ("conformance-IDENTICAL") was
689
721
  WRONG for one class: moxml 0.6.4 stopped decoding numeric
@@ -127,7 +127,8 @@ module Lutaml
127
127
  # A type-less declaration is xs:anyType; its fixed value
128
128
  # still constrains the lexical content (isDefault070).
129
129
  validate_fixed_content(xml_element, schema_element,
130
- xml_element.text_content, collector)
130
+ xml_element.text_content, collector,
131
+ repository: repository)
131
132
  return
132
133
  end
133
134
 
@@ -143,14 +144,16 @@ module Lutaml
143
144
  # complex walk checks the type's model, this checks the
144
145
  # declaration's own fixed content (isDefault077).
145
146
  validate_fixed_content(xml_element, schema_element,
146
- xml_element.text_content, collector)
147
+ xml_element.text_content, collector,
148
+ repository: repository)
147
149
  validate_complex_type_content(xml_element, type_def, collector,
148
150
  repository: repository,
149
151
  declared: schema_element)
150
152
  else
151
153
  # Handle built-in types
152
154
  validate_builtin_type(xml_element, type_def, collector,
153
- declared: schema_element)
155
+ declared: schema_element,
156
+ repository: repository)
154
157
  end
155
158
  end
156
159
 
@@ -557,7 +560,7 @@ module Lutaml
557
560
  #
558
561
  # @return [Boolean]
559
562
  def union_member_covers_builtin?(union_type, override, repository)
560
- members = Array(union_type.union.member_types)
563
+ members = union_type.union.member_types.to_s.split(/\s+/)
561
564
  members.any? do |member|
562
565
  local = member.to_s.split(":").last
563
566
  next true if local == override
@@ -592,7 +595,8 @@ module Lutaml
592
595
  validate_builtin_type(xml_element,
593
596
  builtin_override_reference(xml_element),
594
597
  collector,
595
- declared: schema_element)
598
+ declared: schema_element,
599
+ repository: repository)
596
600
  true
597
601
  end
598
602
 
@@ -653,12 +657,17 @@ module Lutaml
653
657
  end
654
658
 
655
659
  # XSD 1.0 §3.3.3: a fixed value constrains the element — a
656
- # non-empty lexical must equal it in the value space
660
+ # non-empty lexical must equal it in the VALUE space
657
661
  # (elemQ018). Empty content already validates against the
658
- # fixed value through effective_text.
662
+ # fixed value through effective_text. Union/list-typed
663
+ # declarations map each lexical through the FIRST accepting
664
+ # member (§4.1.7) — 'true' equals fixed '1' as boolean-true
665
+ # (stE050), token collapse equates trailing blanks
666
+ # (stE065/066), float forms compare numerically (stE055).
659
667
  #
660
668
  # @return [void]
661
- def validate_fixed_content(xml_element, declared, value, collector)
669
+ def validate_fixed_content(xml_element, declared, value, collector,
670
+ repository: nil)
662
671
  return unless declared.is_a?(Lutaml::Xml::Schema::Xsd::Element)
663
672
 
664
673
  fixed = declared.fixed.to_s
@@ -667,6 +676,8 @@ module Lutaml
667
676
 
668
677
  rule = declared_whitespace_rule(declared)
669
678
  return if canonical(value.to_s, rule) == canonical(fixed, rule)
679
+ return if fixed_value_space_equal?(declared, value.to_s, fixed,
680
+ repository)
670
681
 
671
682
  report_error(
672
683
  collector,
@@ -682,6 +693,76 @@ module Lutaml
682
693
  )
683
694
  end
684
695
 
696
+ # Value-space equality for union/list-typed declarations:
697
+ # each side keys through the first accepting member and the
698
+ # keys must match.
699
+ #
700
+ # @return [Boolean]
701
+ def fixed_value_space_equal?(declared, value, fixed, repository)
702
+ simple = declared.simple_type
703
+ return false unless simple
704
+
705
+ if simple.union
706
+ members = simple.union.member_types.to_s.split(/\s+/)
707
+ return union_keys_equal?(value, fixed, members, repository)
708
+ end
709
+ return false unless simple.list
710
+
711
+ item_union = inline_item_union(simple.list, repository)
712
+ return false unless item_union
713
+
714
+ left = value.split
715
+ right = fixed.split
716
+ return false unless left.size == right.size
717
+
718
+ left.zip(right).all? do |a, b|
719
+ union_keys_equal?(a, b, item_union, repository)
720
+ end
721
+ end
722
+
723
+ # The member_types array of a list's item type: an inline
724
+ # union, else the named union resolved through the
725
+ # repository.
726
+ #
727
+ # @return [Array, nil]
728
+ def inline_item_union(list, repository)
729
+ inline = Array(list.simple_type).first
730
+ return inline.union.member_types.to_s.split(/\s+/) if inline&.union
731
+
732
+ reference = list.item_type.to_s.split(":").last
733
+ named = repository&.resolve_named_type(reference)
734
+ return named.union.member_types.to_s.split(/\s+/) if
735
+ named.is_a?(Lutaml::Xml::Schema::Xsd::SimpleType) && named.union
736
+
737
+ nil
738
+ end
739
+
740
+ # @return [Boolean]
741
+ def union_keys_equal?(value, fixed, members, repository)
742
+ a = first_member_key(value, members, repository)
743
+ b = first_member_key(fixed, members, repository)
744
+ !a.nil? && !b.nil? && a == b
745
+ end
746
+
747
+ # @return [Object, nil]
748
+ def first_member_key(lexical, members, repository)
749
+ members.each do |member|
750
+ local = member.to_s.split(":").last
751
+ key = ValueSpace.union_member_value_key(local, lexical)
752
+ return key unless key.nil?
753
+
754
+ named = repository&.resolve_named_type(local)
755
+ next unless named.is_a?(Lutaml::Xml::Schema::Xsd::SimpleType) &&
756
+ named.union
757
+
758
+ nested = first_member_key(lexical,
759
+ named.union.member_types.to_s.split(/\s+/),
760
+ repository)
761
+ return nested unless nested.nil?
762
+ end
763
+ nil
764
+ end
765
+
685
766
  # @return [Symbol] :preserve, :replace or :collapse
686
767
  def declared_whitespace_rule(declared)
687
768
  reference = declared.type.to_s
@@ -730,7 +811,8 @@ module Lutaml
730
811
  collector, repository: nil,
731
812
  declared: nil)
732
813
  value = effective_text(xml_element, declared)
733
- validate_fixed_content(xml_element, declared, value, collector)
814
+ validate_fixed_content(xml_element, declared, value, collector,
815
+ repository: repository)
734
816
 
735
817
  # Validate base type if present
736
818
  if simple_type.restriction
@@ -1251,7 +1333,8 @@ module Lutaml
1251
1333
  declared: declared)
1252
1334
  elsif builtin_base?(raw_base)
1253
1335
  validate_builtin_type(xml_element, raw_base, collector,
1254
- declared: declared)
1336
+ declared: declared,
1337
+ repository: repository)
1255
1338
  end
1256
1339
  elsif simple_content.restriction
1257
1340
  # Restriction constrains a simple type
@@ -1273,9 +1356,10 @@ module Lutaml
1273
1356
  # @param collector [ResultCollector] Result collector
1274
1357
  # @return [void]
1275
1358
  def validate_builtin_type(xml_element, type_name, collector,
1276
- declared: nil)
1359
+ declared: nil, repository: nil)
1277
1360
  value = effective_text(xml_element, declared)
1278
- validate_fixed_content(xml_element, declared, value, collector)
1361
+ validate_fixed_content(xml_element, declared, value, collector,
1362
+ repository: repository)
1279
1363
  # A simple type has no element content: children under a
1280
1364
  # builtin-typed element are illegal even when the lexical
1281
1365
  # text is fine (dtZ86723 — the xsi:type-overridden root
@@ -120,6 +120,71 @@ module Lutaml
120
120
  "NOTATION" => %w[QName anySimpleType anyType].freeze,
121
121
  }.freeze
122
122
 
123
+ # The VALUE-SPACE key of a lexical under a union member
124
+ # builtin — nil when the member does not accept the lexical.
125
+ # A union maps a lexical through its FIRST accepting member
126
+ # (§4.1.7), so 'true' and '1' are both boolean-true (stE050)
127
+ # and token collapse equates trailing blanks (stE065/066).
128
+ #
129
+ # @param local [String] builtin member local name
130
+ # @param value [String] the lexical
131
+ # @return [Object, nil] a comparable value, or nil
132
+ def self.union_member_value_key(local, value)
133
+ text = value.to_s
134
+ case local
135
+ when "boolean"
136
+ b = text.strip
137
+ return nil unless b.match?(/\A(?:true|false|1|0)\z/)
138
+
139
+ %w[true 1].include?(b)
140
+ when *INTEGER_TYPES
141
+ return nil unless integer_valid?(local, text)
142
+
143
+ Integer(text.strip)
144
+ when "float", "double"
145
+ return nil unless text.strip.match?(
146
+ /\A(?:[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?|INF|-INF|NaN)\z/,
147
+ )
148
+
149
+ f = Float(text.strip)
150
+ f.nan? ? "nan" : f
151
+ when "decimal"
152
+ return nil unless text.strip.match?(
153
+ /\A[+-]?(?:\d+(?:\.\d*)?|\.\d+)\z/,
154
+ )
155
+
156
+ require "bigdecimal"
157
+ BigDecimal(text.strip)
158
+ when "string"
159
+ text
160
+ when "normalizedString"
161
+ text.gsub(/[\t\n\r]/, " ")
162
+ when *COLLAPSED_STRING_TYPES, "language", "Name", "NCName",
163
+ "ID", "IDREF", "NMTOKEN", "anyURI"
164
+ text.strip.split(/\s+/).join(" ")
165
+ when "gYear", "gYearMonth", "gMonth", "gMonthDay", "gDay"
166
+ return nil unless calendar_lexical_valid?(local, text)
167
+
168
+ calendar_key(text)
169
+ when "date", "dateTime", "time"
170
+ return nil unless lexical_answer(local, text) == true
171
+
172
+ temporal_key(text)
173
+ when "duration"
174
+ return nil unless duration_valid?(text)
175
+
176
+ duration_key(text)
177
+ when "hexBinary"
178
+ return nil unless text.match?(/\A(?:[0-9a-fA-F]{2})*\z/)
179
+
180
+ text.upcase
181
+ when "base64Binary"
182
+ return nil unless base64_valid?(text)
183
+
184
+ text
185
+ end
186
+ end
187
+
123
188
  # Whether a builtin override local is validly derived from the
124
189
  # declared local: identity or the declared name sits in the
125
190
  # override's builtin ancestry. TRI-STATE: nil when the override
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Xsd
5
- VERSION = "1.2.114"
5
+ VERSION = "1.2.115"
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.114
4
+ version: 1.2.115
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.