A path that is a single forward-axis step from one context node returns its nodes already in document order with no duplicates, so the document-order sort is redundant. AxisNavigation produces the structural forward axes (child, descendant, descendant-or-self, self, following, following-sibling) in document order, deriving sibling order from the same children array the order key compares, so the two can never disagree. This is the common shape after //x compiles and fuses to a single descendant::x step. The attribute axis (whose order interleaves with namespace declarations), the namespace axis (produced in dictionary order), and every reverse axis (nearest-first) are excluded and still sorted. Measured xpath ~7% faster on the 20k-item generated corpus via a same-state A/B, identical results. XPathSingleStepOrderTests pins document order for each included axis, that the reverse axes still normalize, and that multi-step paths still sort. An adversarial order-correctness review found no query whose order or uniqueness the change breaks.
79 lines
3.1 KiB
Swift
79 lines
3.1 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, taken from a SINGLE context node, yields its nodes
|
|
/// already in document order with no duplicates, so a one-step path over
|
|
/// it needs neither the de-duplication nor the document-order sort. True
|
|
/// for the structural forward axes, which `AxisNavigation` produces in
|
|
/// document order (child by child-array order, descendant by pre-order,
|
|
/// the following families by document position). The attribute axis is
|
|
/// excluded because its order interleaves with namespace declarations,
|
|
/// and the namespace axis because its nodes are produced in dictionary
|
|
/// (hash) order; the sort normalizes both, so they are left to it.
|
|
var preservesDocumentOrderFromSingleContext: Bool {
|
|
switch self {
|
|
case .child, .descendant, .descendantOrSelf, .selfAxis, .followingSibling, .following: 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
|
|
}
|
|
}
|