leptris 1.9.290.0-ppc64le-linux → 1.9.302.0-ppc64le-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.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +98 -0
  3. data/Rakefile +1 -1
  4. data/lib/leptris/version.rb +1 -1
  5. data/lib/leptris/xml/descriptor.rb +19 -0
  6. data/lib/leptris/xml/document.rb +15 -2
  7. data/lib/leptris/xml/ffi.rb +19 -3
  8. data/lib/leptris/xml/xslt.rb +7 -5
  9. data/lib/libleptris.so +0 -0
  10. data/vendor-src/README.md +1 -1
  11. data/vendor-src/libleptris/CHANGELOG.md +386 -0
  12. data/vendor-src/libleptris/CMakeLists.txt +6 -1
  13. data/vendor-src/libleptris/benchmarks/modification/bench_append_attr_pugixml.cpp +36 -0
  14. data/vendor-src/libleptris/bindings/rust/Cargo.lock +1 -1
  15. data/vendor-src/libleptris/bindings/rust/Cargo.toml +3 -1
  16. data/vendor-src/libleptris/cli/CLI_ARCHITECTURE.md +1 -1
  17. data/vendor-src/libleptris/cli/CMakeLists.txt +1 -0
  18. data/vendor-src/libleptris/cli/cli.c +36 -1
  19. data/vendor-src/libleptris/cli/cli.h +39 -2
  20. data/vendor-src/libleptris/cli/commands/xquery.c +1 -31
  21. data/vendor-src/libleptris/cli/commands/xslt.c +118 -0
  22. data/vendor-src/libleptris/cli/main.c +24 -22
  23. data/vendor-src/libleptris/cli/man/leptris-diff.1.adoc +40 -0
  24. data/vendor-src/libleptris/cli/man/leptris-validate.1.adoc +43 -0
  25. data/vendor-src/libleptris/cli/man/leptris-xquery.1.adoc +44 -0
  26. data/vendor-src/libleptris/cli/man/leptris-xslt.1.adoc +49 -0
  27. data/vendor-src/libleptris/src/CMakeLists.txt +26 -0
  28. data/vendor-src/libleptris/src/include/leptris/descriptor.h +25 -0
  29. data/vendor-src/libleptris/src/include/leptris.h +28 -1
  30. data/vendor-src/libleptris/src/leptris/descriptor/plan.c +17 -0
  31. data/vendor-src/libleptris/src/leptris/dom/compact.c +4 -0
  32. data/vendor-src/libleptris/src/leptris/dom/element_modify.c +30 -19
  33. data/vendor-src/libleptris/src/leptris/dom/mut_recycle.c +112 -0
  34. data/vendor-src/libleptris/src/leptris/dom/mut_recycle.h +50 -0
  35. data/vendor-src/libleptris/src/leptris/leptris.c +62 -8
  36. data/vendor-src/libleptris/src/leptris/leptris_internal.h +7 -0
  37. data/vendor-src/libleptris/src/leptris/xpath/collation.c +494 -0
  38. data/vendor-src/libleptris/src/leptris/xpath/collation.h +66 -0
  39. data/vendor-src/libleptris/src/leptris/xpath/compiler.c +9 -3
  40. data/vendor-src/libleptris/src/leptris/xpath/ducet_table.h +21994 -0
  41. data/vendor-src/libleptris/src/leptris/xpath/evaluator_internal.h +6 -0
  42. data/vendor-src/libleptris/src/leptris/xpath/evaluator_operators.c +53 -5
  43. data/vendor-src/libleptris/src/leptris/xpath/evaluator_types.c +53 -8
  44. data/vendor-src/libleptris/src/leptris/xpath/functions.c +506 -67
  45. data/vendor-src/libleptris/src/leptris/xpath/functions_ext31.c +572 -64
  46. data/vendor-src/libleptris/src/leptris/xpath/vm.c +20 -4
  47. data/vendor-src/libleptris/src/leptris/xpath/xpath_public.c +3 -1
  48. data/vendor-src/libleptris/src/leptris/xquery/xquery.c +23 -1
  49. data/vendor-src/libleptris/test/CMakeLists.txt +14 -0
  50. data/vendor-src/libleptris/test/abi/test_descriptor.cpp +88 -0
  51. data/vendor-src/libleptris/test/cli/test_cli.cpp +61 -0
  52. data/vendor-src/libleptris/test/dom/test_document_children.cpp +92 -0
  53. data/vendor-src/libleptris/test/dom/test_dom.cpp +20 -16
  54. data/vendor-src/libleptris/test/dom/test_element_children.cpp +72 -0
  55. data/vendor-src/libleptris/test/memory/test_mut_recycle.cpp +64 -0
  56. data/vendor-src/libleptris/test/xpath/test_collation.cpp +147 -0
  57. data/vendor-src/libleptris/test/xpath/test_compiled.cpp +92 -0
  58. data/vendor-src/libleptris/test/xpath/test_xslt.cpp +114 -0
  59. data/vendor-src/libleptris/test/xquery/qt3/fn/abs.xml +2085 -0
  60. data/vendor-src/libleptris/test/xquery/qt3/fn/apply.xml +214 -0
  61. data/vendor-src/libleptris/test/xquery/qt3/fn/avg.xml +2199 -0
  62. data/vendor-src/libleptris/test/xquery/qt3/fn/ceiling.xml +961 -0
  63. data/vendor-src/libleptris/test/xquery/qt3/fn/collation-key.xml +331 -0
  64. data/vendor-src/libleptris/test/xquery/qt3/fn/filter.xml +387 -0
  65. data/vendor-src/libleptris/test/xquery/qt3/fn/floor.xml +968 -0
  66. data/vendor-src/libleptris/test/xquery/qt3/fn/fold-left.xml +426 -0
  67. data/vendor-src/libleptris/test/xquery/qt3/fn/fold-right.xml +363 -0
  68. data/vendor-src/libleptris/test/xquery/qt3/fn/for-each-pair.xml +585 -0
  69. data/vendor-src/libleptris/test/xquery/qt3/fn/for-each.xml +170 -0
  70. data/vendor-src/libleptris/test/xquery/qt3/fn/max.xml +2018 -0
  71. data/vendor-src/libleptris/test/xquery/qt3/fn/min.xml +1973 -0
  72. data/vendor-src/libleptris/test/xquery/qt3/fn/round-half-to-even.xml +1336 -0
  73. data/vendor-src/libleptris/test/xquery/qt3/fn/round.xml +2539 -0
  74. data/vendor-src/libleptris/test/xquery/qt3/fn/sum.xml +2043 -0
  75. data/vendor-src/libleptris/test/xquery/qt3/misc/UCACollation.xml +1120 -0
  76. data/vendor-src/libleptris/test/xquery/test_qt3.cpp +226 -14
  77. data/vendor-src/libleptris/test/xquery/test_xquery.cpp +208 -0
  78. data/vendor-src/libleptris/tools/gen_ducet_table.py +110 -0
  79. data/vendor-src/libleptris/vcpkg.json +1 -1
  80. data/vendor-src/libleptris/vendor/ducet/allkeys.txt +39435 -0
  81. metadata +33 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 505fb9e6093b335b955a6c776e97beb0cd43d9e498d8f44333b3faccc93bee26
