PureXML/Sources/Schema/SchemaSimpleContentCompatibility.swift

177 lines
8.2 KiB
Swift

/// Type-compatibility findings for simpleType derivations and simpleContent
/// restrictions, split out so SchemaReferences.swift stays within the file
/// length budget.
extension PureXML.Schema.XSDParser {
static func simpleTypeBaseNotComplexFindings(_ schema: XSDTree, in context: PureXML.Schema.XSDContext) -> [PureXML.Schema.SchemaLocatedFinding] {
var findings: [PureXML.Schema.SchemaLocatedFinding] = []
let bindings = PureXML.Schema.XSDNode.namespaceBindings(of: schema)
let targetNamespace = context.targetNamespace
func isComplex(_ qname: String) -> Bool {
let trimmed = qname.trimmingXMLWhitespace()
let prefix = PureXML.Schema.XSDNode.prefix(trimmed)
let uri = prefix.flatMap { bindings[$0] } ?? bindings[""]
let local = PureXML.Schema.XSDNode.stripPrefix(trimmed)
if uri == xsdNamespace {
if targetNamespace == xsdNamespace {
return local == "anyType" || context.complexTypeNodes[local] != nil
} else {
return local == "anyType"
}
}
if uri == targetNamespace || uri == nil || uri == "" {
return context.complexTypeNodes[local] != nil
}
return false
}
func check(_ node: XSDTree) {
let local = PureXML.Schema.XSDNode.localName(node)
if local == "appinfo" || local == "documentation" {
return
}
if node.name?.namespaceURI == xsdNamespace {
switch local {
case "restriction":
Self.checkRestrictionUnderSimpleType(node, isComplex: isComplex, findings: &findings)
case "list":
Self.checkList(node, isComplex: isComplex, findings: &findings)
case "union":
Self.checkUnion(node, isComplex: isComplex, findings: &findings)
default:
break
}
}
for child in PureXML.Schema.XSDNode.elementChildren(node) {
check(child)
}
}
check(schema)
return findings
}
private static func checkRestrictionUnderSimpleType(_ node: XSDTree, isComplex: (String) -> Bool, findings: inout [PureXML.Schema.SchemaLocatedFinding]) {
guard let parent = node.parent, parent.name?.namespaceURI == xsdNamespace, PureXML.Schema.XSDNode.localName(parent) == "simpleType" else { return }
if let base = PureXML.Schema.XSDNode.attribute(node, "base"), isComplex(base) {
findings.append(PureXML.Schema.SchemaLocatedFinding(
reason: "base type '\(base)' of simpleType restriction must be a simple type, not a complex type",
node: node,
))
}
}
private static func checkList(_ node: XSDTree, isComplex: (String) -> Bool, findings: inout [PureXML.Schema.SchemaLocatedFinding]) {
if let itemType = PureXML.Schema.XSDNode.attribute(node, "itemType"), isComplex(itemType) {
findings.append(PureXML.Schema.SchemaLocatedFinding(
reason: "itemType '\(itemType)' of list must be a simple type, not a complex type",
node: node,
))
}
}
private static func checkUnion(_ node: XSDTree, isComplex: (String) -> Bool, findings: inout [PureXML.Schema.SchemaLocatedFinding]) {
if let members = PureXML.Schema.XSDNode.attribute(node, "memberTypes") {
for token in members.split(whereSeparator: \.isWhitespace) where isComplex(String(token)) {
findings.append(PureXML.Schema.SchemaLocatedFinding(
reason: "memberType '\(token)' of union must be a simple type, not a complex type",
node: node,
))
}
}
}
static func xsdNamespaceReferenceFindings(_ schema: XSDTree) -> [PureXML.Schema.SchemaLocatedFinding] {
let targetNamespace = PureXML.Schema.XSDNode.attribute(schema, "targetNamespace")
if targetNamespace == xsdNamespace {
return []
}
var findings: [PureXML.Schema.SchemaLocatedFinding] = []
let bindings = PureXML.Schema.XSDNode.namespaceBindings(of: schema)
func isUndeclaredBuiltin(_ qname: String) -> Bool {
let prefix = PureXML.Schema.XSDNode.prefix(qname)
let uri = prefix.flatMap { bindings[$0] } ?? bindings[""]
if uri == xsdNamespace {
let localName = PureXML.Schema.XSDNode.stripPrefix(qname)
return !referenceBuiltins.contains(localName)
}
return false
}
func check(_ node: XSDTree) {
let local = PureXML.Schema.XSDNode.localName(node)
if local == "appinfo" || local == "documentation" {
return
}
if node.name?.namespaceURI == xsdNamespace {
for attribute in ["type", "base", "itemType"] {
if let qname = PureXML.Schema.XSDNode.attribute(node, attribute), isUndeclaredBuiltin(qname) {
findings.append(PureXML.Schema.SchemaLocatedFinding(reason: "\(attribute) references undeclared type '\(qname)'", node: node))
}
}
if let members = PureXML.Schema.XSDNode.attribute(node, "memberTypes") {
for token in members.split(whereSeparator: \.isWhitespace) {
let qname = String(token)
if isUndeclaredBuiltin(qname) {
findings.append(PureXML.Schema.SchemaLocatedFinding(reason: "memberTypes references undeclared type '\(qname)'", node: node))
}
}
}
}
for child in PureXML.Schema.XSDNode.elementChildren(node) {
check(child)
}
}
check(schema)
return findings
}
static func simpleContentBaseFindings(_ schema: XSDTree, in context: PureXML.Schema.XSDContext) -> [PureXML.Schema.SchemaLocatedFinding] {
var findings: [PureXML.Schema.SchemaLocatedFinding] = []
let bindings = PureXML.Schema.XSDNode.namespaceBindings(of: schema)
let targetNamespace = context.targetNamespace
func check(_ node: XSDTree) {
let local = PureXML.Schema.XSDNode.localName(node)
if local == "appinfo" || local == "documentation" {
return
}
let isSimpleContentRestriction = node.name?.namespaceURI == xsdNamespace && local == "restriction"
&& node.parent?.name?.namespaceURI == xsdNamespace
&& node.parent.flatMap { PureXML.Schema.XSDNode.localName($0) } == "simpleContent"
if isSimpleContentRestriction {
if let base = PureXML.Schema.XSDNode.attribute(node, "base") {
let prefix = PureXML.Schema.XSDNode.prefix(base)
let uri = prefix.flatMap { bindings[$0] } ?? bindings[""]
let localPart = PureXML.Schema.XSDNode.stripPrefix(base)
if uri == targetNamespace || uri == nil || uri == "" {
if let baseNode = context.complexTypeNodes[localPart] {
let children = PureXML.Schema.XSDNode.elementChildren(baseNode)
let hasSimpleContent = children.contains { child in
child.name?.namespaceURI == xsdNamespace && PureXML.Schema.XSDNode.localName(child) == "simpleContent"
}
if !hasSimpleContent {
findings.append(PureXML.Schema.SchemaLocatedFinding(
reason: "base type '\(base)' of simpleContent restriction must be a simple type or complex type with simpleContent",
node: node,
))
}
}
}
}
}
for child in PureXML.Schema.XSDNode.elementChildren(node) {
check(child)
}
}
check(schema)
return findings
}
}