PureXML/Sources/Schema/IdentityFieldType.swift
Mihaela Mihaljevic 74b9332c48 fix(schema): reject identity field whose declared target is a non-simple complex type
cvc-identity-constraint.3 / c-fields-xpaths require an identity-constraint field to
identify a node with a simple type. The prior fix rejected a field whose selected
instance node carries element children (structural). This adds the case where the
field's DECLARED type is a complex type with non-simple content (empty or
element-only/mixed): an attributes-only element resolves to an empty-content complex
type, so its instance node is childless yet not simple-typed.

The field's target type is resolved through the schema type model (the selector
target's declared type, then the field's same-named child element in its content
model) and classified. A simple or simpleContent target, a type reached only
through a named reference, a complex field path, or an unresolvable target is left
alone, so no valid field is rejected. The resolution helpers live in a separate
extension file to keep the validator within the type-body limit.

This catches W3C XSTS idG006. XSTS invalid-instances-accepted 23 -> 22,
valid-instances-rejected held at 0.
2026-06-22 13:10:30 +02:00

45 lines
2.5 KiB
Swift

extension PureXML.Schema.IdentityValidator {
/// Whether the field's DECLARED target is a complex type with non-simple content
/// (empty or element-only/mixed), which is not a valid identity field even when its
/// instance node has no element children (an attributes-only element, XSTS idG006).
/// Resolved through the schema type model: the selector target's type is looked up,
/// the field's same-named child element found in its content model, and its declared
/// type classified. A simple/simpleContent type, a type reached only through a
/// reference, a complex field path, or an unresolvable target is left alone, so no
/// valid field is rejected (cvc-identity-constraint.3 / c-fields-xpaths).
func fieldTargetIsComplexNonSimple(_ field: String, at target: PureXML.Model.TreeNode) -> Bool {
guard !field.contains(where: { "@/[(.".contains($0) }), let targetName = target.name,
case let .complex(targetComplex)? = declaredType(of: targetName),
let fieldType = childElementType(named: field, in: targetComplex.content),
case let .complex(fieldComplex) = fieldType
else { return false }
if case .simpleContent = fieldComplex.content { return false }
return true
}
/// The declared `ElementType` of a global element, by namespaced or local key.
private func declaredType(of name: PureXML.Model.QualifiedName) -> PureXML.Schema.ElementType? {
types[PureXML.Schema.XSDParser.elementDeclarationKey(name)] ?? types[PureXML.Schema.XSDParser.elementKey(name.localName)]
}
/// The declared type of the first element named `local` directly within a content
/// model (descending through model groups), or nil when none matches.
private func childElementType(named local: String, in content: PureXML.Schema.ContentType) -> PureXML.Schema.ElementType? {
switch content {
case let .elementOnly(particle), let .mixed(particle): childElementType(named: local, in: particle)
default: nil
}
}
private func childElementType(named local: String, in particle: PureXML.Schema.Particle) -> PureXML.Schema.ElementType? {
switch particle.term {
case let .element(name, type, _, _, _, _):
name.localName == local ? type : nil
case let .group(group):
group.particles.lazy.compactMap { childElementType(named: local, in: $0) }.first
case .wildcard:
nil
}
}
}