4
- data.tar.gz: 69b07068ad720f0da28fbc713625884621cf067a0fe0134b88bed2eba8549fa5
3
+ metadata.gz: 2764954457c94ab3a836428d2e5fbe63235ce201e99c92446573030d489e1b6a
4
+ data.tar.gz: 33f05143b2d66134a8261bdb45665b800eec9a5edcca3f455d6f4249c47901b6
5
5
  SHA512:
6
- metadata.gz: aba2088b46d2f97563715259e4a8c8f12fa49f5918a42272f849c64030aad24fa0051449e9f29a0e90c5f7e118be49cc8dc9eae14f4575f4c7552c524715205c
7
- data.tar.gz: ea9d90fb9c71b40bcd3692b81d138752a9e4d5aca9943baa6f6a62769f1136773cb0b54e28ed36e0c7ca4a46c7e40b874351f4d76ebdbf227d64611f38502bad
6
+ metadata.gz: abfce50cca667748bb0fee5e17aa61e1980d298238c94a431075fc3d8e5b4fd1f574c2915c7a5990b868822d3e98bbc1980c192430187213695020a65484f7c0
7
+ data.tar.gz: 2d1a21dd0b38bf9aab1a407eda3d21614c606037c23c5444fc3ea0ac6d500636151c49c4f950a4d374da540938c05c0c83c32e6f38087cb45d49c01b1986cb98
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.290"
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leptris
4
- VERSION = "1.9.290.0"
4
+ VERSION = "1.9.302.0"
5
5
  end
@@ -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}"
@@ -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
- if defined?(Leptris::XML::NATIVE_FAST)
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
@@ -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 namespace
304
- # form — ns rows match by (URI, local), wire_name is the
305
- # LOCAL name; NONE keeps wire-name-exact matching.
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
@@ -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 overrides as a Hash of
73
- # name => value. Values bind as XPath STRINGS verbatim — the
74
- # caller owns numeric/boolean quoting (libxslt conventions:
75
- # pass '7' for a number, matching nokogiri's quote_params).
76
- # Absent names keep the param's select/@default.
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,7 +1,7 @@
1
1
  Sources vendored in this gem (recompile rights; the ruby
2
2
  variant builds them at install via extconf.rb):
3
3
 
4
- - libleptris v1.9.290
4
+ - libleptris v1.9.302
5
5
  - utf8proc v2.11.0
6
6
 
7
7
  Rebuild by hand:
@@ -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. -->