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.
53 lines
2.5 KiB
Swift
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)]
|
|
}
|
|
}
|