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.
106 lines
5.9 KiB
Swift
106 lines
5.9 KiB
Swift
extension PureXML.Schema.XSDParser {
|
|
/// Keys of identity-constraint fields whose target element is, by declaration,
|
|
/// not of simple type: the ur-type `anyType` from an absent type, or an inline
|
|
/// complex type without simple content. XSD 1.0 §3.11.4 (cvc-identity-constraint.3)
|
|
/// requires a field to identify a node of simple type, so such a field makes the
|
|
/// instance invalid (XSTS idZ010). Resolved conservatively: a key is recorded only
|
|
/// when the selector and field paths resolve unambiguously, by element-name steps,
|
|
/// to a declared element that is provably non-simple. Any wildcard, descendant
|
|
/// axis, predicate, named (unresolved) type, or `ref` leaves the key out, so a
|
|
/// valid field, which always names a simple-typed element or attribute, is never
|
|
/// flagged.
|
|
static func identityNonSimpleFieldKeys(_ containers: [XSDTree], _ context: PureXML.Schema.XSDContext) -> Set<String> {
|
|
var keys: Set<String> = []
|
|
for container in containers {
|
|
for element in descendants(container, named: "element") {
|
|
let constraints = PureXML.Schema.XSDNode.elementChildren(element).compactMap(constraint)
|
|
for constraint in constraints {
|
|
guard let selectorTarget = pathTargetElement(from: element, path: constraint.selector) else { continue }
|
|
for field in constraint.fields where !field.hasPrefix("@") {
|
|
guard let fieldTarget = pathTargetElement(from: selectorTarget, path: field),
|
|
elementIsDeclaredNonSimple(fieldTarget, context)
|
|
else { continue }
|
|
keys.insert(identityFieldKey(constraint: constraint, field: field))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return keys
|
|
}
|
|
|
|
/// The declared element node a selector or field XPath reaches from `start`,
|
|
/// following only the restricted-XPath child-name steps (a leading or interior
|
|
/// `.` is self). Returns nil for any step that is not a plain name test (a
|
|
/// wildcard `*`, a `//` descendant step, an axis `::`, a predicate `[`, an
|
|
/// attribute `@`), or that names no direct child element, so the caller flags
|
|
/// only paths it has resolved with certainty.
|
|
private static func pathTargetElement(from start: XSDTree, path: String) -> XSDTree? {
|
|
var current: XSDTree? = start
|
|
for rawStep in path.split(separator: "/", omittingEmptySubsequences: false) {
|
|
let step = String(rawStep).trimmingXMLWhitespace()
|
|
if step == "." {
|
|
continue
|
|
}
|
|
guard isSimpleNameTest(step), let node = current,
|
|
let child = directChildElement(named: PureXML.Schema.XSDNode.stripPrefix(step), of: node)
|
|
else { return nil }
|
|
current = child
|
|
}
|
|
return current
|
|
}
|
|
|
|
/// Whether `step` is a plain element name test (`NCName` or `prefix:NCName`),
|
|
/// not a wildcard, axis, predicate, or `//` (an empty step from a split).
|
|
private static func isSimpleNameTest(_ step: String) -> Bool {
|
|
guard !step.isEmpty, !step.contains(where: { "*[]()|@ \t".contains($0) }) else { return false }
|
|
return step.count(where: { $0 == ":" }) <= 1
|
|
}
|
|
|
|
/// The direct child element declaration of `element`'s `complexType` model with
|
|
/// the given local name, descending through model groups but not into another
|
|
/// element's own type, and never matching a `ref` (which has no `name`).
|
|
private static func directChildElement(named local: String, of element: XSDTree) -> XSDTree? {
|
|
guard let complexType = PureXML.Schema.XSDNode.firstChild(element, named: "complexType") else { return nil }
|
|
return modelChildElements(of: complexType).first { PureXML.Schema.XSDNode.attribute($0, "name") == local }
|
|
}
|
|
|
|
/// Every direct element declaration in a complex type's content model, reached
|
|
/// through the model-group and derivation wrappers but stopping at each
|
|
/// `element` (so a nested element's own children are not collected).
|
|
private static func modelChildElements(of node: XSDTree) -> [XSDTree] {
|
|
var result: [XSDTree] = []
|
|
for child in PureXML.Schema.XSDNode.elementChildren(node) {
|
|
switch PureXML.Schema.XSDNode.localName(child) {
|
|
case "element": result.append(child)
|
|
case "sequence", "choice", "all", "group", "complexContent", "restriction", "extension":
|
|
result += modelChildElements(of: child)
|
|
default: break
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
/// Whether `element`'s declared type is provably not a simple type: an explicit
|
|
/// `xsd:anyType`, an inline `complexType` without `simpleContent`, or no type at
|
|
/// all (the ur-type `anyType`). A builtin, `anySimpleType`, an inline
|
|
/// `simpleType`, `simpleContent`, or any named non-builtin type is treated as
|
|
/// possibly-simple and not flagged, so the check never rejects a valid field.
|
|
private static func elementIsDeclaredNonSimple(_ element: XSDTree, _ context: PureXML.Schema.XSDContext) -> Bool {
|
|
if let typeName = PureXML.Schema.XSDNode.attribute(element, "type") {
|
|
let bindings = PureXML.Schema.XSDParser.namespaceBindingsInScope(of: element, defaultBindings: context.namespaceBindings)
|
|
let local = PureXML.Schema.XSDNode.stripPrefix(typeName)
|
|
guard PureXML.Schema.XSDNode.referenceNamespace(typeName, bindings) == PureXML.Schema.XSDParser.xsdNamespace else {
|
|
return false
|
|
}
|
|
return local == "anyType"
|
|
}
|
|
if PureXML.Schema.XSDNode.firstChild(element, named: "simpleType") != nil {
|
|
return false
|
|
}
|
|
if let complex = PureXML.Schema.XSDNode.firstChild(element, named: "complexType") {
|
|
return PureXML.Schema.XSDNode.firstChild(complex, named: "simpleContent") == nil
|
|
}
|
|
return true
|
|
}
|
|
}
|