extension PureXML.Schema.IdentityValidator { func simpleType(of declaration: PureXML.Schema.ElementType) -> PureXML.Schema.SimpleType? { switch declaration { case let .simple(simple): simple case let .complex(complex): if case let .simpleContent(simple) = complex.content { simple } else { nil } case .typeReference: nil } } static func effectiveType(of value: PureXML.XPath.Value) -> PureXML.Schema.SimpleType? { guard let nodes = value.nodes, case let .tree(treeNode)? = nodes.first, treeNode.kind == .element, let local = xsiTypeLocalName(of: treeNode), let builtin = PureXML.Schema.BuiltinType(rawValue: local) else { return nil } return PureXML.Schema.SimpleType(base: builtin) } /// The local name of a tree node's `xsi:type` attribute value, recognized by /// the schema-instance namespace or the conventional `xsi` prefix, or nil. static func xsiTypeLocalName(of node: PureXML.Model.TreeNode) -> String? { let schemaInstance = "http://www.w3.org/2001/XMLSchema-instance" guard let attribute = node.attributes.first(where: { $0.name.localName == "type" && ($0.name.namespaceURI == schemaInstance || $0.name.prefix == "xsi") }) else { return nil } return attribute.value.split(separator: ":").last.map(String.init) ?? attribute.value } func label(_ constraint: PureXML.Schema.IdentityConstraint) -> String { constraint.kind == .key ? "key" : "unique" } /// Whether a constraint collects tuples (`key`/`unique`) rather than /// referencing them (`keyref`). func isNonRef(_ constraint: PureXML.Schema.IdentityConstraint) -> Bool { if case .keyref = constraint.kind { return false } return true } /// The coding-path steps from the constraint-bearing `container` down to a /// selected `target`, so an identity error locates the actual offending /// element rather than the element that merely declares the constraint. /// Returns an empty path if `target` is not a descendant of `container`. func steps(from container: PureXML.Model.TreeNode, to target: PureXML.Model.TreeNode) -> [PureXML.Validation.PathKey] { var chain: [PureXML.Model.TreeNode] = [] var current = target while current !== container { guard let parent = current.parent else { return [] } chain.append(current) current = parent } var steps: [PureXML.Validation.PathKey] = [] var parent = container for node in chain.reversed() { let name = node.name?.description ?? "" if let index = stepIndex.position(of: node, under: parent, name: name) { steps.append(.element(name, index: index)) } else { steps.append(.element(name)) } parent = node } return steps } }