Skip to content

3.0.0

Choose a tag to compare

@DrRataplan DrRataplan released this 17 Dec 13:40
· 507 commits to master since this release

XQuery upgrade facility

Breaking changes:

Correct XPath reverse axes

Problem

Previous versions have an issue with reverse axes: the ancestor,ancestor-or-self, preceding and preceding-sibling axes sometimes mistakenly returned their results in reverse document order. This has been fixed, but editors may accidentally depend on the old behavior. The error only occurred with XPath queries (or partial XPath
queries) which only contained a single step: like ancestor-or-self::* or preceding-sibling::div[@class="something"]. These XPaths used to result in a sequence containing the matching nodes, but in reverse document order. That same applies for expressions like evaluateXPathToFirstNode('ancestor-or-self::*)[1]'): this used to return the closest element ancestor of the context node. This same XPath now returns first element node in document order, which is usually the document element.

Note that XPaths with multiple steps were not affected by this bug. XPaths with the form (ancestor::div/parent::*)[1] correctly resulted in the parent of the first div, in document order: the 'topmost' one. Even XPaths as simple as ./preceding-sibling::* circumvented the bug. Also, predicates (what's between the []
brackets) are always evaluated in the order of the local step: preceding-sibling::*[1] will always return the closest sibling.

Fix

Since only XPaths with reverse axis steps are affected, a search for the reverse axis step names (ancestor, ancestor-or-self, preceding and preceding-sibling) will give you the full list of possibly affected XPaths. Secondly, since only single-step XPaths are affected, XPaths with a / on either side of the "step" can safely be
ignored. Thirdly, since only the order is changed, all XPaths which disregard order can also be ignored. This includes XPaths like count(ancestor::*).

The fix is usually as simple as adding a [last()] filter at some point of the expression:

// Expressions like:
evaluateXPathToFirstNode('ancestor-or-self::div', node, readOnlyBlueprint);
 // Should be rewritten to:
evaluateXPathToFirstNode('ancestor-or-self::div[1]', node, readOnlyBlueprint);