Commit Graph

16 Commits

Author SHA1 Message Date
ca65a334f7 refactor: split oversized types across companion files for lint budgets 2026-08-31 05:00:44 +02:00
380160cc2b refactor(validation): decompose XSD instance validation into composable rules
The two monolithic XSD content/identity rules only satisfied the OpenAPIKit
validation idiom at the wrong granularity: no per-constraint removability,
no isolable failing/near-miss tests. Split them into 32 named rules, each
finding tagged at its origin and cached per XSDContext so the walk still
runs once per document (the #101 decision against duplicating XSD's
recursive type resolution still holds). Wired the streaming path's ID/IDREF
and identity checks through the same framework instead of calling the
validators imperatively, and added the unwrapAndLookup combinator the rule
already named as required.

Also includes the mandated repo-wide swiftformat/swiftlint pass.
2026-07-12 14:12:17 +02:00
d1a371a6a9
perf(schema): make xsd identity-constraint validation linear (#312)
xs:unique/xs:key/xs:keyref were quadratic over a wide list of targets.
The duplicate check compared each value tuple against every tuple already
seen (and a keyref against every referenced key), and the error-location
helper rescanned the parent's whole child list to position each target.

Key tuples in a Set by their raw string when field equality provably
reduces to a raw string comparison (whitespace-preserving lexical types,
no colon, which is most ids); value-space types (numeric, boolean, date),
whitespace-collapsing types, and QName-bearing values keep the exact
pairwise comparison, so which documents pass is unchanged. Position each
target through a per-parent, per-name index built once.

A 16000-item xs:unique document drops from about 4.0 s to about 0.12 s
(~34x), and the cost is now linear. The full local suite and the W3C
XSTS datatype/identity conformance corpus are unchanged.
2026-06-26 02:45:17 +02:00
be8a39394d fix(schema): advance XSTS valid-instances-rejected to 4 (#146)
Consolidates the in-progress #146 work; valid-instances-rejected reaches 4
on the 2006-11-06 XSTS archive (buckets now 1/262/4/147).

Regex: match Unicode scalars rather than grapheme clusters; the empty
pattern matches only the empty string; pin the category repertoire to
Unicode 3.2, the version the corpus is consistent at, so code points added
in 3.2 (Tagalog, Cyrillic Komi) are word characters while later additions
(the Tamil digit zero) stay unassigned and match \D. New XSDCategory and a
3.2 assignment table derived from DerivedAge, replacing the old Unicode31
files.

Content model: assess each child against the particle the content automaton
matched, not a by-name lookup (#180). Two same-named particles keep their
own fixed values, and an element matching a skip wildcard is no longer
validated against its global declaration. Selection is made reproducible
when an ambiguous schema slips past the UPA check.

XSTS runner: compile and merge every schemaDocument in a schemaTest, not
just the first; this also surfaced one previously-masked invalid-instance
acceptance (recorded in the baselines).

Also folds in attribute-group anyAttribute inheritance, extension wildcard
union, namespace-qualified type tables for multi-document
xsi:schemaLocation, per-container elementFormDefault, strict and lax
attribute wildcard validation, built-in type resolution, and xs:redefine
simpleType overrides.
2026-06-16 22:22:05 +02:00
55daca46be fix(schema): implement facet applicability and validation constraints 2026-06-16 00:02:04 +02:00
20b97d1d62 fix(schema): validate notation declarations and enumeration restrictions 2026-06-15 22:27:04 +02:00
b6575150e8 feat(schema): enforce attribute restriction type derivation (derivation-ok-restriction.1)
Require that any restricted attribute has a type validly derived by restriction from its base counterpart. Validates atomic built-in derivation, list item type restrictions, and union member/subset relationships recursively. Updates baseline to 362 accepted invalid schemas.
2026-06-15 17:46:15 +02:00
f86a7c9ade feat(schema): enforce attribute fixed value constraint under restriction 2026-06-15 16:24:32 +02:00
4bd23d8286 feat(schema): built-in xsi:type resolution gated by substitution validity
Resolve a built-in datatype named by xsi:type (xsd:int, xsd:decimal,
xsd:string), which previously resolved only against schema-declared types and
so reported "unknown" and wrongly rejected the element. The built-in is now
resolved when it validly substitutes for the declared type per Schema-Validity
Assessment: the ur-type (anyType/anySimpleType) admits any type, and a bare
built-in declared type admits any built-in derived from it through the XSD
Part 2 datatype lattice (new BuiltinType.derives(from:)). An invalid built-in
substitution is a located error, not a silent acceptance.

Resolving the override correctly exposed two pre-existing gaps, both closed so
the change carries no invalid-instances-accepted regression:

- A simple-typed element rejects non-schema-instance attributes. A simple type
  declares no attribute uses, so an element of simple type (including via an
  xsi:type override, and including an xsi:nil element) may carry only xsi:* and
  namespace attributes. Checked before the nil short-circuit.
- Identity-constraint fields compare in their value space when the selected
  node carries an xsi:type. unique/key/keyref tuples compared lexically, so two
  xsi:type="xsd:decimal" fields 3.0 and 3 were treated as distinct and a unique
  violation was missed. Each field value now carries the type read from the
  node's xsi:type and compares via SimpleType.valueMatches when both share a
  built-in base, otherwise lexically.

XSTS: valid instances rejected 248 to 233; invalid instances accepted held at
165. Identity tests corrected to spec-valid schemas (an element carrying an
attribute must be a complex type, not type="xs:string").
2026-06-13 21:21:35 +02:00
f94b2e43e0 style(schema): satisfy swiftlint on the XSTS conformance additions
The conformance fixes tripped swiftlint --strict (it had been run only as
swiftformat): the test helper binding `s` is too short (now `doc`), the
duration reference dateTimes were a three-member tuple (now (year, month) with
day fixed at 1), and DurationValue.Order nested two levels deep (now the
sibling DurationOrder). No behavior change; full suite and XSTS stay green.
2026-06-13 18:04:25 +02:00
c6305c9d93 fix(schema): treat QName length facets as non-constraining
XSD 1.0 Datatypes 4.3.1 defines the unit of length only for string, anyURI,
binary, and list; for QName it is implementation-defined. We had counted
characters, rejecting valid QName values. Like Xerces and the XSTS NIST set we
now treat length/minLength/maxLength on QName as always satisfied. Lexical
QName validity is still enforced.

The suite's contributors disagree here (NIST: non-constraining; Microsoft: a
character count), which the spec does not resolve, so a single behavior cannot
satisfy both. Net XSTS: valid instances rejected 547 to 502; 4 Microsoft cases
move to invalid instances accepted (257 to 261), recorded as a deliberate
interpretation in docs/xsts-deviations.md.

Refs #147, #129.
2026-06-13 18:04:25 +02:00
e6a0d19c9b fix(schema): enforce ordering facets on xs:duration
minInclusive, maxInclusive, minExclusive, and maxExclusive on xs:duration were
ignored: duration has no total order, so the generic comparator returned no
value and the bounds were skipped. A DurationValue now implements the XSD 1.0
Datatypes 3.2.6.2 partial order, comparing two durations by adding each to the
four reference dateTimes the spec fixes (a month is 28 to 31 days, so the order
is partial). A value incomparable to the bound, such as P1M against P30D, is
outside the ordered range and is rejected.

XSTS: invalid instances accepted 355 to 257. Baseline updated; regression tests
cover the total-order cases, the exclusive bounds, and the incomparable case.

Refs #147, #129.
2026-06-13 18:04:25 +02:00
8f89acc354 fix(schema): enforce pattern and enumeration facets on union types
A union admits any value valid for one of its member types, but pattern and
enumeration (the only constraining facets XSD 1.0 Datatypes 4.1.5 allows on a
union) were ignored, so a member-valid value outside the enumeration or failing
the pattern was accepted. validateUnion now applies both on top of membership.
Enumeration compares in the value space of the admitting member, so 01 equals
an enumerated 1 for an integer member, consistent with the atomic enumeration
path, rather than a lexical compare.

XSTS #147: invalid instances accepted 555 to 355. Baseline updated; regression
tests cover enumeration (including value-space), pattern, and the no-facet case.

Refs #147, #129.
2026-06-13 18:04:25 +02:00
79f424321c feat(relaxng): compare typed <value> in the datatype value space
A <value> pattern now carries its datatype, so matching compares the
instance and the literal in that type's value space rather than purely
lexically. Numeric and date/time types compare by value (1 equals 01,
1.5 equals 1.50); booleans treat 1/true and 0/false as equal; string and
token compare by their whitespace-normalized lexical form, as before.
Untyped values default to the built-in token type, and a bare string
literal in the compact syntax is token-typed. Durations and binary types
fall under the lexical rule.
2026-06-08 17:36:16 +02:00
cadc63ba8b feat(xsd): add list/union simple types, attribute groups, and element/group refs
Extend the schema parser toward full structural parity:

- list simple types validate each whitespace-separated item against the item
  type and count items for the length facets; union simple types admit a value
  valid for any member type. A restriction over a named list or union inherits
  its variety.
- Named attribute groups are expanded (transitively) into every complex type
  that references them.
- Named model groups are expanded at each xs:group ref, with a cycle guard;
  element refs resolve to the referenced global element's declared type,
  through a reserved type-table key so forward and recursive refs settle at
  validation time.

Named simple types now resolve over repeated passes so they may restrict or
list one another regardless of document order. Simple-type and facet parsing
moves into a sibling XSDSimpleParser to keep each parser within bounds.

Closes #65
2026-06-08 08:28:17 +02:00
e862f364aa feat(schema): XSD simple-type datatype library with facets
Add the PureXML.Schema namespace validating XSD Part 2 built-in datatypes
and constraining facets. Built-ins span string/boolean/decimal, the bounded
integer family with intrinsic bounds, float/double, duration, the eight
date/time types, hexBinary, base64Binary, anyURI, QName, and the
Name/NCName/NMTOKEN/language family. Facets: length/minLength/maxLength,
pattern (via the regex engine), enumeration (value-space comparison),
whiteSpace, min/maxInclusive/Exclusive, totalDigits, fractionDigits.
Decimals compare exactly with no floating-point loss; date/time fields are
range-validated with leap years, 24:00:00, and timezone bounds.

Closes #8
2026-06-08 06:29:37 +02:00