leptris 1.9.222.2-arm-linux → 1.9.225.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +27 -0
- data/Rakefile +1 -1
- data/ext/leptris/native/native.c +51 -44
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/document.rb +12 -4
- data/lib/leptris/xml/element.rb +1 -1
- data/lib/leptris/xml/ffi.rb +27 -1
- data/lib/leptris/xml/native.so +0 -0
- data/lib/leptris/xml/serialization.rb +27 -0
- data/lib/leptris/xml.rb +0 -1
- data/lib/libleptris.so +0 -0
- data/vendor-src/README.md +1 -1
- data/vendor-src/libleptris/CHANGELOG.md +73 -0
- data/vendor-src/libleptris/CMakeLists.txt +1 -1
- data/vendor-src/libleptris/src/CMakeLists.txt +2 -0
- data/vendor-src/libleptris/src/include/leptris/sax/sax.h +87 -0
- data/vendor-src/libleptris/src/include/leptris/types.h +7 -1
- data/vendor-src/libleptris/src/include/leptris.h +49 -2
- data/vendor-src/libleptris/src/leptris/dom/digest.c +11 -0
- data/vendor-src/libleptris/src/leptris/dom/elem_pos.c +88 -0
- data/vendor-src/libleptris/src/leptris/dom/element.c +2 -1
- data/vendor-src/libleptris/src/leptris/dom/element.h +89 -56
- data/vendor-src/libleptris/src/leptris/dom/element_query.c +6 -5
- data/vendor-src/libleptris/src/leptris/dom/node_public.c +10 -23
- data/vendor-src/libleptris/src/leptris/dom/text.c +14 -79
- data/vendor-src/libleptris/src/leptris/dom/text.h +21 -33
- data/vendor-src/libleptris/src/leptris/flat/direct_parse.c +150 -58
- data/vendor-src/libleptris/src/leptris/flat/il_scan.h +69 -0
- data/vendor-src/libleptris/src/leptris/html/html_parse.c +3 -0
- data/vendor-src/libleptris/src/leptris/leptris.c +10 -0
- data/vendor-src/libleptris/src/leptris/leptris_internal.h +9 -0
- data/vendor-src/libleptris/src/leptris/sax/records.c +135 -0
- data/vendor-src/libleptris/src/leptris/serialize/serialize.c +61 -47
- data/vendor-src/libleptris/src/leptris/xslt/xslt_exec.c +3 -8
- data/vendor-src/libleptris/test/CMakeLists.txt +2 -0
- data/vendor-src/libleptris/test/dom/test_digest.cpp +49 -0
- data/vendor-src/libleptris/test/dom/test_text_borrowed.cpp +51 -40
- data/vendor-src/libleptris/test/html/test_html.cpp +23 -9
- data/vendor-src/libleptris/test/html/test_html_builder.cpp +190 -0
- data/vendor-src/libleptris/test/perf/test_perf.cpp +28 -17
- data/vendor-src/libleptris/test/sax/test_records.cpp +196 -0
- data/vendor-src/libleptris/vcpkg.json +1 -1
- metadata +7 -3
- data/lib/leptris/xml/html_serialize.rb +0 -97
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3ddb9c850703c6cc75a0d22649d3065ed7535d15e3b0b66db6a7aa8af4d73286
|
|
4
|
+
data.tar.gz: bcf50f7e810b2caa0405f5e9e0043f4682e50c7bf7c03897a74c8731caf05c6a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7c463cf68fb31d8ab1a1b02ec65a15190008d27700326f0b30698268a97fca76176516ef9e5f92a891b24f92b607a6c10d6a8a764e122151c976f1fc54e737ec
|
|
7
|
+
data.tar.gz: cb509afb2b2be7edcdf3502289865febbc607a938e07ce35a1449a047c71240c4c587fe8e6150051bbba6f271526205be0681ea2a14b8ff32f1e7437b98320fa
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,33 @@ 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.225.0] - 2026-09-22
|
|
9
|
+
|
|
10
|
+
### Added — native HTML serialization, construction, and the bulk SAX drain (engine 1.9.223–225)
|
|
11
|
+
|
|
12
|
+
- **`to_html` re-pointed at the engine's native HTML mode**
|
|
13
|
+
(1.9.225, upstream #1309/#1311 — our filing answered in one
|
|
14
|
+
release): `Document#to_html` → `leptris_document_serialize_html`,
|
|
15
|
+
`Element#to_html` → the ext serializer with `html_method: 1`.
|
|
16
|
+
Byte-parity with the interim Ruby walk is pinned by the #309
|
|
17
|
+
spec battery; the native path also fixes the walk's PI branch
|
|
18
|
+
(the Ruby walk crashed on processing instructions — target/data
|
|
19
|
+
readers don't exist on the wrapper). The Ruby walk is removed.
|
|
20
|
+
- **`Document#save(path, mode: :html)`** → `leptris_document_save_html`.
|
|
21
|
+
- **`leptris_document_create_html` attached** (native HTML-mode
|
|
22
|
+
documents — serialize AS HTML by default, Nokogiri behavior);
|
|
23
|
+
the facade's `create` stays on the XML base + doctype preset so
|
|
24
|
+
`to_xml` output on facade docs is unchanged. `html_method`
|
|
25
|
+
(tri-state) rides `SerializeExtStruct` — `to_xml` on an HTML
|
|
26
|
+
document can force XML with `-1` when the face needs it.
|
|
27
|
+
- **Bulk SAX record drain attached** (1.9.223, upstream #1298,
|
|
28
|
+
Canon-reported): `leptris_sax_records_*` — one call yields the
|
|
29
|
+
whole document as a flat record table (tree edges, source
|
|
30
|
+
offsets, flat attribute table with §3.3.3 normalization flags).
|
|
31
|
+
Attach-only until the SAX face wants the bulk lane.
|
|
32
|
+
- 1.9.224 rides along (LeakSanitizer fix on the direct-parse
|
|
33
|
+
failure path). Audit 351/351, suite 780/0.
|
|
34
|
+
|
|
8
35
|
## [1.9.222.0] - 2026-09-22
|
|
9
36
|
|
|
10
37
|
### Fixed — engine sync (1.9.222): the interleaved lane's namespace regression
|
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.225"
|
|
12
12
|
# Vendored alongside libleptris for fn:normalize-unicode (TODO
|
|
13
13
|
# .restructure/20): built per platform with a RELOCATABLE @rpath
|
|
14
14
|
# install name, loaded by ffi.rb before libleptris so the
|
data/ext/leptris/native/native.c
CHANGED
|
@@ -135,6 +135,9 @@ static VALUE c_iteration_scope;
|
|
|
135
135
|
static VALUE c_leptris_error;
|
|
136
136
|
static VALUE c_b_document, c_b_freed, c_ffi_pointer;
|
|
137
137
|
static ID id_ptr_new, id_freed_new, id_alive;
|
|
138
|
+
static ID id_iv_c_address, id_iv_document, id_iv_parent;
|
|
139
|
+
static ID id_iv_structure_memoizable, id_iv_native_fast;
|
|
140
|
+
static ID id_iv_pub_document, id_iv_addr_reads_fast, id_iv_node_type;
|
|
138
141
|
static void check_status_c(int st);
|
|
139
142
|
|
|
140
143
|
#define NT_ELEMENT 0
|
|
@@ -839,8 +842,38 @@ struct trav_state {
|
|
|
839
842
|
VALUE err;
|
|
840
843
|
void *self_ptr;
|
|
841
844
|
int for_visit;
|
|
845
|
+
/* Hoisted per-walk state (was re-resolved per node): the
|
|
846
|
+
* document's wrapper cache and the iteration-scope class
|
|
847
|
+
* check. #312 item 1. */
|
|
848
|
+
VALUE cache;
|
|
849
|
+
VALUE klass_memo_flag;
|
|
842
850
|
};
|
|
843
851
|
|
|
852
|
+
/* Construct-or-fetch the binding wrapper for node_ptr against the
|
|
853
|
+
* per-walk hoisted cache (#312 item 1): static-ID ivar writes (no
|
|
854
|
+
* per-call string interning), the scope class check precomputed. */
|
|
855
|
+
static VALUE trav_wrapper_for(struct trav_state *st, void *node_ptr,
|
|
856
|
+
int kind)
|
|
857
|
+
{
|
|
858
|
+
VALUE key = ULL2NUM((uint64_t)(uintptr_t)node_ptr);
|
|
859
|
+
VALUE node = rb_hash_aref(st->cache, key);
|
|
860
|
+
if (!NIL_P(node)) return node;
|
|
861
|
+
|
|
862
|
+
node = rb_obj_alloc(binding_klass_for(kind));
|
|
863
|
+
rb_ivar_set(node, id_iv_c_address, key);
|
|
864
|
+
rb_ivar_set(node, id_iv_document, st->document);
|
|
865
|
+
rb_ivar_set(node, id_iv_parent, Qnil);
|
|
866
|
+
VALUE fast = st->klass_memo_flag;
|
|
867
|
+
rb_ivar_set(node, id_iv_structure_memoizable, fast);
|
|
868
|
+
rb_ivar_set(node, id_iv_native_fast, fast);
|
|
869
|
+
rb_ivar_set(node, id_iv_pub_document,
|
|
870
|
+
fast == Qtrue ? st->document : Qnil);
|
|
871
|
+
rb_ivar_set(node, id_iv_addr_reads_fast, Qtrue);
|
|
872
|
+
rb_ivar_set(node, id_iv_node_type, INT2FIX(kind));
|
|
873
|
+
rb_hash_aset(st->cache, key, node);
|
|
874
|
+
return node;
|
|
875
|
+
}
|
|
876
|
+
|
|
844
877
|
static VALUE trav_yield_one(VALUE arg)
|
|
845
878
|
{
|
|
846
879
|
return rb_yield(arg);
|
|
@@ -849,33 +882,13 @@ static VALUE trav_yield_one(VALUE arg)
|
|
|
849
882
|
static int trav_cb(void *node_ptr, void *user)
|
|
850
883
|
{
|
|
851
884
|
struct trav_state *st = user;
|
|
852
|
-
VALUE
|
|
885
|
+
VALUE node;
|
|
853
886
|
int kind, state;
|
|
854
887
|
|
|
855
888
|
if (st->err != Qnil)
|
|
856
889
|
return 1;
|
|
857
890
|
kind = f_node_type(node_ptr);
|
|
858
|
-
|
|
859
|
-
key = ULL2NUM((uint64_t)(uintptr_t)node_ptr);
|
|
860
|
-
node = rb_hash_aref(cache, key);
|
|
861
|
-
if (NIL_P(node)) {
|
|
862
|
-
node = rb_obj_alloc(binding_klass_for(kind));
|
|
863
|
-
rb_iv_set(node, "@c_address", key);
|
|
864
|
-
rb_iv_set(node, "@document", st->document);
|
|
865
|
-
rb_iv_set(node, "@parent", Qnil);
|
|
866
|
-
if (rb_obj_class(st->document) == c_iteration_scope) {
|
|
867
|
-
rb_iv_set(node, "@structure_memoizable", Qfalse);
|
|
868
|
-
rb_iv_set(node, "@native_fast", Qfalse);
|
|
869
|
-
rb_iv_set(node, "@pub_document", Qnil);
|
|
870
|
-
} else {
|
|
871
|
-
rb_iv_set(node, "@structure_memoizable", Qtrue);
|
|
872
|
-
rb_iv_set(node, "@native_fast", Qtrue);
|
|
873
|
-
rb_iv_set(node, "@pub_document", st->document);
|
|
874
|
-
}
|
|
875
|
-
rb_iv_set(node, "@addr_reads_fast", Qtrue);
|
|
876
|
-
rb_iv_set(node, "@node_type", INT2FIX(kind));
|
|
877
|
-
rb_hash_aset(cache, key, node);
|
|
878
|
-
}
|
|
891
|
+
node = trav_wrapper_for(st, node_ptr, kind);
|
|
879
892
|
rb_protect(trav_yield_one, node, &state);
|
|
880
893
|
if (state) {
|
|
881
894
|
st->err = rb_errinfo();
|
|
@@ -894,6 +907,9 @@ static VALUE nf_traverse_binding(VALUE self, VALUE document,
|
|
|
894
907
|
st.document = document;
|
|
895
908
|
st.err = Qnil;
|
|
896
909
|
st.self_ptr = (void *)(uintptr_t)NUM2ULL(addr);
|
|
910
|
+
st.cache = binding_cache_of(document);
|
|
911
|
+
st.klass_memo_flag =
|
|
912
|
+
rb_obj_class(document) == c_iteration_scope ? Qfalse : Qtrue;
|
|
897
913
|
st.for_visit = 0;
|
|
898
914
|
f_node_traverse(st.self_ptr, 1 /* TRAVERSE_POST_ORDER */,
|
|
899
915
|
trav_cb, &st);
|
|
@@ -922,33 +938,13 @@ static void visit_cb(void *user, void *node_ptr, int entering,
|
|
|
922
938
|
* happens after the walk, same observable outcome. */
|
|
923
939
|
struct trav_state *st = user;
|
|
924
940
|
struct visit_yield_args va;
|
|
925
|
-
VALUE
|
|
941
|
+
VALUE node;
|
|
926
942
|
int kind, state;
|
|
927
943
|
|
|
928
944
|
if (st->err != Qnil)
|
|
929
945
|
return;
|
|
930
946
|
kind = f_node_type(node_ptr);
|
|
931
|
-
|
|
932
|
-
key = ULL2NUM((uint64_t)(uintptr_t)node_ptr);
|
|
933
|
-
node = rb_hash_aref(cache, key);
|
|
934
|
-
if (NIL_P(node)) {
|
|
935
|
-
node = rb_obj_alloc(binding_klass_for(kind));
|
|
936
|
-
rb_iv_set(node, "@c_address", key);
|
|
937
|
-
rb_iv_set(node, "@document", st->document);
|
|
938
|
-
rb_iv_set(node, "@parent", Qnil);
|
|
939
|
-
if (rb_obj_class(st->document) == c_iteration_scope) {
|
|
940
|
-
rb_iv_set(node, "@structure_memoizable", Qfalse);
|
|
941
|
-
rb_iv_set(node, "@native_fast", Qfalse);
|
|
942
|
-
rb_iv_set(node, "@pub_document", Qnil);
|
|
943
|
-
} else {
|
|
944
|
-
rb_iv_set(node, "@structure_memoizable", Qtrue);
|
|
945
|
-
rb_iv_set(node, "@native_fast", Qtrue);
|
|
946
|
-
rb_iv_set(node, "@pub_document", st->document);
|
|
947
|
-
}
|
|
948
|
-
rb_iv_set(node, "@addr_reads_fast", Qtrue);
|
|
949
|
-
rb_iv_set(node, "@node_type", INT2FIX(kind));
|
|
950
|
-
rb_hash_aset(cache, key, node);
|
|
951
|
-
}
|
|
947
|
+
node = trav_wrapper_for(st, node_ptr, kind);
|
|
952
948
|
va.node = node;
|
|
953
949
|
va.entering = entering ? Qtrue : Qfalse;
|
|
954
950
|
va.depth = INT2NUM(depth);
|
|
@@ -966,6 +962,9 @@ static VALUE nf_visit_binding(VALUE self, VALUE document, VALUE addr)
|
|
|
966
962
|
st.document = document;
|
|
967
963
|
st.err = Qnil;
|
|
968
964
|
st.self_ptr = (void *)(uintptr_t)NUM2ULL(addr);
|
|
965
|
+
st.cache = binding_cache_of(document);
|
|
966
|
+
st.klass_memo_flag =
|
|
967
|
+
rb_obj_class(document) == c_iteration_scope ? Qfalse : Qtrue;
|
|
969
968
|
st.for_visit = 1;
|
|
970
969
|
f_node_visit(st.self_ptr, visit_cb, &st);
|
|
971
970
|
if (st.err != Qnil)
|
|
@@ -2648,6 +2647,14 @@ LEPTRIS_INIT_EXPORT void Init_native(void)
|
|
|
2648
2647
|
id_iv_c_address = rb_intern("@c_address");
|
|
2649
2648
|
id_iv_native_cache = rb_intern("@native_cache");
|
|
2650
2649
|
id_iv_binding_cache = rb_intern("@wrapper_cache");
|
|
2650
|
+
id_iv_c_address = rb_intern("@c_address");
|
|
2651
|
+
id_iv_document = rb_intern("@document");
|
|
2652
|
+
id_iv_parent = rb_intern("@parent");
|
|
2653
|
+
id_iv_structure_memoizable = rb_intern("@structure_memoizable");
|
|
2654
|
+
id_iv_native_fast = rb_intern("@native_fast");
|
|
2655
|
+
id_iv_pub_document = rb_intern("@pub_document");
|
|
2656
|
+
id_iv_addr_reads_fast = rb_intern("@addr_reads_fast");
|
|
2657
|
+
id_iv_node_type = rb_intern("@node_type");
|
|
2651
2658
|
id_iv_version = rb_intern("@version");
|
|
2652
2659
|
id_iv_readonly = rb_intern("@readonly");
|
|
2653
2660
|
id_address = rb_intern("address");
|
data/lib/leptris/version.rb
CHANGED
data/lib/leptris/xml/document.rb
CHANGED
|
@@ -481,16 +481,24 @@ class Leptris::XML::Document
|
|
|
481
481
|
# the perf headroom this face gates on.
|
|
482
482
|
def to_html
|
|
483
483
|
raise Leptris::XML::UseAfterFreeError if @freed.state == :freed
|
|
484
|
-
Leptris::XML::
|
|
484
|
+
Leptris::XML::Serialization.document_html(c_ptr)
|
|
485
485
|
end
|
|
486
486
|
|
|
487
|
-
def save(path, **opts)
|
|
487
|
+
def save(path, mode: :xml, **opts)
|
|
488
488
|
opts_struct, _encoding_anchor = Leptris::XML::Serialization.build_options(
|
|
489
489
|
indent: opts.fetch(:indent, 0),
|
|
490
490
|
no_decl: opts.fetch(:no_decl, false),
|
|
491
491
|
encoding: opts[:encoding])
|
|
492
|
-
status =
|
|
493
|
-
|
|
492
|
+
status =
|
|
493
|
+
if mode == :html
|
|
494
|
+
Leptris::XML::FFI.leptris_document_save_html(
|
|
495
|
+
c_ptr, path, opts_struct.pointer)
|
|
496
|
+
elsif mode != :xml
|
|
497
|
+
raise ArgumentError, "mode must be :xml or :html, got #{mode.inspect}"
|
|
498
|
+
else
|
|
499
|
+
Leptris::XML::FFI.leptris_document_save_file(
|
|
500
|
+
c_ptr, path, opts_struct.pointer)
|
|
501
|
+
end
|
|
494
502
|
Leptris::XML::FFI.check_status(status)
|
|
495
503
|
self
|
|
496
504
|
end
|
data/lib/leptris/xml/element.rb
CHANGED
|
@@ -784,7 +784,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
784
784
|
# Document#to_html for the semantics).
|
|
785
785
|
def to_html
|
|
786
786
|
ensure_alive!
|
|
787
|
-
Leptris::XML::
|
|
787
|
+
Leptris::XML::Serialization.element_html(c_ptr)
|
|
788
788
|
end
|
|
789
789
|
|
|
790
790
|
def canonicalize(version = Leptris::XML::FFI::C14N_1_0,
|
data/lib/leptris/xml/ffi.rb
CHANGED
|
@@ -169,7 +169,11 @@ module Leptris
|
|
|
169
169
|
class SerializeExtStruct < ::FFI::Struct
|
|
170
170
|
layout :indent_text, :int,
|
|
171
171
|
:indent_unit, :pointer,
|
|
172
|
-
:expand_empty, :int
|
|
172
|
+
:expand_empty, :int,
|
|
173
|
+
# libleptris 1.9.225 (#1311): tri-state — 0 = the
|
|
174
|
+
# document's default method, 1 = force HTML, -1 =
|
|
175
|
+
# force XML (to_xml on an HTML document).
|
|
176
|
+
:html_method, :int
|
|
173
177
|
end
|
|
174
178
|
|
|
175
179
|
# Mirrors LeptrisParseOptions (libleptris >= 1.9.0 carries the
|
|
@@ -992,6 +996,28 @@ attach_function :leptris_parse_string,
|
|
|
992
996
|
[:leptris_element, :pointer, :size_t, :pointer, :pointer], :size_t
|
|
993
997
|
attach_function :leptris_document_save_file,
|
|
994
998
|
[:leptris_document, :string, :pointer], :leptris_status
|
|
999
|
+
# HTML construction + serialization (libleptris 1.9.225,
|
|
1000
|
+
# upstream #1309/#1311 — the engine side of #309): native HTML
|
|
1001
|
+
# documents carry the html serialization mode (voids unclosed,
|
|
1002
|
+
# raw-text script/style, explicit closes, no declaration).
|
|
1003
|
+
attach_function :leptris_document_create_html, [], :leptris_document
|
|
1004
|
+
attach_function :leptris_document_serialize_html,
|
|
1005
|
+
[:leptris_document, :pointer], :pointer
|
|
1006
|
+
attach_function :leptris_document_save_html,
|
|
1007
|
+
[:leptris_document, :string, :pointer], :leptris_status
|
|
1008
|
+
# Bulk SAX record drain (libleptris 1.9.223, upstream #1298):
|
|
1009
|
+
# ONE call parses the document into a flat record table —
|
|
1010
|
+
# tree edges, source offsets, and a flat attribute table, all
|
|
1011
|
+
# offset-views into a parser-owned buffer. Attach-only until
|
|
1012
|
+
# the SAX face wants the bulk lane.
|
|
1013
|
+
attach_function :leptris_sax_records_parse,
|
|
1014
|
+
[:string, :size_t, :uint, :pointer], :leptris_status
|
|
1015
|
+
attach_function :leptris_sax_records_count, [:pointer], :size_t
|
|
1016
|
+
attach_function :leptris_sax_records_data, [:pointer], :pointer
|
|
1017
|
+
attach_function :leptris_sax_records_attrs,
|
|
1018
|
+
[:pointer, :pointer], :pointer
|
|
1019
|
+
attach_function :leptris_sax_records_buffer, [:pointer], :string
|
|
1020
|
+
attach_function :leptris_sax_records_free, [:pointer], :void
|
|
995
1021
|
# XSLT 1.0 engine (libleptris 1.9.1): compile once, apply many.
|
|
996
1022
|
# XQuery 1.0 core (libleptris 1.9.64-1.9.66): orchestration
|
|
997
1023
|
# over the XPath engine — prolog (declare variable/namespace/
|
data/lib/leptris/xml/native.so
CHANGED
|
Binary file
|
|
@@ -58,6 +58,33 @@ module Leptris::XML::Serialization
|
|
|
58
58
|
# Default-options element serialization for inner_html's per-child
|
|
59
59
|
# loop: no kwargs, no options rebuild, no method allocation —
|
|
60
60
|
# one FFI fast-path dispatch per child.
|
|
61
|
+
# Document HTML serialization (libleptris 1.9.225, #1311): the
|
|
62
|
+
# native HTML mode — voids unclosed, raw-text script/style,
|
|
63
|
+
# explicit closes, no declaration, DOCTYPE first.
|
|
64
|
+
def self.document_html(c_ptr)
|
|
65
|
+
Leptris::XML::FFI.read_owned_string(
|
|
66
|
+
Leptris::XML::FFI.leptris_document_serialize_html(c_ptr, nil))
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Element-level HTML mode through the ext serializer (the
|
|
70
|
+
# html_method tri-state forces HTML regardless of the document
|
|
71
|
+
# flavor).
|
|
72
|
+
HTML_METHOD_EXT = begin
|
|
73
|
+
ext = Leptris::XML::FFI::SerializeExtStruct.new
|
|
74
|
+
ext[:indent_text] = 0
|
|
75
|
+
ext[:indent_unit] = nil
|
|
76
|
+
ext[:expand_empty] = 0
|
|
77
|
+
ext[:html_method] = 1
|
|
78
|
+
ext
|
|
79
|
+
end
|
|
80
|
+
private_constant :HTML_METHOD_EXT
|
|
81
|
+
|
|
82
|
+
def self.element_html(c_ptr)
|
|
83
|
+
Leptris::XML::FFI.read_owned_string(
|
|
84
|
+
Leptris::XML::FFI.leptris_element_serialize_ext(
|
|
85
|
+
c_ptr, nil, HTML_METHOD_EXT.pointer))
|
|
86
|
+
end
|
|
87
|
+
|
|
61
88
|
def self.element_xml_default(c_ptr)
|
|
62
89
|
return Leptris::XML::Native.fast_element_xml(c_ptr.address, 0, true,
|
|
63
90
|
nil) if
|
data/lib/leptris/xml.rb
CHANGED
|
@@ -21,7 +21,6 @@ module Leptris
|
|
|
21
21
|
autoload :CssToXPath, "leptris/xml/css_to_xpath"
|
|
22
22
|
autoload :CStringArray, "leptris/xml/c_string_array"
|
|
23
23
|
autoload :Serialization, "leptris/xml/serialization"
|
|
24
|
-
autoload :HTMLSerialize, "leptris/xml/html_serialize"
|
|
25
24
|
autoload :SAX, "leptris/xml/sax"
|
|
26
25
|
autoload :XPath, "leptris/xml/xpath"
|
|
27
26
|
autoload :EvaluationContext, "leptris/xml/evaluation_context"
|
data/lib/libleptris.so
CHANGED
|
Binary file
|
data/vendor-src/README.md
CHANGED
|
@@ -1,5 +1,78 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.9.225] - 2026-09-22
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **html: public HTML construction + serialization surface (#1309,
|
|
8
|
+
#1311).** HTML documents now serialize AS HTML by default
|
|
9
|
+
(libxml2/Nokogiri behavior): void elements unclosed (`<br>`,
|
|
10
|
+
`<meta charset="utf-8">`), raw-text script/style verbatim,
|
|
11
|
+
childless non-voids explicitly closed, no XML declaration. New:
|
|
12
|
+
`leptris_document_create_html()`, `leptris_document_serialize_html()`,
|
|
13
|
+
`leptris_document_save_html()`, and `LeptrisSerializeExtOptions.html_method`
|
|
14
|
+
(tri-state: 0 = document default, 1 = force HTML, -1 = force XML —
|
|
15
|
+
to_xml on an HTML doc). Element-level `html_method` maps through the
|
|
16
|
+
ext serializer too. HTML-parsed documents (whatwg + html4 modes)
|
|
17
|
+
carry the mode automatically; XSLT `method="html"` output unchanged
|
|
18
|
+
(#1312).
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
## [1.9.224] - 2026-09-22
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- **mem:** the element source-offset side table (new in this
|
|
26
|
+
release) is now also freed on the direct-parse failure path,
|
|
27
|
+
which bypasses leptris_document_free — caught by CI LeakSanitizer.
|
|
28
|
+
- **test:** the runner-contention perf gate
|
|
29
|
+
(SmallDocumentParseIsFast) retries across settling windows instead
|
|
30
|
+
of failing on a single contended measurement (three CI flakes in
|
|
31
|
+
one day, budget unchanged).
|
|
32
|
+
|
|
33
|
+
### Performance
|
|
34
|
+
|
|
35
|
+
- **dom:** elements shrink **72 -> 64 bytes** (ILP32 56 -> 48;
|
|
36
|
+
#1285 slice 3a): the namespace side-cache pointer became a
|
|
37
|
+
self-relative int32 offset (the attrs' round-19 pattern), and the
|
|
38
|
+
#1124 source offsets moved to a document-owned side table — cold
|
|
39
|
+
diagnostics-only data no longer costs hot-path cache lines.
|
|
40
|
+
Position accessors are unchanged (#1124 specs green). Part of the
|
|
41
|
+
pugi-style node-storage lane (#1222/#1238) (#1308).
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
## [1.9.223] - 2026-09-22
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
|
|
49
|
+
- **sax:** `leptris_sax_records_parse` — a bulk record drain for hot SAX
|
|
50
|
+
loops (#1298, reported by Canon): ONE call returns the whole document
|
|
51
|
+
as a flat record table (element tree edges, start/end offsets, flat
|
|
52
|
+
attribute table with XML 1.0 §3.3.3 normalization flags), all
|
|
53
|
+
NUL-terminated views into a parser-owned buffer. Unsupported
|
|
54
|
+
constructs (comments/PIs/CDATA/DOCTYPE/entities/xmlns) return
|
|
55
|
+
`LEPTRIS_ERROR_NOT_SUPPORTED` — fall back to the pull API. New
|
|
56
|
+
additive status code (#1305).
|
|
57
|
+
|
|
58
|
+
### Fixed
|
|
59
|
+
|
|
60
|
+
- **dom:** `LEPTRIS_DIGEST_ATTR_ORDER` is now actually honored — the
|
|
61
|
+
1.9.205 flag shipped only as a C enum with no env reader and no
|
|
62
|
+
binding carrier, so `strings` never found it in shipped bundles
|
|
63
|
+
(#1297, reported by Canon). `leptris_node_digest` reads the env
|
|
64
|
+
per call and ORs it into the passed flags (#1304).
|
|
65
|
+
|
|
66
|
+
### Performance
|
|
67
|
+
|
|
68
|
+
- **dom:** text nodes shrink **64 → 48 bytes** (#1285 slice 4): the
|
|
69
|
+
`pool`/`borrowed` fields are gone — every parse lane terminates
|
|
70
|
+
text in place and entities decode eagerly at parse time, so
|
|
71
|
+
`leptris_text_get_content` is a plain field read. Content is
|
|
72
|
+
always NUL-terminated; `content_len` is authoritative (#1303).
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
3
76
|
## [1.9.222] - 2026-09-22
|
|
4
77
|
|
|
5
78
|
### Fixed
|
|
@@ -35,6 +35,7 @@ set(LEPTRIS_SOURCES
|
|
|
35
35
|
leptris/dom/node_public.c
|
|
36
36
|
leptris/descriptor/plan.c
|
|
37
37
|
leptris/dom/element.c
|
|
38
|
+
leptris/dom/elem_pos.c
|
|
38
39
|
leptris/dom/element_query.c
|
|
39
40
|
leptris/dom/element_modify.c
|
|
40
41
|
leptris/dom/element_index.c
|
|
@@ -112,6 +113,7 @@ set(LEPTRIS_SOURCES
|
|
|
112
113
|
leptris/sax/iterparse.c
|
|
113
114
|
leptris/sax/streaming.c
|
|
114
115
|
leptris/sax/recorder.c
|
|
116
|
+
leptris/sax/records.c
|
|
115
117
|
|
|
116
118
|
# DTD
|
|
117
119
|
leptris/dtd/model.c
|
|
@@ -596,6 +596,93 @@ LEPTRIS_API const char* leptris_iterparse_error(LeptrisIterparse it);
|
|
|
596
596
|
|
|
597
597
|
LEPTRIS_API void leptris_iterparse_free(LeptrisIterparse it);
|
|
598
598
|
|
|
599
|
+
|
|
600
|
+
/* ============================================================================
|
|
601
|
+
* Bulk SAX records (issue #1298) — one-crossing whole-document drain
|
|
602
|
+
* ============================================================================ */
|
|
603
|
+
|
|
604
|
+
/** Record kinds (LeptrisSaxRecord.kind). */
|
|
605
|
+
enum {
|
|
606
|
+
LEPTRIS_SAX_REC_ELEMENT = 0, /**< start+end collapsed; tree via parent/next_sib */
|
|
607
|
+
LEPTRIS_SAX_REC_TEXT = 1 /**< character data run (entity-free docs only) */
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
/** One element or text record. Offsets are byte positions into the
|
|
611
|
+
* buffer returned by leptris_sax_records_buffer; every view is
|
|
612
|
+
* NUL-terminated at off+len (names and text both). */
|
|
613
|
+
typedef struct LeptrisSaxRecord {
|
|
614
|
+
uint32_t kind; /**< LEPTRIS_SAX_REC_* */
|
|
615
|
+
uint32_t parent; /**< record index, 0xFFFFFFFF for the root */
|
|
616
|
+
uint32_t next_sib; /**< record index, 0xFFFFFFFF = last sibling */
|
|
617
|
+
uint32_t off; /**< element: name; text: content */
|
|
618
|
+
uint32_t len; /**< byte length at off (excl. NUL) */
|
|
619
|
+
uint32_t line; /**< byte offset of '<' (or text start) + 1 (#1124 dp semantics) */
|
|
620
|
+
uint32_t start_tag_end; /**< byte offset after the open '>' (elements; 0 when unknown) */
|
|
621
|
+
uint32_t elem_end; /**< byte offset after the close '>' (self-close == start) */
|
|
622
|
+
uint32_t attr_first; /**< index into the flat attr table, 0xFFFFFFFF = none */
|
|
623
|
+
uint32_t attr_count; /**< number of attributes */
|
|
624
|
+
uint8_t self_closing; /**< element written <a/> */
|
|
625
|
+
} LeptrisSaxRecord;
|
|
626
|
+
|
|
627
|
+
/** One attribute: flat (off,len) views + 3.3.3 normalization need. */
|
|
628
|
+
typedef struct LeptrisSaxAttr {
|
|
629
|
+
uint32_t name_off, name_len; /**< attribute name (qname, NUL-terminated at [name_len]) */
|
|
630
|
+
uint32_t value_off, value_len; /**< raw value (NUL-terminated at [value_len]) */
|
|
631
|
+
uint8_t value_has_ws; /**< literal \t/\n/\r present — apply XML 1.0 3.3.3 normalization */
|
|
632
|
+
} LeptrisSaxAttr;
|
|
633
|
+
|
|
634
|
+
/** Opaque whole-document record table. */
|
|
635
|
+
typedef struct LeptrisSaxRecords LeptrisSaxRecords;
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Parse a whole document into a flat record table (ONE call)
|
|
639
|
+
*
|
|
640
|
+
* The engine-side surface for #1298: hosts with hot SAX loops drain
|
|
641
|
+
* the entire document with a single crossing and then walk plain C
|
|
642
|
+
* structs — no per-event callback dispatch, no per-event string
|
|
643
|
+
* minting C-side. Records are (off,len) views into the parser's
|
|
644
|
+
* private copy of the input; every view is NUL-terminated in place.
|
|
645
|
+
*
|
|
646
|
+
* Coverage: the interleaved lane's scannable subset — elements,
|
|
647
|
+
* text, attributes; well-formed entity-free documents without
|
|
648
|
+
* comments/PIs/CDATA/DOCTYPE/xmlns declarations. Anything outside
|
|
649
|
+
* that subset returns LEPTRIS_ERROR_NOT_SUPPORTED (zero allocation);
|
|
650
|
+
* hosts fall back to the pull API (leptris_pull_new) for those
|
|
651
|
+
* documents. Malformed input returns LEPTRIS_ERROR_PARSE.
|
|
652
|
+
*
|
|
653
|
+
* @param xml Input (copied; the caller's buffer is untouched)
|
|
654
|
+
* @param len Input length in bytes (< 2 GiB)
|
|
655
|
+
* @param flags Reserved; pass 0
|
|
656
|
+
* @param out Receives the table handle
|
|
657
|
+
* @return LEPTRIS_OK, LEPTRIS_ERROR_NULL_ARG, LEPTRIS_ERROR_NOT_SUPPORTED,
|
|
658
|
+
* LEPTRIS_ERROR_PARSE, or LEPTRIS_ERROR_MEMORY
|
|
659
|
+
*
|
|
660
|
+
* Memory: free with leptris_sax_records_free; all views die with it.
|
|
661
|
+
*/
|
|
662
|
+
LEPTRIS_API LeptrisStatus leptris_sax_records_parse(const char* xml,
|
|
663
|
+
size_t len,
|
|
664
|
+
unsigned flags,
|
|
665
|
+
LeptrisSaxRecords** out);
|
|
666
|
+
|
|
667
|
+
/** Number of records. */
|
|
668
|
+
LEPTRIS_API size_t leptris_sax_records_count(const LeptrisSaxRecords* recs);
|
|
669
|
+
|
|
670
|
+
/** Record array (count entries; valid until free). */
|
|
671
|
+
LEPTRIS_API const LeptrisSaxRecord* leptris_sax_records_data(
|
|
672
|
+
const LeptrisSaxRecords* recs);
|
|
673
|
+
|
|
674
|
+
/** Flat attribute table + its length (valid until free). */
|
|
675
|
+
LEPTRIS_API const LeptrisSaxAttr* leptris_sax_records_attrs(
|
|
676
|
+
const LeptrisSaxRecords* recs, size_t* out_attr_count);
|
|
677
|
+
|
|
678
|
+
/** The buffer all record/attr offsets index into (NUL-padded past
|
|
679
|
+
* len). */
|
|
680
|
+
LEPTRIS_API const char* leptris_sax_records_buffer(
|
|
681
|
+
const LeptrisSaxRecords* recs);
|
|
682
|
+
|
|
683
|
+
/** Free the table (invalidates every view). */
|
|
684
|
+
LEPTRIS_API void leptris_sax_records_free(LeptrisSaxRecords* recs);
|
|
685
|
+
|
|
599
686
|
#ifdef __cplusplus
|
|
600
687
|
}
|
|
601
688
|
#endif
|
|
@@ -49,7 +49,8 @@ typedef enum {
|
|
|
49
49
|
LEPTRIS_ERROR_INVALID_ARG = -5, /* Invalid argument */
|
|
50
50
|
LEPTRIS_ERROR_NOT_FOUND = -6, /* Resource not found */
|
|
51
51
|
LEPTRIS_ERROR_IO = -7, /* I/O error (file not found, etc.) */
|
|
52
|
-
LEPTRIS_ERROR_NOT_IMPLEMENTED = -8 /* Feature not yet implemented */
|
|
52
|
+
LEPTRIS_ERROR_NOT_IMPLEMENTED = -8, /* Feature not yet implemented */
|
|
53
|
+
LEPTRIS_ERROR_NOT_SUPPORTED = -9 /* Construct outside this API's subset (additive, #1298) */
|
|
53
54
|
} LeptrisStatus;
|
|
54
55
|
|
|
55
56
|
/* ============================================================================
|
|
@@ -342,6 +343,11 @@ typedef struct {
|
|
|
342
343
|
* Applies to the XML method only — HTML void elements keep
|
|
343
344
|
* their HTML shapes. */
|
|
344
345
|
int expand_empty;
|
|
346
|
+
/* §16.2 HTML method control (#1309): 0 = document default
|
|
347
|
+
* (HTML-parsed and create_html documents serialize as HTML),
|
|
348
|
+
* 1 = force HTML on any document, -1 = force XML (to_xml on an
|
|
349
|
+
* HTML document). XSLT method="html" maps to 1. */
|
|
350
|
+
int html_method;
|
|
345
351
|
} LeptrisSerializeExtOptions;
|
|
346
352
|
|
|
347
353
|
/* ============================================================================
|
|
@@ -191,9 +191,14 @@ LEPTRIS_API size_t leptris_node_child_count(LeptrisNodeRef node);
|
|
|
191
191
|
*
|
|
192
192
|
* @param node Subtree root (any node type; NULL hashes to 0)
|
|
193
193
|
* @param flags LEPTRIS_DIGEST_DROP_WS_TEXT skips whitespace-only
|
|
194
|
-
* text nodes
|
|
194
|
+
* text nodes; LEPTRIS_DIGEST_ATTR_ORDER makes attribute
|
|
195
|
+
* ORDER identity (no sort)
|
|
195
196
|
* @return 64-bit content digest
|
|
196
|
-
|
|
197
|
+
*
|
|
198
|
+
* Environment opt-in (#1297): setting LEPTRIS_DIGEST_ATTR_ORDER=1 in
|
|
199
|
+
* the process environment ORs LEPTRIS_DIGEST_ATTR_ORDER into every
|
|
200
|
+
* call — the carrier for hosts whose binding exposes no flags
|
|
201
|
+
* parameter. "0" or an empty value leaves the passed flags alone. */
|
|
197
202
|
LEPTRIS_API uint64_t leptris_node_digest(LeptrisNodeRef node,
|
|
198
203
|
LeptrisDigestFlags flags);
|
|
199
204
|
|
|
@@ -773,6 +778,19 @@ LEPTRIS_API const char* leptris_pi_node_get_data(LeptrisNodeRef node);
|
|
|
773
778
|
*/
|
|
774
779
|
LEPTRIS_API LeptrisDocument leptris_document_create(void);
|
|
775
780
|
|
|
781
|
+
/**
|
|
782
|
+
* Create an empty HTML-mode document (#1309)
|
|
783
|
+
*
|
|
784
|
+
* Identical to leptris_document_create except the document carries
|
|
785
|
+
* html_mode: the default serializer emits §16.2 HTML output (void
|
|
786
|
+
* elements unclosed, raw-text script/style, HTML attribute
|
|
787
|
+
* escaping). Build with the regular element APIs.
|
|
788
|
+
*
|
|
789
|
+
* @return New empty HTML document, or NULL on failure
|
|
790
|
+
* Memory: free with leptris_document_free.
|
|
791
|
+
*/
|
|
792
|
+
LEPTRIS_API LeptrisDocument leptris_document_create_html(void);
|
|
793
|
+
|
|
776
794
|
/**
|
|
777
795
|
* Attach an element as the document's root element
|
|
778
796
|
*
|
|
@@ -2451,6 +2469,35 @@ LEPTRIS_API char* leptris_document_serialize_ext_sized(LeptrisDocument doc,
|
|
|
2451
2469
|
const LeptrisSerializeExtOptions* ext,
|
|
2452
2470
|
size_t ext_size);
|
|
2453
2471
|
|
|
2472
|
+
/**
|
|
2473
|
+
* Serialize a document with the §16.2 HTML method (#1309)
|
|
2474
|
+
*
|
|
2475
|
+
* Forces the HTML serialization rules (void elements unclosed,
|
|
2476
|
+
* raw-text script/style, HTML attribute escaping) regardless of the
|
|
2477
|
+
* document's mode — the XSLT method="html" semantic, public.
|
|
2478
|
+
* HTML-parsed and create_html documents get this behavior by
|
|
2479
|
+
* default; this entry forces it on any document.
|
|
2480
|
+
*
|
|
2481
|
+
* @param doc Document
|
|
2482
|
+
* @param options Frozen serialize options (NULL = defaults)
|
|
2483
|
+
* @return Serialized HTML string
|
|
2484
|
+
* Memory: Caller must call leptris_free_string() when done.
|
|
2485
|
+
*/
|
|
2486
|
+
LEPTRIS_API char* leptris_document_serialize_html(
|
|
2487
|
+
LeptrisDocument doc, const LeptrisSerializeOptions* options);
|
|
2488
|
+
|
|
2489
|
+
/**
|
|
2490
|
+
* Save a document as HTML to a file (#1309)
|
|
2491
|
+
*
|
|
2492
|
+
* @param doc Document
|
|
2493
|
+
* @param filepath Destination path
|
|
2494
|
+
* @param options Frozen serialize options (NULL = defaults)
|
|
2495
|
+
* @return LEPTRIS_OK (0), or a negative LeptrisStatus on failure
|
|
2496
|
+
*/
|
|
2497
|
+
LEPTRIS_API int leptris_document_save_html(LeptrisDocument doc,
|
|
2498
|
+
const char* filepath,
|
|
2499
|
+
const LeptrisSerializeOptions* options);
|
|
2500
|
+
|
|
2454
2501
|
/**
|
|
2455
2502
|
* Extended element serialization (issue #882): the same option
|
|
2456
2503
|
* surface as leptris_document_serialize_ext — indent_text,
|