PureXML/Sources/Schema/ComplexValidatorDerivation.swift
Mihaela Mihaljevic 43456029ac fix(schema): enforce element-level block against xsi:type substitution
The block on an element declaration (distinct from block on its type) was
parsed into the derivation tables but never threaded to the instance validator,
so an xsi:type that substitutes a type derived by a blocked method was accepted.
elementBlock now flows through XSDCompiled, SchemaDocument, the validation
context, and both ComplexValidator construction sites to blockedSubstitutionError,
which checks the element's own block alongside the type's. It also resolves the
element-ref type indirection (a ref'd child carries an `element:foo` key that
resolves through `types` to the concrete declared type) so the derivation chain
and block checks fire for referenced children, not only the document root.

XSTS: invalid instances accepted 261 to 240. Baseline updated; regression tests
cover element block rejecting a restriction xsi:type, the no-block control, and
an unrelated block not over-rejecting.

Refs #147, #129.
2026-06-13 18:04:25 +02:00

56 lines
2.7 KiB
Swift

extension PureXML.Schema.ComplexValidator {
/// The error when an element's declared type is an abstract complex type and
/// the element supplies no `xsi:type` to name a concrete derived type. An
/// abstract type cannot itself be the type of an instance element.
func abstractTypeError(
named name: String,
child: PureXML.Model.Element,
at path: [PureXML.Validation.PathKey],
) -> PureXML.Validation.ValidationError? {
guard abstractTypes.contains(name), Self.xsiTypeName(child) == nil else { return nil }
return PureXML.Validation.ValidationError(
reason: "element '\(child.name.localName)' has abstract type '\(name)' and requires an xsi:type naming a concrete derived type",
at: path,
)
}
/// The error when an `xsi:type` substitution is forbidden by `block` on the
/// declared type: the substituted type reaches the declared type by a method
/// the declared type lists in `block`. Returns nil when there is no named
/// declared type, no `block`, or the substitution is permitted.
func blockedSubstitutionError(
declared: PureXML.Schema.ElementType,
child: PureXML.Model.Element,
at path: [PureXML.Validation.PathKey],
) -> PureXML.Validation.ValidationError? {
guard case let .typeReference(reference) = declared,
let substitute = Self.xsiTypeName(child), types[substitute] != nil
else {
return nil
}
// A ref'd child carries its type as an element-ref key (`element:foo`)
// that resolves through `types` to the concrete type; follow that chain
// to the declared type name the derivation backbone and `block` use.
var declaredName = reference
var steps = 0
while case let .typeReference(next)? = types[declaredName], next != declaredName, steps <= types.count {
declaredName = next
steps += 1
}
guard let methods = PureXML.Schema.XSDParser.derivationMethods(from: substitute, to: declaredName, typeDerivation) else {
return nil
}
// The substitution is blocked when the derivation method is disallowed by
// either the declared type's `block` or the element declaration's own
// `block` (keyed by the element's name).
let blocked = (typeBlock[declaredName] ?? []).union(elementBlock[child.name.localName] ?? [])
guard !methods.isDisjoint(with: blocked) else {
return nil
}
return PureXML.Validation.ValidationError(
reason: "xsi:type '\(substitute)' is blocked: substitution by this derivation is disallowed",
at: path,
)
}
}