leptris 1.9.292.0-arm-linux → 1.9.302.0-arm-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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -0
  3. data/Rakefile +1 -1
  4. data/lib/leptris/version.rb +1 -1
  5. data/lib/libleptris.so +0 -0
  6. data/vendor-src/README.md +1 -1
  7. data/vendor-src/libleptris/CHANGELOG.md +331 -0
  8. data/vendor-src/libleptris/CMakeLists.txt +6 -1
  9. data/vendor-src/libleptris/benchmarks/modification/bench_append_attr_pugixml.cpp +36 -0
  10. data/vendor-src/libleptris/bindings/rust/Cargo.lock +1 -1
  11. data/vendor-src/libleptris/bindings/rust/Cargo.toml +3 -1
  12. data/vendor-src/libleptris/cli/CLI_ARCHITECTURE.md +1 -1
  13. data/vendor-src/libleptris/cli/CMakeLists.txt +1 -0
  14. data/vendor-src/libleptris/cli/cli.c +36 -1
  15. data/vendor-src/libleptris/cli/cli.h +39 -2
  16. data/vendor-src/libleptris/cli/commands/xquery.c +1 -31
  17. data/vendor-src/libleptris/cli/commands/xslt.c +118 -0
  18. data/vendor-src/libleptris/cli/main.c +24 -22
  19. data/vendor-src/libleptris/cli/man/leptris-diff.1.adoc +40 -0
  20. data/vendor-src/libleptris/cli/man/leptris-validate.1.adoc +43 -0
  21. data/vendor-src/libleptris/cli/man/leptris-xquery.1.adoc +44 -0
  22. data/vendor-src/libleptris/cli/man/leptris-xslt.1.adoc +49 -0
  23. data/vendor-src/libleptris/src/CMakeLists.txt +26 -0
  24. data/vendor-src/libleptris/src/leptris/dom/compact.c +4 -0
  25. data/vendor-src/libleptris/src/leptris/dom/element_modify.c +21 -18
  26. data/vendor-src/libleptris/src/leptris/dom/mut_recycle.c +112 -0
  27. data/vendor-src/libleptris/src/leptris/dom/mut_recycle.h +50 -0
  28. data/vendor-src/libleptris/src/leptris/leptris.c +10 -4
  29. data/vendor-src/libleptris/src/leptris/leptris_internal.h +7 -0
  30. data/vendor-src/libleptris/src/leptris/xpath/collation.c +494 -0
  31. data/vendor-src/libleptris/src/leptris/xpath/collation.h +66 -0
  32. data/vendor-src/libleptris/src/leptris/xpath/ducet_table.h +21994 -0
  33. data/vendor-src/libleptris/src/leptris/xpath/evaluator_internal.h +6 -0
  34. data/vendor-src/libleptris/src/leptris/xpath/evaluator_operators.c +53 -5
  35. data/vendor-src/libleptris/src/leptris/xpath/evaluator_types.c +53 -8
  36. data/vendor-src/libleptris/src/leptris/xpath/functions.c +506 -67
  37. data/vendor-src/libleptris/src/leptris/xpath/functions_ext31.c +572 -64
  38. data/vendor-src/libleptris/src/leptris/xpath/vm.c +20 -4
  39. data/vendor-src/libleptris/src/leptris/xpath/xpath_public.c +3 -1
  40. data/vendor-src/libleptris/src/leptris/xquery/xquery.c +23 -1
  41. data/vendor-src/libleptris/test/CMakeLists.txt +14 -0
  42. data/vendor-src/libleptris/test/cli/test_cli.cpp +61 -0
  43. data/vendor-src/libleptris/test/memory/test_mut_recycle.cpp +64 -0
  44. data/vendor-src/libleptris/test/xpath/test_collation.cpp +147 -0
  45. data/vendor-src/libleptris/test/xpath/test_xslt.cpp +114 -0
  46. data/vendor-src/libleptris/test/xquery/qt3/fn/abs.xml +2085 -0
  47. data/vendor-src/libleptris/test/xquery/qt3/fn/apply.xml +214 -0
  48. data/vendor-src/libleptris/test/xquery/qt3/fn/avg.xml +2199 -0
  49. data/vendor-src/libleptris/test/xquery/qt3/fn/ceiling.xml +961 -0
  50. data/vendor-src/libleptris/test/xquery/qt3/fn/collation-key.xml +331 -0
  51. data/vendor-src/libleptris/test/xquery/qt3/fn/filter.xml +387 -0
  52. data/vendor-src/libleptris/test/xquery/qt3/fn/floor.xml +968 -0
  53. data/vendor-src/libleptris/test/xquery/qt3/fn/fold-left.xml +426 -0
  54. data/vendor-src/libleptris/test/xquery/qt3/fn/fold-right.xml +363 -0
  55. data/vendor-src/libleptris/test/xquery/qt3/fn/for-each-pair.xml +585 -0
  56. data/vendor-src/libleptris/test/xquery/qt3/fn/for-each.xml +170 -0
  57. data/vendor-src/libleptris/test/xquery/qt3/fn/max.xml +2018 -0
  58. data/vendor-src/libleptris/test/xquery/qt3/fn/min.xml +1973 -0
  59. data/vendor-src/libleptris/test/xquery/qt3/fn/round-half-to-even.xml +1336 -0
  60. data/vendor-src/libleptris/test/xquery/qt3/fn/round.xml +2539 -0
  61. data/vendor-src/libleptris/test/xquery/qt3/fn/sum.xml +2043 -0
  62. data/vendor-src/libleptris/test/xquery/qt3/misc/UCACollation.xml +1120 -0
  63. data/vendor-src/libleptris/test/xquery/test_qt3.cpp +226 -14
  64. data/vendor-src/libleptris/test/xquery/test_xquery.cpp +208 -0
  65. data/vendor-src/libleptris/tools/gen_ducet_table.py +110 -0
  66. data/vendor-src/libleptris/vcpkg.json +1 -1
  67. data/vendor-src/libleptris/vendor/ducet/allkeys.txt +39435 -0
  68. metadata +33 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8042dba013f566e1b4dbdb7a14b46d54485a0ff22fadd85f94ec9efe43023b2d
