PureXML/Sources/Schema/SchemaImportNamespace.swift
Mihaela Mihaljevic 4dd1d330cb feat(schema): locate reference-resolution schema diagnostics
Close the last superficially-located checker (#169): referenceFindings was
dispatched as located but its body wrapped the whole aggregate in
.unlocated, so every reference-resolution error still collapsed onto the
schema root. Convert all six sub-collectors to located findings, each
attaching to the node in scope: the core type/base/itemType/memberTypes/ref/
substitutionGroup walk and the xs:-namespace builtin check locate on the
referencing element; the simpleContent base-kind check on the restriction;
the un-imported-namespace check on the referencing element; the
redefine-component-existence check on the redefine's child component; and
the import-namespace-agreement check on the import element.

Every predicate, aggregation order, and reason string is unchanged; only the
location each finding carries differs, verified conformance-neutral by the
full XSTS suite (14383 groups, pinned counts unchanged). Removes two dead
helpers exposed along the way: the unused referenceErrors [String] wrapper
and the unused locatedFindings helper in SchemaCompileValidation. The outer
aggregation is rewritten from a single + chain to incremental appends to keep
the type-checker within budget. Adds a located-diagnostics test per
node-locatable sub-collector; the loader-dependent redefine/import families
are exercised by the XSTS suite.
2026-06-21 18:09:20 +02:00

51 lines
3.0 KiB
Swift

private typealias ImportNode = PureXML.Schema.XSDNode
extension PureXML.Schema.XSDParser {
/// XSD 1.0 `src-import.3.1`/`3.2`: an `import`'s `namespace` attribute must equal
/// the `targetNamespace` of the schema it resolves to (both present and equal, or
/// both absent). Runs only past the cross-document skip guard, so the imported
/// schema is loaded. Checked only when the `schemaLocation` resolved to a loaded
/// container (an unresolved import is the processor's choice and not an error),
/// which is the false-positive boundary. Imports keep their own target namespace
/// (no chameleon), so the imported document's literal `targetNamespace` is used.
static func importNamespaceFindings(_ context: PureXML.Schema.XSDContext, _ containers: [XSDTree]) -> [PureXML.Schema.SchemaLocatedFinding] {
var locationTargetNS: [String: String?] = [:]
for container in containers where ImportNode.localName(container) == "schema" {
guard let location = context.containerLocations[ObjectIdentifier(container)] ?? nil else { continue }
locationTargetNS[location] = ImportNode.attribute(container, "targetNamespace")
}
var findings: [PureXML.Schema.SchemaLocatedFinding] = []
for container in containers {
for node in descendants(container, named: "import") where node.name?.namespaceURI == xsdNamespace {
guard let location = ImportNode.attribute(node, "schemaLocation"),
locationTargetNS.keys.contains(location)
else { continue }
let imported = locationTargetNS[location] ?? nil
let declared = ImportNode.attribute(node, "namespace")
if declared != imported {
findings.append(PureXML.Schema.SchemaLocatedFinding(
reason: "import of '\(location)' declares namespace '\(declared ?? "(none)")', not the imported target namespace '\(imported ?? "(none)")'",
node: node,
))
}
}
}
return findings
}
/// src-redefine.5: a type inside `xs:redefine` must restrict or extend the type it
/// redefines, which lives in the redefining schema's OWN target namespace. So a
/// base bound to an EXPLICIT different namespace (for example an imported type
/// with the same local name) is not self and is invalid. A base with no resolvable
/// namespace is left alone, which keeps this from ever over-rejecting a
/// same-local-name self-reference.
static func redefineBaseIsForeign(_ type: XSDTree, _ rawBase: String?) -> Bool {
guard let rawBase else { return false }
let schema = ImportNode.schemaOwner(type)
let targetNamespace = ImportNode.attribute(schema, "targetNamespace")
let bindings = ImportNode.namespaceBindings(of: schema)
guard let baseNamespace = ImportNode.referenceNamespace(rawBase, bindings) else { return false }
return baseNamespace != targetNamespace
}
}