✨ feat(query): add XPath 2.0 string functions#558
Merged
Conversation
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
Merging this PR will not alter performance
Comparing Footnotes
|
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
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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 existingstarts-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'sstr.lower()/str.upper()the same way there:functions call into CPython.matches(input, pattern[, flags])— regex test, sharing the EXSLTre:testpipeline.replace(input, pattern, repl[, flags])— regex replace of every match, with$Ngroup references rewritten into theremodule'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-setstring-join, 2-argends-with), and the W3C XPath/XQuery F&O spec for exact semantics.closes #542