An identity-constraint (unique/key/keyref) field that selects an absent attribute or an empty element whose declaration carries a default or fixed value now takes that value as its identity-tuple component, so two members can collide on the defaulted value (idG011/idG012 default, idF016/idF017 fixed) where the duplicate was previously missed. The default/fixed value constraints on identity-field targets are gathered at compile time (identityFieldConstraints, mirroring identityFieldTypes) and threaded through to the identity validator. The substitution is exact about absent versus present-empty: an @attr field is defaulted only when the attribute is ABSENT (an empty node-set), never when present with value ""; a "." element field only when the selected element has no character content. Applying a default only adds an identity value where the schema declares one, so it can create a genuine duplicate-key detection but never reject a valid instance. To keep within the file/type-size and one-type-per-file limits, FieldValue, XPathQueryCache, and the schema-location hints were extracted to their own files (behavior-preserving moves). invalid-instances-accepted 50 to 45, no false positive (valid-instances-rejected and valid-schemas-rejected held at 0), no other bucket moved. An adversarial review probed present-empty attributes, element content winning over a default, fixed values, keyref matching, multi-field keys, and merged-schema defaults, and found no over-rejection.
49 lines
2.5 KiB
Swift
49 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)]
|
|
}
|
|
}
|