4
- data.tar.gz: 64f15a3422df76d0a4fb96907ad9af6c2c18c1c27fbfdec9a7bc3d726aba3d1f
3
+ metadata.gz: 47f49af5b253645772676e5540c0d20a668581edc01076a6734bb858db9c566a
4
+ data.tar.gz: 97f001fe35ca5b3817f086b3f1cc41a90ca75a811c2fd2181873101313db57c9
5
5
  SHA512:
6
- metadata.gz: e3d851fdf7ddac30af4d9315124f1003dd6f3095471065e60b29c94545c95b9db76603096f31ed4e4f9ebde6ee276f8efb7f587e315481617ed54d10845b94c4
7
- data.tar.gz: 7f15ac37d81035e9374744d44161800a823d966120eb72249756931a60ad928d7b33ee5788d83bc5dd6448b6d83373cea9f816ca60dbc3df084a7d0121468237
6
+ metadata.gz: 4ddaacc9198cd62499cf2ebefa0138d8085bfec6a87dcd2fe08c22d524f4cd8b6be86f243a4e3ddeb7430d82d9da42afc491f5ce0382ec053b3ea11b9bee80ac
7
+ data.tar.gz: 604cc5641fb356fcef054a0c59338aa2e5361771a85501ec4e7d780261159aed14ff5b33bc10e581929c1836ea9caeda151e480fc3a508e20a7c351640629350
data/CHANGELOG.md CHANGED
@@ -1,3 +1,26 @@
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
+
1
24
  ## [1.9.292.0] - 2026-10-03
2
25
 
3
26
  ### Fixed
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.292"
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.292.0"
4
+ VERSION = "1.9.302.0"
5
5
  end
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.292
4
+ - libleptris v1.9.302
5
5
  - utf8proc v2.11.0
6
6
 
7
7
  Rebuild by hand:
