125 lines
6.1 KiB
Swift
125 lines
6.1 KiB
Swift
extension PureXML.Schema.XSDParser {
|
|
/// Schema-validity findings for component-name uniqueness. Within a schema,
|
|
/// global type names (simpleType and complexType share one symbol space),
|
|
/// global element names, global attribute names, named model-group names, and
|
|
/// named attribute-group names must each be unique; identity-constraint names
|
|
/// (unique/key/keyref) must be unique across the whole schema. A duplicate was
|
|
/// accepted, with a later definition silently overwriting the earlier one.
|
|
///
|
|
/// Only the document's own globals are examined (`xs:redefine` children and
|
|
/// included documents are not direct globals here), so a redefinition is not
|
|
/// mistaken for a clash.
|
|
static func componentNameErrors(_ schema: XSDTree) -> [String] {
|
|
let globals = PureXML.Schema.XSDNode.elementChildren(schema)
|
|
.filter { $0.name?.namespaceURI == xsdNamespace }
|
|
var errors: [String] = []
|
|
errors += duplicateNames(of: ["simpleType", "complexType"], in: globals, label: "type")
|
|
errors += duplicateNames(of: ["element"], in: globals, label: "element")
|
|
errors += duplicateNames(of: ["attribute"], in: globals, label: "attribute")
|
|
errors += duplicateNames(of: ["group"], in: globals, label: "model group")
|
|
errors += duplicateNames(of: ["attributeGroup"], in: globals, label: "attribute group")
|
|
errors += identityConstraintNameErrors(schema)
|
|
errors += keyrefReferErrors(schema)
|
|
return errors
|
|
}
|
|
|
|
private struct KeyrefInfo {
|
|
let name: String
|
|
let refer: String
|
|
let arity: Int
|
|
}
|
|
|
|
/// Findings for a `keyref` whose `refer` does not name a `key` or `unique` in
|
|
/// the document, or whose field arity does not match the referenced key/unique's.
|
|
/// Skipped when the document pulls in external definitions
|
|
/// (`import`/`include`/`redefine`), which the default compile does not load,
|
|
/// so a `refer` into them is never flagged.
|
|
private static func keyrefReferErrors(_ schema: XSDTree) -> [String] {
|
|
if hasExternalReference(schema) { return [] }
|
|
var keyArities: [String: Int] = [:]
|
|
var keyrefs: [KeyrefInfo] = []
|
|
collectKeysAndRefers(schema, keyArities: &keyArities, keyrefs: &keyrefs)
|
|
|
|
var errors: [String] = []
|
|
for keyref in keyrefs {
|
|
if let keyArity = keyArities[keyref.refer] {
|
|
if keyArity != keyref.arity {
|
|
errors.append("keyref '\(keyref.name)' and its referenced key/unique '\(keyref.refer)' must have the same number of fields")
|
|
}
|
|
} else {
|
|
errors.append("keyref refers to undeclared key or unique '\(keyref.refer)'")
|
|
}
|
|
}
|
|
return errors
|
|
}
|
|
|
|
private static func collectKeysAndRefers(
|
|
_ node: XSDTree,
|
|
keyArities: inout [String: Int],
|
|
keyrefs: inout [KeyrefInfo],
|
|
) {
|
|
if let local = PureXML.Schema.XSDNode.localName(node), local == "appinfo" || local == "documentation" { return }
|
|
if node.name?.namespaceURI == xsdNamespace, let local = PureXML.Schema.XSDNode.localName(node) {
|
|
if local == "key" || local == "unique", let name = PureXML.Schema.XSDNode.attribute(node, "name")?.trimmingXMLWhitespace() {
|
|
let arity = PureXML.Schema.XSDNode.elementChildren(node)
|
|
.count(where: { $0.name?.namespaceURI == xsdNamespace && PureXML.Schema.XSDNode.localName($0) == "field" })
|
|
|
|
keyArities[name] = arity
|
|
}
|
|
if local == "keyref" {
|
|
let name = PureXML.Schema.XSDNode.attribute(node, "name")?.trimmingXMLWhitespace()
|
|
let refer = PureXML.Schema.XSDNode.attribute(node, "refer")?.trimmingXMLWhitespace()
|
|
if let name, let refer {
|
|
let arity = PureXML.Schema.XSDNode.elementChildren(node)
|
|
.count(where: { $0.name?.namespaceURI == xsdNamespace && PureXML.Schema.XSDNode.localName($0) == "field" })
|
|
keyrefs.append(KeyrefInfo(name: name, refer: PureXML.Schema.XSDNode.stripPrefix(refer), arity: arity))
|
|
}
|
|
}
|
|
}
|
|
for child in PureXML.Schema.XSDNode.elementChildren(node) {
|
|
collectKeysAndRefers(child, keyArities: &keyArities, keyrefs: &keyrefs)
|
|
}
|
|
}
|
|
|
|
/// Duplicate `name` values among the `kinds` components in `nodes`.
|
|
private static func duplicateNames(of kinds: Set<String>, in nodes: [XSDTree], label: String) -> [String] {
|
|
var seen: Set<String> = []
|
|
var errors: [String] = []
|
|
for node in nodes where kinds.contains(PureXML.Schema.XSDNode.localName(node) ?? "") {
|
|
guard let name = PureXML.Schema.XSDNode.attribute(node, "name")?.trimmingXMLWhitespace() else { continue }
|
|
if !seen.insert(name).inserted {
|
|
errors.append("duplicate \(label) name '\(name)'")
|
|
}
|
|
}
|
|
return errors
|
|
}
|
|
|
|
/// Duplicate identity-constraint names anywhere in the schema document.
|
|
private static func identityConstraintNameErrors(_ schema: XSDTree) -> [String] {
|
|
var seen: Set<String> = []
|
|
var errors: [String] = []
|
|
func walk(_ node: XSDTree) {
|
|
let local = PureXML.Schema.XSDNode.localName(node)
|
|
if local == "appinfo" || local == "documentation" { return }
|
|
if let name = identityConstraintName(node), !seen.insert(name).inserted {
|
|
errors.append("duplicate identity constraint name '\(name)'")
|
|
}
|
|
for child in PureXML.Schema.XSDNode.elementChildren(node) {
|
|
walk(child)
|
|
}
|
|
}
|
|
walk(schema)
|
|
return errors
|
|
}
|
|
|
|
/// The `name` of `node` when it is an identity constraint (`unique`/`key`/
|
|
/// `keyref`) in the XSD namespace, or nil.
|
|
private static func identityConstraintName(_ node: XSDTree) -> String? {
|
|
guard node.name?.namespaceURI == xsdNamespace,
|
|
let local = PureXML.Schema.XSDNode.localName(node),
|
|
["unique", "key", "keyref"].contains(local)
|
|
else { return nil }
|
|
return PureXML.Schema.XSDNode.attribute(node, "name")?.trimmingXMLWhitespace()
|
|
}
|
|
}
|