A restriction or extension inside complexContent has the content model (annotation?, (group|all|choice|sequence)?, (attribute|attributeGroup)*, anyAttribute?). The allowedChildren membership table admitted the right child names but not their order or cardinality, so a second model group, a model group after an attribute, an attribute after anyAttribute, or a second anyAttribute was accepted. A forward-only walk matches the children against an ordered slot model (one model group, unbounded attributes, one anyAttribute). The check runs only in the complexContent context, where a model group belongs to the model, so a simpleContent derivation (facets, no model group) keeps its own rules and no valid schema is rejected. XSTS: invalid schemas accepted 864 to 785, valid schemas rejected held at 72, instance buckets unchanged.
61 lines
3.2 KiB
Swift
61 lines
3.2 KiB
Swift
extension PureXML.Schema.XSDParser {
|
|
/// 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.
|
|
static func complexContentOrderErrors(_ complexContent: XSDTree) -> [String] {
|
|
var errors: [String] = []
|
|
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
|
|
}
|
|
|
|
/// 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<String>, max: Int?)] = [
|
|
(["group", "all", "choice", "sequence"], 1),
|
|
(["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<String>, 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
|
|
}
|
|
}
|