extension PureXML.Schema.XSDParser { static let simpleTypeRestrictionAllowedChildren: Set = { let facets: Set = [ "minExclusive", "minInclusive", "maxExclusive", "maxInclusive", "totalDigits", "fractionDigits", "length", "minLength", "maxLength", "enumeration", "whiteSpace", "pattern", ] return Set(["annotation", "simpleType"]).union(facets) }() static func checkQNamePrefix(_ qname: String, bindings: [String: String]) -> String? { let trimmed = qname.trimmingXMLWhitespace() guard PureXML.Schema.Lexical.isQName(trimmed) else { return nil } if let prefix = PureXML.Schema.XSDNode.prefix(trimmed) { if prefix == "xml" { return nil } if bindings[prefix] == nil { return "prefix '\(prefix)' is not bound" } } return nil } static func checkMemberTypesPrefixes(_ memberTypes: String, bindings: [String: String]) -> String? { let tokens = memberTypes.trimmingXMLWhitespace().split(whereSeparator: \.isWhitespace).map(String.init) for token in tokens { if let err = checkQNamePrefix(token, bindings: bindings) { return err } } return nil } static func isNonNegativeInteger(_ value: String) -> Bool { var digits = Substring(value) if digits.first == "+" { digits = digits.dropFirst() } return !digits.isEmpty && digits.allSatisfy { $0.isASCII && $0.isNumber } } static func occurrenceOrderErrors(_ node: XSDTree) -> [String] { guard let minRaw = PureXML.Schema.XSDNode.attribute(node, "minOccurs"), let maxRaw = PureXML.Schema.XSDNode.attribute(node, "maxOccurs") else { return [] } let minimum = minRaw.trimmingXMLWhitespace() let maximum = maxRaw.trimmingXMLWhitespace() guard maximum != "unbounded", isNonNegativeInteger(minimum), isNonNegativeInteger(maximum), exceeds(minimum, maximum) else { return [] } return ["minOccurs (\(minRaw)) exceeds maxOccurs (\(maxRaw))"] } static func exceeds(_ lhs: String, _ rhs: String) -> Bool { let left = canonicalMagnitude(lhs), right = canonicalMagnitude(rhs) return left.count != right.count ? left.count > right.count : left > right } static func canonicalMagnitude(_ value: String) -> Substring { var digits = Substring(value) if digits.first == "+" { digits = digits.dropFirst() } let trimmed = digits.drop { $0 == "0" } return trimmed.isEmpty ? "0" : trimmed } static func restrictionErrors(_ node: XSDTree, names: [String]) -> [String] { var errors: [String] = [] guard let parent = node.parent, parent.name?.namespaceURI == xsdNamespace, PureXML.Schema.XSDNode.localName(parent) == "simpleType" else { return [] } let simpleTypesCount = names.count { $0 == "simpleType" } if simpleTypesCount > 1 { errors.append("restriction under simpleType may have at most one inline simpleType") } var seenFacet = false for name in names { if name == "annotation" { continue } if name == "simpleType" { if seenFacet { errors.append("inline simpleType must appear before facets in restriction") } } else if simpleTypeRestrictionAllowedChildren.contains(name) { seenFacet = true } } let hasBase = PureXML.Schema.XSDNode.attribute(node, "base") != nil let hasSimpleType = names.contains("simpleType") if hasBase, hasSimpleType { errors.append("restriction under simpleType cannot have both a 'base' attribute and an inline 'simpleType' child") } return errors } static func listErrors(_ node: XSDTree, names: [String]) -> [String] { var errors: [String] = [] let simpleTypes = names.count { $0 == "simpleType" } if simpleTypes > 1 { errors.append("list may have at most one inline simpleType") } let hasItemType = PureXML.Schema.XSDNode.attribute(node, "itemType") != nil let hasSimpleType = names.contains("simpleType") if hasItemType, hasSimpleType { errors.append("list cannot have both an 'itemType' attribute and an inline 'simpleType' child") } return errors } }