PureXML/Sources/Schema/SchemaWildcardExpressibility.swift
Mihaela Mihaljevic 084716a1e3
fix(schema): attribute-wildcard union/intersection per Errata E1-10 (#251)
Generalise the wildcard {namespace constraint} model to the four XSD 1.0 (with
Errata E1-10) expressible forms -- any; a set (with absent); a pair of not and
a namespace name; a pair of not and absent -- replacing the any/##other/set
model that could not represent the union/intersection results. Both not forms
exclude absent, so a union admitting "every namespace except a name, plus
absent" (E1-10 rule 5.1.3) has no expressible form.

A new SchemaWildcardExpressibility compile rule rejects a complexContent
extension whose attribute-wildcard union with its base is not expressible
(src-ct.5), closing XSTS wildZ013 (test328873i). The expressible unions are
computed exactly in XSDComplexContent, so a derived type's wildcard admits the
correct attributes at instance time, closing wildZ013a and wildZ013d.

invalid-schemas-accepted 12 -> 11, invalid-instances-accepted 17 -> 15, with
valid-schemas-rejected and valid-instances-rejected held at 0 (full XSTS
archive). The model is additive for element wildcards (which never produce the
new forms); admits/narrows/UPA-overlap updated and verified unchanged for the
##other cases.

The intersection of two not forms negating different namespace names is not
expressible (src-ct.4), a deferred rule: rather than drop the wildcard and
over-reject valid cross-namespace attributes on an accepted schema, the
intersection stays the less-restrictive not(lhs) over-approximation (FP-safe,
matching prior behaviour). Union/intersection algebra taken clean-room from
Xerces-J XSWildcardDecl. Guarded by SchemaWildcardUnionExpressibilityTests.
2026-06-23 04:56:17 +02:00

50 lines
2.9 KiB
Swift

private typealias WildExprNode = PureXML.Schema.XSDNode
extension PureXML.Schema.XSDParser {
/// cos-ct-extends.1.1 / `src-ct.5`: a complexContent extension's {attribute
/// wildcard} is the wildcard union of the extension's own attribute wildcard and
/// the base type's (XSD 1.0 §3.4.2). With Errata E1-10 some unions are NOT
/// expressible -- both `not` namespace-constraint forms exclude absent, so a
/// union admitting "every namespace except a name, PLUS absent" (rule 5.1.3,
/// e.g. base `##other` unioned with `##local` and other names) has no single
/// expressible form, and the schema is invalid. PureXML formerly widened such a
/// union to `##any` and accepted it (XSTS wildZ013, test328873i).
///
/// The union RESULT for the expressible cases is already computed correctly in
/// `XSDComplexContent` (so instance validation admits exactly the right
/// attributes, XSTS wildZ013a/d); this rule reports the not-expressible ones.
///
/// Disclosed under-rejection (deferred): the INTERSECTION not-expressible case
/// (`src-ct.4` / `src-attribute_group.2`, two `not` forms negating different
/// namespace names within one type or attribute group) is not flagged here; it
/// needs no corpus case and is a separate, rarer rule.
///
/// Located on the `extension` node, the component a developer would correct.
/// Self-contained schemas only (the base must resolve to this document's target
/// namespace), matching the other derivation rules.
static func wildcardUnionExpressibilityFindings(
_ schema: XSDTree,
_ context: PureXML.Schema.XSDContext,
_ types: [String: PureXML.Schema.ElementType],
) -> [PureXML.Schema.SchemaLocatedFinding] {
guard !skipsCrossDocumentRules(schema, compositionLoaded: context.compositionLoaded) else { return [] }
let bindings = context.namespaceBindings
var findings: [PureXML.Schema.SchemaLocatedFinding] = []
for content in descendants(schema, named: "complexContent") {
guard let extensionNode = WildExprNode.firstChild(content, named: "extension"),
let base = WildExprNode.attribute(extensionNode, "base"),
WildExprNode.referenceNamespace(base, bindings) == context.targetNamespace,
case let .complex(complex)? = types[WildExprNode.stripPrefix(base)]
else { continue }
let ownWildcard = attributeWildcard(under: extensionNode, context)
if case .notExpressible = PureXML.Schema.Wildcard.union(ownWildcard, complex.attributeWildcard) {
findings.append(PureXML.Schema.SchemaLocatedFinding(
reason: "the union of this extension's attribute wildcard with the base type's attribute wildcard is not expressible (src-ct.5)",
node: extensionNode,
))
}
}
return findings
}
}