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.
100 lines
5.0 KiB
Swift
100 lines
5.0 KiB
Swift
extension PureXML.Schema.XSDParser {
|
|
/// The effective `xs:anyAttribute` wildcard declared under `node`, including
|
|
/// any `anyAttribute` on nested `attributeGroup` references.
|
|
static func attributeWildcard(
|
|
under node: XSDTree,
|
|
_ context: PureXML.Schema.XSDContext,
|
|
visited: Set<String> = [],
|
|
) -> PureXML.Schema.Wildcard? {
|
|
// Best-effort view for type building: a not-expressible intersection makes
|
|
// the schema invalid (flagged by SchemaWildcardExpressibility), so the type
|
|
// it would have carried is never used to validate a valid instance.
|
|
switch attributeWildcardCombination(under: node, context, visited: visited) {
|
|
case let .constraint(wildcard): wildcard
|
|
case .notExpressible: nil
|
|
}
|
|
}
|
|
|
|
/// The effective `xs:anyAttribute` wildcard under `node`, or `notExpressible`
|
|
/// when intersecting the sources yields no expressible {namespace constraint}.
|
|
static func attributeWildcardCombination(
|
|
under node: XSDTree,
|
|
_ context: PureXML.Schema.XSDContext,
|
|
visited: Set<String> = [],
|
|
) -> PureXML.Schema.WildcardCombination {
|
|
// A complex type's (or attribute group's) effective {attribute wildcard} is
|
|
// the INTERSECTION of the wildcards it draws from its own `anyAttribute` and
|
|
// from every referenced attribute group (XSD 1.0 `cos-aw-intersect`): an
|
|
// attribute is admitted only if every source admits it. (An extension's
|
|
// union with its base wildcard is handled separately, in XSDComplexContent.)
|
|
var combined: PureXML.Schema.WildcardCombination = .constraint(nil)
|
|
func merge(_ next: PureXML.Schema.Wildcard?) {
|
|
guard case let .constraint(current) = combined else { return }
|
|
combined = PureXML.Schema.Wildcard.intersection(current, next)
|
|
}
|
|
if let direct = PureXML.Schema.XSDNode.firstChild(node, named: "anyAttribute").map({ wildcard($0, context) }) {
|
|
merge(direct)
|
|
}
|
|
for child in PureXML.Schema.XSDNode.elementChildren(node) {
|
|
guard case .constraint = combined else { break }
|
|
guard PureXML.Schema.XSDNode.localName(child) == "attributeGroup",
|
|
let ref = PureXML.Schema.XSDNode.attribute(child, "ref")
|
|
else { continue }
|
|
switch referencedGroupWildcard(PureXML.Schema.XSDNode.stripPrefix(ref), context, visited: visited) {
|
|
case let .constraint(found): merge(found)
|
|
case .notExpressible: combined = .notExpressible
|
|
}
|
|
}
|
|
return combined
|
|
}
|
|
|
|
/// The effective wildcard combination of the attribute group referenced by
|
|
/// `name`, resolving a redefine self-reference against the group's base.
|
|
private static func referencedGroupWildcard(
|
|
_ name: String,
|
|
_ context: PureXML.Schema.XSDContext,
|
|
visited: Set<String>,
|
|
) -> PureXML.Schema.WildcardCombination {
|
|
if visited.contains(name) {
|
|
guard context.redefinedAttributeGroups.contains(name),
|
|
let base = context.baseAttributeGroups[name]
|
|
else { return .constraint(nil) }
|
|
return attributeWildcardCombination(under: base, context, visited: visited)
|
|
}
|
|
guard let group = context.attributeGroups[name] else { return .constraint(nil) }
|
|
let scoped = context.scoped(for: PureXML.Schema.XSDNode.schemaOwner(group))
|
|
return attributeWildcardCombination(under: group, scoped, visited: visited.union([name]))
|
|
}
|
|
|
|
/// Parses a wildcard's `namespace` and `processContents` constraints.
|
|
static func wildcard(_ node: XSDTree, _ context: PureXML.Schema.XSDContext) -> PureXML.Schema.Wildcard {
|
|
let processContents: PureXML.Schema.ProcessContents = switch PureXML.Schema.XSDNode.attribute(node, "processContents") {
|
|
case "skip": .skip
|
|
case "lax": .lax
|
|
default: .strict
|
|
}
|
|
let namespace = wildcardNamespace(PureXML.Schema.XSDNode.attribute(node, "namespace"), context)
|
|
return PureXML.Schema.Wildcard(namespace: namespace, processContents: processContents, targetNamespace: context.targetNamespace)
|
|
}
|
|
|
|
private static func wildcardNamespace(_ value: String?, _ context: PureXML.Schema.XSDContext) -> PureXML.Schema.WildcardNamespace {
|
|
switch value {
|
|
case nil, "##any": return .any
|
|
case "##other":
|
|
// `##other` = any namespace name other than the target, never absent.
|
|
// With no target namespace it is simply "any non-absent namespace".
|
|
if let target = context.targetNamespace, !target.isEmpty { return .notNamespace(target) }
|
|
return .notAbsent
|
|
default:
|
|
let uris = (value ?? "").split(whereSeparator: \.isWhitespace).map { token -> String in
|
|
switch token {
|
|
case "##targetNamespace": context.targetNamespace ?? ""
|
|
case "##local": ""
|
|
default: String(token)
|
|
}
|
|
}
|
|
return .enumerated(Set(uris))
|
|
}
|
|
}
|
|
}
|