Add PureXML.XPath: a compiled location-path query engine reimplemented from the XPath 1.0 specification; no upstream code copied. - Compiler parses a path into steps; Evaluator walks the tree returning a Selection node-set (node or attribute) in document order. - Forward axes only (child, descendant //, self ., attribute @), since the value model has no parent pointers. Node tests: name, *, text(), node(), comment(). Predicates: [n], [@a], [@a='v'], [child], [child='v']. - Public surface: XPath.Query (init compiles, evaluate/elements/strings), the Selection enum (element / stringValue), QueryError, and the one-shot PureXML.xpath(_:over:). Upward/sibling axes and the full expression language are explicitly out of scope. - 12 XPath tests; 61 total.
42 lines
1.5 KiB
Swift
42 lines
1.5 KiB
Swift
public extension PureXML.XPath {
|
|
/// A node selected by an XPath query: either a tree node or an attribute
|
|
/// (attributes are not ``PureXML/Model/Node`` values, so they are surfaced
|
|
/// here alongside nodes).
|
|
enum Selection: Equatable, Sendable {
|
|
case node(PureXML.Model.Node)
|
|
case attribute(PureXML.Model.Attribute)
|
|
|
|
/// The selected element, when this selection is an element node.
|
|
public var element: PureXML.Model.Element? {
|
|
guard case let .node(node) = self, case let .element(element) = node else {
|
|
return nil
|
|
}
|
|
return element
|
|
}
|
|
|
|
/// The XPath string-value: an attribute's value, or the concatenation of
|
|
/// all descendant character data for a node.
|
|
public var stringValue: String {
|
|
switch self {
|
|
case let .attribute(attribute):
|
|
attribute.value
|
|
case let .node(node):
|
|
Self.stringValue(of: node)
|
|
}
|
|
}
|
|
|
|
private static func stringValue(of node: PureXML.Model.Node) -> String {
|
|
switch node {
|
|
case let .text(value), let .cdata(value), let .comment(value):
|
|
value
|
|
case let .processingInstruction(_, data):
|
|
data
|
|
case let .element(element):
|
|
element.children.reduce(into: "") { $0 += stringValue(of: $1) }
|
|
case let .document(children):
|
|
children.reduce(into: "") { $0 += stringValue(of: $1) }
|
|
}
|
|
}
|
|
}
|
|
}
|