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.
45 lines
2.5 KiB
Swift
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
|
|
}
|
|
}
|
|
}
|