Add a canonical11 preset / mergeInheritedBase option. On a document subset, the omitted-ancestor xml:base chain is merged into the apex by RFC 3986 reference resolution (vs 1.0 nearest-wins), the apex's own relative xml:base is resolved against it, and xml:id is no longer inherited; xml:lang/xml:space still inherit nearest. A self-contained RFC 3986 resolver (parse, path merge, dot-segment removal) backs the merge, with tests covering the 1.1-vs-1.0 difference, dot segments, xml:id, and lang/space. Part of #105 (Tier 3)
97 lines
4.7 KiB
Swift
97 lines
4.7 KiB
Swift
public extension PureXML.Canonical {
|
|
/// Whether to render every in-scope namespace (inclusive C14N) or only the
|
|
/// namespaces an element and its attributes actually use (exclusive C14N).
|
|
enum Mode: Equatable, Sendable {
|
|
case inclusive
|
|
case exclusive
|
|
}
|
|
|
|
/// The Canonical XML 2.0 `PrefixRewrite` parameter: how namespace prefixes
|
|
/// are spelled in the output.
|
|
enum PrefixRewrite: Equatable, Sendable {
|
|
/// Keep each declaration's original prefix (C14N 1.0/1.1 behavior).
|
|
case retain
|
|
/// Rewrite every prefix to a sequential canonical name (`n0`, `n1`, ...)
|
|
/// assigned in document order of first use, so two documents that differ
|
|
/// only in how they spell their prefixes canonicalize to the same bytes.
|
|
/// The reserved `xml` prefix is never rewritten.
|
|
case sequential
|
|
}
|
|
|
|
/// A Canonical XML 2.0 `QNameAware` label: an attribute (or element) whose
|
|
/// textual value is a QName, so its prefix is rewritten alongside the
|
|
/// structural prefixes under ``PrefixRewrite/sequential``.
|
|
struct QNameAwareLabel: Equatable, Sendable {
|
|
/// The namespace URI of the labelled attribute or element (empty for none).
|
|
public let namespaceURI: String
|
|
/// The local name of the labelled attribute or element.
|
|
public let localName: String
|
|
/// Whether the label names an element (its text content is the QName)
|
|
/// rather than an attribute (its value is the QName).
|
|
public let isElement: Bool
|
|
|
|
public init(namespaceURI: String = "", localName: String, isElement: Bool = false) {
|
|
self.namespaceURI = namespaceURI
|
|
self.localName = localName
|
|
self.isElement = isElement
|
|
}
|
|
}
|
|
|
|
/// Options for canonicalization. The default is inclusive C14N without
|
|
/// comments, matching the common XML-signature use.
|
|
///
|
|
/// For whole-document and whole-subtree canonicalization, C14N 1.0 and 1.1
|
|
/// produce identical output (their differences only affect `xml:*` attribute
|
|
/// inheritance into selected document subsets), so this single inclusive mode
|
|
/// covers both.
|
|
struct Options: Equatable, Sendable {
|
|
public var mode: Mode
|
|
public var includeComments: Bool
|
|
/// Prefixes always rendered in exclusive mode even when not visibly used
|
|
/// (the `InclusiveNamespaces` PrefixList).
|
|
public var inclusiveNamespacePrefixes: [String]
|
|
/// The Canonical XML 2.0 `TrimTextNodes` parameter: when true, leading and
|
|
/// trailing whitespace is stripped from each text node, and a text node
|
|
/// that is all whitespace is dropped.
|
|
public var trimTextNodes: Bool
|
|
/// The Canonical XML 2.0 `PrefixRewrite` parameter (default ``PrefixRewrite/retain``).
|
|
public var prefixRewrite: PrefixRewrite
|
|
/// The Canonical XML 2.0 `QNameAware` labels: attributes or elements whose
|
|
/// value is a QName, so its prefix is rewritten under sequential rewrite.
|
|
public var qnameAwareLabels: [QNameAwareLabel]
|
|
/// The Canonical XML 1.1 `xml:base` handling: when canonicalizing a document
|
|
/// subset, the `xml:base` values of omitted ancestors are merged into the
|
|
/// apex by RFC 3986 reference resolution (rather than 1.0's nearest-wins),
|
|
/// and `xml:id` is not inherited. Off by default (1.0 behavior).
|
|
public var mergeInheritedBase: Bool
|
|
|
|
public init(
|
|
mode: Mode = .inclusive,
|
|
includeComments: Bool = false,
|
|
inclusiveNamespacePrefixes: [String] = [],
|
|
trimTextNodes: Bool = false,
|
|
prefixRewrite: PrefixRewrite = .retain,
|
|
qnameAwareLabels: [QNameAwareLabel] = [],
|
|
mergeInheritedBase: Bool = false,
|
|
) {
|
|
self.mode = mode
|
|
self.includeComments = includeComments
|
|
self.inclusiveNamespacePrefixes = inclusiveNamespacePrefixes
|
|
self.trimTextNodes = trimTextNodes
|
|
self.prefixRewrite = prefixRewrite
|
|
self.qnameAwareLabels = qnameAwareLabels
|
|
self.mergeInheritedBase = mergeInheritedBase
|
|
}
|
|
|
|
/// Inclusive C14N without comments.
|
|
public static let inclusive = Options(mode: .inclusive)
|
|
/// Exclusive C14N without comments.
|
|
public static let exclusive = Options(mode: .exclusive)
|
|
/// Canonical XML 2.0 with text-node trimming enabled.
|
|
public static let canonical2 = Options(mode: .inclusive, trimTextNodes: true)
|
|
/// Canonical XML 1.1: inclusive, with `xml:base` merging on subset apex
|
|
/// inheritance and no `xml:id` inheritance.
|
|
public static let canonical11 = Options(mode: .inclusive, mergeInheritedBase: true)
|
|
}
|
|
}
|