lutaml-xsd 1.2.94 → 1.2.96
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 +4 -4
- data/TODO.conformance.md +44 -0
- data/lib/lutaml/xsd/validation/rules/attribute_validation_rule.rb +74 -0
- data/lib/lutaml/xsd/validation/rules/identity_constraints_rule.rb +137 -17
- data/lib/lutaml/xsd/validation/schema_definition_resolver.rb +220 -0
- data/lib/lutaml/xsd/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b47e6206406d15078ca3896061eb50d415a599a3a6fe62df9a59d191c31f410a
|
|
4
|
+
data.tar.gz: d60605597341f39c26d19ac3bb8d88e6a0ff06d4f7ad03c9504d6903fc6a6ca9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 231da0fb555d2591583462fa173efef19ee7835938aab19b52a3dc637a2e1c62a2e97f428fe492bea6b7b6891da3af45602a8ebf7112c1733d914098e32a3c38
|
|
7
|
+
data.tar.gz: baa25447cd81057253277cf53348399487b15a2d8b5735e3531a57f6f87d7dec4dc12bfe0a3e2c87f80a20f9580c094b823616b093c5764f95789a1b0724d373
|
data/TODO.conformance.md
CHANGED
|
@@ -526,7 +526,51 @@ tuple keys, node-set field violations and absent-field rows handled, cross-eleme
|
|
|
526
526
|
STACK IS PINNED BACK to the known-good 1.9.406.0/0.6.2/0.8.161
|
|
527
527
|
(98.28% restored, zero regressions vs the 406 baseline). The
|
|
528
528
|
407 wins ride again when #47 and #529 land. NET: no score
|
|
529
|
+
- ID-VALUE-CONSTRAINT ROUND (2026-11-07): +1 IC (idZ011; suite
|
|
530
|
+
4619->4620 = 98.38%, zero regressions, rspec green). Value
|
|
531
|
+
constraints (fixed/default) are FORBIDDEN on attribute declarations
|
|
532
|
+
typed xs:ID or derived from it (XSD 1.0 §3.2.6) — the schema is
|
|
533
|
+
uncompilable, so every instance reached is invalid regardless of
|
|
534
|
+
instance use. The attribute rule's declaration-scoped step 1c
|
|
535
|
+
checks the builtin-prefixed reference, ref-resolved declarations,
|
|
536
|
+
and the inline/named restriction base chain (depth 6); the
|
|
537
|
+
idZ011_a companion (xs:STRING default) stays clean. Family probes:
|
|
538
|
+
Attribute 113/113 clean, AttributeGroup 14 = the parked moxml#522
|
|
539
|
+
set unchanged. PROBE LESSON (repeat): single-process family probes
|
|
540
|
+
CRAWL after ~50 engine-heavy cases (engine-state accumulation;
|
|
541
|
+
enumeration order is unstable so the hang point moves) — the case
|
|
542
|
+
runs alone in <1s; batched methodology or the full measure is the
|
|
543
|
+
only reliable family signal at this scale. Remaining IC
|
|
544
|
+
singletons: idZ012 (IDREFS default references no ID), idZ015 (a
|
|
545
|
+
skip-wildcard-governed attribute supplies no IC field value).
|
|
546
|
+
|
|
529
547
|
change; the pin protects the register.
|
|
548
|
+
- NON-SIMPLE-FIELD ROUND (2026-11-07): +2 IC (idK012, idZ010; suite
|
|
549
|
+
4617->4619 = 98.36%, zero regressions, rspec green). An identity
|
|
550
|
+
field takes a value only from a SIMPLE-typed node: a field hitting
|
|
551
|
+
an element with element children (idK012's pid, complexContent
|
|
552
|
+
restriction of anyType) or one DECLARED non-simple — complex
|
|
553
|
+
content, or xsd:anyType as the typeless-local default (idZ010's
|
|
554
|
+
A3/A22, the cross-namespace import shape) — previously contributed
|
|
555
|
+
its concatenated text as a clean row. The rule consults the field's
|
|
556
|
+
declaration via SchemaDefinitionResolver#field_declaration_simple:
|
|
557
|
+
the IC owner's content model descends the raw selector path, the
|
|
558
|
+
selector element's own type descends the raw field path (base
|
|
559
|
+
chains included; refs resolve to the global declaration). THREE
|
|
560
|
+
guards keep it sound: a resolvable xsi:type override governs the
|
|
561
|
+
INSTANCE value space (idF-class overrides stay simple, idL090
|
|
562
|
+
decimal/string fields untouched); unresolvable descents yield nil
|
|
563
|
+
= no determination (the lenient path is untouched); unresolvable
|
|
564
|
+
named types are simple only when builtin-prefixed. LESSON: the
|
|
565
|
+
tri-state verdicts live in NON-predicate methods — rubocop -A
|
|
566
|
+
rewrites `return nil` guards in `?`-methods to `return false`,
|
|
567
|
+
which silently hardened the lenient path twice before the probes
|
|
568
|
+
caught it. The remaining three IC singletons have DIFFERENT roots
|
|
569
|
+
(next rounds): idZ011 (schema-side xs:ID value-constraint
|
|
570
|
+
prohibition, §3.2.6), idZ012 (IDREFS default "abc" references no
|
|
571
|
+
ID), idZ015 (a skip-wildcard-governed attribute supplies no IC
|
|
572
|
+
field value).
|
|
573
|
+
|
|
530
574
|
|
|
531
575
|
- IC ANCESTOR-SCOPE ROUND (2026-11-05): +1 IC (idZ008; suite
|
|
532
576
|
4614->4617 = 98.32% with the prefix-resolution and
|
|
@@ -106,6 +106,13 @@ module Lutaml
|
|
|
106
106
|
xml_attributes, collector,
|
|
107
107
|
wildcards: wildcards)
|
|
108
108
|
|
|
109
|
+
# Step 1c: an xs:ID declaration cannot carry a value
|
|
110
|
+
# constraint — the schema itself is uncompilable, so the
|
|
111
|
+
# instance is invalid wherever the declaration is reached
|
|
112
|
+
# (XSD 1.0 §3.2.6, idZ011).
|
|
113
|
+
validate_id_value_constraints(schema_attributes, collector,
|
|
114
|
+
xml_element, repository)
|
|
115
|
+
|
|
109
116
|
# Step 2: Validate attribute values
|
|
110
117
|
validate_attribute_values(xml_element, resolved_attributes,
|
|
111
118
|
xml_attributes, collector,
|
|
@@ -436,6 +443,73 @@ module Lutaml
|
|
|
436
443
|
end
|
|
437
444
|
end
|
|
438
445
|
|
|
446
|
+
# An xs:ID declaration with fixed or default is an illegal
|
|
447
|
+
# value constraint (XSD 1.0 §3.2.6): the type definition is
|
|
448
|
+
# or is derived from xs:ID. Declaration-scoped — fires on the
|
|
449
|
+
# element's type regardless of instance use (idZ011).
|
|
450
|
+
#
|
|
451
|
+
# @return [void]
|
|
452
|
+
def validate_id_value_constraints(schema_attrs, collector,
|
|
453
|
+
xml_element, repository)
|
|
454
|
+
schema_attrs.each do |schema_attr|
|
|
455
|
+
next if schema_attr.fixed.to_s.empty? &&
|
|
456
|
+
schema_attr.default.to_s.empty?
|
|
457
|
+
next unless id_typed_attribute?(schema_attr, repository)
|
|
458
|
+
|
|
459
|
+
report_error(
|
|
460
|
+
collector,
|
|
461
|
+
code: "id_value_constraint_violation",
|
|
462
|
+
message: "Attribute '#{schema_attr.name}' is typed " \
|
|
463
|
+
"xs:ID (or derived from it) and cannot carry " \
|
|
464
|
+
"a fixed or default value constraint",
|
|
465
|
+
location: xml_element.xpath,
|
|
466
|
+
context: { attribute: schema_attr.name },
|
|
467
|
+
)
|
|
468
|
+
end
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
# Whether the declaration's type is xs:ID or derives from it:
|
|
472
|
+
# a builtin-prefixed reference, or a restriction base chain
|
|
473
|
+
# (inline or named) whose terminal is xs:ID.
|
|
474
|
+
#
|
|
475
|
+
# @return [Boolean]
|
|
476
|
+
def id_typed_attribute?(schema_attr, repository)
|
|
477
|
+
return true if builtin_id_type?(schema_attr.type)
|
|
478
|
+
|
|
479
|
+
ref = schema_attr.ref.to_s
|
|
480
|
+
if ref.match?(/\S/) && repository
|
|
481
|
+
resolved = repository.resolve_attribute(ref.split(":").last,
|
|
482
|
+
nil)
|
|
483
|
+
return true if resolved && builtin_id_type?(resolved.type)
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
named = schema_attr.type.to_s.split(":").last
|
|
487
|
+
chain = schema_attr.simple_type ||
|
|
488
|
+
(named && repository&.resolve_named_type(named))
|
|
489
|
+
id_derived_chain?(chain, repository, 0)
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
# @return [Boolean]
|
|
493
|
+
def builtin_id_type?(reference)
|
|
494
|
+
parts = reference.to_s.split(":")
|
|
495
|
+
%w[xs xsd].include?(parts.first) && parts.last == "ID"
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
# @param type [Lutaml::Xml::Schema::Xsd::SimpleType, nil]
|
|
499
|
+
# @return [Boolean]
|
|
500
|
+
def id_derived_chain?(type, repository, depth)
|
|
501
|
+
return false if depth > 6 || type.nil?
|
|
502
|
+
|
|
503
|
+
base = type.restriction&.base.to_s
|
|
504
|
+
return true if builtin_id_type?(base)
|
|
505
|
+
return false if base.empty?
|
|
506
|
+
|
|
507
|
+
id_derived_chain?(
|
|
508
|
+
repository&.resolve_named_type(base.split(":").last),
|
|
509
|
+
repository, depth + 1
|
|
510
|
+
)
|
|
511
|
+
end
|
|
512
|
+
|
|
439
513
|
# Check if attribute is required
|
|
440
514
|
#
|
|
441
515
|
# @param schema_attr [Lutaml::Xsd::Attribute] Schema attribute
|
|
@@ -62,7 +62,8 @@ module Lutaml
|
|
|
62
62
|
Array(schema_element.key).each do |constraint|
|
|
63
63
|
evaluated_constraints << constraint.name.to_s if constraint.name
|
|
64
64
|
values = collect_values(xml_element, constraint, collector,
|
|
65
|
-
repository, target_ns, :key
|
|
65
|
+
repository, target_ns, :key,
|
|
66
|
+
schema_element)
|
|
66
67
|
report_duplicates(collector, xml_element, constraint, values,
|
|
67
68
|
"duplicate_key_violation")
|
|
68
69
|
collected[constraint.name] = values if constraint.name
|
|
@@ -71,7 +72,8 @@ module Lutaml
|
|
|
71
72
|
Array(schema_element.unique).each do |constraint|
|
|
72
73
|
evaluated_constraints << constraint.name.to_s if constraint.name
|
|
73
74
|
values = collect_values(xml_element, constraint, collector,
|
|
74
|
-
repository, target_ns, :unique
|
|
75
|
+
repository, target_ns, :unique,
|
|
76
|
+
schema_element)
|
|
75
77
|
report_duplicates(collector, xml_element, constraint, values,
|
|
76
78
|
"duplicate_unique_violation")
|
|
77
79
|
collected[constraint.name] = values if constraint.name
|
|
@@ -83,7 +85,8 @@ module Lutaml
|
|
|
83
85
|
next if referred.nil?
|
|
84
86
|
|
|
85
87
|
collect_values(xml_element, constraint, collector,
|
|
86
|
-
repository, target_ns, :keyref
|
|
88
|
+
repository, target_ns, :keyref,
|
|
89
|
+
schema_element).each do |value|
|
|
87
90
|
next if referred.include?(value)
|
|
88
91
|
|
|
89
92
|
report_error(
|
|
@@ -107,24 +110,53 @@ module Lutaml
|
|
|
107
110
|
|
|
108
111
|
private
|
|
109
112
|
|
|
113
|
+
# Field values (and violation flags) for ONE selector-matched
|
|
114
|
+
# node: per field, the supply value, the node's text/attr
|
|
115
|
+
# value, or a non-simple verdict.
|
|
116
|
+
#
|
|
117
|
+
# @return [Array(Array, Boolean, Boolean)] [values, ambiguous,
|
|
118
|
+
# non_simple]
|
|
119
|
+
def evaluate_field_row(node, fields, raws, constraint,
|
|
120
|
+
owner_element, repository)
|
|
121
|
+
values = []
|
|
122
|
+
ambiguous = false
|
|
123
|
+
non_simple = false
|
|
124
|
+
fields.each_with_index do |field, index|
|
|
125
|
+
matched = field_nodes(node, field)
|
|
126
|
+
# A field evaluating to a node-set with more than one
|
|
127
|
+
# member violates the constraint (XSD 1.0 §3.11.4).
|
|
128
|
+
ambiguous = true if matched.size > 1
|
|
129
|
+
values << if matched.first.nil?
|
|
130
|
+
field_supply_value(node, field, repository)
|
|
131
|
+
elsif matched.first.is_a?(Moxml::Element) &&
|
|
132
|
+
field_node_non_simple?(
|
|
133
|
+
matched.first, constraint, raws[index],
|
|
134
|
+
owner_element, repository
|
|
135
|
+
)
|
|
136
|
+
# An element field hitting an element with
|
|
137
|
+
# ELEMENT children (complex content), or one
|
|
138
|
+
# DECLARED non-simple (complex content or
|
|
139
|
+
# xsd:anyType — the typeless default), supplies
|
|
140
|
+
# no identity value: the field "does evaluate
|
|
141
|
+
# to a node of non-simple type" (idK012/idZ010).
|
|
142
|
+
non_simple = true
|
|
143
|
+
nil
|
|
144
|
+
else
|
|
145
|
+
field_node_value(matched.first)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
[values, ambiguous, non_simple]
|
|
149
|
+
end
|
|
150
|
+
|
|
110
151
|
def collect_values(xml_element, constraint, collector, repository,
|
|
111
|
-
target_namespace, kind)
|
|
152
|
+
target_namespace, kind, owner_element)
|
|
112
153
|
fields = field_xpaths(constraint, target_namespace)
|
|
154
|
+
raws = raw_fields(constraint)
|
|
113
155
|
selector_xpaths(constraint, target_namespace).flat_map do |selector|
|
|
114
156
|
Array(xml_element.moxml_element.xpath(selector)).map do |node|
|
|
115
|
-
values =
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
matched = field_nodes(node, field)
|
|
119
|
-
# A field evaluating to a node-set with more than one
|
|
120
|
-
# member violates the constraint (XSD 1.0 §3.11.4).
|
|
121
|
-
ambiguous = true if matched.size > 1
|
|
122
|
-
values << if matched.first
|
|
123
|
-
field_node_value(matched.first)
|
|
124
|
-
else
|
|
125
|
-
field_supply_value(node, field, repository)
|
|
126
|
-
end
|
|
127
|
-
end
|
|
157
|
+
values, ambiguous, non_simple =
|
|
158
|
+
evaluate_field_row(node, fields, raws, constraint,
|
|
159
|
+
owner_element, repository)
|
|
128
160
|
|
|
129
161
|
if ambiguous
|
|
130
162
|
report_error(
|
|
@@ -138,6 +170,19 @@ module Lutaml
|
|
|
138
170
|
next
|
|
139
171
|
end
|
|
140
172
|
|
|
173
|
+
if non_simple
|
|
174
|
+
report_error(
|
|
175
|
+
collector,
|
|
176
|
+
code: "field_non_simple_type_violation",
|
|
177
|
+
message: "Field #{fields.join(', ')} of #{kind} " \
|
|
178
|
+
"'#{constraint.name}' evaluates to a node of " \
|
|
179
|
+
"non-simple type in '#{node.name}'",
|
|
180
|
+
location: xml_element.xpath,
|
|
181
|
+
context: { constraint: constraint.name },
|
|
182
|
+
)
|
|
183
|
+
next
|
|
184
|
+
end
|
|
185
|
+
|
|
141
186
|
# A qualified node has every field present; an absent
|
|
142
187
|
# field means the node does not contribute a row —
|
|
143
188
|
# except for xs:KEY, where a selected node whose fields
|
|
@@ -292,6 +337,67 @@ module Lutaml
|
|
|
292
337
|
repository.declared_attribute_supply(parent_type, attr_local)
|
|
293
338
|
end
|
|
294
339
|
|
|
340
|
+
# Whether an element field's node supplies no identity value:
|
|
341
|
+
# it has element children, is declared non-simple (complex
|
|
342
|
+
# content or xsd:anyType — the typeless default), or carries a
|
|
343
|
+
# resolvable xsi:type override to a non-simple type — the
|
|
344
|
+
# override governs the INSTANCE value space, so a simple
|
|
345
|
+
# override keeps the field simple no matter what the schema
|
|
346
|
+
# declares (idF-class).
|
|
347
|
+
#
|
|
348
|
+
# @param matched [Moxml::Element]
|
|
349
|
+
# @return [Boolean]
|
|
350
|
+
def field_node_non_simple?(matched, constraint, raw_field,
|
|
351
|
+
owner_element, repository)
|
|
352
|
+
override = override_governs_simple(matched, repository)
|
|
353
|
+
return !override unless override.nil?
|
|
354
|
+
|
|
355
|
+
matched.elements.any? ||
|
|
356
|
+
declared_field_non_simple?(constraint, raw_field,
|
|
357
|
+
owner_element, repository)
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
# The simple/non-simple verdict of the field node's xsi:type
|
|
361
|
+
# override, or nil when the node has no resolvable override.
|
|
362
|
+
#
|
|
363
|
+
# @return [Boolean, nil]
|
|
364
|
+
def override_governs_simple(node, repository)
|
|
365
|
+
override = node.attributes.find do |candidate|
|
|
366
|
+
candidate.name == "type" &&
|
|
367
|
+
candidate.namespace&.uri == XSI_NAMESPACE
|
|
368
|
+
end
|
|
369
|
+
return unless override
|
|
370
|
+
|
|
371
|
+
parts = override.value.to_s.split(":")
|
|
372
|
+
local = parts.last
|
|
373
|
+
return if local.nil? || local.empty?
|
|
374
|
+
return local != "anyType" if %w[xs xsd].include?(parts.first)
|
|
375
|
+
|
|
376
|
+
case repository&.resolve_named_type(local)
|
|
377
|
+
when Lutaml::Xml::Schema::Xsd::SimpleType then true
|
|
378
|
+
when Lutaml::Xml::Schema::Xsd::ComplexType then false
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
# Whether the field's element declaration — reached by
|
|
383
|
+
# descending the IC owner's content model through the raw
|
|
384
|
+
# selector path, then the raw field step — is non-simple
|
|
385
|
+
# (complex content or xsd:anyType). Unresolvable paths allow
|
|
386
|
+
# no determination.
|
|
387
|
+
#
|
|
388
|
+
# @return [Boolean]
|
|
389
|
+
def declared_field_non_simple?(constraint, raw_field,
|
|
390
|
+
owner_element, repository)
|
|
391
|
+
return false unless repository && owner_element
|
|
392
|
+
|
|
393
|
+
owner_type = owner_element.complex_type ||
|
|
394
|
+
repository.resolve_type(owner_element)
|
|
395
|
+
raw_selectors(constraint).any? do |selector_raw|
|
|
396
|
+
repository.field_declaration_simple(owner_type, selector_raw,
|
|
397
|
+
raw_field) == false
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
|
|
295
401
|
def report_duplicates(collector, xml_element, constraint, values,
|
|
296
402
|
code)
|
|
297
403
|
values.group_by(&:itself).each do |value, group|
|
|
@@ -347,6 +453,20 @@ module Lutaml
|
|
|
347
453
|
end
|
|
348
454
|
end
|
|
349
455
|
|
|
456
|
+
# The selector steps as WRITTEN (unions split the same way the
|
|
457
|
+
# adapted list splits) — the declared-field determination
|
|
458
|
+
# descends the schema through these.
|
|
459
|
+
#
|
|
460
|
+
# @return [Array<String>]
|
|
461
|
+
def raw_selectors(constraint)
|
|
462
|
+
raw = if constraint.selector.is_a?(Lutaml::Xml::Schema::Xsd::Selector)
|
|
463
|
+
constraint.selector.xpath
|
|
464
|
+
else
|
|
465
|
+
constraint.selector
|
|
466
|
+
end
|
|
467
|
+
raw.to_s.split(/\s*\|\s*/).reject(&:empty?)
|
|
468
|
+
end
|
|
469
|
+
|
|
350
470
|
# The field steps as WRITTEN (unions split the same way the
|
|
351
471
|
# adapted list splits) — the absent-row decision reads the
|
|
352
472
|
# step's own namespace governance from these.
|
|
@@ -527,8 +527,228 @@ module Lutaml
|
|
|
527
527
|
particle_for_type(resolve_named_type(local_name))
|
|
528
528
|
end
|
|
529
529
|
|
|
530
|
+
# Whether the element declaration an identity-constraint field
|
|
531
|
+
# binds to is SIMPLE-typed. A field may take an identity value
|
|
532
|
+
# only from a simple-typed node: a complex type with complex
|
|
533
|
+
# content (element children, idK012) or xsd:anyType — the
|
|
534
|
+
# default for a typeless local declaration (idZ010's A3/A22) —
|
|
535
|
+
# supplies no identity value. The declaration is reached by
|
|
536
|
+
# descending the IC owner's content model through the raw
|
|
537
|
+
# selector path, then the selector element's own type through
|
|
538
|
+
# the raw field path.
|
|
539
|
+
#
|
|
540
|
+
# @param owner_type [Lutaml::Xml::Schema::Xsd::ComplexType, nil]
|
|
541
|
+
# @param selector_raw [String] the selector XPath as written
|
|
542
|
+
# @param field_raw [String] the field XPath as written
|
|
543
|
+
# @return [Boolean, nil] nil when unresolvable (no determination)
|
|
544
|
+
def field_declaration_simple(owner_type, selector_raw, field_raw)
|
|
545
|
+
sel_type = path_bound_type(owner_type, selector_raw)
|
|
546
|
+
bound = sel_type && path_bound_with_decl(sel_type, field_raw)
|
|
547
|
+
return unless bound
|
|
548
|
+
|
|
549
|
+
field_type, field_decl, owner_self = bound
|
|
550
|
+
return type_supplies_identity_value?(field_type) if owner_self
|
|
551
|
+
return unless field_decl
|
|
552
|
+
|
|
553
|
+
element_declaration_simple?(field_decl)
|
|
554
|
+
end
|
|
555
|
+
|
|
530
556
|
private
|
|
531
557
|
|
|
558
|
+
# Whether a TYPE carries an identity value for a field bound to
|
|
559
|
+
# the selector node itself (a "." field): a simple type or a
|
|
560
|
+
# complex type with simple content does; complex content does not.
|
|
561
|
+
#
|
|
562
|
+
# @param type [Object]
|
|
563
|
+
# @return [Boolean, nil]
|
|
564
|
+
def type_supplies_identity_value?(type)
|
|
565
|
+
case type
|
|
566
|
+
when Lutaml::Xml::Schema::Xsd::SimpleType then true
|
|
567
|
+
when Lutaml::Xml::Schema::Xsd::ComplexType
|
|
568
|
+
!type.simple_content.nil?
|
|
569
|
+
end
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
# The type an identity-constraint XPath (selector or field)
|
|
573
|
+
# binds to, descended through the content model from +type+.
|
|
574
|
+
# A bare "." binds +type+ itself; each named step locates the
|
|
575
|
+
# child element declaration and descends into its type.
|
|
576
|
+
#
|
|
577
|
+
# @param type [Object, nil]
|
|
578
|
+
# @param raw_path [String] the XPath as written
|
|
579
|
+
# @return [Object, nil]
|
|
580
|
+
def path_bound_type(type, raw_path, depth = 0)
|
|
581
|
+
path_bound_with_decl(type, raw_path, depth)&.first
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
# The bound type AND the final element declaration of an
|
|
585
|
+
# identity-constraint XPath. The declaration is nil when the
|
|
586
|
+
# path is a bare "." (the bound node IS the starting element).
|
|
587
|
+
#
|
|
588
|
+
# @param type [Object, nil]
|
|
589
|
+
# @param raw_path [String] the XPath as written
|
|
590
|
+
# @return [Array(Object, Object, Boolean), nil] [type, decl,
|
|
591
|
+
# owner_self] — nil when unresolvable
|
|
592
|
+
def path_bound_with_decl(type, raw_path, depth = 0)
|
|
593
|
+
return nil if type.nil? || depth > 8
|
|
594
|
+
|
|
595
|
+
steps = raw_path.to_s.split("/").reject do |step|
|
|
596
|
+
step.empty? || step == "."
|
|
597
|
+
end
|
|
598
|
+
current = type
|
|
599
|
+
decl = nil
|
|
600
|
+
steps.each_with_index do |step, index|
|
|
601
|
+
local = step.split(":").last
|
|
602
|
+
return nil if step.start_with?("@") || local.nil? || local == "*"
|
|
603
|
+
|
|
604
|
+
decl = declared_child_element(current, local, depth)
|
|
605
|
+
return nil unless decl
|
|
606
|
+
|
|
607
|
+
current = declared_element_type(decl, depth)
|
|
608
|
+
# A nil type mid-path (typeless or unresolvable named)
|
|
609
|
+
# cannot descend further; a nil type on the FINAL step
|
|
610
|
+
# still binds its declaration — typeless defaults to
|
|
611
|
+
# xsd:anyType, which the caller's predicate decides.
|
|
612
|
+
return nil if current.nil? && index < steps.length - 1
|
|
613
|
+
end
|
|
614
|
+
[current, decl, steps.empty?]
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
# The element declaration for +local_name+ in the content model
|
|
618
|
+
# of +type+: its own particles first, then the derivation base
|
|
619
|
+
# chain (derived types inherit the base's particles).
|
|
620
|
+
#
|
|
621
|
+
# @param type [Object, nil]
|
|
622
|
+
# @param local_name [String]
|
|
623
|
+
# @param depth [Integer] recursion guard
|
|
624
|
+
# @return [Lutaml::Xml::Schema::Xsd::Element, nil]
|
|
625
|
+
def declared_child_element(type, local_name, depth = 0)
|
|
626
|
+
return nil if depth > 8
|
|
627
|
+
return nil unless type.is_a?(Lutaml::Xml::Schema::Xsd::ComplexType)
|
|
628
|
+
|
|
629
|
+
found = find_particle_element(particle_for_type(type), local_name,
|
|
630
|
+
depth)
|
|
631
|
+
return found if found
|
|
632
|
+
|
|
633
|
+
content = type.complex_content
|
|
634
|
+
part = content && (Array(content.extension).first ||
|
|
635
|
+
Array(content.restriction).first)
|
|
636
|
+
return nil unless part
|
|
637
|
+
|
|
638
|
+
declared_child_element(
|
|
639
|
+
resolve_named_type(part.base.to_s.split(":").last),
|
|
640
|
+
local_name, depth + 1
|
|
641
|
+
)
|
|
642
|
+
end
|
|
643
|
+
|
|
644
|
+
# Depth-first search for an Element particle named +local_name+.
|
|
645
|
+
# A ref particle binds the GLOBAL declaration (the descent needs
|
|
646
|
+
# its type); prefix-insensitive, the same trade the adapted
|
|
647
|
+
# XPath evaluation makes.
|
|
648
|
+
#
|
|
649
|
+
# @param node [Object, Array, nil]
|
|
650
|
+
# @param local_name [String]
|
|
651
|
+
# @param depth [Integer] recursion guard
|
|
652
|
+
# @return [Lutaml::Xml::Schema::Xsd::Element, nil]
|
|
653
|
+
def find_particle_element(node, local_name, depth = 0)
|
|
654
|
+
return nil if node.nil? || depth > 24
|
|
655
|
+
|
|
656
|
+
if node.is_a?(Array)
|
|
657
|
+
node.each do |child|
|
|
658
|
+
found = find_particle_element(child, local_name, depth + 1)
|
|
659
|
+
return found if found
|
|
660
|
+
end
|
|
661
|
+
return nil
|
|
662
|
+
end
|
|
663
|
+
|
|
664
|
+
if node.is_a?(Lutaml::Xml::Schema::Xsd::Element)
|
|
665
|
+
return node if node.name.to_s.split(":").last == local_name
|
|
666
|
+
|
|
667
|
+
ref_local = node.ref.to_s.split(":").last
|
|
668
|
+
return nil unless ref_local == local_name
|
|
669
|
+
|
|
670
|
+
return resolve_global(ref_local, nil)
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
nested = CONTAINER_NESTED_KINDS[node.class]
|
|
674
|
+
nested&.each do |kind|
|
|
675
|
+
found = find_particle_element(Array(node.public_send(kind)),
|
|
676
|
+
local_name, depth + 1)
|
|
677
|
+
return found if found
|
|
678
|
+
end
|
|
679
|
+
(CONTAINER_LEAF_KINDS[node.class] || []).each do |kind|
|
|
680
|
+
Array(node.public_send(kind)).each do |leaf|
|
|
681
|
+
next unless leaf
|
|
682
|
+
|
|
683
|
+
if leaf.is_a?(Lutaml::Xml::Schema::Xsd::Group)
|
|
684
|
+
resolved = if leaf.ref
|
|
685
|
+
resolve_group(leaf.ref.to_s
|
|
686
|
+
.split(":").last)
|
|
687
|
+
else
|
|
688
|
+
leaf
|
|
689
|
+
end
|
|
690
|
+
found = find_particle_element(resolved, local_name,
|
|
691
|
+
depth + 1)
|
|
692
|
+
return found if found
|
|
693
|
+
elsif leaf.is_a?(Lutaml::Xml::Schema::Xsd::Element)
|
|
694
|
+
found = find_particle_element(leaf, local_name, depth + 1)
|
|
695
|
+
return found if found
|
|
696
|
+
end
|
|
697
|
+
end
|
|
698
|
+
end
|
|
699
|
+
nil
|
|
700
|
+
end
|
|
701
|
+
|
|
702
|
+
# The TYPE a bound element declaration carries: its inline
|
|
703
|
+
# complexType or simpleType, else the named type its +type+
|
|
704
|
+
# attribute resolves to.
|
|
705
|
+
#
|
|
706
|
+
# @param element_decl [Lutaml::Xml::Schema::Xsd::Element]
|
|
707
|
+
# @param depth [Integer] recursion guard
|
|
708
|
+
# @return [Object, nil]
|
|
709
|
+
def declared_element_type(element_decl, _depth = 0)
|
|
710
|
+
return element_decl.complex_type if element_decl.complex_type
|
|
711
|
+
return element_decl.simple_type if element_decl.simple_type
|
|
712
|
+
|
|
713
|
+
name = element_decl.type.to_s
|
|
714
|
+
return nil if name.empty?
|
|
715
|
+
|
|
716
|
+
resolve_named_type(name.split(":").last)
|
|
717
|
+
end
|
|
718
|
+
|
|
719
|
+
# Whether a bound element declaration's type is SIMPLE: an
|
|
720
|
+
# inline simpleType, a simple-named type, or a complex type
|
|
721
|
+
# with SIMPLE content (text value carries the identity value).
|
|
722
|
+
# Complex content — including xsd:anyType, the default for a
|
|
723
|
+
# typeless declaration — is NOT simple.
|
|
724
|
+
#
|
|
725
|
+
# @param element_decl [Lutaml::Xml::Schema::Xsd::Element]
|
|
726
|
+
# @return [Boolean, nil] nil when the named type is unresolvable
|
|
727
|
+
def element_declaration_simple?(element_decl)
|
|
728
|
+
return true if element_decl.simple_type
|
|
729
|
+
|
|
730
|
+
complex = element_decl.complex_type
|
|
731
|
+
return !complex.simple_content.nil? if complex
|
|
732
|
+
|
|
733
|
+
name = element_decl.type.to_s
|
|
734
|
+
return false if name.empty?
|
|
735
|
+
|
|
736
|
+
parts = name.split(":")
|
|
737
|
+
return false if parts.last == "anyType"
|
|
738
|
+
|
|
739
|
+
resolved = resolve_named_type(parts.last)
|
|
740
|
+
case resolved
|
|
741
|
+
when Lutaml::Xml::Schema::Xsd::SimpleType then true
|
|
742
|
+
when Lutaml::Xml::Schema::Xsd::ComplexType
|
|
743
|
+
!resolved.simple_content.nil?
|
|
744
|
+
else
|
|
745
|
+
# Unresolvable but builtin-prefixed (xs:/xsd: both bind the
|
|
746
|
+
# schema namespace) is simple; unresolvable otherwise allows
|
|
747
|
+
# no determination.
|
|
748
|
+
parts.first.to_s.match?(/\Axs(d)?\z/) || nil
|
|
749
|
+
end
|
|
750
|
+
end
|
|
751
|
+
|
|
532
752
|
def particle_for_type(type)
|
|
533
753
|
case type
|
|
534
754
|
when Lutaml::Xml::Schema::Xsd::ComplexType
|
data/lib/lutaml/xsd/version.rb
CHANGED