@@ -1,5 +1,336 @@
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
+
3
334
  ## [1.9.292] - 2026-10-02
4
335
 
5
336
  ### Added
@@ -3,7 +3,7 @@
3
3
  cmake_minimum_required(VERSION 3.20)
4
4
 
5
5
  project(leptris
6
- VERSION 1.9.292
6
+ VERSION 1.9.302
7
7
  DESCRIPTION "Fast XML parser and XPath evaluator in pure C"
8
8
  LANGUAGES C CXX
9
9
  )
@@ -30,6 +30,7 @@ option(LEPTRIS_BUILD_MAN_PAGES "Generate man pages with asciidoctor" OFF)
30
30
  option(LEPTRIS_BUILD_BENCHMARKS "Build performance benchmarks" OFF)
31
31
  option(LEPTRIS_ENABLE_UTF8PROC "Enable Unicode support via utf8proc" ON)
32
32
  option(LEPTRIS_ENABLE_ICONV "Enable encoding conversion via iconv" ON)
33
+ option(LEPTRIS_ENABLE_DUCET "Vendor DUCET table for UCA collation (requires utf8proc)" ON)
33
34
  option(LEPTRIS_ENABLE_ASAN "Enable AddressSanitizer (catches heap bugs at runtime)" OFF)
34
35
  option(LEPTRIS_ENABLE_FUZZING "Build libFuzzer harness" OFF)
35
36
  option(LEPTRIS_BUILD_DOCS "Build Doxygen API docs" OFF)
@@ -341,6 +342,10 @@ if(LEPTRIS_BUILD_CLI AND LEPTRIS_BUILD_MAN_PAGES)
341
342
  cli/man/leptris-parse.1.adoc
342
343
  cli/man/leptris-xpath.1.adoc
343
344
  cli/man/leptris-format.1.adoc
345
+ cli/man/leptris-validate.1.adoc
346
+ cli/man/leptris-diff.1.adoc
347
+ cli/man/leptris-xquery.1.adoc
348
+ cli/man/leptris-xslt.1.adoc
344
349
  )
345
350
 
346
351
  foreach(ADOC_SOURCE ${ADOC_MAN_SOURCES})
@@ -44,6 +44,42 @@ int main(void) {
44
44
  (t1 - t0) / N_APPEND);
45
45
  }
46
46
 
47
+ /* ---- mut-doc-cycle: 20k create/build/free cycles, 1024
48
+ * children each (1 elem block + ~7 name blocks per cycle). The
49
+ * mut-block recycle target: without it every cycle frees those
50
+ * blocks back to malloc (madvise) and the next cycle re-mallocs
51
+ * and re-faults them on the carve memsets. */
52
+ const int N_CYCLE = 20000;
53
+ const int CYCLE_CHILDREN = 1024;
54
+ {
55
+ double t0 = now_ns();
56
+ for (int i = 0; i < N_CYCLE; i++) {
57
+ LeptrisDocument d = leptris_document_create();
58
+ LeptrisElement root = leptris_element_create(d, "root");
59
+ leptris_document_set_root(d, root);
60
+ for (int c = 0; c < CYCLE_CHILDREN; c++) {
61
+ LeptrisElement e = leptris_element_create(d, "c");
62
+ leptris_element_append_child(root, e);
63
+ }
64
+ leptris_document_free(d);
65
+ }
66
+ double t1 = now_ns();
67
+ printf("mutcycle leptris: %7.1f ns/child\n",
68
+ (t1 - t0) / ((double)N_CYCLE * CYCLE_CHILDREN));
69
+ }
70
+ {
71
+ double t0 = now_ns();
72
+ for (int i = 0; i < N_CYCLE; i++) {
73
+ pugi::xml_document d;
74
+ pugi::xml_node root = d.append_child("root");
75
+ for (int c = 0; c < CYCLE_CHILDREN; c++)
76
+ root.append_child("c");
77
+ }
78
+ double t1 = now_ns();
79
+ printf("mutcycle pugixml: %7.1f ns/child\n",
80
+ (t1 - t0) / ((double)N_CYCLE * CYCLE_CHILDREN));
81
+ }
82
+
47
83
  /* ---- attr-parse: parse 20k docs, 4 elems x 16 attrs ---- */
