PureXML/Sources/Schema/SchemaImportNamespace.swift
Mihaela Mihaljevic a444604a80 fix(schema): a redefined type must derive from itself, not a same-named foreign type (src-redefine.5); invalid-schemas-accepted 84 to 81 (#161)
A type inside xs:redefine must restrict or extend the type it redefines, which
lives in the redefining schema's own target namespace. checkRedefine compared
local names only (derivationInfo strips the prefix), so a base like foo:b-ct
-- an imported type sharing the redefined type's local name -- was treated as
self and accepted. Now the base's namespace is resolved: a base bound to an
explicit namespace other than the redefining schema's target namespace is not
self and is rejected (src-redefine.5).

redefineBaseIsForeign (SchemaImportNamespace.swift) leaves a base with no
resolvable namespace alone, so an unprefixed or own-namespace self-reference
never over-rejects -- the false-positive boundary, the same namespace-aware
fix applied across the campaign.

XSTS: invalid-schemas-accepted 84 to 81 (schQ2/schQ4/schP3),
valid-schemas-rejected held at 0, no other bucket moved. Differential:
agree-valid unchanged at 3161, PureXML-stricter unchanged at 87 over 9618
schemas. Third step of the cross-document composition campaign (#161).
2026-06-17 23:00:07 +02:00

48 lines
2.8 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 importNamespaceErrors(_ context: PureXML.Schema.XSDContext, _ containers: [XSDTree]) -> [String] {
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 errors: [String] = []
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 {
errors.append("import of '\(location)' declares namespace '\(declared ?? "(none)")', not the imported target namespace '\(imported ?? "(none)")'")
}
}
}
return errors
}
/// 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
}
}