leptris 1.9.290.0-aarch64-linux → 1.9.302.0-aarch64-linux
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +98 -0
- data/Rakefile +1 -1
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/descriptor.rb +19 -0
- data/lib/leptris/xml/document.rb +15 -2
- data/lib/leptris/xml/ffi.rb +19 -3
- data/lib/leptris/xml/xslt.rb +7 -5
- data/lib/libleptris.so +0 -0
- data/vendor-src/README.md +1 -1
- data/vendor-src/libleptris/CHANGELOG.md +386 -0
- data/vendor-src/libleptris/CMakeLists.txt +6 -1
- data/vendor-src/libleptris/benchmarks/modification/bench_append_attr_pugixml.cpp +36 -0
- data/vendor-src/libleptris/bindings/rust/Cargo.lock +1 -1
- data/vendor-src/libleptris/bindings/rust/Cargo.toml +3 -1
- data/vendor-src/libleptris/cli/CLI_ARCHITECTURE.md +1 -1
- data/vendor-src/libleptris/cli/CMakeLists.txt +1 -0
- data/vendor-src/libleptris/cli/cli.c +36 -1
- data/vendor-src/libleptris/cli/cli.h +39 -2
- data/vendor-src/libleptris/cli/commands/xquery.c +1 -31
- data/vendor-src/libleptris/cli/commands/xslt.c +118 -0
- data/vendor-src/libleptris/cli/main.c +24 -22
- data/vendor-src/libleptris/cli/man/leptris-diff.1.adoc +40 -0
- data/vendor-src/libleptris/cli/man/leptris-validate.1.adoc +43 -0
- data/vendor-src/libleptris/cli/man/leptris-xquery.1.adoc +44 -0
- data/vendor-src/libleptris/cli/man/leptris-xslt.1.adoc +49 -0
- data/vendor-src/libleptris/src/CMakeLists.txt +26 -0
- data/vendor-src/libleptris/src/include/leptris/descriptor.h +25 -0
- data/vendor-src/libleptris/src/include/leptris.h +28 -1
- data/vendor-src/libleptris/src/leptris/descriptor/plan.c +17 -0
- data/vendor-src/libleptris/src/leptris/dom/compact.c +4 -0
- data/vendor-src/libleptris/src/leptris/dom/element_modify.c +30 -19
- data/vendor-src/libleptris/src/leptris/dom/mut_recycle.c +112 -0
- data/vendor-src/libleptris/src/leptris/dom/mut_recycle.h +50 -0
- data/vendor-src/libleptris/src/leptris/leptris.c +62 -8
- data/vendor-src/libleptris/src/leptris/leptris_internal.h +7 -0
- data/vendor-src/libleptris/src/leptris/xpath/collation.c +494 -0
- data/vendor-src/libleptris/src/leptris/xpath/collation.h +66 -0
- data/vendor-src/libleptris/src/leptris/xpath/compiler.c +9 -3
- data/vendor-src/libleptris/src/leptris/xpath/ducet_table.h +21994 -0
- data/vendor-src/libleptris/src/leptris/xpath/evaluator_internal.h +6 -0
- data/vendor-src/libleptris/src/leptris/xpath/evaluator_operators.c +53 -5
- data/vendor-src/libleptris/src/leptris/xpath/evaluator_types.c +53 -8
- data/vendor-src/libleptris/src/leptris/xpath/functions.c +506 -67
- data/vendor-src/libleptris/src/leptris/xpath/functions_ext31.c +572 -64
- data/vendor-src/libleptris/src/leptris/xpath/vm.c +20 -4
- data/vendor-src/libleptris/src/leptris/xpath/xpath_public.c +3 -1
- data/vendor-src/libleptris/src/leptris/xquery/xquery.c +23 -1
- data/vendor-src/libleptris/test/CMakeLists.txt +14 -0
- data/vendor-src/libleptris/test/abi/test_descriptor.cpp +88 -0
- data/vendor-src/libleptris/test/cli/test_cli.cpp +61 -0
- data/vendor-src/libleptris/test/dom/test_document_children.cpp +92 -0
- data/vendor-src/libleptris/test/dom/test_dom.cpp +20 -16
- data/vendor-src/libleptris/test/dom/test_element_children.cpp +72 -0
- data/vendor-src/libleptris/test/memory/test_mut_recycle.cpp +64 -0
- data/vendor-src/libleptris/test/xpath/test_collation.cpp +147 -0
- data/vendor-src/libleptris/test/xpath/test_compiled.cpp +92 -0
- data/vendor-src/libleptris/test/xpath/test_xslt.cpp +114 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/abs.xml +2085 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/apply.xml +214 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/avg.xml +2199 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/ceiling.xml +961 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/collation-key.xml +331 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/filter.xml +387 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/floor.xml +968 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/fold-left.xml +426 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/fold-right.xml +363 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/for-each-pair.xml +585 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/for-each.xml +170 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/max.xml +2018 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/min.xml +1973 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/round-half-to-even.xml +1336 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/round.xml +2539 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/sum.xml +2043 -0
- data/vendor-src/libleptris/test/xquery/qt3/misc/UCACollation.xml +1120 -0
- data/vendor-src/libleptris/test/xquery/test_qt3.cpp +226 -14
- data/vendor-src/libleptris/test/xquery/test_xquery.cpp +208 -0
- data/vendor-src/libleptris/tools/gen_ducet_table.py +110 -0
- data/vendor-src/libleptris/vcpkg.json +1 -1
- data/vendor-src/libleptris/vendor/ducet/allkeys.txt +39435 -0
- metadata +33 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f0c0acc96d46fa6c52961fa5ea4f896f9ed9ca895125ffda6ad474ab6b7f4cb2
|
|
4
|
+
data.tar.gz: 1d933d9a8ce4c210ba9ca03e68216625ed41e3d9122d1c7eb449ce374910951e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fdfbb09db03c6e7757ab9f3fecb94fede0840456cde6830f20861e41e17ae71d13acc98bb31b130d923127a73c60fa5d25702d51ade8c535c20a1791f884ca68
|
|
7
|
+
data.tar.gz: 0bc734310aef6db4743e0c36f4e33d4f66315e28df973a0ef9eb9157d9714bdacdd9729571ed4648cf613a04ca666bcbd91468dd51fe21d1e806f48ba4e2a674
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,101 @@
|
|
|
1
|
+
## [1.9.302.0] - 2026-10-05
|
|
2
|
+
|
|
3
|
+
### Engine sync (libleptris 1.9.293-1.9.302)
|
|
4
|
+
|
|
5
|
+
Ten upstream releases ride in this pin — internal fixes and
|
|
6
|
+
performance with NO binding-surface changes (zero header deltas
|
|
7
|
+
292..302; the audit gate confirms 363/362 unchanged). Highlights:
|
|
8
|
+
|
|
9
|
+
- **XQuery correctness**: a false() bound anywhere read as truthy;
|
|
10
|
+
fn:not uses the effective boolean value; fn:abs(()) is empty;
|
|
11
|
+
string lt/gt are codepoint comparisons; fn:avg edge shapes;
|
|
12
|
+
aggregates atomize array arguments; `instance of xs:integer`
|
|
13
|
+
demands integrality; xs:byte/unsignedByte/unsignedInt
|
|
14
|
+
constructors; fn:sum#2's zero argument.
|
|
15
|
+
- **Collation**: UCA over the vendored DUCET table (slices 1-4) —
|
|
16
|
+
fn:normalize-unicode F&O conformance, collation-aware string
|
|
17
|
+
functions, 71+1078 QT3 cases adopted, implicit weights and
|
|
18
|
+
alternate=blanked.
|
|
19
|
+
- **Performance**: per-thread recycle of mutation blocks (DOM
|
|
20
|
+
create/build/free).
|
|
21
|
+
- **Rust bindings**: first tokenless crates.io release (leptris
|
|
22
|
+
v1.4.1) from CI.
|
|
23
|
+
|
|
24
|
+
## [1.9.292.0] - 2026-10-03
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- **Document#root= adopts foreign roots (leptris-ruby#371)**:
|
|
29
|
+
vendored 1.9.292 (engine #1495) — a foreign element is no longer
|
|
30
|
+
rejected with EINVAL; the subtree is deep-copied into this
|
|
31
|
+
document's pool (all node kinds, attributes, namespace
|
|
32
|
+
declarations), the source document loses the element, and the
|
|
33
|
+
detached original stays resolvable until the source frees. The
|
|
34
|
+
binding routes cross-document installs through the new
|
|
35
|
+
leptris_document_set_root_ex and wraps the INSTALLED handle — a
|
|
36
|
+
fresh pointer, not the source. Same-document installs keep the
|
|
37
|
+
native fast path. The old "rejects foreign" construction spec now
|
|
38
|
+
pins adoption.
|
|
39
|
+
|
|
40
|
+
## [1.9.291.0] - 2026-10-03
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- **Removed nodes leave clean orphans (leptris-ruby#370)**:
|
|
44
|
+
vendored 1.9.291 — `remove_child` / `remove_all_children` clear
|
|
45
|
+
the removed nodes' own sibling links, so a later re-append never
|
|
46
|
+
splices the stale sibling run into the new parent (nodes were
|
|
47
|
+
reachable from two parents, up to self-containing cycles).
|
|
48
|
+
- **`prefix:*` is namespace-scoped (leptris-ruby#368)**: three
|
|
49
|
+
fused compiler sites lowered `//m:*` to the any-element opcode;
|
|
50
|
+
prefixed wildcards now resolve through the caller's bindings.
|
|
51
|
+
|
|
52
|
+
### Added
|
|
53
|
+
|
|
54
|
+
- **Descriptor row-layout skew gate (engine #1490 follow-up)**:
|
|
55
|
+
the five new struct-size accessors are attached and the
|
|
56
|
+
descriptor face verifies the engine's compiled row sizes against
|
|
57
|
+
the binding's FFI structs before every plan build — version
|
|
58
|
+
skew fails loudly instead of misparsing rows.
|
|
59
|
+
|
|
60
|
+
## [1.9.290.0] - 2026-10-02
|
|
61
|
+
|
|
62
|
+
### Fixed
|
|
63
|
+
|
|
64
|
+
- **Params values evaluate as XPath expressions (engine 1.9.288,
|
|
65
|
+
#1481 semantics correction)**: the #360 face shipped briefly on
|
|
66
|
+
raw-string binding — under libleptris 1.9.288+ the value strings
|
|
67
|
+
evaluate as XPath EXPRESSIONS, the libxslt `params` convention
|
|
68
|
+
callers know from nokogiri (whose quote_params exists to add the
|
|
69
|
+
quotes): `"4 * 3"` binds 12, `"'S'"` binds the string `"S"`,
|
|
70
|
+
`string(/r/@v)` reads the source, a malformed expression fails
|
|
71
|
+
the transform. The face docs and specs updated to the corrected
|
|
72
|
+
contract; callers passing pre-quoted values (nokogiri-style)
|
|
73
|
+
are already correct.
|
|
74
|
+
|
|
75
|
+
### Added — engine sync (1.9.290)
|
|
76
|
+
|
|
77
|
+
- Namespace forms for attribute plan rows: `ns_form` / `ns_uri`
|
|
78
|
+
on the descriptor AttrPlan (#1486/#1487) — the binding packs the
|
|
79
|
+
fields (zero = the historical wire-name lookup); exposing
|
|
80
|
+
ns-exact rows from plan trees lands with the first consumer.
|
|
81
|
+
|
|
82
|
+
### Performance — engine sync (1.9.288-1.9.289)
|
|
83
|
+
|
|
84
|
+
- Vendored libleptris 1.9.288-1.9.289: the #1481 params semantics
|
|
85
|
+
correction, plus the 289 HTML-path performance work (slice 6 of
|
|
86
|
+
#1218): open-id integer compares for the scope/table-context
|
|
87
|
+
stack walks and id-LUT membership for the foster/formatting
|
|
88
|
+
checks — cumulative ~+15% on the table-rows fixture; the
|
|
89
|
+
3.4-7x deficit at 1.9.203 is now a 1.3-1.55x lead over
|
|
90
|
+
htmlReadMemory.
|
|
91
|
+
- **Descriptor AttrPlan ABI adoption (#1486)**: the attribute-row
|
|
92
|
+
plan struct gains trailing ns-form fields (additive to the
|
|
93
|
+
frozen v1 ABI — the #1115 pattern). The binding packs the full
|
|
94
|
+
layout (FFI zero-fill keeps unset = the historical wire-name
|
|
95
|
+
lookup); riding 1.9.288+ without the extension reads past the
|
|
96
|
+
allocation and segfaults the descriptor face. Namespace-form
|
|
97
|
+
attribute rows are engine-ready; the binding face follows.
|
|
98
|
+
|
|
1
99
|
## [1.9.287.0] - 2026-10-02
|
|
2
100
|
|
|
3
101
|
### Added
|
data/Rakefile
CHANGED
|
@@ -8,7 +8,7 @@ RSpec::Core::RakeTask.new(:spec)
|
|
|
8
8
|
# Pin for `rake compile` and the platform-gem builds. Keep in lockstep
|
|
9
9
|
# with .github/workflows/build.yml (which calls `rake compile`) and the
|
|
10
10
|
# CHANGELOG when libleptris releases.
|
|
11
|
-
LIBLEPTRIS_VERSION = "1.9.
|
|
11
|
+
LIBLEPTRIS_VERSION = "1.9.302"
|
|
12
12
|
# Vendored alongside libleptris for fn:normalize-unicode (TODO
|
|
13
13
|
# .restructure/20): built per platform with a RELOCATABLE @rpath
|
|
14
14
|
# install name, loaded by ffi.rb before libleptris so the
|
data/lib/leptris/version.rb
CHANGED
|
@@ -96,6 +96,25 @@ class Leptris::XML::Descriptor
|
|
|
96
96
|
# Ruby-side spec memory is transient (the engine deep-copies).
|
|
97
97
|
def self.build(tree)
|
|
98
98
|
abi = Leptris::XML::FFI.leptris_plan_abi_version
|
|
99
|
+
# Row-layout skew gate (1.9.291, engine #1490 follow-up): the
|
|
100
|
+
# engine reports its compiled row sizes; a mismatch means the
|
|
101
|
+
# binding's FFI structs and the vendored engine disagree —
|
|
102
|
+
# fail loudly instead of misparsing rows.
|
|
103
|
+
{
|
|
104
|
+
Leptris::XML::FFI.leptris_plan_spec_struct_size => Leptris::XML::FFI::PlanSpec,
|
|
105
|
+
Leptris::XML::FFI.leptris_plan_element_row_size => Leptris::XML::FFI::ElementPlan,
|
|
106
|
+
Leptris::XML::FFI.leptris_plan_child_row_size => Leptris::XML::FFI::ChildPlan,
|
|
107
|
+
Leptris::XML::FFI.leptris_plan_attr_row_size => Leptris::XML::FFI::AttrPlan,
|
|
108
|
+
Leptris::XML::FFI.leptris_plan_predicate_row_size =>
|
|
109
|
+
Leptris::XML::FFI::AttrPredicate
|
|
110
|
+
}.each do |engine_size, struct|
|
|
111
|
+
next if engine_size == struct.size
|
|
112
|
+
|
|
113
|
+
raise Leptris::XML::Error,
|
|
114
|
+
"descriptor row-layout skew: engine #{engine_size}B vs " \
|
|
115
|
+
"binding #{struct.size}B for #{struct.name} — the vendored " \
|
|
116
|
+
"engine and the binding structs disagree"
|
|
117
|
+
end
|
|
99
118
|
if abi != Leptris::XML::FFI::PLAN_ABI_VERSION
|
|
100
119
|
raise Leptris::XML::Error,
|
|
101
120
|
"descriptor ABI v#{abi} != binding v#{Leptris::XML::FFI::PLAN_ABI_VERSION}"
|
data/lib/leptris/xml/document.rb
CHANGED
|
@@ -384,7 +384,20 @@ class Leptris::XML::Document
|
|
|
384
384
|
# TODO.perf/33: gates + bump + engine set_root in one C
|
|
385
385
|
# dispatch (the FFI call plus the c_ptr materialization fed
|
|
386
386
|
# ~5% of a fresh-doc build).
|
|
387
|
-
|
|
387
|
+
# Cross-document adoption (1.9.292, #371): the engine DEEP-COPIES
|
|
388
|
+
# the foreign subtree into this document's pool and reports the
|
|
389
|
+
# installed element — a fresh handle distinct from the source
|
|
390
|
+
# pointer. Same-document installs keep the native fast path.
|
|
391
|
+
cross = element.document && !element.document.equal?(self)
|
|
392
|
+
installed_ptr = nil
|
|
393
|
+
if cross
|
|
394
|
+
out = ::FFI::MemoryPointer.new(:pointer)
|
|
395
|
+
Leptris::XML::FFI.check_status(
|
|
396
|
+
Leptris::XML::FFI.leptris_document_set_root_ex(
|
|
397
|
+
c_ptr, element.c_ptr, out))
|
|
398
|
+
installed_ptr = out.read_pointer
|
|
399
|
+
@version += 1
|
|
400
|
+
elsif defined?(Leptris::XML::NATIVE_FAST)
|
|
388
401
|
Leptris::XML::Native.set_binding_root(self, element.c_address)
|
|
389
402
|
else
|
|
390
403
|
Leptris::XML::FFI.check_status(
|
|
@@ -394,7 +407,7 @@ class Leptris::XML::Document
|
|
|
394
407
|
# Seed the root memo through wrap: a cross-document element
|
|
395
408
|
# must enter THIS document's identity cache with @document
|
|
396
409
|
# pointing here, not ride its source-document wrapper.
|
|
397
|
-
@root = Leptris::XML::Node.wrap(element.c_ptr, self)
|
|
410
|
+
@root = Leptris::XML::Node.wrap(installed_ptr || element.c_ptr, self)
|
|
398
411
|
@root_version = @version
|
|
399
412
|
Leptris::XML::Node.invalidate_cross_document!(element, self)
|
|
400
413
|
element
|
data/lib/leptris/xml/ffi.rb
CHANGED
|
@@ -300,9 +300,10 @@ module Leptris
|
|
|
300
300
|
:predicate_count, :uint16,
|
|
301
301
|
:pad_pred, :uint16,
|
|
302
302
|
:predicates, :pointer,
|
|
303
|
-
# libleptris 1.9.289 (#1486): attribute-level
|
|
304
|
-
# form
|
|
305
|
-
#
|
|
303
|
+
# libleptris 1.9.289 (#1486): attribute-level ns
|
|
304
|
+
# form (additive trailing to the frozen v1 ABI —
|
|
305
|
+
# the #1115 child-row pattern). FFI zero-fills, so
|
|
306
|
+
# unset = the historical wire-name lookup.
|
|
306
307
|
:ns_form, :uint8,
|
|
307
308
|
:pad_ns, :uint8,
|
|
308
309
|
:ns_uri, :pointer
|
|
@@ -372,6 +373,15 @@ module Leptris
|
|
|
372
373
|
attach_function :leptris_node_byte_offset,
|
|
373
374
|
[:leptris_node_ref], :size_t
|
|
374
375
|
|
|
376
|
+
# Struct-size accessors (1.9.291, engine #1490 follow-up):
|
|
377
|
+
# the engine's compiled row layouts — the descriptor face
|
|
378
|
+
# verifies them against the FFI structs it packs and fails
|
|
379
|
+
# loudly on version skew instead of misparsing rows.
|
|
380
|
+
attach_function :leptris_plan_spec_struct_size, [], :size_t
|
|
381
|
+
attach_function :leptris_plan_element_row_size, [], :size_t
|
|
382
|
+
attach_function :leptris_plan_child_row_size, [], :size_t
|
|
383
|
+
attach_function :leptris_plan_attr_row_size, [], :size_t
|
|
384
|
+
attach_function :leptris_plan_predicate_row_size, [], :size_t
|
|
375
385
|
attach_function :leptris_plan_abi_version, [], :uint32
|
|
376
386
|
attach_function :leptris_plan_build,
|
|
377
387
|
[:pointer, :pointer], :leptris_plan
|
|
@@ -484,6 +494,12 @@ attach_function :leptris_parse_string,
|
|
|
484
494
|
[], :leptris_document
|
|
485
495
|
attach_function :leptris_document_set_root,
|
|
486
496
|
[:leptris_document, :leptris_element], :leptris_status
|
|
497
|
+
# 1.9.292 (#371): foreign roots are ADOPTED — deep-copied into
|
|
498
|
+
# this document's pool; out_installed reports the fresh
|
|
499
|
+
# handle (wrap THAT, not the source pointer). Legacy
|
|
500
|
+
# set_root adopts too but cannot report the installed node.
|
|
501
|
+
attach_function :leptris_document_set_root_ex,
|
|
502
|
+
[:leptris_document, :leptris_element, :pointer], :leptris_status
|
|
487
503
|
attach_function :leptris_document_root,
|
|
488
504
|
[:leptris_document], :leptris_element
|
|
489
505
|
# The document node — navigation head over the whole tree
|
data/lib/leptris/xml/xslt.rb
CHANGED
|
@@ -69,11 +69,13 @@ module Leptris::XML::XSLT
|
|
|
69
69
|
# Apply to +document+ (not modified) and return the result tree
|
|
70
70
|
# as an owning Document — query it with xpath/css like any other.
|
|
71
71
|
#
|
|
72
|
-
# params (#360): top-level xsl:param
|
|
73
|
-
# name => value. Values
|
|
74
|
-
#
|
|
75
|
-
#
|
|
76
|
-
#
|
|
72
|
+
# params (#360, engine 1.9.288 semantics): top-level xsl:param
|
|
73
|
+
# overrides as a Hash of name => value. Values evaluate as
|
|
74
|
+
# XPath EXPRESSIONS — the libxslt `params` convention callers
|
|
75
|
+
# know from nokogiri (whose quote_params exists to add the
|
|
76
|
+
# quotes): "7 * 6" binds 42, "'7'" binds the string "7",
|
|
77
|
+
# "string(/r/@v)" reads the source. A malformed expression
|
|
78
|
+
# fails the transform. Absent names keep select/@default.
|
|
77
79
|
def apply_to(document, params: nil)
|
|
78
80
|
raw, buffer, = with_param_pairs(params) do |pairs, count|
|
|
79
81
|
if pairs
|
data/lib/libleptris.so
CHANGED
|
Binary file
|
data/vendor-src/README.md
CHANGED
|
@@ -1,5 +1,391 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.9.302] - 2026-10-04
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **XQuery: aggregates atomize array arguments** — `fn:avg`,
|
|
8
|
+
`fn:sum`, `fn:min` and `fn:max` take `xs:anyAtomicType*`, so a
|
|
9
|
+
dense array argument now contributes its members instead of
|
|
10
|
+
leaking the internal carrier: `avg([1,2,3,4,5])` = 3,
|
|
11
|
+
`sum([1,2,3,4,5])` = 15 (fn-avg-10, fn-sum-11, fn-min-19,
|
|
12
|
+
fn-max-19). A new shared decoder turns carriers keyed exactly
|
|
13
|
+
1..n into positional members, recursing into nested arrays
|
|
14
|
+
(`[[1,2],[3]]` → 1,2,3); maps and sparse carriers stay single
|
|
15
|
+
items, and `item()`-typed functions such as `fn:count` still see
|
|
16
|
+
an array as one item, per XDM. `fn:sum`'s per-item accumulation
|
|
17
|
+
moved into a helper so atomized members feed the existing
|
|
18
|
+
int64-exact and xs:float channels unchanged.
|
|
19
|
+
- **XQuery: `instance of` over the integer family demands
|
|
20
|
+
integrality** — `xs:integer` and its derived types
|
|
21
|
+
(int/long/short/byte/unsigned*/nonNegative…) now require an
|
|
22
|
+
integral value on both the sequence-member and scalar paths:
|
|
23
|
+
`fn:abs(1.1) instance of xs:integer` is false,
|
|
24
|
+
`fn:abs(-3) instance of xs:integer` is true.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- **XQuery: `fn:avg` edge shapes** — `avg(())` returns the empty
|
|
29
|
+
sequence under XQuery (XPath 1.0 parity untouched), and averages
|
|
30
|
+
over `xs:yearMonthDuration` format through the shared months
|
|
31
|
+
formatter (cbcl-avg-006: `P1Y`). Also freed a nested-array
|
|
32
|
+
carrier on an allocation-failure path in the atomizing decoder.
|
|
33
|
+
- QT3 re-pins from one verified run: fn/avg 164→173, fn/max
|
|
34
|
+
165→167, fn/min 165→167, fn/sum 203→204 (+14 adopted cases;
|
|
35
|
+
total pins 1134 → 1148; 61/61 suites green).
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
## [1.9.301] - 2026-10-04
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- **XQuery: a false() bound anywhere read as truthy** — values
|
|
44
|
+
stringified to a marker-less "false" that every effective-boolean-
|
|
45
|
+
value site saw as a non-empty string, so `let $x := false() return
|
|
46
|
+
if ($x)...` took the then-branch and fold accumulators ignored a
|
|
47
|
+
false() zero (fold-left-004). Variable bindings, local:function
|
|
48
|
+
thunk arguments and the fold accumulators/items now carry the
|
|
49
|
+
boolean type marker; the EBV read honors it on singleton synthetic
|
|
50
|
+
members (XML 1.0 forbids raw control characters in text content,
|
|
51
|
+
so the marker cannot collide with document data); named-reference
|
|
52
|
+
closure calls spell marked booleans back to literals; both string
|
|
53
|
+
consumers render them as true/false. (PR #1522)
|
|
54
|
+
|
|
55
|
+
- **fn:not uses the effective boolean value** — not(false()) is
|
|
56
|
+
true while fn:empty((false())) stays false; real nodes keep the
|
|
57
|
+
XPath 1.0 existence rule, so libxml2 parity is unchanged.
|
|
58
|
+
(PR #1522)
|
|
59
|
+
|
|
60
|
+
- **fn:abs(()) is the empty sequence** under the XQuery surface;
|
|
61
|
+
**fn:sum((), $zero) returns a non-numeric zero verbatim**
|
|
62
|
+
(sum((), xs:dayTimeDuration("PT0S")) = PT0S); **fn:avg over
|
|
63
|
+
dayTimeDurations** divides the total seconds and formats
|
|
64
|
+
(fn-avg-4: PT5H30M); and the kept lexical zero in fn:sum is freed
|
|
65
|
+
on every result path (a Linux ASAN catch — macOS has no leak
|
|
66
|
+
sanitizer). (PR #1522)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
## [1.9.300] - 2026-10-04
|
|
71
|
+
|
|
72
|
+
### Added
|
|
73
|
+
|
|
74
|
+
- **XQuery: the xs:float and int64 typed-atom channels survive the
|
|
75
|
+
numeric function hop (+49 QT3 agreements; the fifteen-family batch
|
|
76
|
+
pins rise 1078→1127 and fn:round is fully green at 220/220)**.
|
|
77
|
+
fn:abs, fn:round, fn:round-half-to-even and the avg/min/max
|
|
78
|
+
aggregates copy the float tag onto their results (round over
|
|
79
|
+
xs:float("3.4028235E38") spells 3.4028235E38, not the widened
|
|
80
|
+
double digits); fn:sum keeps an exact long-long total when every
|
|
81
|
+
member is an integer lexical (fn:sum#2's zero seeds it); fn:floor,
|
|
82
|
+
fn:ceiling, fn:round and fn:abs pass int64 atoms through unchanged
|
|
83
|
+
(floor(xs:integer("999999999999999999")) stays exact, not 1e18).
|
|
84
|
+
(PR #1520)
|
|
85
|
+
|
|
86
|
+
- **xs:byte, xs:unsignedByte and xs:unsignedInt constructors** —
|
|
87
|
+
the three integer subtypes were unregistered and errored as
|
|
88
|
+
unknown functions. (PR #1520)
|
|
89
|
+
|
|
90
|
+
### Fixed
|
|
91
|
+
|
|
92
|
+
- **is_int results now also carry the double field** — eq and
|
|
93
|
+
boolean() comparisons read number_value, which the int64 path
|
|
94
|
+
left unset (sum((3,4,5)) eq 12 was false). (PR #1520)
|
|
95
|
+
|
|
96
|
+
- **The float-precision speller snaps into the float domain before
|
|
97
|
+
spelling** — double arithmetic can leave a non-float-representable
|
|
98
|
+
value whose shortest float round-trip never terminates, spelling
|
|
99
|
+
18-digit mantissas. (PR #1520)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
## [1.9.299] - 2026-10-04
|
|
104
|
+
|
|
105
|
+
### Added
|
|
106
|
+
|
|
107
|
+
- **XQuery: 1078 QT3 cases adopted across fifteen fn families** —
|
|
108
|
+
the numeric aggregates and rounding set (abs, avg, ceiling, floor,
|
|
109
|
+
max, min, round, round-half-to-even, sum) plus the higher-order
|
|
110
|
+
combiners (apply, filter, for-each, for-each-pair, fold-left,
|
|
111
|
+
fold-right), vendored verbatim from the upstream QT3 mirror. The
|
|
112
|
+
named per-suite excludes document the typed-atom lane's remaining
|
|
113
|
+
levers (xs:float spelling through aggregates, int64-exact
|
|
114
|
+
multi-argument sums, instance-of results). (PR #1518)
|
|
115
|
+
|
|
116
|
+
- **fn:sum#2** — the zero argument returns its value for the empty
|
|
117
|
+
sequence: fn:sum((1,2,3), 10) = 16, fn:sum((), 5) = 5.
|
|
118
|
+
(PR #1518)
|
|
119
|
+
|
|
120
|
+
### Fixed
|
|
121
|
+
|
|
122
|
+
- **Number spelling destroyed integral magnitudes** — the shared
|
|
123
|
+
trailing-zero trim ran on dot-free integer renderings, so 1e18
|
|
124
|
+
spelled "1"; it now runs only when a decimal point is present.
|
|
125
|
+
Negative zero also spells "-0" (fn:round(-0.01) = -0), and
|
|
126
|
+
fn:round preserves -0 for inputs in (-0.5, 0). (PR #1518)
|
|
127
|
+
|
|
128
|
+
- **Value carriers overflowed at DBL_MAX** — the ext31 string
|
|
129
|
+
carrier used the libxml2-parity printer, which renders DBL_MAX
|
|
130
|
+
with 18 significant digits; strtod read-back of that string
|
|
131
|
+
overflows to infinity, so fn:avg((1.7976931348623157E308)) was
|
|
132
|
+
INF. Carriers now use the injective shortest-round-trip spelling.
|
|
133
|
+
(PR #1518)
|
|
134
|
+
|
|
135
|
+
- **fn:sum rejected non-nodeset arguments** — single-item sequences
|
|
136
|
+
(passed as scalars by the XQuery adapter) errored out. (PR #1518)
|
|
137
|
+
|
|
138
|
+
- **fn:round()/fn:round-half-to-even(()) now return the empty
|
|
139
|
+
sequence** under the XQuery surface; XPath 1.0 coercion stays NaN
|
|
140
|
+
(libxml2 parity unchanged). (PR #1518)
|
|
141
|
+
|
|
142
|
+
- **fn:max/fn:min over non-numeric items** (xs:string, xs:anyURI)
|
|
143
|
+
compare in codepoint order and return the extreme item instead of
|
|
144
|
+
coercing to zero. (PR #1518)
|
|
145
|
+
|
|
146
|
+
- **QT3 runner: the standard `emptydoc`/`empty` environments
|
|
147
|
+
resolve to the empty context** — cases carrying them were
|
|
148
|
+
silently skipped (the abs/ceiling/floor/fold-* families adopted
|
|
149
|
+
zero cases before this). (PR #1518)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
## [1.9.298] - 2026-10-04
|
|
154
|
+
|
|
155
|
+
### Added
|
|
156
|
+
|
|
157
|
+
- **XPath/XQuery: the six remaining string functions honor UCA
|
|
158
|
+
collations (DUCET lane closed)** — starts-with, ends-with,
|
|
159
|
+
contains, substring-before, substring-after and deep-equal now
|
|
160
|
+
route `http://www.w3.org/2013/collation/UCA` URIs through the
|
|
161
|
+
collation module. Matching scans codepoint-aligned windows under
|
|
162
|
+
the collation compare (every collation-unit boundary falls
|
|
163
|
+
between codepoints, so the scan is exact across contractions and
|
|
164
|
+
normalization); substring-before/after slice the leftmost-shortest
|
|
165
|
+
occurrence. deep-equal threads the collation into plain string
|
|
166
|
+
atoms and map values, while \x03-marked carriers keep their
|
|
167
|
+
value-space equality. **QT3 adoption rises by 73 cases**:
|
|
168
|
+
contains 46→63, starts-with 43→56, ends-with 34→48,
|
|
169
|
+
substring-before 12→26, substring-after 16→29, deep-equal
|
|
170
|
+
221→223. (PR #1515)
|
|
171
|
+
|
|
172
|
+
- **Collation: UCA 7.1.1 implicit weights** — codepoints with no
|
|
173
|
+
DUCET mapping sort by the two-CE algorithm: the registered
|
|
174
|
+
@implicitweights ranges (Tangut/Tangut-Supplement base FB00,
|
|
175
|
+
Khitan FB02, Nushu FB01) order ahead of the unassigned groups
|
|
176
|
+
(base 0xFB80 + 2*(cp>>15)), each codepoint emitting an
|
|
177
|
+
even/odd CE pair that orders within its group by codepoint.
|
|
178
|
+
(PR #1515)
|
|
179
|
+
|
|
180
|
+
- **Collation: `alternate=blanked`** (fn-contains-35..38) — the
|
|
181
|
+
third W3C UCA variable-weighting value is accepted, and variable
|
|
182
|
+
collation elements now leave every ordinary level under
|
|
183
|
+
shifted/blanked weighting (shifted retains its quaternary
|
|
184
|
+
re-add; blanked drops them wholly — ICU variable semantics).
|
|
185
|
+
(PR #1515)
|
|
186
|
+
|
|
187
|
+
- **CLI: `leptris xslt` command** — apply a stylesheet to a
|
|
188
|
+
document and print the serialized result tree:
|
|
189
|
+
`leptris xslt -s style.xsl input.xml`. Conventions match
|
|
190
|
+
xsltproc: INPUT is a file path, `-` or absent reads stdin. The
|
|
191
|
+
stylesheet compiles via leptris_xslt_parse_file, so
|
|
192
|
+
xsl:include/xsl:import resolve relative to the sheet. New
|
|
193
|
+
CLI_ERROR_XSLT exit code (7). (PR #1516)
|
|
194
|
+
|
|
195
|
+
- **CLI: man pages for every command** — new leptris-xslt.1 plus
|
|
196
|
+
the missing leptris-xquery.1, leptris-diff.1 and
|
|
197
|
+
leptris-validate.1, all registered for generation. (PR #1516)
|
|
198
|
+
|
|
199
|
+
### Fixed
|
|
200
|
+
|
|
201
|
+
- **CLI: `--help` was missing registered commands** — the usage
|
|
202
|
+
text hand-maintained its own command list, so the registered
|
|
203
|
+
`diff` and `xquery` commands were invisible. The help listing
|
|
204
|
+
now iterates the command registry (the descriptors are the
|
|
205
|
+
single source of truth), and adding a command can no longer
|
|
206
|
+
leave help stale. xquery's private stream reader is promoted to
|
|
207
|
+
shared cli_read_stream/cli_read_file. (PR #1516)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
## [1.9.297] - 2026-10-03
|
|
212
|
+
|
|
213
|
+
### Fixed
|
|
214
|
+
|
|
215
|
+
- **XQuery: string lt/gt are codepoint value comparisons** — the
|
|
216
|
+
shared XPath 1.0 comparison numeric-coerced strings, so two
|
|
217
|
+
collation-keys that both spell numbers compared as equal numbers.
|
|
218
|
+
Both comparison sites now codepoint-compare string operands when
|
|
219
|
+
the evaluation is XQuery-flavored (the evaluator's string branch
|
|
220
|
+
and the VM twin); XPath 1.0 evaluation is unchanged.
|
|
221
|
+
(PR #1513)
|
|
222
|
+
|
|
223
|
+
- **XQuery: fn:codepoints-to-string encodes every item of its
|
|
224
|
+
sequence argument** — the collector collapsed a parenthesized
|
|
225
|
+
sequence to one scalar (its space-joined spelling), so
|
|
226
|
+
(37, 65500, 37) encoded as just the first codepoint. Collation-key
|
|
227
|
+
adoption rises to 12/56 run-and-agree. (PR #1513)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
## [1.9.296] - 2026-10-03
|
|
232
|
+
|
|
233
|
+
### Added
|
|
234
|
+
|
|
235
|
+
- **XPath/XQuery: 71 QT3 collation cases adopted (DUCET slice 4)** —
|
|
236
|
+
the misc/UCACollation corpus (62 cases: the UCA ordering battery
|
|
237
|
+
over DUCET weights) and fn/collation-key (9 cases). The collation
|
|
238
|
+
URI grammar now parses the W3C semicolon-separated parameter form,
|
|
239
|
+
accepts lang= (DUCET is the root locale), and applies
|
|
240
|
+
caseFirst=upper|lower by swapping the DUCET case tertiaries.
|
|
241
|
+
fn:collation-key gains its 1-argument form (the static default
|
|
242
|
+
collation; codepoint keys are the UTF-8 bytes) and its keys are
|
|
243
|
+
lowercase hex — injective AND order-preserving, so lt/gt on two
|
|
244
|
+
keys is byte-wise key order. The QT3 runner now credits an
|
|
245
|
+
error alternative inside any-of for a failed evaluation, gates the
|
|
246
|
+
optional simple-uca-fallback feature (this engine raises FOCH0002
|
|
247
|
+
per the strict F&O reading), and lifts the pre-DUCET
|
|
248
|
+
collation/UCA exclusion under a DUCET build. (PR #1511)
|
|
249
|
+
|
|
250
|
+
### Fixed
|
|
251
|
+
|
|
252
|
+
- the DUCET implicit-weight fallback dropped a mask that broke
|
|
253
|
+
ordering between high codepoints. (PR #1511)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
## [1.9.295] - 2026-10-03
|
|
258
|
+
|
|
259
|
+
### Added
|
|
260
|
+
|
|
261
|
+
- **XPath: collation-aware functions over the vendored DUCET
|
|
262
|
+
(slices 2-3)** — fn:compare's 3-argument form honors its collation
|
|
263
|
+
URI (unknown needed collations raise FOCH0002; unknown-but-unneeded
|
|
264
|
+
ones are ignored per the QT3 any-of expectations);
|
|
265
|
+
fn:collation-key($key, $collation) is new, returning the UCA sort
|
|
266
|
+
key as base64 text (the result-type enum is ABI-frozen with no
|
|
267
|
+
binary kind; base64 is injective, so key equality is byte equality);
|
|
268
|
+
fn:index-of, fn:distinct-values and fn:sort route plain-string
|
|
269
|
+
comparisons through the collation while typed atoms keep their
|
|
270
|
+
value-space equality. DUCET-gated: builds without utf8proc keep the
|
|
271
|
+
previous behavior. (PRs #1508, #1509)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
## [1.9.294] - 2026-10-03
|
|
276
|
+
|
|
277
|
+
### Added
|
|
278
|
+
|
|
279
|
+
- **XPath: UCA collation over the vendored DUCET table (slice 1)** —
|
|
280
|
+
`http://www.w3.org/2013/collation/UCA` collation URIs parse
|
|
281
|
+
`strength` and `alternate` parameters per F&O 3.x (defaults:
|
|
282
|
+
tertiary, non-ignorable) and compare through multi-level sort keys:
|
|
283
|
+
NFD (utf8proc) → DUCET weights (Unicode 16.0.0, 38,443 single
|
|
284
|
+
codepoints + 964 contractions, vendored with an offline table
|
|
285
|
+
generator) → primary/secondary/tertiary levels with 0x00
|
|
286
|
+
separators; `alternate=shifted` demotes variable primaries to the
|
|
287
|
+
quaternary level. Codepoint and html-ascii-case-insensitive
|
|
288
|
+
collations keep their semantics. Unmapped codepoints get a coarse
|
|
289
|
+
deterministic fallback (the full UCA 7.1.1 implicit-weight
|
|
290
|
+
algorithm is a later slice). New CMake option
|
|
291
|
+
`LEPTRIS_ENABLE_DUCET` (default on, requires utf8proc; builds
|
|
292
|
+
without utf8proc degrade to codepoint collation). Function
|
|
293
|
+
wiring (fn:compare, fn:collation-key, default collation) and
|
|
294
|
+
QT3 UCA case adoption are the next slices. (PR #1506)
|
|
295
|
+
|
|
296
|
+
- **XQuery: fn:normalize-unicode F&O conformance** — the
|
|
297
|
+
single-argument form normalizes with NFC instead of returning the
|
|
298
|
+
input unchanged; unknown normalization forms raise FOCH0003
|
|
299
|
+
instead of silently falling back to NFC; the form argument is
|
|
300
|
+
case-insensitive with surrounding whitespace tolerated
|
|
301
|
+
(FULLY-NORMALIZED is gone from F&O 3.x; its satisfied="false"
|
|
302
|
+
twins run). utf8proc-gated end to end; the 29-run
|
|
303
|
+
fn/normalize-unicode QT3 family is adopted. (PR #1505)
|
|
304
|
+
|
|
305
|
+
### Fixed
|
|
306
|
+
|
|
307
|
+
- DUCET builds without utf8proc (cppcheck leg, Rust lane, ILP32,
|
|
308
|
+
Windows static) degrade to codepoint collation with a warning
|
|
309
|
+
instead of failing the configure. (PR #1506)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
## [1.9.293] - 2026-10-03
|
|
314
|
+
|
|
315
|
+
### Performance
|
|
316
|
+
|
|
317
|
+
- DOM: per-thread recycle of mutation blocks. Document create/build/free
|
|
318
|
+
cycles now park their elem (45KB), name (4KB) and attr (5KB) blocks on
|
|
319
|
+
thread-local free-lists instead of returning them to malloc — the next
|
|
320
|
+
cycle carves from hot pages instead of re-faulting fresh ones. Byte-capped
|
|
321
|
+
(8MB elem, 4MB name/attr); POSIX threads drain via a pthread_key
|
|
322
|
+
destructor at thread exit, and `leptris_explicit_cleanup()` drains
|
|
323
|
+
synchronously. Mutation doc-cycle row: 19.4 → 15.1 ns/child (−22%), gap
|
|
324
|
+
to pugixml on that shape narrows 1.66x → 1.29x. Single-document append
|
|
325
|
+
throughput is unchanged. (PR #1503)
|
|
326
|
+
|
|
327
|
+
### Rust bindings
|
|
328
|
+
|
|
329
|
+
- First fully tokenless crates.io release: leptris v1.4.1 published from CI
|
|
330
|
+
via crates.io Trusted Publishing (`rust-lang/crates-io-auth-action` —
|
|
331
|
+
cargo itself has no native OIDC support). The Rust release lane now needs
|
|
332
|
+
no registry token of any kind. (PRs #1497, #1499–#1502)
|
|
333
|
+
|
|
334
|
+
## [1.9.292] - 2026-10-02
|
|
335
|
+
|
|
336
|
+
### Added
|
|
337
|
+
|
|
338
|
+
- **DOM: `set_root` adopts foreign roots (leptris-ruby #371)** — an element
|
|
339
|
+
owned by another document is no longer rejected with EINVAL. Nodes are
|
|
340
|
+
pool-owned by their document, so adoption deep-copies the subtree into the
|
|
341
|
+
target document's pool (all node kinds, attributes, and namespace
|
|
342
|
+
declarations survive); the source document loses the element (root pointers
|
|
343
|
+
cleared, element spliced out of its document-child chain — Nokogiri steal
|
|
344
|
+
observability), and the detached original stays resolvable in its own pool
|
|
345
|
+
until the source document is freed. The new
|
|
346
|
+
`leptris_document_set_root_ex(doc, root, &installed)` reports the installed
|
|
347
|
+
element — a fresh handle distinct from the source pointer — so bindings wrap
|
|
348
|
+
the right node; the legacy entry keeps its signature and adopts too.
|
|
349
|
+
Parent-attached elements still reject EINVAL, and same-document installs
|
|
350
|
+
are unchanged.
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
## [1.9.291] - 2026-10-02
|
|
355
|
+
|
|
356
|
+
### Fixed
|
|
357
|
+
|
|
358
|
+
- **DOM: removed nodes leave clean orphans** — `leptris_element_remove_child`
|
|
359
|
+
unlinked the child from the parent's list but left the child's own
|
|
360
|
+
`next_sibling` intact, so a later append spliced the stale sibling run into
|
|
361
|
+
the new parent — nodes reachable from two parents, up to self-containing
|
|
362
|
+
cycles that never terminate in serialization. `leptris_element_remove_all_children`
|
|
363
|
+
had the same defect through its parent-backpointer walk. Both now clear the
|
|
364
|
+
removed nodes' own sibling links, matching `leptris_node_unlink` and libxml2
|
|
365
|
+
`xmlUnlinkNode` semantics (leptris-ruby#370).
|
|
366
|
+
- **XPath: `prefix:*` is namespace-scoped** — three bytecode fusion sites in
|
|
367
|
+
the compiler classified a prefixed wildcard as a plain wildcard and lowered
|
|
368
|
+
`.//m:*` and `/descendant-or-self::m:*` to the any-element fast opcodes,
|
|
369
|
+
ignoring the caller's namespace bindings entirely: a bound `m:*` matched
|
|
370
|
+
every element (no-namespace ones included), and the unbound literal-prefix
|
|
371
|
+
fallback never ran. Prefixed wildcards now stay on the generic step path,
|
|
372
|
+
whose matcher resolves the test prefix through the evaluation context's
|
|
373
|
+
bindings — URI equality across prefixes, literal-prefix comparison when
|
|
374
|
+
unbound (leptris-ruby#368).
|
|
375
|
+
|
|
376
|
+
### Added
|
|
377
|
+
|
|
378
|
+
- **Plan API: struct-size accessors for binding skew detection** —
|
|
379
|
+
`leptris_plan_spec_struct_size`, `leptris_plan_{element,child,attr,predicate}_row_size`
|
|
380
|
+
return the engine's compiled row layouts so bindings can verify them against
|
|
381
|
+
the headers they compiled with and fail loudly on version skew instead of
|
|
382
|
+
misparsing rows. Root cause behind #1490 (the engine's nested `ns_form`
|
|
383
|
+
semantics were correct; the consumer straddled an older engine with newer
|
|
384
|
+
headers). A nested-child regression spec now pins all three `ns_form`
|
|
385
|
+
behaviors, including that a non-matching URI keeps the child element.
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
|
|
3
389
|
## [1.9.290] - 2026-10-02
|
|
4
390
|
|
|
5
391
|
<!-- Edit this section with the actual release notes. -->
|