extension PureXML.Schema.XSDParser { /// The finding, if any, for a `simpleType`: its content is exactly one of /// `restriction`/`list`/`union` (XSD 1.0 Datatypes), so neither an empty /// `simpleType` nor one combining two derivations (two unions, a union and a /// list, ...) is valid. The `allowedChildren` membership check admits each name /// but not the cardinality. static func simpleTypeContentErrors(_ children: [String]) -> [String] { let derivations = children.count { $0 == "restriction" || $0 == "list" || $0 == "union" } return derivations == 1 ? [] : ["a simpleType must contain exactly one of restriction, list, or union"] } /// Content-model order and cardinality for a `complexContent` derivation, the /// ordering complement to the set-membership `allowedChildren` check (which /// admits the right child names but not their sequence or count). /// /// A `restriction`/`extension` inside `complexContent` has the model /// `(annotation?, (group | all | choice | sequence)?, (attribute | attributeGroup)*, anyAttribute?)` /// (XSD 1.0 Structures): an optional model group, then any number of /// `attribute`/`attributeGroup`, then an optional `anyAttribute`. A second model /// group, a model group after an attribute, an attribute after `anyAttribute`, or /// a second `anyAttribute` (the ctG/ctH families) was previously accepted. /// Only the `complexContent` context is checked, where a model group is part of /// the model; a `simpleContent` derivation (facets, no model group) has a /// different model and is left to its own rules. /// The number of `restriction`/`extension` children of a `simpleContent` or /// `complexContent`. Its content model requires exactly one, so zero (an empty /// ``) or more than one is a structural error. private static func derivationCount(_ content: XSDTree) -> Int { PureXML.Schema.XSDNode.elementChildren(content).count { child in guard child.name?.namespaceURI == xsdNamespace, let local = PureXML.Schema.XSDNode.localName(child) else { return false } return local == "restriction" || local == "extension" } } static func complexContentOrderErrors(_ complexContent: XSDTree) -> [String] { var errors: [String] = [] if derivationCount(complexContent) != 1 { errors.append("a complexContent must contain exactly one 'restriction' or 'extension'") } for derivation in PureXML.Schema.XSDNode.elementChildren(complexContent) { guard let local = PureXML.Schema.XSDNode.localName(derivation), local == "restriction" || local == "extension" else { continue } let names = PureXML.Schema.XSDNode.elementChildren(derivation) .filter { $0.name?.namespaceURI == xsdNamespace } .compactMap(PureXML.Schema.XSDNode.localName) .filter { $0 != "annotation" } if let reason = slotOrderViolation(names, slots: complexDerivationSlots) { errors.append("the content of a complexContent '\(local)' must be a model group, then attributes, then anyAttribute: \(reason)") } } return errors } /// Content-model order for a complexType's shorthand form (no `simpleContent`/ /// `complexContent`), which shares the derivation's slot model: an optional model /// group, then `attribute`/`attributeGroup`, then an optional `anyAttribute`. /// `complexTypeContentErrors` already checks cardinality and the exclusivity of a /// content spec with model groups/attributes; this adds the ordering (a model /// group after an attribute, an attribute after `anyAttribute`, a second /// `anyAttribute`). `annotation` and the content specs are not part of the slots. static func complexTypeOrderErrors(_ complexType: XSDTree) -> [String] { let names = PureXML.Schema.XSDNode.elementChildren(complexType) .filter { $0.name?.namespaceURI == xsdNamespace } .compactMap(PureXML.Schema.XSDNode.localName) .filter { $0 != "annotation" && $0 != "simpleContent" && $0 != "complexContent" } guard let reason = slotOrderViolation(names, slots: complexDerivationSlots) else { return [] } return ["the content of a complexType must be a model group, then attributes, then anyAttribute: \(reason)"] } /// Content-model order for a `simpleContent` derivation (XSD 1.0 Structures), /// whose model has NO model group: a `restriction` is /// `(annotation?, (simpleType?, facet*), (attribute | attributeGroup)*, anyAttribute?)` /// and an `extension` is `(annotation?, (attribute | attributeGroup)*, anyAttribute?)`. /// So a model group anywhere, a facet or `simpleType` in an extension, a facet /// before its `simpleType`, an attribute before a facet, an attribute after /// `anyAttribute`, or a second `anyAttribute` was accepted by the membership /// table but is invalid (the ctD family). static func simpleContentOrderErrors(_ simpleContent: XSDTree) -> [String] { var errors: [String] = [] if derivationCount(simpleContent) != 1 { errors.append("a simpleContent must contain exactly one 'restriction' or 'extension'") } for derivation in PureXML.Schema.XSDNode.elementChildren(simpleContent) { guard let local = PureXML.Schema.XSDNode.localName(derivation) else { continue } let slots: [(members: Set, max: Int?)] switch local { case "restriction": slots = simpleContentRestrictionSlots case "extension": slots = simpleContentExtensionSlots default: continue } let names = PureXML.Schema.XSDNode.elementChildren(derivation) .filter { $0.name?.namespaceURI == xsdNamespace } .compactMap(PureXML.Schema.XSDNode.localName) .filter { $0 != "annotation" } if let reason = slotOrderViolation(names, slots: slots) { errors.append("the content of a simpleContent '\(local)' is malformed: \(reason)") } } return errors } /// The ordered slots of a `complexContent` derivation: each lists its admitted /// names and how many children may fill it (nil is unbounded). private static let complexDerivationSlots: [(members: Set, max: Int?)] = [ (["group", "all", "choice", "sequence"], 1), (["attribute", "attributeGroup"], nil), (["anyAttribute"], 1), ] private static let constrainingFacets: Set = [ "minExclusive", "minInclusive", "maxExclusive", "maxInclusive", "totalDigits", "fractionDigits", "length", "minLength", "maxLength", "enumeration", "whiteSpace", "pattern", ] /// The ordered slots of a `simpleContent` `restriction`: an optional `simpleType`, /// then facets, then attributes, then an optional `anyAttribute`. private static let simpleContentRestrictionSlots: [(members: Set, max: Int?)] = [ (["simpleType"], 1), (constrainingFacets, nil), (["attribute", "attributeGroup"], nil), (["anyAttribute"], 1), ] /// The ordered slots of a `simpleContent` `extension`: attributes, then an /// optional `anyAttribute` (no facets, `simpleType`, or model group). private static let simpleContentExtensionSlots: [(members: Set, max: Int?)] = [ (["attribute", "attributeGroup"], nil), (["anyAttribute"], 1), ] /// The first child that does not fit the ordered slot model, or nil. Each child /// must fill the current slot or one after it (order is forward-only), and a slot /// with a finite `max` may not be overfilled. private static func slotOrderViolation(_ children: [String], slots: [(members: Set, max: Int?)]) -> String? { var slotIndex = 0 var counts = [Int](repeating: 0, count: slots.count) for child in children { var index = slotIndex while index < slots.count, !slots[index].members.contains(child) { index += 1 } guard index < slots.count else { return "'\(child)' is out of order" } counts[index] += 1 if let maximum = slots[index].max, counts[index] > maximum { return "'\(child)' appears too many times" } slotIndex = index } return nil } }