Re-applies templates to the current node in the current mode, considering only templates of lower import precedence than the one being instantiated, falling back to the built-in rule when none match. Threads the current mode and import precedence through the evaluation context, and moves the transformer runtime types into XSLTRuntime.swift (namespaced, via private aliases) to stay within the file-length and namespacing gates.
194 lines
8.0 KiB
Swift
194 lines
8.0 KiB
Swift
public extension PureXML.XSLT {
|
|
/// One part of an attribute value template: literal text, or an embedded XPath
|
|
/// expression written `{expr}`.
|
|
enum ValuePart: Sendable {
|
|
case literal(String)
|
|
case expression(String)
|
|
}
|
|
|
|
/// An attribute value template (a string with `{expr}` substitutions).
|
|
typealias ValueTemplate = [ValuePart]
|
|
|
|
/// A literal-result-element attribute, whose value is an attribute value
|
|
/// template.
|
|
struct LiteralAttribute: Sendable {
|
|
public var name: PureXML.Model.QualifiedName
|
|
public var value: ValueTemplate
|
|
}
|
|
|
|
/// A sort specification for `apply-templates`/`for-each`.
|
|
struct Sort: Sendable {
|
|
public var select: String
|
|
public var descending: Bool
|
|
public var numeric: Bool
|
|
}
|
|
|
|
/// One instruction of a template body (a sequence constructor item).
|
|
indirect enum Instruction: Sendable {
|
|
/// Literal character data copied to the result.
|
|
case literalText(String)
|
|
/// `xsl:value-of`: the string value of an XPath expression.
|
|
case valueOf(select: String)
|
|
/// `xsl:apply-templates` with an optional node-set selection, mode, sorts,
|
|
/// and passed parameters.
|
|
case applyTemplates(select: String?, mode: String?, sorts: [Sort], parameters: [Binding])
|
|
/// `xsl:for-each` over a node-set.
|
|
case forEach(select: String, sorts: [Sort], body: [Instruction])
|
|
/// `xsl:if`.
|
|
case ifInstruction(test: String, body: [Instruction])
|
|
/// `xsl:choose` with its `when` branches and optional `otherwise`.
|
|
case choose(whens: [Branch], otherwise: [Instruction])
|
|
/// A literal result element, copied to the output with its attributes and
|
|
/// any `use-attribute-sets`.
|
|
case literalElement(name: PureXML.Model.QualifiedName, attributes: [LiteralAttribute], useAttributeSets: [String], body: [Instruction])
|
|
/// `xsl:element` whose name is an attribute value template, with any
|
|
/// `use-attribute-sets`.
|
|
case element(name: ValueTemplate, useAttributeSets: [String], body: [Instruction])
|
|
/// `xsl:attribute` whose name is an attribute value template.
|
|
case attribute(name: ValueTemplate, body: [Instruction])
|
|
/// `xsl:copy`: a shallow copy of the context node.
|
|
case copy(body: [Instruction])
|
|
/// `xsl:copy-of`: a deep copy of a selected node-set.
|
|
case copyOf(select: String)
|
|
/// `xsl:call-template` by name with passed parameters.
|
|
case callTemplate(name: String, parameters: [Binding])
|
|
/// `xsl:variable` bound for the rest of the sequence constructor.
|
|
case variable(name: String, select: String?, body: [Instruction])
|
|
/// `xsl:number`: a generated sequence number for the context node.
|
|
case number(count: String?, from: String?, format: String)
|
|
/// `xsl:comment`: a comment node whose text is the instantiated body.
|
|
case comment(body: [Instruction])
|
|
/// `xsl:processing-instruction` whose target is `name` and whose data is
|
|
/// the instantiated body.
|
|
case processingInstruction(name: ValueTemplate, body: [Instruction])
|
|
/// `xsl:message`: instantiates its body as a diagnostic. `terminate` ends
|
|
/// the transformation with that text; it produces no result-tree output.
|
|
case message(terminate: Bool, body: [Instruction])
|
|
/// `xsl:apply-imports`: re-applies templates to the current node, in the
|
|
/// current mode, considering only templates of lower import precedence.
|
|
case applyImports
|
|
}
|
|
|
|
/// The `xsl:output` controls that shape serialization: the output method, and
|
|
/// the declaration and indentation settings. Each is nil when unspecified, so
|
|
/// the serializer falls back to the caller's options.
|
|
struct Output: Sendable {
|
|
public var method: String?
|
|
public var indent: Bool?
|
|
public var omitXMLDeclaration: Bool?
|
|
public var encoding: String?
|
|
public var version: String?
|
|
public var standalone: Bool?
|
|
|
|
public init(
|
|
method: String? = nil,
|
|
indent: Bool? = nil,
|
|
omitXMLDeclaration: Bool? = nil,
|
|
encoding: String? = nil,
|
|
version: String? = nil,
|
|
standalone: Bool? = nil,
|
|
) {
|
|
self.method = method
|
|
self.indent = indent
|
|
self.omitXMLDeclaration = omitXMLDeclaration
|
|
self.encoding = encoding
|
|
self.version = version
|
|
self.standalone = standalone
|
|
}
|
|
|
|
/// This output's settings with `other`'s non-nil settings layered over them.
|
|
func merged(with other: Output) -> Output {
|
|
Output(
|
|
method: other.method ?? method,
|
|
indent: other.indent ?? indent,
|
|
omitXMLDeclaration: other.omitXMLDeclaration ?? omitXMLDeclaration,
|
|
encoding: other.encoding ?? encoding,
|
|
version: other.version ?? version,
|
|
standalone: other.standalone ?? standalone,
|
|
)
|
|
}
|
|
}
|
|
|
|
/// An `xsl:attribute-set`: the `xsl:attribute` instructions it contributes and
|
|
/// the other attribute sets it includes (`use-attribute-sets`).
|
|
struct AttributeSet: Sendable {
|
|
public var attributes: [Instruction]
|
|
public var use: [String]
|
|
|
|
public init(attributes: [Instruction], use: [String]) {
|
|
self.attributes = attributes
|
|
self.use = use
|
|
}
|
|
}
|
|
|
|
/// An `xsl:key` declaration: a name, the nodes it indexes (a match pattern),
|
|
/// and the key value of each (a `use` expression).
|
|
struct Key: Sendable {
|
|
public var name: String
|
|
public var match: String
|
|
public var use: String
|
|
}
|
|
|
|
/// One `xsl:when` branch of a choose.
|
|
struct Branch: Sendable {
|
|
public var test: String
|
|
public var body: [Instruction]
|
|
}
|
|
|
|
/// A name binding: an `xsl:param` declaration (with a default) or an
|
|
/// `xsl:with-param` value, supplied either by `select` or by a body.
|
|
struct Binding: Sendable {
|
|
public var name: String
|
|
public var select: String?
|
|
public var body: [Instruction]
|
|
}
|
|
|
|
/// A template rule: a match pattern and/or a name, a mode, a priority, its
|
|
/// import precedence (higher wins, used to resolve `xsl:import`), its declared
|
|
/// parameters, and a body.
|
|
struct Template: Sendable {
|
|
public var match: String?
|
|
public var name: String?
|
|
public var mode: String?
|
|
public var priority: Double
|
|
public var importPrecedence: Int
|
|
public var parameters: [Binding]
|
|
public var body: [Instruction]
|
|
}
|
|
|
|
/// A compiled stylesheet: its template rules, global variables, keys, output
|
|
/// controls, and the `xsl:strip-space`/`xsl:preserve-space` name tests (with
|
|
/// `xsl:include`/`xsl:import` already folded in).
|
|
struct Stylesheet: Sendable {
|
|
public var templates: [Template]
|
|
public var globals: [Instruction]
|
|
public var keys: [Key]
|
|
public var output: Output
|
|
/// Element name tests whose whitespace-only text children are stripped from
|
|
/// the source (an NCName, a qualified name, or `*`).
|
|
public var stripSpace: Set<String>
|
|
/// Element name tests that keep their whitespace, overriding `stripSpace`.
|
|
public var preserveSpace: Set<String>
|
|
/// Named `xsl:attribute-set` declarations, keyed by name.
|
|
public var attributeSets: [String: AttributeSet]
|
|
|
|
public init(
|
|
templates: [Template],
|
|
globals: [Instruction],
|
|
keys: [Key],
|
|
output: Output,
|
|
stripSpace: Set<String> = [],
|
|
preserveSpace: Set<String> = [],
|
|
attributeSets: [String: AttributeSet] = [:],
|
|
) {
|
|
self.templates = templates
|
|
self.globals = globals
|
|
self.keys = keys
|
|
self.output = output
|
|
self.stripSpace = stripSpace
|
|
self.preserveSpace = preserveSpace
|
|
self.attributeSets = attributeSets
|
|
}
|
|
}
|
|
}
|