leptris 1.9.178.0 → 1.9.181.0
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 +28 -0
- data/Rakefile +6 -2
- data/TODO.perf/38-bulk-walk-and-bench-gate.md +23 -0
- data/ext/build_windows_native.rb +8 -4
- data/ext/leptris/native/extconf.rb +18 -0
- data/ext/leptris/native/native.c +131 -0
- data/lib/leptris/vendor/aarch64-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/aarch64-linux-musl/libleptris.so +0 -0
- data/lib/leptris/vendor/arm64-darwin/libleptris.dylib +0 -0
- data/lib/leptris/vendor/x86_64-darwin/libleptris.dylib +0 -0
- data/lib/leptris/vendor/x86_64-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/x86_64-linux-musl/libleptris.so +0 -0
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/document.rb +54 -0
- data/lib/leptris/xml/ffi.rb +22 -0
- data/lib/leptris/xml/native_layer.rb +5 -4
- data/lib/leptris/xml/node.rb +13 -0
- data/lib/leptris/xml/relaxng.rb +13 -1
- data/lib/leptris.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 35e0cd2470e2f0e12a01424ec1ca0fe753d5835fc07d67ec54c2fd5ba257afcc
|
|
4
|
+
data.tar.gz: 69a9520e7bb23117005fa8695c353f75aed6da034ca0f70e79a556c47c3df78c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7579134233ffb721e9ef41955b47389fd71ba327ca1d00d28a34bb8e72454ce23bf823ca3572b94054edb44eef68563f67a2510f6544394f36379ecc9e2f7613
|
|
7
|
+
data.tar.gz: d23c31c7c660c429d0d05ee158028cc72adeb7b48c2917f07387e4e1a63a608c304dea736614da99c7d906bf6091d26cbac833618a0bb47b75896729ce69cf69
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,34 @@ All notable changes to Leptris will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.9.181.0] - 2026-09-16
|
|
9
|
+
|
|
10
|
+
### Changed — libleptris 1.9.178 → 1.9.181 (lockstep)
|
|
11
|
+
|
|
12
|
+
- **RNG error accumulation (upstream #878)**: `RelaxNG#validate`
|
|
13
|
+
now returns ALL errors, not just the first. The new
|
|
14
|
+
`leptris_rng_error_count/_message/_line/_column` surface is
|
|
15
|
+
attached, and each message is composed Jing-style as
|
|
16
|
+
`line:col: error: message` (the raw accumulated messages carry
|
|
17
|
+
no prefix; `leptris_rng_error` remains the back-compat
|
|
18
|
+
first-error accessor).
|
|
19
|
+
- **Parser-recorded source positions (upstream #1124)**:
|
|
20
|
+
`Node#source_position` returns `{ line:, col_start:, col_end }`
|
|
21
|
+
via the new `leptris_node_source_position` (column spans follow
|
|
22
|
+
the Jing convention; created nodes report zeros).
|
|
23
|
+
- 1.9.179/1.9.180 engine fixes ride along: ASAN use-after-free on
|
|
24
|
+
the line-break table, a leak on the parse-failure path, and a
|
|
25
|
+
leak of the in-place document.
|
|
26
|
+
|
|
27
|
+
## [1.9.178.1] - 2026-09-16
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- **Bulk hydration surface (TODO.perf/38, #230)**: `Document#snapshot(node)` returns C-materialized flat rows (`kind`, `name`, `prefix`, `uri`, flat attribute pairs, text, depth), and `Document#walk_subtree(node) { |row| ... }` yields the same rows. The native snapshot avoids per-node binding wrappers and per-attribute Ruby calls; the cursor uses the safe snapshot-backed path after the direct Proc-callback prototype crashed MRI.
|
|
32
|
+
- **Deterministic document lifetime (#231)**: `Document.open(xml) { |doc| ... }` guarantees `doc.free` on normal and exceptional exit. The strong wrapper identity cache remains the default deliberately so `doc.root.equal?(doc.root)` stays valid while the document is alive.
|
|
33
|
+
- **Benchmark gate (#229)**: `LEPTRIS_BENCH_LOAD_MAX` overrides the load gate; the default is core-normalized (`max(logical_cores * 1.5, 4.0)`) rather than the old absolute load threshold.
|
|
34
|
+
- **Windows native layer actually loads (fixes the silent gap since 1.9.174.2)**: the per-minor DLLs are renamed dot-free (`native_3_3.so`, `native_4_0.so`) and the matching `Init_native_3_3`/`Init_native_3_4`/`Init_native_4_0` symbols are exported. MRI derives a C extension's init symbol from the basename cut at the first dot — the previous `native-3.3.so` made Ruby look for `Init_native-3`, which cannot exist as a C symbol, so every Windows load failed and the #207 fallback quietly ran FFI. Windows test legs now exercise the native suite for real.
|
|
35
|
+
|
|
8
36
|
## [1.9.178.0] - 2026-09-16
|
|
9
37
|
|
|
10
38
|
### Changed — libleptris 1.9.177 → 1.9.178 (lockstep)
|
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.181"
|
|
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
|
|
@@ -292,7 +292,11 @@ platforms.each do |platform|
|
|
|
292
292
|
spec.files += Dir.glob("lib/{libutf8proc.3.dylib,libutf8proc.so.3,libutf8proc.so,utf8proc.dll}")
|
|
293
293
|
if platform.include?("mingw")
|
|
294
294
|
# #207/#227: per-Ruby-minor DLLs (PE must bind its Ruby).
|
|
295
|
-
|
|
295
|
+
# DOT-FREE names — MRI derives the init symbol from the
|
|
296
|
+
# basename cut at the first dot (native_3_3.so ->
|
|
297
|
+
# Init_native_3_3; the dotted form made Ruby look for
|
|
298
|
+
# "Init_native-3", which cannot exist).
|
|
299
|
+
spec.files += Dir.glob("lib/leptris/xml/native_*.so")
|
|
296
300
|
else
|
|
297
301
|
spec.files += Dir.glob("lib/leptris/xml/native.{bundle,so,dll}")
|
|
298
302
|
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# 38 — Bulk hydration + bench gate + Document.open
|
|
2
|
+
|
|
3
|
+
Status: DONE (1.9.178.1)
|
|
4
|
+
|
|
5
|
+
## Outcome
|
|
6
|
+
|
|
7
|
+
- **#229**: `benchmark/native_vs_binding.rb` now accepts
|
|
8
|
+
`LEPTRIS_BENCH_LOAD_MAX`; default threshold is core-normalized
|
|
9
|
+
(`max(logical_cores * 1.5, 4.0)`) instead of absolute load >20.
|
|
10
|
+
- **#230**: `Document#snapshot(node)` returns C-materialized flat
|
|
11
|
+
rows (`kind`, `name`, `prefix`, `uri`, flat `[name,value]` attrs,
|
|
12
|
+
text, depth), and `Document#walk_subtree(node) { |row| ... }`
|
|
13
|
+
yields the same rows. The native snapshot has no per-node Node
|
|
14
|
+
wrappers or per-attribute Ruby calls. The cursor currently yields
|
|
15
|
+
the safe snapshot rows; the direct Proc callback prototype was
|
|
16
|
+
rejected after crashing MRI and is not shipped.
|
|
17
|
+
- **#231**: `Document.open(xml) { |doc| ... }` guarantees `doc.free`
|
|
18
|
+
on normal and exceptional block exit. The strong identity cache
|
|
19
|
+
remains the default deliberately: weak identity would break
|
|
20
|
+
`doc.root.equal?(doc.root)` while the document remains alive.
|
|
21
|
+
|
|
22
|
+
Specs: 674/0 in both native and FFI modes. Downstream follow-ups
|
|
23
|
+
posted to moxml#230/#231 and metanorma/uniword#49.
|
data/ext/build_windows_native.rb
CHANGED
|
@@ -2,15 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
# Builds the native read layer DLL for the CURRENT Ruby and
|
|
4
4
|
# installs it under its minor-versioned name
|
|
5
|
-
# (lib/leptris/xml/
|
|
5
|
+
# (lib/leptris/xml/native_<major>_<minor>.so).
|
|
6
6
|
#
|
|
7
7
|
# #207/#227: a PE DLL cannot resolve Ruby imports lazily — it
|
|
8
8
|
# must bind the build Ruby's x64-ucrt-rubyNNN.dll. One DLL per
|
|
9
9
|
# supported minor therefore ships in the Windows platform gems,
|
|
10
10
|
# and native_layer picks by RUBY_VERSION at require.
|
|
11
11
|
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
12
|
+
# The name is DOT-FREE: MRI derives a C extension's init symbol
|
|
13
|
+
# from the require feature's basename cut at the first dot, so
|
|
14
|
+
# native_3_3.so resolves Init_native_3_3 (a valid C symbol,
|
|
15
|
+
# exported by native.c) — a dotted native-3.3.so would make Ruby
|
|
16
|
+
# look for "Init_native-3", which cannot exist.
|
|
14
17
|
|
|
15
18
|
require "rbconfig"
|
|
16
19
|
require "fileutils"
|
|
@@ -18,6 +21,7 @@ require "fileutils"
|
|
|
18
21
|
root = File.expand_path("..", __dir__)
|
|
19
22
|
ext_dir = File.join(root, "ext", "leptris", "native")
|
|
20
23
|
minor = RUBY_VERSION[/\A\d+\.\d+/]
|
|
24
|
+
minor_us = minor.tr(".", "_")
|
|
21
25
|
|
|
22
26
|
Dir.chdir(ext_dir) do
|
|
23
27
|
system(RbConfig.ruby, "extconf.rb") or abort "extconf failed under #{RUBY_VERSION}"
|
|
@@ -27,7 +31,7 @@ Dir.chdir(ext_dir) do
|
|
|
27
31
|
abort "make failed under #{RUBY_VERSION}" unless success
|
|
28
32
|
so = Dir.glob("native.{so,dll}").first
|
|
29
33
|
abort "native bundle not produced under #{RUBY_VERSION}" unless so
|
|
30
|
-
dest = File.join(root, "lib", "leptris", "xml", "
|
|
34
|
+
dest = File.join(root, "lib", "leptris", "xml", "native_#{minor_us}.so")
|
|
31
35
|
# The artifact must reference only this minor's Ruby DLL.
|
|
32
36
|
imported = `strings #{so} 2>/dev/null`[/[a-z0-9-]*ruby\d{3,}\.dll/i]
|
|
33
37
|
if imported && !imported.include?("ruby#{minor.delete('.')}")
|
|
@@ -34,4 +34,22 @@ if RUBY_PLATFORM =~ /darwin/
|
|
|
34
34
|
RbConfig::MAKEFILE_CONFIG["LIBRUBYARG_SHARED"] = ""
|
|
35
35
|
RbConfig::MAKEFILE_CONFIG["LIBRUBYARG_STATIC"] = ""
|
|
36
36
|
end
|
|
37
|
+
if Gem.win_platform?
|
|
38
|
+
# Windows names the artifact per Ruby minor, DOT-FREE (#227
|
|
39
|
+
# fix): MRI derives the init symbol from the basename cut at
|
|
40
|
+
# the first dot, so native_3_4.so needs Init_native_3_4
|
|
41
|
+
# exported. GNU ld auto-exports only when no explicit export
|
|
42
|
+
# is present — pin the export table with a .def file so the
|
|
43
|
+
# wrapper init names are certain to be exported.
|
|
44
|
+
def_path = File.expand_path("native_exports.def", __dir__)
|
|
45
|
+
File.write(def_path, <<~DEF)
|
|
46
|
+
EXPORTS
|
|
47
|
+
Init_native
|
|
48
|
+
Init_native_3_3
|
|
49
|
+
Init_native_3_4
|
|
50
|
+
Init_native_4_0
|
|
51
|
+
DEF
|
|
52
|
+
win_def = " #{def_path.tr('/', '\\\\')}"
|
|
53
|
+
$DLDFLAGS << win_def unless $DLDFLAGS.include?("native_exports.def")
|
|
54
|
+
end
|
|
37
55
|
create_makefile("leptris/xml/native")
|
data/ext/leptris/native/native.c
CHANGED
|
@@ -1716,6 +1716,123 @@ static VALUE nf_fast_element_xml(VALUE self, VALUE addr,
|
|
|
1716
1716
|
encoding);
|
|
1717
1717
|
}
|
|
1718
1718
|
|
|
1719
|
+
/* ---- Bulk hydration walker (TODO.perf/38, #230) -----------------
|
|
1720
|
+
* Pre-order walk that materializes one Hash per element node
|
|
1721
|
+
* (kind, name, prefix, uri, attrs as Array<[name,value]>, text,
|
|
1722
|
+
* depth, line) and yields it to a Ruby Proc (walk_subtree) or
|
|
1723
|
+
* collects it into an Array (snapshot). No per-node Node wrapper
|
|
1724
|
+
* construction — the consumer iterates rows and builds their
|
|
1725
|
+
* typed objects directly. */
|
|
1726
|
+
/* Node kinds (mirror types.h). Documented in descriptor comment. */
|
|
1727
|
+
#define WS_NODE_ELEMENT 0
|
|
1728
|
+
#define WS_NODE_TEXT 1
|
|
1729
|
+
#define WS_NODE_COMMENT 2
|
|
1730
|
+
#define WS_NODE_CDATA 3
|
|
1731
|
+
#define WS_NODE_PI 4
|
|
1732
|
+
#define WS_NODE_DOCTYPE 5
|
|
1733
|
+
|
|
1734
|
+
static VALUE build_element_row(VALUE doc, void *elem, int depth)
|
|
1735
|
+
{
|
|
1736
|
+
VALUE row = rb_hash_new();
|
|
1737
|
+
rb_hash_aset(row, ID2SYM(rb_intern("kind")),
|
|
1738
|
+
rb_utf8_str_new_cstr("element"));
|
|
1739
|
+
const char *name = f_elem_name(elem);
|
|
1740
|
+
rb_hash_aset(row, ID2SYM(rb_intern("name")),
|
|
1741
|
+
name ? rb_utf8_str_new_cstr(name) : Qnil);
|
|
1742
|
+
rb_hash_aset(row, ID2SYM(rb_intern("prefix")),
|
|
1743
|
+
f_elem_prefix(elem) ? rb_utf8_str_new_cstr(f_elem_prefix(elem)) : Qnil);
|
|
1744
|
+
rb_hash_aset(row, ID2SYM(rb_intern("uri")),
|
|
1745
|
+
f_elem_ns(elem) ? rb_utf8_str_new_cstr(f_elem_ns(elem)) : Qnil);
|
|
1746
|
+
/* attrs: name/value pairs as Array<[name, value]>. */
|
|
1747
|
+
VALUE attrs = rb_ary_new();
|
|
1748
|
+
void *a = f_attr_first(elem);
|
|
1749
|
+
while (a) {
|
|
1750
|
+
VALUE pair = rb_ary_new_capa(2);
|
|
1751
|
+
const char *an = f_attr_name(a);
|
|
1752
|
+
const char *av = f_attr_value(elem, a);
|
|
1753
|
+
rb_ary_push(pair, an ? rb_utf8_str_new_cstr(an) : Qnil);
|
|
1754
|
+
rb_ary_push(pair, av ? rb_utf8_str_new_cstr(av) : Qnil);
|
|
1755
|
+
rb_ary_push(attrs, pair);
|
|
1756
|
+
a = f_attr_next(a);
|
|
1757
|
+
}
|
|
1758
|
+
rb_hash_aset(row, ID2SYM(rb_intern("attrs")), attrs);
|
|
1759
|
+
/* text: first text-child content (moxml-style common case). */
|
|
1760
|
+
void *c = f_first_child(elem);
|
|
1761
|
+
VALUE text = Qnil;
|
|
1762
|
+
while (c) {
|
|
1763
|
+
if (f_node_type(c) == WS_NODE_TEXT) {
|
|
1764
|
+
const char *t = f_text_content(c);
|
|
1765
|
+
text = t ? rb_utf8_str_new_cstr(t) : Qnil;
|
|
1766
|
+
break;
|
|
1767
|
+
}
|
|
1768
|
+
c = f_next_sibling(c);
|
|
1769
|
+
}
|
|
1770
|
+
rb_hash_aset(row, ID2SYM(rb_intern("text")), text);
|
|
1771
|
+
rb_hash_aset(row, ID2SYM(rb_intern("depth")), INT2NUM(depth));
|
|
1772
|
+
return row;
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
static VALUE build_text_row(int kind, int depth, const char *t)
|
|
1776
|
+
{
|
|
1777
|
+
VALUE row = rb_hash_new();
|
|
1778
|
+
rb_hash_aset(row, ID2SYM(rb_intern("kind")),
|
|
1779
|
+
rb_utf8_str_new_cstr(kind == WS_NODE_TEXT ? "text"
|
|
1780
|
+
: kind == WS_NODE_COMMENT ? "comment"
|
|
1781
|
+
: kind == WS_NODE_CDATA ? "cdata" : "other"));
|
|
1782
|
+
rb_hash_aset(row, ID2SYM(rb_intern("text")),
|
|
1783
|
+
t ? rb_utf8_str_new_cstr(t) : Qnil);
|
|
1784
|
+
rb_hash_aset(row, ID2SYM(rb_intern("depth")), INT2NUM(depth));
|
|
1785
|
+
return row;
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
static void yield_or_collect_row(VALUE out_or_block, VALUE row, int collect)
|
|
1789
|
+
{
|
|
1790
|
+
if (collect) {
|
|
1791
|
+
rb_ary_push(out_or_block, row);
|
|
1792
|
+
} else {
|
|
1793
|
+
rb_funcall(out_or_block, rb_intern("call"), 1, row);
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
static void walk_subtree_impl(VALUE doc, void *node, int depth, VALUE out_or_block, int collect)
|
|
1798
|
+
{
|
|
1799
|
+
int kind = f_node_type(node);
|
|
1800
|
+
if (kind == WS_NODE_ELEMENT) {
|
|
1801
|
+
void *c;
|
|
1802
|
+
yield_or_collect_row(out_or_block, build_element_row(doc, node, depth), collect);
|
|
1803
|
+
c = f_first_child(node);
|
|
1804
|
+
while (c) {
|
|
1805
|
+
walk_subtree_impl(doc, c, depth + 1, out_or_block, collect);
|
|
1806
|
+
c = f_next_sibling(c);
|
|
1807
|
+
}
|
|
1808
|
+
} else if (kind == WS_NODE_DOCTYPE || kind == 9 /* DOCUMENT */) {
|
|
1809
|
+
void *dc = f_first_child(node);
|
|
1810
|
+
while (dc) {
|
|
1811
|
+
walk_subtree_impl(doc, dc, depth, out_or_block, collect);
|
|
1812
|
+
dc = f_next_sibling(dc);
|
|
1813
|
+
}
|
|
1814
|
+
} else if (kind == WS_NODE_TEXT || kind == WS_NODE_COMMENT ||
|
|
1815
|
+
kind == WS_NODE_CDATA || kind == WS_NODE_PI) {
|
|
1816
|
+
const char *t = NULL;
|
|
1817
|
+
if (kind == WS_NODE_TEXT)
|
|
1818
|
+
t = f_text_content(node);
|
|
1819
|
+
else if (kind == WS_NODE_COMMENT)
|
|
1820
|
+
t = f_comment_content(node);
|
|
1821
|
+
else if (kind == WS_NODE_CDATA)
|
|
1822
|
+
t = f_cdata_content(node);
|
|
1823
|
+
else if (kind == WS_NODE_PI)
|
|
1824
|
+
t = f_pi_data(node);
|
|
1825
|
+
yield_or_collect_row(out_or_block, build_text_row(kind, depth, t), collect);
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
static VALUE nf_snapshot_subtree(VALUE self, VALUE document, VALUE addr)
|
|
1830
|
+
{
|
|
1831
|
+
VALUE out = rb_ary_new();
|
|
1832
|
+
walk_subtree_impl(document, (void *)(uintptr_t)NUM2ULL(addr), 0, out, 1);
|
|
1833
|
+
return out;
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1719
1836
|
/* ---- inner_html in one C pass (TODO.perf/18) --------------------
|
|
1720
1837
|
* Serializes the receiver's children into one growable buffer:
|
|
1721
1838
|
* elements via leptris_element_serialize_into (the same opts the
|
|
@@ -2253,6 +2370,8 @@ void Init_native(void)
|
|
|
2253
2370
|
nf_append_markup, 3);
|
|
2254
2371
|
rb_define_module_function(m_native, "fast_inner_xml",
|
|
2255
2372
|
nf_fast_inner_xml, 1);
|
|
2373
|
+
rb_define_module_function(m_native, "snapshot_subtree",
|
|
2374
|
+
nf_snapshot_subtree, 2);
|
|
2256
2375
|
rb_define_module_function(m_native, "doc_handle_attach",
|
|
2257
2376
|
nf_doc_handle_attach, 1);
|
|
2258
2377
|
rb_define_module_function(m_native, "doc_handle_release",
|
|
@@ -2277,3 +2396,15 @@ void Init_native(void)
|
|
|
2277
2396
|
rb_define_module_function(m_native, "fast_element_xml",
|
|
2278
2397
|
nf_fast_element_xml, 4);
|
|
2279
2398
|
}
|
|
2399
|
+
|
|
2400
|
+
#ifdef _WIN32
|
|
2401
|
+
/* MRI derives a C extension's init symbol from the require
|
|
2402
|
+
* feature's basename cut at the first dot. The Windows per-minor
|
|
2403
|
+
* DLLs are therefore named dot-free (native_3_3.so etc.), and
|
|
2404
|
+
* each needs its exact init name exported. All are defined here
|
|
2405
|
+
* unconditionally: a DLL only ever loads under the Ruby minor it
|
|
2406
|
+
* was built against (PE binds its build Ruby's runtime DLL). */
|
|
2407
|
+
void Init_native_3_3(void) { Init_native(); }
|
|
2408
|
+
void Init_native_3_4(void) { Init_native(); }
|
|
2409
|
+
void Init_native_4_0(void) { Init_native(); }
|
|
2410
|
+
#endif
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/lib/leptris/version.rb
CHANGED
data/lib/leptris/xml/document.rb
CHANGED
|
@@ -73,6 +73,60 @@ class Leptris::XML::Document
|
|
|
73
73
|
@wrapper_cache ||= {}
|
|
74
74
|
end
|
|
75
75
|
|
|
76
|
+
# #231: deterministic scope — yields the document, guarantees
|
|
77
|
+
# teardown at block exit (even on raise). Frameworks with their
|
|
78
|
+
# own finalizer timing worries can wrap every parse in this and
|
|
79
|
+
# skip the GC-via-finalizer slot entirely.
|
|
80
|
+
def self.open(xml_or_io, **kwargs)
|
|
81
|
+
doc = parse(xml_or_io, **kwargs)
|
|
82
|
+
return doc unless block_given?
|
|
83
|
+
begin
|
|
84
|
+
yield doc
|
|
85
|
+
ensure
|
|
86
|
+
doc.free
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# #230: bulk hydration walks — one C pass per subtree, no per-node
|
|
91
|
+
# Ruby wrapper construction. snapshot returns an Array of rows
|
|
92
|
+
# (consumer iterates and builds typed objects in one pass);
|
|
93
|
+
# walk_subtree yields rows through a block (lower peak memory for
|
|
94
|
+
# very large subtrees).
|
|
95
|
+
def snapshot(node)
|
|
96
|
+
if defined?(Leptris::XML::NATIVE_FAST)
|
|
97
|
+
Leptris::XML::Native.snapshot_subtree(
|
|
98
|
+
self, node.respond_to?(:c_address) ? node.c_address : nil)
|
|
99
|
+
else
|
|
100
|
+
rows = []
|
|
101
|
+
walk_subtree(node) { |row| rows << row }
|
|
102
|
+
rows
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def walk_subtree(node, &block)
|
|
107
|
+
return enum_for(:walk_subtree, node) unless block
|
|
108
|
+
if defined?(Leptris::XML::NATIVE_FAST)
|
|
109
|
+
# The public cursor yields the same C-materialized rows as
|
|
110
|
+
# #snapshot. The C cursor prototype was deliberately not
|
|
111
|
+
# exposed: a Proc-calling recursive C walk crashed under
|
|
112
|
+
# MRI; snapshot is the safe one-pass bulk surface.
|
|
113
|
+
snapshot(node).each(&block)
|
|
114
|
+
return
|
|
115
|
+
end
|
|
116
|
+
# FFI fallback: recursive walk (the historical per-node shape).
|
|
117
|
+
if node.is_a?(Leptris::XML::Node)
|
|
118
|
+
yield(element_row(node)) if node.element?
|
|
119
|
+
node.element_children.each { |c| walk_subtree(c, &block) }
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def element_row(el)
|
|
124
|
+
{ kind: "element", name: el.name, prefix: el.namespace&.prefix,
|
|
125
|
+
uri: el.namespace&.href, attrs: el.attributes,
|
|
126
|
+
text: el.element_children.find { |c| c.text? }&.content,
|
|
127
|
+
depth: 0 }
|
|
128
|
+
end
|
|
129
|
+
|
|
76
130
|
def self.parse(xml_or_io, options: nil, readonly: false, recover: false)
|
|
77
131
|
xml = xml_or_io.is_a?(String) ? xml_or_io : xml_or_io.read
|
|
78
132
|
if xml.empty?
|
data/lib/leptris/xml/ffi.rb
CHANGED
|
@@ -473,6 +473,17 @@ attach_function :leptris_parse_string,
|
|
|
473
473
|
[:leptris_node_ref], :leptris_status
|
|
474
474
|
attach_function :leptris_node_line,
|
|
475
475
|
[:leptris_node_ref], :int
|
|
476
|
+
# Parser-recorded source position (libleptris 1.9.180,
|
|
477
|
+
# #1124): {line, col_start, col_end} — Jing's diagnostic
|
|
478
|
+
# convention (column after the start tag's '>' / the
|
|
479
|
+
# element's final '>').
|
|
480
|
+
class SourcePosition < ::FFI::Struct
|
|
481
|
+
layout :line, :int,
|
|
482
|
+
:col_start, :int,
|
|
483
|
+
:col_end, :int
|
|
484
|
+
end
|
|
485
|
+
attach_function :leptris_node_source_position,
|
|
486
|
+
[:leptris_node_ref, :pointer], :void
|
|
476
487
|
attach_function :leptris_node_compare,
|
|
477
488
|
[:leptris_node_ref, :leptris_node_ref], :int
|
|
478
489
|
attach_function :leptris_node_traverse,
|
|
@@ -1004,6 +1015,17 @@ attach_function :leptris_parse_string,
|
|
|
1004
1015
|
[:leptris_relaxng, :leptris_document], :int
|
|
1005
1016
|
attach_function :leptris_rng_error,
|
|
1006
1017
|
[:leptris_relaxng], :string
|
|
1018
|
+
# Accumulated validation errors (libleptris 1.9.179, #878):
|
|
1019
|
+
# the validator no longer stops at the first failure;
|
|
1020
|
+
# leptris_rng_error keeps returning the first (back-compat).
|
|
1021
|
+
attach_function :leptris_rng_error_count,
|
|
1022
|
+
[:leptris_relaxng], :size_t
|
|
1023
|
+
attach_function :leptris_rng_error_message,
|
|
1024
|
+
[:leptris_relaxng, :size_t], :string
|
|
1025
|
+
attach_function :leptris_rng_error_line,
|
|
1026
|
+
[:leptris_relaxng, :size_t], :int
|
|
1027
|
+
attach_function :leptris_rng_error_column,
|
|
1028
|
+
[:leptris_relaxng, :size_t], :int
|
|
1007
1029
|
|
|
1008
1030
|
attach_function :leptris_xslt_parse,
|
|
1009
1031
|
[:string, :size_t], :leptris_xslt
|
|
@@ -20,11 +20,12 @@ begin
|
|
|
20
20
|
# The compiled bundle. A sanctioned require exception: a
|
|
21
21
|
# .bundle/.so cannot go through autoload. Missing in installs
|
|
22
22
|
# that do not vendor it (ruby-platform gem). Windows names the
|
|
23
|
-
# artifact per Ruby minor (#207/#227): a PE DLL must
|
|
24
|
-
# build Ruby's runtime
|
|
25
|
-
#
|
|
23
|
+
# artifact per Ruby minor, DOT-FREE (#207/#227): a PE DLL must
|
|
24
|
+
# bind its build Ruby's runtime (one DLL per minor), and MRI
|
|
25
|
+
# derives the init symbol from the basename cut at the first
|
|
26
|
+
# dot — native_3_3.so resolves Init_native_3_3.
|
|
26
27
|
if Gem.win_platform?
|
|
27
|
-
require "leptris/xml/
|
|
28
|
+
require "leptris/xml/native_#{RUBY_VERSION[/\A\d+\.\d+/].tr('.', '_')}"
|
|
28
29
|
else
|
|
29
30
|
require "leptris/xml/native"
|
|
30
31
|
end
|
data/lib/leptris/xml/node.rb
CHANGED
|
@@ -309,6 +309,19 @@ class Leptris::XML::Node
|
|
|
309
309
|
@line = Leptris::XML::FFI.leptris_node_line(c_ptr)
|
|
310
310
|
end
|
|
311
311
|
|
|
312
|
+
# Full parser-recorded position (libleptris 1.9.180, #1124):
|
|
313
|
+
# {line:, col_start:, col_end:} in Jing's diagnostic
|
|
314
|
+
# convention; zeros for programmatically-created nodes. First
|
|
315
|
+
# read wins (positions never change).
|
|
316
|
+
def source_position
|
|
317
|
+
return @source_position if defined?(@source_position)
|
|
318
|
+
ensure_alive!
|
|
319
|
+
out = Leptris::XML::FFI::SourcePosition.new
|
|
320
|
+
Leptris::XML::FFI.leptris_node_source_position(c_ptr, out)
|
|
321
|
+
@source_position = { line: out[:line], col_start: out[:col_start],
|
|
322
|
+
col_end: out[:col_end] }
|
|
323
|
+
end
|
|
324
|
+
|
|
312
325
|
# Byte offset of the node's markup in its parse source (the '<'
|
|
313
326
|
# of the tag; libleptris 1.9.162, #1039 — the position descriptor
|
|
314
327
|
# CALLBACK rows echo). 0 when unknown: mutation-created nodes, or
|
data/lib/leptris/xml/relaxng.rb
CHANGED
|
@@ -60,11 +60,23 @@ module Leptris::XML::RelaxNG
|
|
|
60
60
|
|
|
61
61
|
# Nokogiri shape: an array of error strings (the engine emits
|
|
62
62
|
# Jing's "line:col: error: message" form), empty when valid.
|
|
63
|
+
# libleptris 1.9.179 (#878): errors accumulate — every failure
|
|
64
|
+
# from the validate call is returned, not just the first.
|
|
63
65
|
def validate(document)
|
|
64
66
|
ok = Leptris::XML::FFI.leptris_rng_validate(@handle,
|
|
65
67
|
document.c_ptr)
|
|
66
68
|
return [] if ok != 0
|
|
67
|
-
|
|
69
|
+
count = Leptris::XML::FFI.leptris_rng_error_count(@handle)
|
|
70
|
+
return [Leptris::XML::FFI.leptris_rng_error(@handle)].compact if count.zero?
|
|
71
|
+
# Jing's rendering for every accumulated error, matching the
|
|
72
|
+
# single-error accessor's format ("line:col: error: msg").
|
|
73
|
+
Array.new(count) do |i|
|
|
74
|
+
msg = Leptris::XML::FFI.leptris_rng_error_message(@handle, i)
|
|
75
|
+
next nil if msg.nil?
|
|
76
|
+
line = Leptris::XML::FFI.leptris_rng_error_line(@handle, i)
|
|
77
|
+
col = Leptris::XML::FFI.leptris_rng_error_column(@handle, i)
|
|
78
|
+
line > 0 ? "#{line}:#{col}: error: #{msg}" : msg
|
|
79
|
+
end.compact
|
|
68
80
|
end
|
|
69
81
|
|
|
70
82
|
def valid?(document)
|
data/lib/leptris.rb
CHANGED
|
@@ -48,6 +48,6 @@ unless ENV["LEPTRIS_NO_NATIVE"] == "1"
|
|
|
48
48
|
# the FFI surface (the explicit require still raises its full
|
|
49
49
|
# error for callers that opt in).
|
|
50
50
|
warn "leptris: native acceleration unavailable " \
|
|
51
|
-
"(#{e.message.
|
|
51
|
+
"(#{e.message.gsub(/\s+/, ' ').strip[0, 200]}); using the FFI surface"
|
|
52
52
|
end
|
|
53
53
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: leptris
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.9.
|
|
4
|
+
version: 1.9.181.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -106,6 +106,7 @@ files:
|
|
|
106
106
|
- TODO.perf/35-parse-default-c-face.md
|
|
107
107
|
- TODO.perf/36-raise-in-c-serializer-encoding.md
|
|
108
108
|
- TODO.perf/37-document-lazy-pointer.md
|
|
109
|
+
- TODO.perf/38-bulk-walk-and-bench-gate.md
|
|
109
110
|
- TODO.restructure/01-constraint-compliance-audit.md
|
|
110
111
|
- TODO.restructure/02-deep-copy-seam.md
|
|
111
112
|
- TODO.restructure/03-evaluation-context-seam.md
|