Skip to content

✨ feat(query): add XPath 2.0 string functions#558

Merged
gaborbernat merged 3 commits into
tox-dev:mainfrom
gaborbernat:feat/542-xpath2-functions
Jul 7, 2026
Merged

✨ feat(query): add XPath 2.0 string functions#558
gaborbernat merged 3 commits into
tox-dev:mainfrom
gaborbernat:feat/542-xpath2-functions

Conversation

@gaborbernat

Copy link
Copy Markdown
Member

Adds the string convenience subset of XPath 2.0 to the native engine, so expressions ported from elementpath, lxml, or htmlquery that reach for these functions run without registration:

  • ends-with(a, b) — suffix test, the mirror of the existing starts-with.
  • string-join(seq, sep) — the string-values of a node-set joined by a separator (the XPath-1.0-engine reading, where a node-set is the sequence).
  • lower-case(s) / upper-case(s) — Unicode case mapping, delegated to CPython's str.lower()/str.upper() the same way the re: functions call into CPython.
  • matches(input, pattern[, flags]) — regex test, sharing the EXSLT re:test pipeline.
  • replace(input, pattern, repl[, flags]) — regex replace of every match, with $N group references rewritten into the re module's template form.

All dispatch in the compiled-C function table alongside the 1.0 core and the EXSLT namespaces; the full XPath 2.0 sequence, type, and FLWOR machinery stays out of scope. reverse() is left out because the engine keeps every node-set in document order and re-sorts after each step, so a reversed set could not survive a path.

Competitor design was drawn from elementpath (arg orders, flag letters, the $N→group rewrite), antchfx/xpath (node-set string-join, 2-arg ends-with), and the W3C XPath/XQuery F&O spec for exact semantics.

closes #542

Add the string convenience subset of XPath 2.0 to the native engine so
expressions ported from elementpath, lxml, or htmlquery run unchanged:
ends-with, string-join(seq, sep), lower-case, upper-case (Unicode case
mapping), and the regex matches/replace spellings. matches shares the
EXSLT re:test pipeline; replace maps to a global re.sub after rewriting
its $N group references into the re module's template form. All dispatch
in the compiled-C function table alongside the 1.0 core and EXSLT sets,
with no full-2.0 sequence or type machinery behind them.

The differential test cross-checks the subset against elementpath, the
reference XPath 2.0 processor, over the same document.

Closes tox-dev#542
@gaborbernat gaborbernat added enhancement New feature or request area:selectors CSS selector / XPath query engine labels Jul 7, 2026
@codspeed-hq

codspeed-hq Bot commented Jul 7, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 62 untouched benchmarks
⏩ 18 skipped benchmarks1


Comparing gaborbernat:feat/542-xpath2-functions (ebb3af2) with main (053eeb2)

Open in CodSpeed

Footnotes

  1. 18 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

Every string-function param returns a bool or str scalar from
elementpath.select, never a list, so the single-element unwrap never
ran and left a partial branch that failed the 100% Python coverage gate.
Complete the Diátaxis set for the XPath 2.0 string functions: a
reference list in the query page and a tutorial step showing
upper-case/ends-with in an xpath() call.
@gaborbernat gaborbernat merged commit e0b857d into tox-dev:main Jul 7, 2026
48 checks passed
gaborbernat added a commit to gaborbernat/turbohtml that referenced this pull request Jul 7, 2026
Vendor the W3C QT3 (XQuery/XPath 3.1) conformance suite as a pinned
shallow submodule and drive it from tests/conformance. The harness runs
every fn:* case turbohtml's XPath engine can express -- the XPath 1.0/2.0
string, numeric, and boolean functions behind tox-dev#558 -- against the case's
asserted result, and marks the families turbohtml omits by design xfail
with a per-case, spec-justified reason.

281 in-scope cases pass; 2301 are xfailed (XQuery-only, xs: schema
types, sequences/ranges/value-comparisons, XPath 3.x, XSD-dialect regex,
the typed error-code taxonomy, XML namespace decomposition); 49 xpass
where turbohtml still rejects invalid input. No engine bug surfaced:
every deviation traces to a documented boundary.

