Add xs:any and xs:anyAttribute wildcards with namespace and processContents constraints (skip/lax/strict), and resolve the list built-in datatypes (IDREFS, ENTITIES, NMTOKENS) to their item types so each list item validates. WildcardNamespace models ##any/##other/enumerated URI sets; Wildcard.admits gates undeclared attributes and wildcard-matched children. ContentNFA.follow exposes the follow set for completions. Closes #77
170 lines
5.9 KiB
Swift
170 lines
5.9 KiB
Swift
public extension PureXML.Schema {
|
|
/// A model-group compositor.
|
|
enum Compositor: Sendable {
|
|
/// An ordered list (`xs:sequence`).
|
|
case sequence
|
|
/// One of the alternatives (`xs:choice`).
|
|
case choice
|
|
/// Each member once, in any order (`xs:all`).
|
|
case all
|
|
}
|
|
|
|
/// The namespace constraint of a wildcard (`xs:any`/`xs:anyAttribute`).
|
|
enum WildcardNamespace: Sendable, Equatable {
|
|
/// `##any`: a name in any namespace, or none.
|
|
case any
|
|
/// `##other`: a name in any namespace other than the target, and not in no
|
|
/// namespace.
|
|
case other
|
|
/// A specific set of namespace URIs; the empty string stands for no
|
|
/// namespace (`##local`).
|
|
case enumerated(Set<String>)
|
|
}
|
|
|
|
/// How a wildcard-matched item is validated: `skip` (no validation), `lax`
|
|
/// (validate if a declaration is found), or `strict` (a declaration is
|
|
/// required).
|
|
enum ProcessContents: Sendable, Equatable {
|
|
case skip
|
|
case lax
|
|
case strict
|
|
}
|
|
|
|
/// An element or attribute wildcard (`xs:any`/`xs:anyAttribute`): which
|
|
/// namespaces it admits, and how matched items are validated.
|
|
struct Wildcard: Sendable, Equatable {
|
|
public var namespace: WildcardNamespace
|
|
public var processContents: ProcessContents
|
|
/// The schema's target namespace, needed to resolve `##other`.
|
|
public var targetNamespace: String?
|
|
|
|
public init(
|
|
namespace: WildcardNamespace = .any,
|
|
processContents: ProcessContents = .strict,
|
|
targetNamespace: String? = nil,
|
|
) {
|
|
self.namespace = namespace
|
|
self.processContents = processContents
|
|
self.targetNamespace = targetNamespace
|
|
}
|
|
|
|
/// Whether `name` is admitted by this wildcard's namespace constraint.
|
|
public func admits(_ name: PureXML.Model.QualifiedName) -> Bool {
|
|
let namespaceURI = name.namespaceURI ?? ""
|
|
switch namespace {
|
|
case .any:
|
|
return true
|
|
case .other:
|
|
return !namespaceURI.isEmpty && namespaceURI != (targetNamespace ?? "")
|
|
case let .enumerated(uris):
|
|
return uris.contains(namespaceURI)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The term of a particle: an element declaration, a nested model group, or a
|
|
/// wildcard.
|
|
indirect enum Term: Sendable {
|
|
case element(name: PureXML.Model.QualifiedName, type: ElementType?)
|
|
case group(Group)
|
|
case wildcard(Wildcard)
|
|
}
|
|
|
|
/// A particle: a term with an occurrence range. `maxOccurs` nil means
|
|
/// unbounded.
|
|
struct Particle: Sendable {
|
|
public var minOccurs: Int
|
|
public var maxOccurs: Int?
|
|
public var term: Term
|
|
|
|
public init(minOccurs: Int = 1, maxOccurs: Int? = 1, term: Term) {
|
|
self.minOccurs = minOccurs
|
|
self.maxOccurs = maxOccurs
|
|
self.term = term
|
|
}
|
|
}
|
|
|
|
/// A model group: a compositor over particles.
|
|
struct Group: Sendable {
|
|
public var compositor: Compositor
|
|
public var particles: [Particle]
|
|
|
|
public init(compositor: Compositor, particles: [Particle]) {
|
|
self.compositor = compositor
|
|
self.particles = particles
|
|
}
|
|
}
|
|
|
|
/// The declared type of an element: a simple type (text only), a complex one,
|
|
/// or a named reference resolved against the schema's type table (which lets
|
|
/// recursive schemas be represented without an infinite value).
|
|
indirect enum ElementType: Sendable {
|
|
case simple(SimpleType)
|
|
case complex(ComplexType)
|
|
case typeReference(String)
|
|
}
|
|
|
|
/// A value constraint on an attribute or element: a default value supplied
|
|
/// when the item is absent, or a fixed value the item must equal.
|
|
enum ValueConstraint: Sendable, Equatable {
|
|
case `default`(String)
|
|
case fixed(String)
|
|
|
|
/// The fixed value, when this is a `fixed` constraint.
|
|
public var fixedValue: String? {
|
|
if case let .fixed(value) = self { return value }
|
|
return nil
|
|
}
|
|
}
|
|
|
|
/// An attribute use on a complex type.
|
|
struct AttributeUse: Sendable {
|
|
public var name: PureXML.Model.QualifiedName
|
|
public var type: SimpleType
|
|
public var required: Bool
|
|
public var valueConstraint: ValueConstraint?
|
|
|
|
public init(
|
|
name: PureXML.Model.QualifiedName,
|
|
type: SimpleType,
|
|
required: Bool = false,
|
|
valueConstraint: ValueConstraint? = nil,
|
|
) {
|
|
self.name = name
|
|
self.type = type
|
|
self.required = required
|
|
self.valueConstraint = valueConstraint
|
|
}
|
|
}
|
|
|
|
/// The content of a complex type.
|
|
indirect enum ContentType: Sendable {
|
|
/// No child elements or non-whitespace text.
|
|
case empty
|
|
/// Character data validated against a simple type, no child elements.
|
|
case simpleContent(SimpleType)
|
|
/// Child elements matching the particle; no non-whitespace text.
|
|
case elementOnly(Particle)
|
|
/// Child elements matching the particle, with text allowed between them.
|
|
case mixed(Particle)
|
|
}
|
|
|
|
/// A complex type: its attribute uses, an optional attribute wildcard
|
|
/// (`xs:anyAttribute`) admitting further attributes, and its content model.
|
|
struct ComplexType: Sendable {
|
|
public var attributes: [AttributeUse]
|
|
public var attributeWildcard: Wildcard?
|
|
public var content: ContentType
|
|
|
|
public init(
|
|
attributes: [AttributeUse] = [],
|
|
attributeWildcard: Wildcard? = nil,
|
|
content: ContentType = .empty,
|
|
) {
|
|
self.attributes = attributes
|
|
self.attributeWildcard = attributeWildcard
|
|
self.content = content
|
|
}
|
|
}
|
|
}
|