Commit Graph

18 Commits

Author SHA1 Message Date
dd10f32abb feat(schema): a simpleType must have exactly one of restriction/list/union
Per XSD 1.0 Datatypes a simpleType's content is
(annotation?, (restriction | list | union)). The membership table admitted each
name without the cardinality, so an empty simpleType or one combining two
derivations (two unions, a union and a list, two restrictions) was accepted.
The exactly-one rule is now enforced, mirroring the named-group compositor check.

XSTS: invalid schemas accepted 537 to 527, valid schemas rejected held at 72,
instance buckets unchanged.
2026-06-14 14:30:25 +02:00
11c4eacdff feat(schema): validate final/block derivation-control value space
The final and block attributes are #all or a whitespace list of method tokens,
but the admitted tokens depend on the component: element final is
extension/restriction, element block adds substitution, complexType final/block
are extension/restriction, simpleType final is list/union/restriction. The
tokens were parsed leniently (unknown ignored, substitution admitted
everywhere), so final="substitution" on an element, a misspelled or miscased
token, or #all mixed with other tokens was accepted.

The list is validated against the component's exact set; finalDefault/
blockDefault (the wider schema-level value space) and foreign attributes are
left alone.

XSTS: invalid schemas accepted 580 to 537, valid schemas rejected held at 72,
instance buckets unchanged.
2026-06-14 14:19:10 +02:00
6a7fb1d594 feat(schema): enforce simpleContent derivation content order
A simpleContent restriction is (annotation?, (simpleType?, facet*),
(attribute|attributeGroup)*, anyAttribute?) and an extension is
(annotation?, (attribute|attributeGroup)*, anyAttribute?): no model group, no
facets/simpleType in an extension, attributes before a single anyAttribute. The
membership table admitted the names but not the order or the model-group ban, so
a model group inside simpleContent, a facet in an extension, a misordered facet
or attribute, or a second anyAttribute (the ctD family) was accepted.

The derivation's children are matched against the context's ordered slot model,
reusing the slot walk built for complexContent. The structure walk's
per-component dispatch is factored into a helper to keep its complexity bounded.

XSTS: invalid schemas accepted 594 to 580, valid schemas rejected held at 72,
instance buckets unchanged.
2026-06-14 14:05:49 +02:00
3e3b937e89 feat(schema): validate built-in-typed default/fixed values
An element/attribute default/fixed value must be a valid value of its type
(Attribute/Element Locally Valid, XSD 1.0). A fixed="abc" on an xs:integer or a
default="1 2" on an xs:int was accepted because the value constraint was never
checked at compile time.

The value is now validated against its type when the type resolves to a
built-in datatype (namespace-aware: only a type in the XSD namespace),
normalising whitespace per the type so a default=" 5 " on an integer stays
valid. A named or inline user type is left to its own rules, so no valid value
constraint is rejected.

XSTS: invalid schemas accepted 638 to 621, valid schemas rejected held at 72,
instance buckets unchanged.
2026-06-14 11:55:55 +02:00
0143381f67 feat(schema): enforce facet applicability to simple-type variety
A constraining facet must apply to its simple type's variety (XSD 1.0 Part 2):
a list may be constrained only by length/minLength/maxLength, pattern,
enumeration, whiteSpace; a union only by pattern and enumeration. A value-bound
facet (maxInclusive, totalDigits, ...) constrains an atomic value space, so
restricting a list of integer by maxInclusive was wrongly accepted.

The base's variety is resolved from the raw tree (this runs before the types
are compiled), namespace-aware so a user type sharing a built-in's local name
does not flip the variety: a base in the XSD namespace is a built-in (only the
list datatypes are list-variety), a base in the schema's own target namespace
resolves to a declared global, and an external or unresolvable base is skipped.

XSTS: invalid schemas accepted 664 to 638, valid schemas rejected held at 72,
instance buckets unchanged.
2026-06-14 11:36:21 +02:00
9d4ce304c2 feat(schema): enforce complexType shorthand content order
A complexType's shorthand content (no simpleContent/complexContent) has the
model (annotation?, (group|all|choice|sequence)?, (attribute|attributeGroup)*,
anyAttribute?). The existing content-shape check caught the wrong number of
model groups and content-spec exclusivity, but not the order, so a model group
after an attribute, an attribute after anyAttribute, or a second anyAttribute
(the ctB family) was accepted.

The complexType's direct children, excluding annotation and the content specs,
are matched against the same ordered slot model used for a complexContent
derivation.

