refactor(schema): extract shared base-component resolution for redefines

The attribute-group and model-group redefine-restriction rules built the
namespaced base-component pool and did the namespaced-key-plus-chameleon-fallback
lookup with duplicated code and near-identical comments. Extract baseComponents(in:
named:) and resolveBaseComponent(_:name:owner:) so the recurring namespace-keying
and chameleon-fallback pattern lives in one place. Behavior-preserving: all redefine
unit tests pass and the XSTS buckets are unchanged (0/48/0/31).
This commit is contained in:
Mihaela Mihaljevic 2026-06-18 20:20:26 +02:00
parent f8ba6fef5e
commit d046189ad6

View File

@ -56,27 +56,12 @@ extension PureXML.Schema.XSDParser {
/// prohibited check fires only when the original has no attribute wildcard (which
/// could otherwise admit the attribute).
static func redefineAttributeGroupRestrictionErrors(_ containers: [XSDTree]) -> [String] {
// Key the base by NAMESPACED identity: an attribute group is identified by
// {name, target namespace}, and a redefinition's base is in the redefining
// schema's own target namespace, so a same-local-name group imported from a
// different namespace must not be mistaken for the base (matching the
// namespace guard `redefineSelfReferenceErrors` already uses).
var base: [String: XSDTree] = [:]
for container in containers where RedefineNode.localName(container) != "redefine" {
for group in RedefineNode.children(container, named: "attributeGroup") {
if let name = RedefineNode.attribute(group, "name") { base[namespacedKey(name, owner: group)] = group }
}
}
let base = baseComponents(in: containers, named: "attributeGroup")
var errors: [String] = []
for container in containers where RedefineNode.localName(container) == "redefine" {
for redefinition in RedefineNode.children(container, named: "attributeGroup") {
guard let name = RedefineNode.attribute(redefinition, "name"),
// The redefined schema's components are in the redefining schema's
// own namespace, or in none when it is a chameleon (no
// targetNamespace); try both. A base with a different explicit
// namespace keys under neither, so no cross-namespace group is
// mistaken for the original.
let baseGroup = base[namespacedKey(name, owner: redefinition)] ?? base["{}\(name)"]
let baseGroup = resolveBaseComponent(base, name: name, owner: redefinition)
else { continue }
errors += attributeGroupRestrictionErrors(name: name, base: baseGroup, redefinition: redefinition)
}
@ -92,6 +77,31 @@ extension PureXML.Schema.XSDParser {
return "{\(target)}\(name)"
}
/// Named components of `kind` declared directly in the non-redefine containers,
/// keyed by namespaced identity: an attribute group or model group is identified by
/// {name, target namespace}, so a same-local-name component imported from a
/// different namespace is not mistaken for the redefinition's base (the namespace
/// guard `redefineSelfReferenceErrors` already uses). This is the pool a
/// redefinition's base is drawn from.
private static func baseComponents(in containers: [XSDTree], named kind: String) -> [String: XSDTree] {
var base: [String: XSDTree] = [:]
for container in containers where RedefineNode.localName(container) != "redefine" {
for component in RedefineNode.children(container, named: kind) {
if let name = RedefineNode.attribute(component, "name") { base[namespacedKey(name, owner: component)] = component }
}
}
return base
}
/// The base component a redefinition redefines: looked up by namespaced identity,
/// then by the chameleon `{}name` key, since a redefined schema with no
/// `targetNamespace` is absorbed into the redefining schema's namespace. A base in a
/// DIFFERENT explicit namespace keys under neither, so no cross-namespace component
/// is mistaken for the original.
private static func resolveBaseComponent(_ base: [String: XSDTree], name: String, owner: XSDTree) -> XSDTree? {
base[namespacedKey(name, owner: owner)] ?? base["{}\(name)"]
}
private static func attributeGroupRestrictionErrors(name: String, base: XSDTree, redefinition: XSDTree) -> [String] {
var errors: [String] = []
let children = RedefineNode.elementChildren(redefinition)
@ -167,23 +177,13 @@ extension PureXML.Schema.XSDParser {
types: [String: PureXML.Schema.ElementType],
derivation: [String: PureXML.Schema.TypeDerivation],
) -> [String] {
var base: [String: XSDTree] = [:]
for container in containers where RedefineNode.localName(container) != "redefine" {
for group in RedefineNode.children(container, named: "group") {
if let name = RedefineNode.attribute(group, "name") { base[namespacedKey(name, owner: group)] = group }
}
}
let base = baseComponents(in: containers, named: "group")
var errors: [String] = []
for container in containers where RedefineNode.localName(container) == "redefine" {
for redefinition in RedefineNode.children(container, named: "group") {
guard let name = RedefineNode.attribute(redefinition, "name"),
!hasGroupSelfReference(redefinition, name: name),
// The redefined schema's components live in the redefining schema's
// own namespace, or in none when it is a chameleon (no
// targetNamespace) absorbed on redefine; try both keys. A base with
// a DIFFERENT explicit namespace keys under neither, so no
// cross-namespace component is mistaken for the original.
let original = base[namespacedKey(name, owner: redefinition)] ?? base["{}\(name)"],
let original = resolveBaseComponent(base, name: name, owner: redefinition),
let redefined = modelGroup(in: redefinition, context),
let baseline = modelGroup(in: original, context),
// A `maxOccurs=0` ("pointless") member in the original puts the