/// 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 } }