The two monolithic XSD content/identity rules only satisfied the OpenAPIKit validation idiom at the wrong granularity: no per-constraint removability, no isolable failing/near-miss tests. Split them into 32 named rules, each finding tagged at its origin and cached per XSDContext so the walk still runs once per document (the #101 decision against duplicating XSD's recursive type resolution still holds). Wired the streaming path's ID/IDREF and identity checks through the same framework instead of calling the validators imperatively, and added the unwrapAndLookup combinator the rule already named as required. Also includes the mandated repo-wide swiftformat/swiftlint pass.
117 lines
5.4 KiB
Swift
117 lines
5.4 KiB
Swift
struct ReferenceCheckContext {
|
|
let types: Set<String>
|
|
let pools: [String: Set<String>]
|
|
let bindings: [String: String]
|
|
let targetNamespace: String?
|
|
let foreignPools: [String?: [String: Set<String>]]
|
|
/// A no-`targetNamespace` included schema whose globals adopt the includer's namespace.
|
|
let chameleonNamespace: Bool
|
|
}
|
|
|
|
extension PureXML.Schema.XSDParser {
|
|
/// The built-in type names a reference may name without a declaration: the
|
|
/// XSD Part 2 datatypes, the built-in list types, and the ur-types.
|
|
static let referenceBuiltins: Set<String> = {
|
|
var names = Set(PureXML.Schema.BuiltinType.allCases.map(\.rawValue))
|
|
names.formUnion(["anyType", "anySimpleType", "anyAtomicType", "NOTATION", "IDREFS", "ENTITIES", "NMTOKENS"])
|
|
return names
|
|
}()
|
|
|
|
/// Namespaces commonly referenced without a loadable schema document (`xml:`,
|
|
/// and similar). References into them stand down rather than being rejected.
|
|
static let unloadedReferenceNamespaces: Set<String> = [
|
|
"http://www.w3.org/XML/1998/namespace",
|
|
]
|
|
|
|
static func referenceURI(for qname: String, bindings: [String: String]) -> String? {
|
|
let prefix = PureXML.Schema.XSDNode.prefix(qname)
|
|
var uri = prefix.flatMap { bindings[$0] } ?? bindings[""]
|
|
if uri == nil, prefix == "xml" {
|
|
uri = "http://www.w3.org/XML/1998/namespace"
|
|
}
|
|
return uri
|
|
}
|
|
|
|
static func isUndeclaredReferenceType(_ qname: String, in context: ReferenceCheckContext) -> Bool {
|
|
let uri = referenceURI(for: qname, bindings: context.bindings)
|
|
let localPart = referenceLocalName(qname)
|
|
if uri == xsdNamespace {
|
|
if context.targetNamespace == xsdNamespace {
|
|
return !referenceBuiltins.contains(localPart) && !context.types.contains(localPart)
|
|
} else {
|
|
return !referenceBuiltins.contains(localPart)
|
|
}
|
|
}
|
|
if let uri, unloadedReferenceNamespaces.contains(uri) {
|
|
return false
|
|
}
|
|
let inTargetNamespace = uri == context.targetNamespace
|
|
|| ((uri == nil || uri == "") && (context.targetNamespace == nil || context.targetNamespace == ""))
|
|
if inTargetNamespace {
|
|
return !context.types.contains(localPart)
|
|
}
|
|
if let foreignTypes = context.foreignPools[uri]?["type"] {
|
|
return !foreignTypes.contains(localPart)
|
|
}
|
|
if uri == nil || uri == "" {
|
|
// A no-namespace (unprefixed, no default `xmlns`) reference resolves to
|
|
// the absent namespace. It matches a chameleon-included global adopting
|
|
// the includer's namespace; otherwise, when the schema has a non-empty
|
|
// target namespace, it cannot name a type that lives there, so it is
|
|
// undeclared (XSTS addB009 / test66059). This mirrors the same branch in
|
|
// `isUndeclaredReferenceRef`.
|
|
if context.chameleonNamespace, context.types.contains(localPart) {
|
|
return false
|
|
}
|
|
if !(context.targetNamespace == nil || context.targetNamespace == "") {
|
|
return true
|
|
}
|
|
return !context.types.contains(localPart)
|
|
}
|
|
return false
|
|
}
|
|
|
|
static func isUndeclaredReferenceRef(_ qname: String, poolName: String, in context: ReferenceCheckContext) -> Bool {
|
|
let uri = referenceURI(for: qname, bindings: context.bindings)
|
|
let localPart = referenceLocalName(qname)
|
|
let inTargetNamespace = uri == context.targetNamespace
|
|
|| ((uri == nil || uri == "") && (context.targetNamespace == nil || context.targetNamespace == ""))
|
|
if inTargetNamespace {
|
|
return context.pools[poolName]?.contains(localPart) != true
|
|
}
|
|
if let uri, unloadedReferenceNamespaces.contains(uri) {
|
|
return false
|
|
}
|
|
if let foreign = context.foreignPools[uri]?[poolName] {
|
|
return !foreign.contains(localPart)
|
|
}
|
|
if uri == xsdNamespace {
|
|
// The XSD namespace declares no referenceable user component (element,
|
|
// group, attribute, attribute group), so a reference resolving to it is
|
|
// undeclared. This is reached when an unprefixed reference picks up a
|
|
// default `xmlns` of the XSD namespace, so it is NOT in the target
|
|
// namespace (XSTS xsd013/xsd014); the schema that itself targets the XSD
|
|
// namespace is the `inTargetNamespace` case above, and a genuinely loaded
|
|
// XSD-namespace document is the `foreignPools` case just before.
|
|
return true
|
|
}
|
|
if uri == nil || uri == "" {
|
|
if context.chameleonNamespace, context.pools[poolName]?.contains(localPart) == true {
|
|
return false
|
|
}
|
|
if !(context.targetNamespace == nil || context.targetNamespace == "") {
|
|
return true
|
|
}
|
|
return context.pools[poolName]?.contains(localPart) != true
|
|
}
|
|
return false
|
|
}
|
|
|
|
/// The local part of a QName reference, after the whitespace normalization a
|
|
/// `whiteSpace="collapse"` QName attribute receives (a value may be written
|
|
/// with surrounding or, harmlessly, interior whitespace).
|
|
static func referenceLocalName(_ qname: String) -> String {
|
|
PureXML.Schema.XSDNode.stripPrefix(qname.trimmingXMLWhitespace())
|
|
}
|
|
}
|