PureXML/Sources/Schema/SchemaDocument.swift
Mihaela Mihaljevic 4345e6170e feat(xsd): xsi:nil/nillable and fixed/default value constraints
Closes #75, on the validation framework. XSD instance-level features:

- nillable elements: an xsi:nil="true" element is accepted (its content model is
  not checked) only when the element is declared nillable and carries no content;
  xsi:nil on a non-nillable element, or a nilled element with content, is a
  located error.
- fixed value constraints: a present fixed attribute must equal its fixed value,
  and a fixed element's text must match.
- default/fixed are parsed onto AttributeUse (per use) and collected per element
  name (nillable set + value constraints), threaded through XSDContext to the
  ComplexValidator.

All findings are located ValidationErrors. Collection and helper methods move to
same-file extensions to stay within type-body limits.
2026-06-08 13:03:06 +02:00

59 lines
2.7 KiB
Swift

public extension PureXML.Schema {
/// An error compiling or applying a schema.
enum SchemaError: Swift.Error, Equatable, Sendable, CustomStringConvertible {
case notASchema
public var description: String {
switch self {
case .notASchema: "the document is not an xs:schema"
}
}
}
/// A compiled XSD schema: its global element declarations and named-type
/// table, parsed from a schema document, used to validate instance documents.
struct Document: Sendable {
private let elements: [String: ElementType]
private let types: [String: ElementType]
private let constraints: [String: [IdentityConstraint]]
private let nillableElements: Set<String>
private let elementConstraints: [String: ValueConstraint]
/// Compiles a schema document. `schemaLoader` resolves the
/// `schemaLocation` of `xs:include`, `xs:import`, and `xs:redefine` to
/// schema source; it returns nil (the default) when external schemas are
/// not available, which keeps compilation from reaching the filesystem or
/// network by default.
public init(_ xsd: String, schemaLoader: @escaping (String) -> String? = { _ in nil }) throws {
let compiled = try XSDParser.parse(xsd, loader: schemaLoader)
elements = compiled.elements
types = compiled.types
constraints = compiled.constraints
nillableElements = compiled.nillableElements
elementConstraints = compiled.elementConstraints
}
/// Validates an instance document against the schema, returning one located
/// ``PureXML/Validation/ValidationError`` per violation. Reports an error
/// when the root element has no global declaration.
public func validate(_ xml: String) throws -> [PureXML.Validation.ValidationError] {
guard case let .document(children) = try PureXML.parse(xml),
let root = children.compactMap(\.element).first
else {
return [.init(reason: "the document has no root element", at: [])]
}
guard let declaration = elements[root.name.localName] else {
return [.init(reason: "no element declaration for '\(root.name.localName)'", at: [])]
}
let context = PureXML.Validation.XSDContext(
types: types,
constraints: constraints,
rootDeclaration: declaration,
nillableElements: nillableElements,
elementConstraints: elementConstraints,
)
return PureXML.Validation.XSD.validator().errors(for: .element(root), in: context)
}
}
}