Omitted from the coverage gate like the *_differential.py oracles; the
module skips when the submodule is absent.
gaborbernat added a commit to gaborbernat/turbohtml that referenced this pull request Jul 7, 2026
Vendor the W3C QT3 (XQuery/XPath 3.1) conformance suite as a pinned
shallow submodule and drive it from tests/conformance. The harness runs
every fn:* case turbohtml's XPath engine can express -- the XPath 1.0/2.0
string, numeric, and boolean functions behind tox-dev#558 -- against the case's
asserted result, and marks the families turbohtml omits by design xfail
with a per-case, spec-justified reason.

281 in-scope cases pass; 2301 are xfailed (XQuery-only, xs: schema
types, sequences/ranges/value-comparisons, XPath 3.x, XSD-dialect regex,
the typed error-code taxonomy, XML namespace decomposition); 49 xpass
where turbohtml still rejects invalid input. No engine bug surfaced:
every deviation traces to a documented boundary.

Omitted from the coverage gate like the *_differential.py oracles; the
module skips when the submodule is absent.
gaborbernat added a commit to gaborbernat/turbohtml that referenced this pull request Jul 7, 2026
Vendor the W3C QT3 (XQuery/XPath 3.1) conformance suite as a pinned
shallow submodule and drive it from tests/conformance. The harness runs
every fn:* case turbohtml's XPath engine can express -- the XPath 1.0/2.0
string, numeric, and boolean functions behind tox-dev#558 -- against the case's
asserted result, and marks the families turbohtml omits by design xfail
with a per-case, spec-justified reason.

281 in-scope cases pass; 2301 are xfailed (XQuery-only, xs: schema
types, sequences/ranges/value-comparisons, XPath 3.x, XSD-dialect regex,
the typed error-code taxonomy, XML namespace decomposition); 49 xpass
where turbohtml still rejects invalid input. No engine bug surfaced:
every deviation traces to a documented boundary.

Omitted from the coverage gate like the *_differential.py oracles; the
module skips when the submodule is absent.
gaborbernat added a commit to gaborbernat/turbohtml that referenced this pull request Jul 7, 2026
Vendor the W3C QT3 (XQuery/XPath 3.1) conformance suite as a pinned
shallow submodule and drive it from tests/conformance. The harness runs
every fn:* case turbohtml's XPath engine can express -- the XPath 1.0/2.0
string, numeric, and boolean functions behind tox-dev#558 -- against the case's
asserted result, and marks the families turbohtml omits by design xfail
with a per-case, spec-justified reason.

281 in-scope cases pass; 2301 are xfailed (XQuery-only, xs: schema
types, sequences/ranges/value-comparisons, XPath 3.x, XSD-dialect regex,
the typed error-code taxonomy, XML namespace decomposition); 49 xpass
where turbohtml still rejects invalid input. No engine bug surfaced:
every deviation traces to a documented boundary.

Omitted from the coverage gate like the *_differential.py oracles; the
module skips when the submodule is absent.
gaborbernat added a commit to gaborbernat/turbohtml that referenced this pull request Jul 7, 2026
Vendor the W3C QT3 (XQuery/XPath 3.1) conformance suite as a pinned
shallow submodule and drive it from tests/conformance. The harness runs
every fn:* case turbohtml's XPath engine can express -- the XPath 1.0/2.0
string, numeric, and boolean functions behind tox-dev#558 -- against the case's
asserted result, and marks the families turbohtml omits by design xfail
with a per-case, spec-justified reason.

281 in-scope cases pass; 2301 are xfailed (XQuery-only, xs: schema
types, sequences/ranges/value-comparisons, XPath 3.x, XSD-dialect regex,
the typed error-code taxonomy, XML namespace decomposition); 49 xpass
where turbohtml still rejects invalid input. No engine bug surfaced:
every deviation traces to a documented boundary.

