Each location step accumulated its context nodes' axis results into a Set<Node> to drop nodes reachable from more than one context, hashing and copying every node. The wrapped TreeNode is reference-counted, so each copy is ARC traffic, and the profile showed the Set insert, hashing, and Node copies dominating evaluation. De-duplication is needed only when two distinct context nodes can reach the same node. Skip it on a single context and on the axes whose results are disjoint per context (child, attribute, namespace, self): those produce the identical node sequence the Set would, since every insertion succeeds, with no hashing. Keep the Set on the axes that genuinely overlap (descendant, ancestor, parent, the siblings, following/preceding). Predicates stay applied per context and the path result still passes through the final order-and-unique pass, so results are identical. Measured xpath ~13x to ~10x slower than libxml2 on the 20k-item generated corpus via a same-state A/B, ~28% faster, same result. XPathNodeSetDedupTests pins that a node shared by many contexts through ancestor/parent/descendant is still counted once, the disjoint axes return every distinct result, and an overlapping union still de-duplicates. The full XPath suite and the Apache Xalan XSLT gold-output corpus stay green.
63 lines
2.2 KiB
Swift
63 lines
2.2 KiB
Swift
extension PureXML.XPath {
|
|
/// The thirteen XPath 1.0 axes. Every axis is reachable now that the tree is
|
|
/// parent-aware, so upward and sibling navigation are first-class.
|
|
enum Axis: Equatable {
|
|
case child
|
|
case descendant
|
|
case parent
|
|
case ancestor
|
|
case followingSibling
|
|
case precedingSibling
|
|
case following
|
|
case preceding
|
|
case attribute
|
|
case namespace
|
|
case selfAxis
|
|
case descendantOrSelf
|
|
case ancestorOrSelf
|
|
|
|
/// Whether the axis is a reverse axis (its nodes are numbered in reverse
|
|
/// document order for positional predicates).
|
|
var isReverse: Bool {
|
|
switch self {
|
|
case .parent, .ancestor, .precedingSibling, .preceding, .ancestorOrSelf:
|
|
true
|
|
default:
|
|
false
|
|
}
|
|
}
|
|
|
|
/// Whether the axis yields disjoint node-sets from distinct context nodes,
|
|
/// so accumulating its results across a step's context nodes can never
|
|
/// produce a duplicate and needs no cross-context de-duplication. A child,
|
|
/// attribute, or namespace node belongs to exactly one element, and the
|
|
/// self axis maps each distinct context to itself; every other axis
|
|
/// (descendant, ancestor, the siblings, following/preceding, parent) can
|
|
/// reach one node from two different contexts.
|
|
var yieldsDisjointResults: Bool {
|
|
switch self {
|
|
case .child, .attribute, .namespace, .selfAxis: true
|
|
default: false
|
|
}
|
|
}
|
|
|
|
/// The principal node kind of the axis: attributes for the attribute axis,
|
|
/// namespaces for the namespace axis, elements otherwise. Governs what a
|
|
/// name test or `*` selects.
|
|
var principalKind: PrincipalKind {
|
|
switch self {
|
|
case .attribute: .attribute
|
|
case .namespace: .namespace
|
|
default: .element
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The principal node kind selected by name tests on an axis.
|
|
enum PrincipalKind: Equatable {
|
|
case element
|
|
case attribute
|
|
case namespace
|
|
}
|
|
}
|