PureXML/Sources/Schema/IdentityValidatorDefaults.swift
Mihaela Mihaljevic 380160cc2b refactor(validation): decompose XSD instance validation into composable rules
The two monolithic XSD content/identity rules only satisfied the OpenAPIKit
validation idiom at the wrong granularity: no per-constraint removability,
no isolable failing/near-miss tests. Split them into 32 named rules, each
finding tagged at its origin and cached per XSDContext so the walk still
runs once per document (the #101 decision against duplicating XSD's
recursive type resolution still holds). Wired the streaming path's ID/IDREF
and identity checks through the same framework instead of calling the
validators imperatively, and added the unwrapAndLookup combinator the rule
already named as required.

Also includes the mandated repo-wide swiftformat/swiftlint pass.
2026-07-12 14:12:17 +02:00

53 lines
2.5 KiB
Swift

extension PureXML.Schema.IdentityValidator {
/// The identity value a field takes from a declared `default`/`fixed` when the
/// instance supplies none, or nil when the instance supplies a value (the
/// caller then uses it). The substituted value carries the field's resolved
/// type so it compares in the same value space as an explicit one.
func defaultedValue(
_ field: String,
value: PureXML.XPath.Value,
type: PureXML.Schema.SimpleType?,
constraint: PureXML.Schema.IdentityConstraint,
at node: PureXML.Model.TreeNode,
) -> FieldValue? {
guard isFieldEmpty(field, value: value),
let constrained = resolveFieldConstraint(field, constraint: constraint, at: node)
else { return nil }
return FieldValue(string: constrained.value, type: type, namespaceBindings: namespaceBindings(at: node))
}
/// Whether a field has no instance-supplied value, so a declared
/// `default`/`fixed` may stand in. An `@attr` field is empty only when the
/// attribute is ABSENT (empty node-set); a present attribute, even value "",
/// is its own value. A `.` field is empty when the selected element has no
/// character content; any other field is empty only on an empty node-set.
func isFieldEmpty(_ field: String, value: PureXML.XPath.Value) -> Bool {
guard let nodes = value.nodes else { return false }
if field.hasPrefix("@") {
return nodes.isEmpty
}
if field == "." {
guard case let .tree(element)? = nodes.first, element.kind == .element else { return nodes.isEmpty }
return !element.children.contains { $0.kind == .text || $0.kind == .cdata }
}
return nodes.isEmpty
}
/// The `default`/`fixed` declared on a field's target, keyed the same way as
/// ``resolveFieldType(_:constraint:at:)`` so a multi-branch `.` selector
/// resolves to its branch's element.
func resolveFieldConstraint(
_ field: String,
constraint: PureXML.Schema.IdentityConstraint,
at target: PureXML.Model.TreeNode,
) -> PureXML.Schema.ValueConstraint? {
if field == ".", let local = target.name?.localName {
let key = PureXML.Schema.XSDParser.identityFieldKey(constraint: constraint, field: field, targetLocal: local)
if let value = fieldConstraints[key] {
return value
}
}
return fieldConstraints[PureXML.Schema.XSDParser.identityFieldKey(constraint: constraint, field: field)]
}
}