Omitted from the coverage gate like the *_differential.py oracles; the
module skips when the submodule is absent.
gaborbernat added a commit to gaborbernat/turbohtml that referenced this pull request Jul 7, 2026
Vendor the W3C QT3 (XQuery/XPath 3.1) conformance suite as a pinned
shallow submodule and drive it from tests/conformance. The harness runs
every fn:* case turbohtml's XPath engine can express -- the XPath 1.0/2.0
string, numeric, and boolean functions behind tox-dev#558 -- against the case's
asserted result, and marks the families turbohtml omits by design xfail
with a per-case, spec-justified reason.

281 in-scope cases pass; 2301 are xfailed (XQuery-only, xs: schema
types, sequences/ranges/value-comparisons, XPath 3.x, XSD-dialect regex,
the typed error-code taxonomy, XML namespace decomposition); 49 xpass
where turbohtml still rejects invalid input. No engine bug surfaced:
every deviation traces to a documented boundary.

Omitted from the coverage gate like the *_differential.py oracles; the
module skips when the submodule is absent.
gaborbernat added a commit to gaborbernat/turbohtml that referenced this pull request Jul 7, 2026
Vendor the W3C QT3 (XQuery/XPath 3.1) conformance suite as a pinned
shallow submodule and drive it from tests/conformance. The harness runs
every fn:* case turbohtml's XPath engine can express -- the XPath 1.0/2.0
string, numeric, and boolean functions behind tox-dev#558 -- against the case's
asserted result, and marks the families turbohtml omits by design xfail
with a per-case, spec-justified reason.

281 in-scope cases pass; 2301 are xfailed (XQuery-only, xs: schema
types, sequences/ranges/value-comparisons, XPath 3.x, XSD-dialect regex,
the typed error-code taxonomy, XML namespace decomposition); 49 xpass
where turbohtml still rejects invalid input. No engine bug surfaced:
every deviation traces to a documented boundary.

Omitted from the coverage gate like the *_differential.py oracles; the
module skips when the submodule is absent.
gaborbernat added a commit to gaborbernat/turbohtml that referenced this pull request Jul 7, 2026
Vendor the W3C QT3 (XQuery/XPath 3.1) conformance suite as a pinned
shallow submodule and drive it from tests/conformance. The harness runs
every fn:* case turbohtml's XPath engine can express -- the XPath 1.0/2.0
string, numeric, and boolean functions behind tox-dev#558 -- against the case's
asserted result, and marks the families turbohtml omits by design xfail
with a per-case, spec-justified reason.

281 in-scope cases pass; 2301 are xfailed (XQuery-only, xs: schema
types, sequences/ranges/value-comparisons, XPath 3.x, XSD-dialect regex,
the typed error-code taxonomy, XML namespace decomposition); 49 xpass
where turbohtml still rejects invalid input. No engine bug surfaced:
every deviation traces to a documented boundary.

Omitted from the coverage gate like the *_differential.py oracles; the
module skips when the submodule is absent.
gaborbernat added a commit that referenced this pull request Jul 7, 2026
* test(xpath): validate fn:* against W3C QT3 suite

Vendor the W3C QT3 (XQuery/XPath 3.1) conformance suite as a pinned
shallow submodule and drive it from tests/conformance. The harness runs
every fn:* case turbohtml's XPath engine can express -- the XPath 1.0/2.0
string, numeric, and boolean functions behind #558 -- against the case's
asserted result, and marks the families turbohtml omits by design xfail
with a per-case, spec-justified reason.

281 in-scope cases pass; 2301 are xfailed (XQuery-only, xs: schema
types, sequences/ranges/value-comparisons, XPath 3.x, XSD-dialect regex,
the typed error-code taxonomy, XML namespace decomposition); 49 xpass
where turbohtml still rejects invalid input. No engine bug surfaced:
every deviation traces to a documented boundary.

Omitted from the coverage gate like the *_differential.py oracles; the
module skips when the submodule is absent.

* 🔧 chore: drop changelog fragment (no changelog on conformance PRs)
@gaborbernat gaborbernat deleted the feat/542-xpath2-functions branch July 10, 2026 18:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:selectors CSS selector / XPath query engine enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Selectors: XPath 2.0 convenience functions

1 participant