XSTS: invalid schemas accepted 687 to 664, valid schemas rejected held at 72,
instance buckets unchanged.
2026-06-14 11:15:24 +02:00
e016775a82 feat(schema): validate wildcard namespace constraint value space
An any/anyAttribute namespace constraint is ('##any' | '##other') standing
alone, or a list of namespace URIs and the ##targetNamespace/##local tokens.
The wildcard parser mapped any unrecognised token to a literal URI, so a
misspelled token (##all, ##target), an ##any/##other inside a list, or any
other ##-prefixed token was accepted.

The unprefixed namespace value is now tokenised and checked against that
grammar, with ##any/##other admitted only as the whole value. A prefixed
(foreign) attribute sharing the local name is the author's own and is not
checked, so no valid schema is rejected.

XSTS: invalid schemas accepted 736 to 687, valid schemas rejected held at 72,
instance buckets unchanged.
2026-06-14 11:00:45 +02:00
cfaf9b7dd1 feat(schema): validate identity-constraint selector/field XPath subset
An identity constraint's selector and field xpath must be in the XSD 1.0
restricted-XPath subset (Part 1, 3.11.6), far narrower than full XPath: a
leading optional .//, then /-separated steps, a step being '.', a name test
(QName, '*', or NCName:*), or a child:: axis; a field path may end in an
attribute step (@x or attribute::x). No predicates, no other axes, no absolute
or interior //. The xpath was stored raw and compiled lazily with try?, so a
malformed one was silently accepted.

It is now lexed into a whitespace-tolerant token stream and parsed against the
grammar: whitespace between tokens is allowed (child :: a, . //.) while // must
be adjacent and a name test's own ':' may not be spaced. The name lexer uses
the XML NameChar set so a non-ASCII NCName is accepted.

The two identity tests that fed a malformed xpath and expected a
validation-time located error now assert the stronger compile-time rejection.

XSTS: invalid schemas accepted 785 to 736, valid schemas rejected held at 72,
instance buckets unchanged.
2026-06-14 10:30:06 +02:00
8a7445c5bb feat(schema): enforce complexContent derivation content order
A restriction or extension inside complexContent has the content model
(annotation?, (group|all|choice|sequence)?, (attribute|attributeGroup)*,
anyAttribute?). The allowedChildren membership table admitted the right child
names but not their order or cardinality, so a second model group, a model
group after an attribute, an attribute after anyAttribute, or a second
anyAttribute was accepted.

A forward-only walk matches the children against an ordered slot model (one
model group, unbounded attributes, one anyAttribute). The check runs only in
the complexContent context, where a model group belongs to the model, so a
simpleContent derivation (facets, no model group) keeps its own rules and no
valid schema is rejected.

XSTS: invalid schemas accepted 864 to 785, valid schemas rejected held at 72,
instance buckets unchanged.
2026-06-14 09:56:04 +02:00
243a6dbc9b feat(schema): enforce Element Declarations Consistent (cos-element-consistent)
Within one complex type's content model, two element declarations with the same
name must have the same type definition. Two same-name elements with different
types were accepted. elementDeclsConsistentErrors collects element particles
across the content model -- descending through model groups and the content-
derivation wrappers, but never into an element's own nested type, which is a
separate content model -- and flags a name seen with more than one distinct type
key (a type reference by local name, a distinct token per inline type, or the
untyped ur-type).

Modeled clean-room on Xerces (libxml2 enforces this only partially). Substitution-
group expansion is not folded in, an accepted under-rejection.

XSTS: invalid schemas accepted 925 to 909 (-16); valid schemas rejected held at
72; instance buckets unchanged.
2026-06-14 01:47:43 +02:00
39222ca760 feat(schema): enforce schema-vocabulary attribute applicability
The attribute sibling of the content-model table. Each XSD component admits a
fixed set of attributes; an unprefixed attribute outside that set (a typo like
nullable, a stray foo, name on a sequence) was accepted because nothing checked
attribute applicability. allowedAttributes now lists each component's admitted
unprefixed attributes (the permissive union of global and local use), and
attributeApplicabilityErrors rejects the rest, plus ref co-occurring with name
or type.

Foreign (prefixed) attributes and namespace declarations are the schema author's
own and are not checked, so no valid schema is rejected.

XSTS: invalid schemas accepted 956 to 925 (-31); valid schemas rejected held at
72; instance buckets unchanged.
2026-06-14 01:18:03 +02:00
b37e97b5a9 feat(schema): a named group must contain exactly one model group
XSD 1.0 gives a named model-group definition's content as
(annotation?, (all | choice | sequence)) -- exactly one compositor. The
allowedChildren set-membership check allowed any number, so an xs:group with two
compositors, or with none, was accepted. namedGroupErrors now enforces the
cardinality, gated on the node being a definition (has a name) so a group
reference is never flagged.

XSTS: invalid schemas accepted 977 to 956 (-21); valid schemas rejected held at
72; instance buckets unchanged.
2026-06-14 01:04:25 +02:00
09a0f9bf5c feat(schema): reject a particle whose minOccurs exceeds its maxOccurs
An occurrence range like minOccurs="5" maxOccurs="2" is empty and illegal in
XSD, but the validator accepted it (only the individual values were checked).
occurrenceOrderErrors now compares the two on a particle that carries both: the
comparison is by canonical magnitude (sign and leading zeros stripped, length
then lexicographic), so it holds beyond a machine integer; maxOccurs="unbounded"
is never exceeded and an equal single-point range is allowed.

XSTS: invalid schemas accepted 997 to 977 (-20); valid schemas rejected held at
72; instance buckets unchanged.
2026-06-14 00:47:51 +02:00
6fbc92593e feat(schema): enforce the complexType content-model shape
XSD 1.0 allows a complexType's content to be simpleContent, or complexContent,
or a model group followed by attributes, never a mix. The allowedChildren
set-membership check permitted illegal combinations, so a complexType with two
simpleContent, simpleContent beside a sequence, or two model groups was accepted.

complexTypeContentErrors now enforces, over the complexType's direct children:
at most one of simpleContent/complexContent; when one is present, no direct model
group and no direct attribute (those belong inside the extension/restriction);
and at most one model group. Only direct children are examined, so attributes
nested inside a derivation are not flagged.

XSTS: invalid schemas accepted 1051 to 997 (-54); valid schemas rejected held at
72; instance buckets unchanged. Also folds in a behavior-identical lint cleanup
of the reference-walk external-definition check.
2026-06-14 00:32:54 +02:00
cbeea0e3c2 feat(schema): reject unresolvable references at compile time
Every QName a schema names must resolve to a declared component or a built-in:
type/base/itemType/memberTypes to a type, element/attribute/group/attributeGroup
ref to the matching global component, and element substitutionGroup to a global
element. An undeclared reference was previously accepted at compile time and only
surfaced lazily during instance validation, if at all.

References are matched by local name, with the value trimmed (a QName attribute
is whiteSpace=collapse). The whole check is skipped when the document declares an
import/include/redefine, since the default compile does not load externals and
the pools would be incomplete; that guard is gated on the XSD namespace so a
foreign element named "import" does not trip it.

XSTS: invalid schemas accepted 1092 to 1051 (-41); valid schemas rejected held
at 72 (an interim version regressed one valid schema on a whitespace-padded
base="  xs:string ", fixed by trimming); instance buckets unchanged.
2026-06-14 00:17:29 +02:00
ca8fd27877 feat(schema): check name and reference attribute lexical validity
Extends the compile-time attribute-model check. A component's name must be a
valid NCName, the reference attributes type/base/ref/itemType/refer/
substitutionGroup must be lexical QNames, and memberTypes a list of QNames.
These were unchecked, so name="", name="123", name="a:b", or type=":_" left the
schema wrongly accepted.

Only the lexical form is checked (a QName's prefix is resolved elsewhere), so a
valid reference is never rejected; each of these attribute names has one type
throughout XSD, so checking by local name is safe.

XSTS: invalid schemas accepted 1190 to 1129 (-61); valid schemas rejected held
at 72 (no over-rejection); instance buckets unchanged.
2026-06-13 23:16:47 +02:00
8011f3d2e8 feat(schema): validate schema-vocabulary attribute values at compile time
The attribute-model sibling of the structural content-model check, folded into
the same schema-document walk. Enumerated attributes (form,
elementFormDefault, attributeFormDefault, use, processContents, mixed, abstract,
nillable) and the occurrence attributes (minOccurs a nonNegativeInteger,
maxOccurs a nonNegativeInteger or unbounded) are checked against their fixed
value space. An out-of-space value (use="foo", mixed="yes", minOccurs="-1") was
previously ignored and the schema accepted.

Each of these names has one meaning throughout XSD, so checking by local name is
safe. Only unprefixed attributes on XSD-namespace elements are checked, so a
foreign attribute is never flagged; values are trimmed before comparison.

XSTS: invalid schemas accepted 1313 to 1190 (-123); valid schemas rejected held
at 72 (no over-rejection); instance buckets unchanged.
2026-06-13 23:01:42 +02:00
6147666e49 feat(schema): structural validity against the schema-for-schemas content model
The general mechanism for the structural tail of the schema-validity gap, in
place of hand-coding a rule per component. A data-driven table encodes the XSD
1.0 schema-for-schemas child content model; at compile time it rejects any
component child the model does not admit, more than one annotation (or an
annotation that is not first, except on schema/redefine, which allow several),
and an identity constraint missing its selector or field.

The walk visits only XSD-namespace elements and skips appinfo/documentation
foreign content, so foreign children are never flagged; restriction/extension
use the permissive union of their simple/complex/simpleContent contexts, so a
child valid in some context is never rejected. One table covers every component.

XSTS: invalid schemas accepted 1688 to 1313 (-375); valid schemas rejected held
at 72 (no over-rejection); instance buckets unchanged.
2026-06-13 22:48:20 +02:00