PureXML/Sources/XSLT/XSLTModel.swift
Mihaela Mihaljevic 23db1289ed feat(xslt): add xsl:include/import, xsl:output, comment, and processing-instruction
Round out the XSLT 1.0 stylesheet and output controls:

- xsl:include folds another stylesheet's templates, keys, globals, and output
  at the same import precedence; xsl:import folds them one precedence lower,
  so the importing stylesheet's rules win conflicts. Both resolve their href
  through the injected document loader, never the filesystem.
- xsl:output drives serialization: method (xml or text), indent,
  omit-xml-declaration, encoding, version, and standalone, layered over the
  caller's emitter options. The text method emits character data only.
- xsl:comment and xsl:processing-instruction produce comment and PI nodes
  from their instantiated bodies.

Templates now carry an import precedence that the matcher ranks ahead of
priority and document order.

Closes #64
2026-06-08 08:08:38 +02:00

148 lines
6.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.
case literalElement(name: PureXML.Model.QualifiedName, attributes: [LiteralAttribute], body: [Instruction])
/// `xsl:element` whose name is an attribute value template.
case element(name: ValueTemplate, 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])
}
/// 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: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, and
/// output controls (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
}
}