The two monolithic XSD content/identity rules only satisfied the OpenAPIKit validation idiom at the wrong granularity: no per-constraint removability, no isolable failing/near-miss tests. Split them into 32 named rules, each finding tagged at its origin and cached per XSDContext so the walk still runs once per document (the #101 decision against duplicating XSD's recursive type resolution still holds). Wired the streaming path's ID/IDREF and identity checks through the same framework instead of calling the validators imperatively, and added the unwrapAndLookup combinator the rule already named as required. Also includes the mandated repo-wide swiftformat/swiftlint pass.
63 lines
3.6 KiB
Swift
63 lines
3.6 KiB
Swift
extension PureXML.Schema.ComplexValidator {
|
|
/// The errors from an `xsi:nil="true"` element: rejecting it when the
|
|
/// element is not nillable, or when it carries content. Returns nil when
|
|
/// the element is not nilled.
|
|
func nilErrors(_ element: PureXML.Model.Element, fixedValue: String? = nil, at path: XSDPath) -> [XSDFinding]? {
|
|
let name = element.name.localName
|
|
// cvc-elt.3.1: a non-nillable element must carry no `xsi:nil` attribute at
|
|
// all, whatever its value (so `xsi:nil="false"` is equally forbidden, not
|
|
// only `xsi:nil="true"`).
|
|
if Self.nilAttributeValue(element) != nil, !nillableElements.contains(name) {
|
|
return [XSDFinding(.nilAttributeRequiresNillable, reason: "element '\(name)' is not nillable but has xsi:nil", at: path)]
|
|
}
|
|
guard Self.isNil(element) else { return nil }
|
|
if Self.hasContent(element) {
|
|
return [XSDFinding(.nilElementMustBeEmpty, reason: "nil element '\(name)' must be empty", at: path)]
|
|
}
|
|
// cvc-elt.3.2.2: a nilled element may not have a fixed {value constraint}.
|
|
// The matched particle's constraint is authoritative; the by-local-name map
|
|
// is a fallback for paths without a particle (e.g. the resolved-type entry).
|
|
if (fixedValue ?? elementConstraints[name]?.fixedValue) != nil {
|
|
return [XSDFinding(.nilElementNoFixedConstraint, reason: "nil element '\(name)' must not have a fixed value constraint", at: path)]
|
|
}
|
|
return []
|
|
}
|
|
|
|
/// The text an element's simple content is validated against: its character
|
|
/// content, or, when the element is empty, its `default`/`fixed` value (an
|
|
/// empty element takes that value, and the value, not the empty string, must
|
|
/// satisfy the type). The matched particle's constraint takes precedence over
|
|
/// the by-local-name `elementConstraints` map, exactly as the tree path's
|
|
/// `validateChild` resolves it.
|
|
func effectiveSimpleText(for element: PureXML.Model.Element, particleConstraint: PureXML.Schema.ValueConstraint? = nil) -> String {
|
|
let text = Self.rawTextContent(element)
|
|
let constraint = particleConstraint ?? elementConstraints[element.name.localName]
|
|
return text.isEmpty ? (constraint?.value ?? text) : text
|
|
}
|
|
|
|
/// The error from a `fixed` element value constraint: the element's text
|
|
/// must equal the fixed value in the element type's value space when known.
|
|
func elementFixedErrors(
|
|
_ element: PureXML.Model.Element,
|
|
valueType: PureXML.Schema.SimpleType? = nil,
|
|
particleFixed: String? = nil,
|
|
at path: XSDPath,
|
|
) -> [XSDFinding] {
|
|
guard let fixed = particleFixed ?? elementConstraints[element.name.localName]?.fixedValue else { return [] }
|
|
// A fixed element's content is its fixed character value: it may not carry
|
|
// element children, even under a mixed content type (cvc-elt.5.2.2.2.1).
|
|
if !element.children.compactMap(\.element).isEmpty {
|
|
return [XSDFinding(.fixedElementNoChildren, reason: "fixed element '\(element.name.localName)' must not have element children", at: path)]
|
|
}
|
|
let text = Self.rawTextContent(element)
|
|
// An empty element takes the fixed value; only present content must equal it.
|
|
if text.isEmpty {
|
|
return []
|
|
}
|
|
if let valueType, valueType.valueMatches(text, literal: fixed) {
|
|
return []
|
|
}
|
|
return text == fixed ? [] : [XSDFinding(.fixedElementValueMatches, reason: "element '\(element.name.localName)' is fixed and must be '\(fixed)'", at: path)]
|
|
}
|
|
}
|