A complex type's effective {attribute wildcard} is the intersection of the
anyAttribute wildcards it draws from its own declaration and each referenced
attribute group (XSD 1.0 cos-aw-intersect), not their union. An attribute is
admitted only if every source admits it, so two attribute groups constraining
disjoint namespaces admit nothing and an attribute in either is rejected.
attributeWildcard(under:_:visited:) combined the sources with Wildcard.union,
which let through an attribute that only one source allowed. It now uses a new
Wildcard.intersection covering the namespace cases (##any identity, enumerated
set intersection, enumerated-with-##other exclusion via exclude(_:otherThan:)).
A type with a single anyAttribute source is unchanged (intersection with the
nil identity is the lone wildcard), and a complexContent extension's union with
its base wildcard (XSDComplexContent) is untouched. The inexpressible
##other/##other-different-target and mismatched processContents cases resolve to
the less restrictive result, so the change never over-rejects.
XSTS invalid-instances-accepted 94 to 89, no false positive (valid-instances-
rejected and valid-schemas-rejected held at 0 across the whole corpus), no other
bucket moved. An adversarial review exercised nested groups, ##other/##local and
cross-import namespace cases, processContents, extension-vs-restriction, and
single-source invariance, and found no new over-rejection.
67 lines
3.4 KiB
Swift
67 lines
3.4 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? {
|
|
// 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.Wildcard?
|
|
if let direct = PureXML.Schema.XSDNode.firstChild(node, named: "anyAttribute").map({ wildcard($0, context) }) {
|
|
combined = PureXML.Schema.Wildcard.intersection(combined, direct)
|
|
}
|
|
for child in PureXML.Schema.XSDNode.elementChildren(node) {
|
|
guard PureXML.Schema.XSDNode.localName(child) == "attributeGroup",
|
|
let ref = PureXML.Schema.XSDNode.attribute(child, "ref")
|
|
else { continue }
|
|
let name = PureXML.Schema.XSDNode.stripPrefix(ref)
|
|
if visited.contains(name) {
|
|
guard context.redefinedAttributeGroups.contains(name),
|
|
let base = context.baseAttributeGroups[name],
|
|
let found = attributeWildcard(under: base, context, visited: visited)
|
|
else { continue }
|
|
combined = PureXML.Schema.Wildcard.intersection(combined, found)
|
|
continue
|
|
}
|
|
guard let group = context.attributeGroups[name] else { continue }
|
|
let scoped = context.scoped(for: PureXML.Schema.XSDNode.schemaOwner(group))
|
|
if let found = attributeWildcard(under: group, scoped, visited: visited.union([name])) {
|
|
combined = PureXML.Schema.Wildcard.intersection(combined, found)
|
|
}
|
|
}
|
|
return combined
|
|
}
|
|
|
|
/// 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": return .other
|
|
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))
|
|
}
|
|
}
|
|
}
|