Compose schemas across documents and resolve types at the instance: - xs:include, xs:import, and xs:redefine pull a transitive closure of schema documents through an injected loader (resolved by schemaLocation), base schemas first so the including schema wins; a redefine's nested definitions override the schema it includes. - substitution groups: an xs:element ref to a head also admits every element that substitutes for it, expanded at parse time into a choice over the head and its transitive members. - xsi:type overrides an element's declared type with the named type at the instance; XML Schema instance attributes are exempt from attribute checks. The schema parser splits across XSDContext (shared tree helpers, document collection, parsing context), XSDSimpleParser (simple types and facets), and XSDParser (elements, complex types, model groups) to keep each within bounds. Closes #66
44 lines
2.0 KiB
Swift
44 lines
2.0 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]
|
|
|
|
/// 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 {
|
|
(elements, types) = try XSDParser.parse(xsd, loader: schemaLoader)
|
|
}
|
|
|
|
/// Validates an instance document against the schema, returning one issue
|
|
/// per violation. Reports an issue when the root element has no global
|
|
/// declaration.
|
|
public func validate(_ xml: String) throws -> [PureXML.Validation.Issue] {
|
|
guard case let .document(children) = try PureXML.parse(xml),
|
|
let root = children.compactMap(\.element).first
|
|
else {
|
|
return [.init(severity: .error, message: "the document has no root element")]
|
|
}
|
|
guard let declaration = elements[root.name.localName] else {
|
|
return [.init(severity: .error, message: "no element declaration for '\(root.name.localName)'")]
|
|
}
|
|
return ComplexValidator(types: types).validate(root, as: declaration)
|
|
}
|
|
}
|
|
}
|