48
84
  std::string xml = "<r>";
49
85
  for (int e = 0; e < 4; e++) {
@@ -4,4 +4,4 @@ version = 4
4
4
 
5
5
  [[package]]
6
6
  name = "leptris"
7
- version = "1.4.0"
7
+ version = "1.4.1"
@@ -1,10 +1,12 @@
1
1
  [package]
2
2
  name = "leptris"
3
- version = "1.4.0"
3
+ version = "1.4.1"
4
4
  edition = "2021"
5
5
  description = "Rust bindings for libleptris — fast XML 1.0 parsing, XPath 1.0, SAX"
6
6
  license = "MIT"
7
7
  readme = "README.md"
8
+ repository = "https://github.com/leptris/leptris"
9
+ homepage = "https://www.leptris.org"
8
10
  links = "leptris"
9
11
  build = "build.rs"
10
12
 
@@ -73,7 +73,7 @@ The Leptris CLI is a **thin layer** on top of the C library API (`libleptris`).
73
73
 
74
74
  Every aspect of the CLI follows MECE principles:
75
75
 
76
- - **Commands**: Each command has distinct responsibility (parse, xpath, format, version)
76
+ - **Commands**: Each command has distinct responsibility (parse, validate, xpath, format, diff, xquery, xslt, version — the registry is the single source of truth for this list and for --help)
77
77
  - **Options**: Three exclusive sources (CLI → ENV → Default)
78
78
  - **Output formats**: Three formats (XML, JSON, text), never mixed
79
79
  - **Error levels**: Three levels (warning, error, fatal), no overlap
@@ -14,6 +14,7 @@ add_executable(leptris-cli
14
14
  commands/xpath.c
15
15
  commands/format.c
16
16
  commands/xquery.c
17
+ commands/xslt.c
17
18
  )
18
19
 
19
20
  # Link to libleptris. When both static and shared are built, link the
@@ -5,6 +5,7 @@
5
5
 
6
6
  #include "cli.h"
7
7
  #include "error.h"
8
+ #include <stdio.h>
8
9
  #include <stdlib.h>
9
10
  #include <string.h>
10
11
 
@@ -89,7 +90,6 @@ cli_command_t* cli_registry_find(
89
90
  void cli_registry_print_all(const cli_registry_t* registry) {
90
91
  if (!registry) return;
91
92
 
92
- printf("Available commands:\n");
93
93
  for (size_t i = 0; i < registry->count; i++) {
94
94
  printf(" %-12s %s\n",
95
95
  registry->commands[i]->name,
@@ -97,6 +97,40 @@ void cli_registry_print_all(const cli_registry_t* registry) {
97
97
  }
98
98
  }
99
99
 
100
+ /* ------------------------------------------------------------------------- */
101
+ /* Shared Command Utilities */
102
+ /* ------------------------------------------------------------------------- */
103
+
104
+ char* cli_read_stream(FILE* fp, size_t* out_len) {
105
+ size_t cap = 4096, len = 0;
106
+ char* buf = (char*)malloc(cap);
107
+ if (!buf) return NULL;
108
+ for (;;) {
109
+ size_t n = fread(buf + len, 1, cap - len - 1, fp);
110
+ len += n;
111
+ if (len + 1 >= cap) {
112
+ cap *= 2;
113
+ char* grown = (char*)realloc(buf, cap);
114
+ if (!grown) { free(buf); return NULL; }
115
+ buf = grown;
116
+ continue;
117
+ }
118
+ break;
119
+ }
120
+ buf[len] = 0;
121
+ if (out_len) *out_len = len;
122
+ return buf;
123
+ }
124
+
125
+ char* cli_read_file(const char* path, size_t* out_len) {
126
+ if (strcmp(path, "-") == 0) return cli_read_stream(stdin, out_len);
127
+ FILE* fp = fopen(path, "rb");
128
+ if (!fp) return NULL;
129
+ char* s = cli_read_stream(fp, out_len);
130
+ fclose(fp);
131
+ return s;
132
+ }
133
+
100
134
  /* ------------------------------------------------------------------------- */
101
135
  /* Helper Functions */
102
136
  /* ------------------------------------------------------------------------- */
@@ -110,6 +144,7 @@ const char* cli_result_to_string(cli_result_t result) {
110
144
  case CLI_ERROR_ARGS: return "invalid arguments";
111
145
  case CLI_ERROR_MEMORY: return "memory allocation failed";
112
146
  case CLI_ERROR_INTERNAL: return "internal error";
147
+ case CLI_ERROR_XSLT: return "xslt error";
113
148
  default: return "unknown error";
114
149
  }
115
150
  }
@@ -17,6 +17,7 @@
17
17
  #define LEPTRIS_CLI_H
18
18
 
19
19
  #include <stddef.h>
20
+ #include <stdio.h>
20
21
 
21
22
  #ifdef __cplusplus
22
23
  extern "C" {
@@ -43,7 +44,8 @@ typedef enum {
43
44
  CLI_ERROR_IO = 3, /**< I/O error (file not found, etc.) */
44
45
  CLI_ERROR_ARGS = 4, /**< Invalid arguments */
45
46
  CLI_ERROR_MEMORY = 5, /**< Memory allocation failed */
46
- CLI_ERROR_INTERNAL = 6 /**< Internal error (bug) */
47
+ CLI_ERROR_INTERNAL = 6, /**< Internal error (bug) */
48
+ CLI_ERROR_XSLT = 7 /**< XSLT transformation failed */
47
49
  } cli_result_t;
48
50
 
49
51
  /* ------------------------------------------------------------------------- */
@@ -166,12 +168,39 @@ cli_command_t* cli_registry_find(
166
168
  /**
167
169
  * Print all registered commands
168
170
  *
169
- * Used for global --help output.
171
+ * Prints one aligned "name — description" line per registered
172
+ * command, in registration order, without a section header — the
173
+ * caller frames the listing. This is the single source of truth
174
+ * for the global --help command list: command descriptors carry
175
+ * their own description, and no hand-maintained copy of the list
176
+ * exists anywhere else.
170
177
  *
171
178
  * @param registry Registry to print
172
179
  */
173
180
  void cli_registry_print_all(const cli_registry_t* registry);
174
181
 
182
+ /* ------------------------------------------------------------------------- */
183
+ /* Shared Command Utilities */
184
+ /* ------------------------------------------------------------------------- */
185
+
186
+ /**
187
+ * Read a whole stream into a NUL-terminated heap buffer
188
+ *
189
+ * @param fp Stream to read (not closed)
190
+ * @param out_len Length in bytes excluding the NUL, or NULL
191
+ * @return Buffer (caller frees), or NULL on allocation failure
192
+ */
193
+ char* cli_read_stream(FILE* fp, size_t* out_len);
194
+
195
+ /**
196
+ * Read a whole file into a NUL-terminated heap buffer
197
+ *
198
+ * @param path File path, or "-" for stdin
199
+ * @param out_len Length in bytes excluding the NUL, or NULL
200
+ * @return Buffer (caller frees), NULL on open or allocation failure
201
+ */
202
+ char* cli_read_file(const char* path, size_t* out_len);
203
+
175
204
  /* ------------------------------------------------------------------------- */
176
205
  /* Standard Commands (Forward Declarations) */
177
206
  /* ------------------------------------------------------------------------- */
@@ -225,6 +254,14 @@ extern cli_command_t* cli_command_validate(void);
225
254
  */
226
255
  extern cli_command_t* cli_command_xquery(void);
227
256
 
257
+ /**
258
+ * Get the 'xslt' command
259
+ *
260
+ * Command: leptris xslt -s STYLESHEET [INPUT]
261
+ * Purpose: Apply an XSLT stylesheet to an XML document
262
+ */
263
+ extern cli_command_t* cli_command_xslt(void);
264
+
228
265
  #ifdef __cplusplus
229
266
  }
230
267
  #endif