An identity-constraint attribute field's type was resolved only among the constraint host's descendant elements, so when the selector targets a global element referenced from the host (e.g. unique over `.//uid` with field `@val`), the field type was unknown and values compared lexically. Two keys equal in value space but not lexically (3.0 and 3 for xsd:decimal) were then treated as distinct and the duplicate was accepted. The attribute type now resolves through the selector's target global element, so values compare in their value space. XSTS invalid-instances-accepted 133 to 132 (idF010), no other bucket moved.
267 lines
13 KiB
Swift
267 lines
13 KiB
Swift
extension PureXML.Schema.XSDParser {
|
|
/// Gathers `nillable` and `default`/`fixed` value constraints from every
|
|
/// element declaration at any depth, keyed by the element's name.
|
|
static func elementMetadata(_ containers: [XSDTree]) -> (Set<String>, [String: PureXML.Schema.ValueConstraint]) {
|
|
var nillable: Set<String> = []
|
|
var constraints: [String: PureXML.Schema.ValueConstraint] = [:]
|
|
for container in containers {
|
|
for element in descendants(container, named: "element") {
|
|
guard let name = PureXML.Schema.XSDNode.attribute(element, "name") else { continue }
|
|
if PureXML.Schema.XSDNode.attribute(element, "nillable") == "true" { nillable.insert(name) }
|
|
if let constraint = valueConstraint(of: element) { constraints[name] = constraint }
|
|
}
|
|
}
|
|
return (nillable, constraints)
|
|
}
|
|
|
|
/// Gathers identity constraints (`unique`, `key`, `keyref`) declared on any
|
|
/// element at any depth, keyed by the element's name.
|
|
static func identityConstraints(_ containers: [XSDTree]) -> [String: [PureXML.Schema.IdentityConstraint]] {
|
|
var map: [String: [PureXML.Schema.IdentityConstraint]] = [:]
|
|
for container in containers {
|
|
for element in descendants(container, named: "element") {
|
|
guard let name = PureXML.Schema.XSDNode.attribute(element, "name") else { continue }
|
|
let constraints = PureXML.Schema.XSDNode.elementChildren(element).compactMap(constraint)
|
|
if !constraints.isEmpty { map[name, default: []] += constraints }
|
|
}
|
|
}
|
|
return map
|
|
}
|
|
|
|
/// Simple types for identity-constraint field paths, keyed by
|
|
/// ``identityFieldKey(constraint:field:)``.
|
|
static func identityFieldTypes(_ containers: [XSDTree], _ context: PureXML.Schema.XSDContext) -> [String: PureXML.Schema.SimpleType] {
|
|
var types: [String: PureXML.Schema.SimpleType] = [:]
|
|
for container in containers {
|
|
for element in descendants(container, named: "element") {
|
|
let constraints = PureXML.Schema.XSDNode.elementChildren(element).compactMap(constraint)
|
|
guard !constraints.isEmpty else { continue }
|
|
for constraint in constraints {
|
|
for field in constraint.fields {
|
|
if field == ".", selectorBranches(constraint.selector).count > 1 {
|
|
for branch in selectorBranches(constraint.selector) {
|
|
if let type = declaredElementSimpleType(named: branch, under: element, context) {
|
|
types[identityFieldKey(constraint: constraint, field: field, targetLocal: branch)] = type
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
if let type = identityFieldType(field, host: element, constraint: constraint, containers: containers, context) {
|
|
types[identityFieldKey(constraint: constraint, field: field)] = type
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return types
|
|
}
|
|
|
|
static func identityFieldKey(constraint: PureXML.Schema.IdentityConstraint, field: String, targetLocal: String? = nil) -> String {
|
|
if let targetLocal { return "\(constraint.name)|\(field)|\(targetLocal)" }
|
|
return "\(constraint.name)|\(field)"
|
|
}
|
|
|
|
private static func selectorBranches(_ selector: String) -> [String] {
|
|
let token = selector.split(separator: "/").last.map(String.init) ?? selector
|
|
guard token.contains("|") else {
|
|
return selectorTargetLocalName(selector).map { [$0] } ?? []
|
|
}
|
|
return token.split(separator: "|").compactMap { branch in
|
|
let local = PureXML.Schema.XSDNode.stripPrefix(String(branch))
|
|
return local.isEmpty ? nil : local
|
|
}
|
|
}
|
|
|
|
private static func identityFieldType(
|
|
_ field: String,
|
|
host: XSDTree,
|
|
constraint: PureXML.Schema.IdentityConstraint,
|
|
containers: [XSDTree],
|
|
_ context: PureXML.Schema.XSDContext,
|
|
) -> PureXML.Schema.SimpleType? {
|
|
if field.hasPrefix("@") {
|
|
let attributeName = identityFieldAttributeName(field)
|
|
if let type = attributeType(named: attributeName, under: host, context) { return type }
|
|
// The selector's target may be a global element referenced from the
|
|
// host rather than nested under it; the attribute is then on that
|
|
// element's own complex type, so field values compare in its value
|
|
// space (e.g. 3.0 and 3 are the same xsd:decimal key).
|
|
guard let target = selectorTargetLocalName(constraint.selector),
|
|
let element = globalElementNode(named: target, containers),
|
|
let complex = PureXML.Schema.XSDNode.firstChild(element, named: "complexType")
|
|
else { return nil }
|
|
for attribute in descendants(complex, named: "attribute") {
|
|
guard PureXML.Schema.XSDNode.attribute(attribute, "name") == attributeName else { continue }
|
|
if let use = PureXML.Schema.XSDParser.attributeUse(attribute, context) { return use.type }
|
|
}
|
|
return nil
|
|
}
|
|
if field == "." {
|
|
let local = selectorTargetLocalName(constraint.selector)
|
|
return declaredElementSimpleType(named: local, under: host, context)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
private static func identityFieldAttributeName(_ field: String) -> String {
|
|
let token = field.split(separator: "|").first.map(String.init) ?? field
|
|
guard token.hasPrefix("@") else { return String(field.dropFirst()) }
|
|
return String(token.dropFirst())
|
|
}
|
|
|
|
private static func selectorTargetLocalName(_ selector: String) -> String? {
|
|
let token = selector.split(separator: "/").last.map(String.init) ?? selector
|
|
return PureXML.Schema.XSDNode.stripPrefix(token)
|
|
}
|
|
|
|
/// The top-level (global) element declaration node of the given local name.
|
|
private static func globalElementNode(named local: String, _ containers: [XSDTree]) -> XSDTree? {
|
|
for container in containers {
|
|
for child in PureXML.Schema.XSDNode.elementChildren(container) {
|
|
guard PureXML.Schema.XSDNode.localName(child) == "element",
|
|
PureXML.Schema.XSDNode.attribute(child, "name") == local
|
|
else { continue }
|
|
return child
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
private static func declaredElementSimpleType(
|
|
named local: String?,
|
|
under host: XSDTree,
|
|
_ context: PureXML.Schema.XSDContext,
|
|
) -> PureXML.Schema.SimpleType? {
|
|
guard let local else { return elementSimpleType(under: host, context) }
|
|
guard let complex = PureXML.Schema.XSDNode.firstChild(host, named: "complexType") else { return nil }
|
|
for child in descendants(complex, named: "element") {
|
|
guard PureXML.Schema.XSDNode.attribute(child, "name") == local,
|
|
let typeName = PureXML.Schema.XSDNode.attribute(child, "type")
|
|
else { continue }
|
|
let stripped = PureXML.Schema.XSDNode.stripPrefix(typeName)
|
|
let uri = PureXML.Schema.XSDNode.referenceNamespace(typeName, context.namespaceBindings)
|
|
if uri == PureXML.Schema.XSDParser.xsdNamespace, let builtin = PureXML.Schema.BuiltinType(rawValue: stripped) {
|
|
return PureXML.Schema.SimpleType(base: builtin)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
private static func attributeType(named name: String, under host: XSDTree, _ context: PureXML.Schema.XSDContext) -> PureXML.Schema.SimpleType? {
|
|
for element in descendants(host, named: "element") {
|
|
guard let complex = PureXML.Schema.XSDNode.firstChild(element, named: "complexType") else { continue }
|
|
for attribute in descendants(complex, named: "attribute") {
|
|
guard PureXML.Schema.XSDNode.attribute(attribute, "name") == name else { continue }
|
|
if let use = PureXML.Schema.XSDParser.attributeUse(attribute, context) {
|
|
return use.type
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
private static func elementSimpleType(under host: XSDTree, _ context: PureXML.Schema.XSDContext) -> PureXML.Schema.SimpleType? {
|
|
guard let complex = PureXML.Schema.XSDNode.firstChild(host, named: "complexType") else { return nil }
|
|
for child in descendants(complex, named: "element") {
|
|
guard let typeName = PureXML.Schema.XSDNode.attribute(child, "type") else { continue }
|
|
let local = PureXML.Schema.XSDNode.stripPrefix(typeName)
|
|
let uri = PureXML.Schema.XSDNode.referenceNamespace(typeName, context.namespaceBindings)
|
|
if uri == PureXML.Schema.XSDParser.xsdNamespace, let builtin = PureXML.Schema.BuiltinType(rawValue: local) {
|
|
return PureXML.Schema.SimpleType(base: builtin)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
private static func constraint(_ node: XSDTree) -> PureXML.Schema.IdentityConstraint? {
|
|
let kind: PureXML.Schema.IdentityConstraintKind
|
|
switch PureXML.Schema.XSDNode.localName(node) {
|
|
case "unique": kind = .unique
|
|
case "key": kind = .key
|
|
case "keyref": kind = .keyref(refer: PureXML.Schema.XSDNode.stripPrefix(PureXML.Schema.XSDNode.attribute(node, "refer") ?? ""))
|
|
default: return nil
|
|
}
|
|
let selector = PureXML.Schema.XSDNode.firstChild(node, named: "selector").flatMap { PureXML.Schema.XSDNode.attribute($0, "xpath") } ?? ""
|
|
let fields = PureXML.Schema.XSDNode.children(node, named: "field").compactMap { PureXML.Schema.XSDNode.attribute($0, "xpath") }
|
|
return PureXML.Schema.IdentityConstraint(
|
|
name: PureXML.Schema.XSDNode.attribute(node, "name") ?? "",
|
|
kind: kind,
|
|
selector: selector,
|
|
fields: fields,
|
|
namespaceBindings: PureXML.Schema.XSDParser.namespaceBindingsInScope(of: node, defaultBindings: [:]),
|
|
)
|
|
}
|
|
|
|
static func descendants(_ node: XSDTree, named name: String) -> [XSDTree] {
|
|
var result: [XSDTree] = []
|
|
for child in PureXML.Schema.XSDNode.elementChildren(node) {
|
|
if PureXML.Schema.XSDNode.localName(child) == name { result.append(child) }
|
|
result += descendants(child, named: name)
|
|
}
|
|
return result
|
|
}
|
|
|
|
/// The qualified name of a local element declaration: in the target namespace
|
|
/// when `elementFormDefault` (or the element's own `form`) is qualified,
|
|
/// otherwise in no namespace.
|
|
static func localElementName(_ name: String, _ form: String?, _ context: PureXML.Schema.XSDContext) -> PureXML.Model.QualifiedName {
|
|
let qualified = form == "qualified" || (form == nil && context.elementFormQualified)
|
|
return PureXML.Model.QualifiedName(localName: name, namespaceURI: qualified ? context.targetNamespace : nil)
|
|
}
|
|
|
|
static func elementKey(_ name: String) -> String {
|
|
"element:\(name)"
|
|
}
|
|
|
|
/// The local name from a type-table key (`type:{namespace}local` or a legacy bare name).
|
|
static func bareTypeLocalName(_ reference: String) -> String {
|
|
guard reference.hasPrefix("type:") else { return reference }
|
|
let key = String(reference.dropFirst("type:".count))
|
|
guard let close = key.firstIndex(of: "}") else { return key }
|
|
return String(key[key.index(after: close)...])
|
|
}
|
|
|
|
/// The type-table key for a global type declaration in a specific namespace.
|
|
static func typeDeclarationKey(_ localName: String, namespaceURI: String?) -> String {
|
|
"type:\(PureXML.Schema.ComplexValidator.key(PureXML.Model.QualifiedName(localName: localName, namespaceURI: namespaceURI)))"
|
|
}
|
|
|
|
/// The type-table key for a global element declaration in a specific namespace.
|
|
static func elementDeclarationKey(_ name: PureXML.Model.QualifiedName) -> String {
|
|
"element:\(PureXML.Schema.ComplexValidator.key(name))"
|
|
}
|
|
|
|
/// The lookup key for a global attribute declaration in a specific namespace.
|
|
static func attributeDeclarationKey(_ name: PureXML.Model.QualifiedName) -> String {
|
|
"attribute:\(PureXML.Schema.ComplexValidator.key(name))"
|
|
}
|
|
|
|
static func indexByName(_ nodes: [XSDTree]) -> [String: XSDTree] {
|
|
var index: [String: XSDTree] = [:]
|
|
for node in nodes {
|
|
if let name = PureXML.Schema.XSDNode.attribute(node, "name") { index[name] = node }
|
|
}
|
|
return index
|
|
}
|
|
|
|
static func elementTypeName(_ node: XSDTree) -> String? {
|
|
if let typeName = PureXML.Schema.XSDNode.attribute(node, "type") {
|
|
return PureXML.Schema.XSDNode.stripPrefix(typeName)
|
|
}
|
|
if PureXML.Schema.XSDNode.firstChild(node, named: "simpleType") != nil || PureXML.Schema.XSDNode.firstChild(node, named: "complexType") != nil {
|
|
return nil
|
|
}
|
|
return "anyType"
|
|
}
|
|
|
|
static func derivation(_ node: XSDTree) -> XSDTree? {
|
|
PureXML.Schema.XSDNode.firstChild(node, named: "restriction") ?? PureXML.Schema.XSDNode.firstChild(node, named: "extension")
|
|
}
|
|
|
|
static func valueConstraint(of node: XSDTree) -> PureXML.Schema.ValueConstraint? {
|
|
if let fixed = PureXML.Schema.XSDNode.attribute(node, "fixed") { return .fixed(fixed) }
|
|
if let value = PureXML.Schema.XSDNode.attribute(node, "default") { return .default(value) }
|
|
return nil
|
|
}
|
|
}
|