PureXML/Sources/Schema/IdentityConstraint.swift
Mihaela Mihaljevic 39a91c258e fix(schema): resolve identity-constraint XPath prefixes against the schema (#147)
Identity-constraint selector and field XPath prefixes were resolved against
the instance document's namespace bindings, so a constraint declared with
xmlns:v="..." and a selector like .//v:vehicle matched nothing when the
instance used a default namespace instead of the v prefix. The selector then
selected no nodes, no key tuples were collected, and duplicate-key and
keyref violations went undetected.

Prefixes now resolve against the namespace bindings in scope where the
constraint is declared in the schema (XSD Part 1, Identity-constraint
Definition), captured at parse time onto IdentityConstraint. A QName field
value still compares using the instance's bindings, so QName keys are
unaffected.

XSTS invalid-instances-accepted 143 to 133: the idc004/5/6 namespaced
key/keyref cluster and namespace-resolution cases (idZ010 and kin), no other
bucket moved.
2026-06-17 00:03:13 +02:00

44 lines
1.8 KiB
Swift

public extension PureXML.Schema {
/// The kind of an ``IdentityConstraint``, and for a keyref the name of the
/// key it refers to.
enum IdentityConstraintKind: Sendable, Equatable {
/// Tuples must be distinct where present; a node missing a field is
/// excluded rather than rejected.
case unique
/// Tuples must be distinct and every field must be present.
case key
/// Every tuple must equal a tuple of the named key or unique in scope.
case keyref(refer: String)
}
/// An XSD identity constraint declared on an element: `xs:unique`, `xs:key`,
/// or `xs:keyref`. The `selector` XPath chooses the nodes the constraint
/// ranges over (relative to the declaring element), and each `field` XPath
/// extracts one component of the value tuple from a selected node.
struct IdentityConstraint: Sendable {
public var name: String
public var kind: IdentityConstraintKind
public var selector: String
public var fields: [String]
/// Prefix-to-namespace bindings in scope where the constraint is declared
/// in the schema. Selector and field XPath prefixes resolve against these
/// (XSD Part 1, Identity-constraint Definition), not against the instance
/// document, so `.//v:vehicle` works whatever prefix the instance uses.
public var namespaceBindings: [String: String]
public init(
name: String,
kind: IdentityConstraintKind,
selector: String,
fields: [String],
namespaceBindings: [String: String] = [:],
) {
self.name = name
self.kind = kind
self.selector = selector
self.fields = fields
self.namespaceBindings = namespaceBindings
}
}
}