leptris 1.9.174.9 → 1.9.177.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 +49 -0
- data/Rakefile +7 -2
- data/TODO.perf/36-raise-in-c-serializer-encoding.md +34 -0
- data/TODO.perf/37-document-lazy-pointer.md +28 -0
- data/ext/leptris/native/native.c +62 -20
- 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 +84 -6
- data/lib/leptris/xml/element.rb +22 -33
- data/lib/leptris/xml/entity_reference.rb +13 -0
- data/lib/leptris/xml/ffi.rb +27 -0
- data/lib/leptris/xml/node.rb +3 -2
- data/lib/leptris/xml/parse_options.rb +15 -0
- data/lib/leptris/xml/searchable.rb +8 -0
- data/lib/leptris/xml/serialization.rb +8 -7
- data/lib/leptris/xml/text.rb +2 -3
- data/lib/leptris/xml.rb +1 -0
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9144461ce7cc58b12f57c1a5c497336f5712568654db476342b32b9d6d35dc93
|
|
4
|
+
data.tar.gz: a6fe5f35bf5891c3794f8bf5533c115b26b9eb19545d361aeafcb3b9b1935af0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ee1362b97425fd76000f28d3232badcb7dd23d979e992cfd296ca17ee6bddeb716bd93b01830f22fd17103475cbfa4eccbcc918691625982fe4b79ca760de897
|
|
7
|
+
data.tar.gz: e1079b8c90d48d43e17d7ad3e7ebb55a8cbf87246cd61d0eed9eb2a44fc7c3cc91e5f2c034843ee1914f818d1c686b9596b433f96f2e22795fe02a005a75c21b
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,55 @@ 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.177.0] - 2026-09-16
|
|
9
|
+
|
|
10
|
+
### Changed — libleptris 1.9.174 → 1.9.177 (lockstep)
|
|
11
|
+
|
|
12
|
+
- **Node-surface parity (upstream #1094, #212 — the big one)**:
|
|
13
|
+
entity references (`&name;` stays a first-class
|
|
14
|
+
`EntityReference` node under the new
|
|
15
|
+
`ParseOptions.keep_entity_refs` flag — text splits around them,
|
|
16
|
+
character references still expand, serialization is verbatim;
|
|
17
|
+
`Document#create_entity_reference`), the XML declaration
|
|
18
|
+
(`xml_version`/`xml_encoding`/`xml_standalone` readers and
|
|
19
|
+
writers — `#version` stays the mutation counter), programmatic
|
|
20
|
+
DOCTYPE (`Document#set_doctype` with PUBLIC/SYSTEM ids,
|
|
21
|
+
serializing in document position), and document-PI parity
|
|
22
|
+
(`add_pi` returns the PI node with identity and epilog
|
|
23
|
+
anchoring; `Document#remove_child` removes PIs/comments). The
|
|
24
|
+
ext's kind dispatch knows the new node kind.
|
|
25
|
+
- **C14N: redundant namespace redeclarations omitted** (upstream
|
|
26
|
+
#1096) — spec-pinned.
|
|
27
|
+
- **Document lifecycle** (upstream #1093): pool destroy parks the
|
|
28
|
+
arenas — the fresh-doc build row's teardown share should drop;
|
|
29
|
+
clean-host battery pending.
|
|
30
|
+
- XSLT/XPath/HTML perf + conformance run (1.9.175-177).
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- `Leptris::XML::EntityReference`, the ten new FFI faces, the
|
|
35
|
+
`KEEP_ENTITY_REFS` parse flag.
|
|
36
|
+
|
|
37
|
+
## [1.9.174.10] - 2026-09-16
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
|
|
41
|
+
- **Status raises in C (TODO.perf/36)**: every C mutation face
|
|
42
|
+
raises `Leptris::XML::Error` with the exact `status_message`
|
|
43
|
+
format — the Ruby `check_status` dispatch leaves the fast
|
|
44
|
+
mutation paths (and the success/lift-marker collision it
|
|
45
|
+
briefly introduced — Qtrue on success now — was caught by the
|
|
46
|
+
bottom-up construction spec). `to_xml(encoding:)` rides the ext
|
|
47
|
+
serializer face (encoding VALUE, nil → NULL).
|
|
48
|
+
- **Document lazy Pointer (TODO.perf/37)**: `#c_ptr` materializes
|
|
49
|
+
once over `@c_address` (freed docs answer nil); the parse/create
|
|
50
|
+
faces skip the Pointer mint entirely. The ffi gem rejects
|
|
51
|
+
Integers for typedef'd pointer params — the search paths keep
|
|
52
|
+
the lazy Pointer.
|
|
53
|
+
- **Atomic bundle vendoring**: replacing a mapped `native.bundle`
|
|
54
|
+
in place gets the next loader SIGKILLed (CODESIGNING Invalid
|
|
55
|
+
Page) — the Rakefile now cp+mv's atomically.
|
|
56
|
+
|
|
8
57
|
## [1.9.174.9] - 2026-09-16
|
|
9
58
|
|
|
10
59
|
### Changed
|
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.177"
|
|
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
|
|
@@ -172,7 +172,12 @@ task :compile do
|
|
|
172
172
|
# Build-log proof of the linkage contract: libSystem only.
|
|
173
173
|
puts `otool -L #{bundle}`
|
|
174
174
|
end
|
|
175
|
-
|
|
175
|
+
# Atomic replace: overwriting a bundle another process still
|
|
176
|
+
# has mapped gets that loader SIGKILLed (CODESIGNING Invalid
|
|
177
|
+
# Page) — cp to a temp name, then mv.
|
|
178
|
+
dest = "lib/leptris/xml/#{File.basename(bundle)}"
|
|
179
|
+
cp(bundle, "#{dest}.new")
|
|
180
|
+
mv("#{dest}.new", dest)
|
|
176
181
|
puts "Vendored native layer (#{File.basename(bundle)}) into lib/leptris/xml/"
|
|
177
182
|
end
|
|
178
183
|
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# 36 — Status raises in C + encoding on the serializer faces
|
|
2
|
+
|
|
3
|
+
Status: DONE (1.9.174.10)
|
|
4
|
+
|
|
5
|
+
Every C mutation face returns INT2FIX(st) for a Ruby check_status
|
|
6
|
+
dispatch (~30ns per mutation, plus a whole class of
|
|
7
|
+
"forgot-to-check" bug the compiler cannot catch). With
|
|
8
|
+
leptris_status_string and leptris_last_error both dlsym'd, the
|
|
9
|
+
faces raise Leptris::XML::Error in C with the exact
|
|
10
|
+
status_message format ("base" or "base (detail)") — call sites
|
|
11
|
+
shrink to the bare face call.
|
|
12
|
+
|
|
13
|
+
The ext serializer faces skip the encoding-tagged shape entirely
|
|
14
|
+
(to_xml(encoding: ...) still builds the options struct through
|
|
15
|
+
FFI): the faces gain an encoding VALUE (nil -> NULL, the Ruby
|
|
16
|
+
string's bytes stay valid across the synchronous engine call).
|
|
17
|
+
|
|
18
|
+
Gates: every mutation's failure message byte-identical to
|
|
19
|
+
check_status; encoding-tagged to_xml output identical to the FFI
|
|
20
|
+
path (declaration, no anchor lifetime issues); suite both modes.
|
|
21
|
+
|
|
22
|
+
## Outcome (1.9.174.10)
|
|
23
|
+
|
|
24
|
+
All seven mutation faces raise Leptris::XML::Error in C with the
|
|
25
|
+
exact status_message format (status_string + optional
|
|
26
|
+
" (last_error)") — the Ruby check_status dispatch disappears from
|
|
27
|
+
every fast mutation. TRAP FOUND: the faces' success return became
|
|
28
|
+
Qnil, colliding with the lift-needed marker — every fast mutation
|
|
29
|
+
fell through to the slow path and re-applied (order-corrupting
|
|
30
|
+
for inserts; the v180 bottom-up spec caught it). Success now
|
|
31
|
+
returns Qtrue. The serializer faces take an encoding VALUE (nil
|
|
32
|
+
-> NULL; the Ruby string's bytes live across the synchronous
|
|
33
|
+
call), and to_xml(encoding:) rides the ext face instead of the
|
|
34
|
+
options-struct FFI path.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# 37 — Document lazy Pointer + address-based xpath contexts
|
|
2
|
+
|
|
3
|
+
Status: DONE (1.9.174.10)
|
|
4
|
+
|
|
5
|
+
Documents still mint an FFI::Pointer eagerly (the parse/create
|
|
6
|
+
faces pay the funcall + one allocation + its GC visit per
|
|
7
|
+
document). Make #c_ptr lazy like Node's (attr_reader becomes a
|
|
8
|
+
materializer over @c_address), skip the Pointer in
|
|
9
|
+
build_binding_document, and feed the search paths Integer
|
|
10
|
+
addresses (the ffi gem converts Integers for :pointer params) so
|
|
11
|
+
per-xpath document Pointers never materialize at all.
|
|
12
|
+
|
|
13
|
+
Gates: c_ptr stability and public API unchanged; every FFI
|
|
14
|
+
document consumer still works (options paths, c14n, fragment
|
|
15
|
+
fallback); suite both modes.
|
|
16
|
+
|
|
17
|
+
## Outcome (1.9.174.10)
|
|
18
|
+
|
|
19
|
+
Document#c_ptr materializes lazily over @c_address (freed docs
|
|
20
|
+
answer nil — never re-materialize); build_binding_document skips
|
|
21
|
+
the Pointer funcall (one alloc + ~200ns per document). TRAP
|
|
22
|
+
FOUND: this ffi version does NOT convert Integers for typedef'd
|
|
23
|
+
pointer params — passing c_address into the compiled-eval calls
|
|
24
|
+
failed with ":pointer argument is not a valid pointer"; the
|
|
25
|
+
search paths keep passing the (now lazy, once-per-document)
|
|
26
|
+
Pointer. Also: replacing a mapped native.bundle in place gets
|
|
27
|
+
the next loader SIGKILLed (CODESIGNING Invalid Page) — the
|
|
28
|
+
vendoring cp is now an atomic cp+mv.
|
data/ext/leptris/native/native.c
CHANGED
|
@@ -97,6 +97,7 @@ typedef void *(*elem_copy_fn)(void *, void *);
|
|
|
97
97
|
typedef void *(*parse_str_fn)(const char *, size_t, void *);
|
|
98
98
|
typedef void *(*parse_frag_fn)(const char *, size_t, void *, int *);
|
|
99
99
|
typedef const char *(*last_err_fn)(void);
|
|
100
|
+
typedef const char *(*status_str_fn)(int);
|
|
100
101
|
static set_str_fn f_elem_set_name, f_elem_set_text, f_text_set_content;
|
|
101
102
|
static node_unlink_fn f_node_unlink;
|
|
102
103
|
static traverse_fn f_node_traverse;
|
|
@@ -107,6 +108,7 @@ static elem_copy_fn f_elem_copy;
|
|
|
107
108
|
static parse_str_fn f_parse_str;
|
|
108
109
|
static parse_frag_fn f_parse_frag;
|
|
109
110
|
static last_err_fn f_last_err;
|
|
111
|
+
static status_str_fn f_status_str;
|
|
110
112
|
static set_root_fn f_set_root;
|
|
111
113
|
static doc_free_fn f_doc_free;
|
|
112
114
|
|
|
@@ -125,8 +127,10 @@ static void resolve_binding_classes(void);
|
|
|
125
127
|
static VALUE binding_cache_of(VALUE document);
|
|
126
128
|
static VALUE binding_klass_for(int kind);
|
|
127
129
|
static VALUE c_iteration_scope;
|
|
130
|
+
static VALUE c_leptris_error;
|
|
128
131
|
static VALUE c_b_document, c_b_freed, c_ffi_pointer;
|
|
129
132
|
static ID id_ptr_new, id_freed_new, id_alive;
|
|
133
|
+
static void check_status_c(int st);
|
|
130
134
|
|
|
131
135
|
#define NT_ELEMENT 0
|
|
132
136
|
|
|
@@ -230,6 +234,7 @@ static void resolve_symbols(const char *lib_path)
|
|
|
230
234
|
f_parse_str = (parse_str_fn)lib_sym(h, "leptris_parse_string");
|
|
231
235
|
f_parse_frag = (parse_frag_fn)lib_sym(h, "leptris_parse_fragment");
|
|
232
236
|
f_last_err = (last_err_fn)lib_sym(h, "leptris_last_error");
|
|
237
|
+
f_status_str = (status_str_fn)lib_sym(h, "leptris_status_string");
|
|
233
238
|
f_set_root = (set_root_fn)lib_sym(h, "leptris_document_set_root");
|
|
234
239
|
f_doc_free = (doc_free_fn)lib_sym(h, "leptris_document_free");
|
|
235
240
|
if (!f_elem_name || !f_text_content || !f_attr ||
|
|
@@ -243,7 +248,7 @@ static void resolve_symbols(const char *lib_path)
|
|
|
243
248
|
!f_text_set_content || !f_node_unlink ||
|
|
244
249
|
!f_node_traverse || !f_node_visit || !f_free_str ||
|
|
245
250
|
!f_node_xpath || !f_elem_copy || !f_parse_str ||
|
|
246
|
-
!f_parse_frag || !f_last_err ||
|
|
251
|
+
!f_parse_frag || !f_last_err || !f_status_str ||
|
|
247
252
|
!f_doc_free ||
|
|
248
253
|
!f_elem_prefix || !f_xp_count || !f_xp_nodes_ex ||
|
|
249
254
|
!f_xp_node_kind || !f_xp_node_name || !f_xp_node_value ||
|
|
@@ -640,7 +645,8 @@ static VALUE nf_append_binding_child(VALUE self, VALUE document,
|
|
|
640
645
|
rb_ivar_set(document, id_iv_version,
|
|
641
646
|
LONG2FIX(FIX2LONG(rb_ivar_get(document, id_iv_version)) + 1));
|
|
642
647
|
st = f_append_child(parent, child);
|
|
643
|
-
|
|
648
|
+
check_status_c(st);
|
|
649
|
+
return Qtrue;
|
|
644
650
|
}
|
|
645
651
|
|
|
646
652
|
/* C-bound set_attribute (TODO.perf/11, #204 gap row 0.25x): the
|
|
@@ -669,7 +675,8 @@ static VALUE nf_set_binding_attribute(VALUE self, VALUE document,
|
|
|
669
675
|
* embedded-NUL value raises rather than truncates. */
|
|
670
676
|
st = f_set_attr(node, RSTRING_PTR(StringValue(name)),
|
|
671
677
|
StringValueCStr(value));
|
|
672
|
-
|
|
678
|
+
check_status_c(st);
|
|
679
|
+
return Qtrue;
|
|
673
680
|
}
|
|
674
681
|
|
|
675
682
|
/* C-bound value mutations (TODO.perf/23): the same gates + bump
|
|
@@ -692,7 +699,8 @@ static VALUE nf_set_binding_name(VALUE self, VALUE document,
|
|
|
692
699
|
rb_ivar_set(document, id_iv_version,
|
|
693
700
|
LONG2FIX(FIX2LONG(rb_ivar_get(document, id_iv_version)) + 1));
|
|
694
701
|
st = f_elem_set_name(node, StringValueCStr(name));
|
|
695
|
-
|
|
702
|
+
check_status_c(st);
|
|
703
|
+
return Qtrue;
|
|
696
704
|
}
|
|
697
705
|
|
|
698
706
|
/* Element text or text-node content — kind-dispatched in C. */
|
|
@@ -716,7 +724,8 @@ static VALUE nf_set_binding_text(VALUE self, VALUE document,
|
|
|
716
724
|
st = f_node_type(node) == NT_ELEMENT
|
|
717
725
|
? f_elem_set_text(node, StringValueCStr(content))
|
|
718
726
|
: f_text_set_content(node, StringValueCStr(content));
|
|
719
|
-
|
|
727
|
+
check_status_c(st);
|
|
728
|
+
return Qtrue;
|
|
720
729
|
}
|
|
721
730
|
|
|
722
731
|
static VALUE nf_unlink_binding_node(VALUE self, VALUE document,
|
|
@@ -737,7 +746,8 @@ static VALUE nf_unlink_binding_node(VALUE self, VALUE document,
|
|
|
737
746
|
rb_ivar_set(document, id_iv_version,
|
|
738
747
|
LONG2FIX(FIX2LONG(rb_ivar_get(document, id_iv_version)) + 1));
|
|
739
748
|
st = f_node_unlink(node);
|
|
740
|
-
|
|
749
|
+
check_status_c(st);
|
|
750
|
+
return Qtrue;
|
|
741
751
|
}
|
|
742
752
|
|
|
743
753
|
/* C-bound root= (TODO.perf/33): gates + bump + engine set_root
|
|
@@ -761,7 +771,8 @@ static VALUE nf_set_binding_root(VALUE self, VALUE document,
|
|
|
761
771
|
rb_ivar_set(document, id_iv_version,
|
|
762
772
|
LONG2FIX(FIX2LONG(rb_ivar_get(document, id_iv_version)) + 1));
|
|
763
773
|
st = f_set_root(doc_ptr_of(document), node);
|
|
764
|
-
|
|
774
|
+
check_status_c(st);
|
|
775
|
+
return Qtrue;
|
|
765
776
|
}
|
|
766
777
|
|
|
767
778
|
/* ---- C-yield traversal (TODO.perf/27) ----------------------------
|
|
@@ -1074,7 +1085,8 @@ static VALUE nf_insert_binding_child(VALUE self, VALUE document,
|
|
|
1074
1085
|
rb_raise(rb_eArgError, "invalid insert mode %d", m);
|
|
1075
1086
|
return Qnil;
|
|
1076
1087
|
}
|
|
1077
|
-
|
|
1088
|
+
check_status_c(st);
|
|
1089
|
+
return Qtrue;
|
|
1078
1090
|
}
|
|
1079
1091
|
|
|
1080
1092
|
/* Builder factories (#149): create in C, wrap as NativeNode — no
|
|
@@ -1200,7 +1212,8 @@ static VALUE nn_address(VALUE self)
|
|
|
1200
1212
|
* Init; the per-node Ruby frames (wrap/construct/memo/alive)
|
|
1201
1213
|
* disappear. */
|
|
1202
1214
|
static VALUE c_b_element = Qundef, c_b_text, c_b_comment, c_b_cdata,
|
|
1203
|
-
c_b_pi, c_b_node, c_b_result_text,
|
|
1215
|
+
c_b_pi, c_b_entity_ref, c_b_node, c_b_result_text,
|
|
1216
|
+
c_b_result_attr,
|
|
1204
1217
|
c_b_attr, c_ffi_pointer, c_b_document, c_b_freed;
|
|
1205
1218
|
static VALUE c_iteration_scope;
|
|
1206
1219
|
static ID id_ptr_new;
|
|
@@ -1217,6 +1230,7 @@ static void resolve_binding_classes(void)
|
|
|
1217
1230
|
c_b_comment = rb_path2class("Leptris::XML::Comment");
|
|
1218
1231
|
c_b_cdata = rb_path2class("Leptris::XML::CDATA");
|
|
1219
1232
|
c_b_pi = rb_path2class("Leptris::XML::ProcessingInstruction");
|
|
1233
|
+
c_b_entity_ref = rb_path2class("Leptris::XML::EntityReference");
|
|
1220
1234
|
c_b_node = rb_path2class("Leptris::XML::Node");
|
|
1221
1235
|
c_b_result_text = rb_path2class("Leptris::XML::ResultText");
|
|
1222
1236
|
c_b_result_attr = rb_path2class("Leptris::XML::ResultAttr");
|
|
@@ -1225,12 +1239,14 @@ static void resolve_binding_classes(void)
|
|
|
1225
1239
|
c_b_document = rb_path2class("Leptris::XML::Document");
|
|
1226
1240
|
c_b_freed = rb_path2class("Leptris::XML::Document::Freed");
|
|
1227
1241
|
c_iteration_scope = rb_path2class("Leptris::XML::IterationScope");
|
|
1242
|
+
c_leptris_error = rb_path2class("Leptris::XML::Error");
|
|
1228
1243
|
id_ptr_new = rb_intern("new");
|
|
1229
1244
|
rb_gc_register_mark_object(c_b_element);
|
|
1230
1245
|
rb_gc_register_mark_object(c_b_text);
|
|
1231
1246
|
rb_gc_register_mark_object(c_b_comment);
|
|
1232
1247
|
rb_gc_register_mark_object(c_b_cdata);
|
|
1233
1248
|
rb_gc_register_mark_object(c_b_pi);
|
|
1249
|
+
rb_gc_register_mark_object(c_b_entity_ref);
|
|
1234
1250
|
rb_gc_register_mark_object(c_b_node);
|
|
1235
1251
|
rb_gc_register_mark_object(c_b_result_text);
|
|
1236
1252
|
rb_gc_register_mark_object(c_b_result_attr);
|
|
@@ -1239,6 +1255,7 @@ static void resolve_binding_classes(void)
|
|
|
1239
1255
|
rb_gc_register_mark_object(c_b_document);
|
|
1240
1256
|
rb_gc_register_mark_object(c_b_freed);
|
|
1241
1257
|
rb_gc_register_mark_object(c_iteration_scope);
|
|
1258
|
+
rb_gc_register_mark_object(c_leptris_error);
|
|
1242
1259
|
}
|
|
1243
1260
|
|
|
1244
1261
|
static VALUE binding_klass_for(int kind)
|
|
@@ -1249,6 +1266,7 @@ static VALUE binding_klass_for(int kind)
|
|
|
1249
1266
|
case 2: return c_b_comment;
|
|
1250
1267
|
case 3: return c_b_cdata;
|
|
1251
1268
|
case 4: return c_b_pi;
|
|
1269
|
+
case 10: return c_b_entity_ref; /* #212 / upstream #1094 */
|
|
1252
1270
|
default: return c_b_node;
|
|
1253
1271
|
}
|
|
1254
1272
|
}
|
|
@@ -1635,7 +1653,8 @@ static char *ser_buf;
|
|
|
1635
1653
|
static size_t ser_cap;
|
|
1636
1654
|
|
|
1637
1655
|
static VALUE fast_serialize(serialize_into_fn fn, void *node,
|
|
1638
|
-
int indent, int xml_declaration
|
|
1656
|
+
int indent, int xml_declaration,
|
|
1657
|
+
VALUE encoding)
|
|
1639
1658
|
{
|
|
1640
1659
|
struct serialize_opts opts;
|
|
1641
1660
|
char stack_buf[4096];
|
|
@@ -1646,7 +1665,11 @@ static VALUE fast_serialize(serialize_into_fn fn, void *node,
|
|
|
1646
1665
|
|
|
1647
1666
|
opts.indent = indent;
|
|
1648
1667
|
opts.xml_declaration = xml_declaration;
|
|
1649
|
-
|
|
1668
|
+
/* The Ruby string's bytes stay valid across the synchronous
|
|
1669
|
+
* engine call (TODO.perf/36). */
|
|
1670
|
+
opts.encoding = NIL_P(encoding)
|
|
1671
|
+
? NULL
|
|
1672
|
+
: StringValueCStr(encoding);
|
|
1650
1673
|
/* Probe the largest buffer we own: a big scratch from a prior
|
|
1651
1674
|
* call usually fits (single serialization); cold calls start
|
|
1652
1675
|
* on the stack buffer. */
|
|
@@ -1672,21 +1695,25 @@ static VALUE fast_serialize(serialize_into_fn fn, void *node,
|
|
|
1672
1695
|
}
|
|
1673
1696
|
|
|
1674
1697
|
static VALUE nf_fast_document_xml(VALUE self, VALUE addr,
|
|
1675
|
-
VALUE indent, VALUE decl
|
|
1698
|
+
VALUE indent, VALUE decl,
|
|
1699
|
+
VALUE encoding)
|
|
1676
1700
|
{
|
|
1677
1701
|
(void)self;
|
|
1678
1702
|
return fast_serialize(f_doc_serialize,
|
|
1679
1703
|
(void *)(uintptr_t)NUM2ULL(addr),
|
|
1680
|
-
NUM2INT(indent), RTEST(decl) ? 1 : 0
|
|
1704
|
+
NUM2INT(indent), RTEST(decl) ? 1 : 0,
|
|
1705
|
+
encoding);
|
|
1681
1706
|
}
|
|
1682
1707
|
|
|
1683
1708
|
static VALUE nf_fast_element_xml(VALUE self, VALUE addr,
|
|
1684
|
-
VALUE indent, VALUE decl
|
|
1709
|
+
VALUE indent, VALUE decl,
|
|
1710
|
+
VALUE encoding)
|
|
1685
1711
|
{
|
|
1686
1712
|
(void)self;
|
|
1687
1713
|
return fast_serialize(f_elem_serialize,
|
|
1688
1714
|
(void *)(uintptr_t)NUM2ULL(addr),
|
|
1689
|
-
NUM2INT(indent), RTEST(decl) ? 1 : 0
|
|
1715
|
+
NUM2INT(indent), RTEST(decl) ? 1 : 0,
|
|
1716
|
+
encoding);
|
|
1690
1717
|
}
|
|
1691
1718
|
|
|
1692
1719
|
/* ---- inner_html in one C pass (TODO.perf/18) --------------------
|
|
@@ -1861,8 +1888,6 @@ static VALUE nf_copy_binding_element(VALUE self, VALUE document,
|
|
|
1861
1888
|
return Qnil;
|
|
1862
1889
|
doc_addr = ULL2NUM((uint64_t)(uintptr_t)doc);
|
|
1863
1890
|
new_doc = rb_obj_alloc(c_b_document);
|
|
1864
|
-
rb_iv_set(new_doc, "@c_ptr",
|
|
1865
|
-
rb_funcall(c_ffi_pointer, id_ptr_new, 1, doc_addr));
|
|
1866
1891
|
rb_iv_set(new_doc, "@c_address", doc_addr);
|
|
1867
1892
|
freed = rb_funcall(c_b_freed, id_freed_new, 1, ID2SYM(id_alive));
|
|
1868
1893
|
rb_iv_set(new_doc, "@freed", freed);
|
|
@@ -1909,6 +1934,23 @@ static VALUE build_binding_document(void *doc)
|
|
|
1909
1934
|
return new_doc;
|
|
1910
1935
|
}
|
|
1911
1936
|
|
|
1937
|
+
/* check_status in C (TODO.perf/36): the exact status_message
|
|
1938
|
+
* format — "base" or "base (detail)" — so the mutation faces
|
|
1939
|
+
* raise instead of returning codes for a Ruby dispatch. */
|
|
1940
|
+
static void check_status_c(int st)
|
|
1941
|
+
{
|
|
1942
|
+
const char *base, *detail;
|
|
1943
|
+
|
|
1944
|
+
if (st == 0)
|
|
1945
|
+
return;
|
|
1946
|
+
base = f_status_str(st);
|
|
1947
|
+
detail = f_last_err();
|
|
1948
|
+
if (detail && *detail)
|
|
1949
|
+
rb_raise(c_leptris_error, "%s (%s)",
|
|
1950
|
+
base ? base : "?", detail);
|
|
1951
|
+
rb_raise(c_leptris_error, "%s", base ? base : "?");
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1912
1954
|
/* Document.parse default path in one dispatch (TODO.perf/35):
|
|
1913
1955
|
* leptris_parse_string + wrapper + handle. Qnil = parse failure
|
|
1914
1956
|
* (the caller raises with the same message shape). */
|
|
@@ -1974,7 +2016,7 @@ static VALUE nf_append_markup(VALUE self, VALUE document,
|
|
|
1974
2016
|
next_child = f_next_sibling(child);
|
|
1975
2017
|
status = f_append_child(parent, child);
|
|
1976
2018
|
if (status != 0)
|
|
1977
|
-
|
|
2019
|
+
check_status_c(status);
|
|
1978
2020
|
count++;
|
|
1979
2021
|
}
|
|
1980
2022
|
return INT2FIX(count);
|
|
@@ -2231,7 +2273,7 @@ void Init_native(void)
|
|
|
2231
2273
|
nf_bulk_element_children, 2);
|
|
2232
2274
|
rb_define_module_function(m_native, "bulk_xpath", nf_bulk_xpath, 2);
|
|
2233
2275
|
rb_define_module_function(m_native, "fast_document_xml",
|
|
2234
|
-
nf_fast_document_xml,
|
|
2276
|
+
nf_fast_document_xml, 4);
|
|
2235
2277
|
rb_define_module_function(m_native, "fast_element_xml",
|
|
2236
|
-
nf_fast_element_xml,
|
|
2278
|
+
nf_fast_element_xml, 4);
|
|
2237
2279
|
}
|
|
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
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
require "ffi"
|
|
4
4
|
|
|
5
5
|
class Leptris::XML::Document
|
|
6
|
-
attr_reader :c_ptr
|
|
7
6
|
|
|
8
7
|
# @api private
|
|
9
8
|
# Internal flag container shared between the Document instance and its
|
|
@@ -57,6 +56,19 @@ class Leptris::XML::Document
|
|
|
57
56
|
# document for trees that are freed before any wrap.
|
|
58
57
|
end
|
|
59
58
|
|
|
59
|
+
# TODO.perf/37: the Integer address is canonical; the Pointer
|
|
60
|
+
# materializes only when read (parse/create faces skip it).
|
|
61
|
+
# A freed document answers nil — never re-materialize a stale
|
|
62
|
+
# or absent address.
|
|
63
|
+
def c_ptr
|
|
64
|
+
@c_ptr ||= (@c_address && ::FFI::Pointer.new(@c_address))
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# The Integer twin of #c_ptr.
|
|
68
|
+
def c_address
|
|
69
|
+
@c_address
|
|
70
|
+
end
|
|
71
|
+
|
|
60
72
|
def wrapper_cache
|
|
61
73
|
@wrapper_cache ||= {}
|
|
62
74
|
end
|
|
@@ -308,8 +320,7 @@ class Leptris::XML::Document
|
|
|
308
320
|
# dispatch (the FFI call plus the c_ptr materialization fed
|
|
309
321
|
# ~5% of a fresh-doc build).
|
|
310
322
|
if defined?(Leptris::XML::NATIVE_FAST)
|
|
311
|
-
Leptris::XML::
|
|
312
|
-
Leptris::XML::Native.set_binding_root(self, element.c_address))
|
|
323
|
+
Leptris::XML::Native.set_binding_root(self, element.c_address)
|
|
313
324
|
else
|
|
314
325
|
Leptris::XML::FFI.check_status(
|
|
315
326
|
Leptris::XML::FFI.leptris_document_set_root(c_ptr, element.c_ptr))
|
|
@@ -494,15 +505,82 @@ class Leptris::XML::Document
|
|
|
494
505
|
Leptris::XML::Node.wrap(ptr, self)
|
|
495
506
|
end
|
|
496
507
|
|
|
497
|
-
# Append a document-level processing instruction. Returns
|
|
508
|
+
# Append a document-level processing instruction. Returns the PI
|
|
509
|
+
# node (epilog anchor — after the root; identity via
|
|
510
|
+
# leptris_document_append_pi, libleptris 1.9.176 / #212).
|
|
498
511
|
def add_pi(target, data = "")
|
|
499
|
-
witness = Leptris::XML::FFI.
|
|
512
|
+
witness = Leptris::XML::FFI.leptris_document_append_pi(
|
|
500
513
|
c_ptr, target.to_s, data.to_s)
|
|
501
|
-
raise Leptris::XML::Error, "
|
|
514
|
+
raise Leptris::XML::Error, "leptris_document_append_pi failed" if witness.null?
|
|
515
|
+
@version += 1
|
|
516
|
+
Leptris::XML::Node.wrap(witness, self)
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
# Remove a document-level PI or comment from the document
|
|
520
|
+
# children chain (libleptris 1.9.176 / #212). The node stays
|
|
521
|
+
# document-owned. Raises when the node is not in the chain.
|
|
522
|
+
def remove_child(node)
|
|
523
|
+
Leptris::XML::FFI.check_status(
|
|
524
|
+
Leptris::XML::FFI.leptris_document_remove_child(c_ptr, node.c_ptr))
|
|
502
525
|
@version += 1
|
|
503
526
|
self
|
|
504
527
|
end
|
|
505
528
|
|
|
529
|
+
# Create an &name; entity reference node (libleptris 1.9.176 /
|
|
530
|
+
# #212): serializes back verbatim once attached.
|
|
531
|
+
def create_entity_reference(name)
|
|
532
|
+
ptr = Leptris::XML::FFI.leptris_entity_ref_node_create(
|
|
533
|
+
c_ptr, name.to_s)
|
|
534
|
+
raise Leptris::XML::Error, "leptris_entity_ref_node_create failed" if ptr.null?
|
|
535
|
+
Leptris::XML::Node.wrap_fresh(ptr, self, Leptris::XML::FFI::NODE_ENTITY_REF)
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
# The XML declaration (libleptris 1.9.176 / #212). `#version`
|
|
539
|
+
# stays the mutation counter — the declaration reads are
|
|
540
|
+
# xml_-prefixed.
|
|
541
|
+
def xml_version
|
|
542
|
+
Leptris::XML::FFI.leptris_document_version(c_ptr)
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
def xml_standalone
|
|
546
|
+
v = Leptris::XML::FFI.leptris_document_standalone(c_ptr)
|
|
547
|
+
v < 0 ? nil : (v == 1)
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
def xml_encoding
|
|
551
|
+
Leptris::XML::FFI.leptris_document_encoding(c_ptr)
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
def xml_version=(value)
|
|
555
|
+
Leptris::XML::FFI.check_status(
|
|
556
|
+
Leptris::XML::FFI.leptris_document_set_version(c_ptr, value.to_s))
|
|
557
|
+
value
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
def xml_encoding=(value)
|
|
561
|
+
Leptris::XML::FFI.check_status(
|
|
562
|
+
Leptris::XML::FFI.leptris_document_set_encoding(c_ptr, value.to_s))
|
|
563
|
+
value
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
def xml_standalone=(value)
|
|
567
|
+
Leptris::XML::FFI.check_status(
|
|
568
|
+
Leptris::XML::FFI.leptris_document_set_standalone(c_ptr,
|
|
569
|
+
value.nil? ? -1 : (value ? 1 : 0)))
|
|
570
|
+
value
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
# Programmatic DOCTYPE (libleptris 1.9.176 / #212): create and
|
|
574
|
+
# set the document's DOCTYPE (root name + optional PUBLIC/SYSTEM
|
|
575
|
+
# ids); serializes in document position. Returns the DocType.
|
|
576
|
+
def set_doctype(name, public_id: nil, system_id: nil)
|
|
577
|
+
dt = Leptris::XML::FFI.leptris_document_set_doctype(
|
|
578
|
+
c_ptr, name.to_s, public_id, system_id)
|
|
579
|
+
raise Leptris::XML::Error, "leptris_document_set_doctype failed" if dt.null?
|
|
580
|
+
@version += 1
|
|
581
|
+
Leptris::XML::DocType.new(dt, self)
|
|
582
|
+
end
|
|
583
|
+
|
|
506
584
|
# Append a document-level comment (epilog position, after the
|
|
507
585
|
# root element) — the add_pi twin (libleptris 1.9.160, #1032;
|
|
508
586
|
# parsed document-level comments round-tripped since 1.9.3/#578,
|
data/lib/leptris/xml/element.rb
CHANGED
|
@@ -25,9 +25,8 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
25
25
|
|
|
26
26
|
def name=(new_name)
|
|
27
27
|
if @native_fast
|
|
28
|
-
Leptris::XML::
|
|
29
|
-
|
|
30
|
-
@document, @c_address, new_name))
|
|
28
|
+
Leptris::XML::Native.set_binding_name(
|
|
29
|
+
@document, @c_address, new_name)
|
|
31
30
|
return @name = new_name
|
|
32
31
|
end
|
|
33
32
|
ensure_writable!
|
|
@@ -54,9 +53,8 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
54
53
|
|
|
55
54
|
def content=(new_content)
|
|
56
55
|
if @native_fast
|
|
57
|
-
Leptris::XML::
|
|
58
|
-
|
|
59
|
-
@document, @c_address, new_content.to_s))
|
|
56
|
+
Leptris::XML::Native.set_binding_text(
|
|
57
|
+
@document, @c_address, new_content.to_s)
|
|
60
58
|
@content = new_content.to_s
|
|
61
59
|
@content_version = @document.version if @document
|
|
62
60
|
return new_content
|
|
@@ -138,9 +136,8 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
138
136
|
# set in one dispatch; the bump drops the version-stamped
|
|
139
137
|
# attribute memos on both surfaces.
|
|
140
138
|
if native_fast_children?
|
|
141
|
-
Leptris::XML::
|
|
142
|
-
|
|
143
|
-
@document, @c_address, key.to_s, value.to_s))
|
|
139
|
+
Leptris::XML::Native.set_binding_attribute(
|
|
140
|
+
@document, @c_address, key.to_s, value.to_s)
|
|
144
141
|
seed_attribute_memo(key.to_s, value.to_s)
|
|
145
142
|
return value
|
|
146
143
|
end
|
|
@@ -374,10 +371,8 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
374
371
|
# bump + engine insert in one dispatch; Qnil = the child needs
|
|
375
372
|
# the namespace lift — the full path below handles it.
|
|
376
373
|
if native_fast_children?
|
|
377
|
-
|
|
378
|
-
@document, @c_address, node.c_ptr.address, 1)
|
|
379
|
-
unless st.nil?
|
|
380
|
-
Leptris::XML::FFI.check_status(st)
|
|
374
|
+
unless Leptris::XML::Native.insert_binding_child(
|
|
375
|
+
@document, @c_address, node.c_ptr.address, 1).nil?
|
|
381
376
|
Leptris::XML::Node.invalidate_cross_document!(node, @document)
|
|
382
377
|
return node
|
|
383
378
|
end
|
|
@@ -397,10 +392,8 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
397
392
|
# bump + engine insert in one dispatch; Qnil = the child needs
|
|
398
393
|
# the namespace lift — the full path below handles it.
|
|
399
394
|
if native_fast_children?
|
|
400
|
-
|
|
401
|
-
@document, @c_address, node.c_ptr.address, 2)
|
|
402
|
-
unless st.nil?
|
|
403
|
-
Leptris::XML::FFI.check_status(st)
|
|
395
|
+
unless Leptris::XML::Native.insert_binding_child(
|
|
396
|
+
@document, @c_address, node.c_ptr.address, 2).nil?
|
|
404
397
|
Leptris::XML::Node.invalidate_cross_document!(node, @document)
|
|
405
398
|
return node
|
|
406
399
|
end
|
|
@@ -420,10 +413,8 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
420
413
|
# bump + engine insert in one dispatch; Qnil = the child needs
|
|
421
414
|
# the namespace lift — the full path below handles it.
|
|
422
415
|
if native_fast_children?
|
|
423
|
-
|
|
424
|
-
@document, @c_address, node.c_ptr.address, 3)
|
|
425
|
-
unless st.nil?
|
|
426
|
-
Leptris::XML::FFI.check_status(st)
|
|
416
|
+
unless Leptris::XML::Native.insert_binding_child(
|
|
417
|
+
@document, @c_address, node.c_ptr.address, 3).nil?
|
|
427
418
|
Leptris::XML::Node.invalidate_cross_document!(node, @document)
|
|
428
419
|
return node
|
|
429
420
|
end
|
|
@@ -539,10 +530,10 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
539
530
|
# means the child needs the namespace lift — fall through
|
|
540
531
|
# to the full path.
|
|
541
532
|
if native_fast_children?
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
unless
|
|
545
|
-
|
|
533
|
+
# The face raises on failure (TODO.perf/36); Qnil = the
|
|
534
|
+
# child needs the lift — the full path handles it.
|
|
535
|
+
unless Leptris::XML::Native.append_binding_child(
|
|
536
|
+
@document, @c_address, node_or_markup.c_ptr.address).nil?
|
|
546
537
|
Leptris::XML::Node.invalidate_cross_document!(node_or_markup, @document)
|
|
547
538
|
return node_or_markup
|
|
548
539
|
end
|
|
@@ -562,17 +553,15 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
562
553
|
if native_fast_children?
|
|
563
554
|
r = Leptris::XML::Native.append_markup(
|
|
564
555
|
@document, @c_address, node_or_markup)
|
|
565
|
-
if r
|
|
556
|
+
if r != -1
|
|
557
|
+
# Appends raise in C on status failures (TODO.perf/36);
|
|
558
|
+
# -1 alone means the parse failed.
|
|
566
559
|
return Leptris::XML::NodeSet.new(@document, []) if r.zero?
|
|
567
560
|
return Leptris::XML::NodeSet.new(@document, last_added(r))
|
|
568
561
|
end
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
Leptris::XML::DocumentFragment.parse(node_or_markup, @document)
|
|
573
|
-
else
|
|
574
|
-
Leptris::XML::FFI.check_status(-r - 1000)
|
|
575
|
-
end
|
|
562
|
+
# parse failure: reproduce the exact error via the legacy
|
|
563
|
+
# path (it re-fails fast)
|
|
564
|
+
Leptris::XML::DocumentFragment.parse(node_or_markup, @document)
|
|
576
565
|
end
|
|
577
566
|
frag = Leptris::XML::DocumentFragment.parse(node_or_markup, @document)
|
|
578
567
|
added = []
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# An unexpanded &name; entity reference — a first-class node when
|
|
4
|
+
# parsing with ParseOptions.keep_entity_refs (libleptris 1.9.176,
|
|
5
|
+
# upstream #1094 / leptris-ruby#212), or created via
|
|
6
|
+
# Document#create_entity_reference. Serializes back verbatim as
|
|
7
|
+
# &name;; text runs around it split into their own Text siblings.
|
|
8
|
+
class Leptris::XML::EntityReference < Leptris::XML::Node
|
|
9
|
+
def name
|
|
10
|
+
Leptris::XML::FFI.leptris_entity_ref_node_name(c_ptr)
|
|
11
|
+
end
|
|
12
|
+
alias_method :node_name, :name
|
|
13
|
+
end
|
data/lib/leptris/xml/ffi.rb
CHANGED
|
@@ -337,6 +337,29 @@ attach_function :leptris_parse_string,
|
|
|
337
337
|
# node_first_child without the intermediate handle).
|
|
338
338
|
attach_function :leptris_document_first_child,
|
|
339
339
|
[:leptris_document], :leptris_node_ref
|
|
340
|
+
# Node-surface parity (libleptris 1.9.176, upstream #1094,
|
|
341
|
+
# leptris-ruby#212): entity references, the XML declaration,
|
|
342
|
+
# programmatic DOCTYPE, and document-PI identity/removal.
|
|
343
|
+
attach_function :leptris_entity_ref_node_create,
|
|
344
|
+
[:leptris_document, :string], :leptris_node_ref
|
|
345
|
+
attach_function :leptris_entity_ref_node_name,
|
|
346
|
+
[:leptris_node_ref], :string
|
|
347
|
+
attach_function :leptris_document_version,
|
|
348
|
+
[:leptris_document], :string
|
|
349
|
+
attach_function :leptris_document_standalone,
|
|
350
|
+
[:leptris_document], :int
|
|
351
|
+
attach_function :leptris_document_set_version,
|
|
352
|
+
[:leptris_document, :string], :leptris_status
|
|
353
|
+
attach_function :leptris_document_set_encoding,
|
|
354
|
+
[:leptris_document, :string], :leptris_status
|
|
355
|
+
attach_function :leptris_document_set_standalone,
|
|
356
|
+
[:leptris_document, :int], :leptris_status
|
|
357
|
+
attach_function :leptris_document_set_doctype,
|
|
358
|
+
[:leptris_document, :string, :string, :string], :leptris_doctype
|
|
359
|
+
attach_function :leptris_document_append_pi,
|
|
360
|
+
[:leptris_document, :string, :string], :leptris_node_ref
|
|
361
|
+
attach_function :leptris_document_remove_child,
|
|
362
|
+
[:leptris_document, :leptris_node_ref], :leptris_status
|
|
340
363
|
# Wrap-free subtree visitation (1.9.20, upstream #645a):
|
|
341
364
|
# enter/leave pairs for elements, depth from the walk's root,
|
|
342
365
|
# one C call — no NodeSet/Array churn per level. The document
|
|
@@ -1070,11 +1093,15 @@ attach_function :leptris_parse_string,
|
|
|
1070
1093
|
NODE_COMMENT = 2
|
|
1071
1094
|
NODE_CDATA = 3
|
|
1072
1095
|
NODE_PI = 4
|
|
1096
|
+
NODE_ENTITY_REF = 10
|
|
1073
1097
|
# Internal XPath-synthetic text node (leptris/types.h): the
|
|
1074
1098
|
# carrier for sequence/map/array items in result nodesets —
|
|
1075
1099
|
# readable only through the result handle, so the binding
|
|
1076
1100
|
# captures the value at materialization (ResultText).
|
|
1077
1101
|
NODE_SYNTHETIC_TEXT = 8
|
|
1102
|
+
# #1094: &name; references stay unexpanded first-class nodes
|
|
1103
|
+
# when this flag parses (character references still expand).
|
|
1104
|
+
LEPTRIS_PARSE_KEEP_ENTITY_REFS = 4
|
|
1078
1105
|
NODE_DOCTYPE = 5
|
|
1079
1106
|
NODE_ATTRIBUTE = 6
|
|
1080
1107
|
|
data/lib/leptris/xml/node.rb
CHANGED
|
@@ -132,6 +132,8 @@ class Leptris::XML::Node
|
|
|
132
132
|
Leptris::XML::CDATA.new(c_ptr, document, parent: parent, node_type: node_type)
|
|
133
133
|
when Leptris::XML::FFI::NODE_PI
|
|
134
134
|
Leptris::XML::ProcessingInstruction.new(c_ptr, document, parent: parent, node_type: node_type)
|
|
135
|
+
when Leptris::XML::FFI::NODE_ENTITY_REF
|
|
136
|
+
Leptris::XML::EntityReference.new(c_ptr, document, parent: parent, node_type: node_type)
|
|
135
137
|
else
|
|
136
138
|
new(c_ptr, document, parent: parent, node_type: node_type)
|
|
137
139
|
end
|
|
@@ -486,8 +488,7 @@ class Leptris::XML::Node
|
|
|
486
488
|
|
|
487
489
|
def unlink
|
|
488
490
|
if @native_fast
|
|
489
|
-
Leptris::XML::
|
|
490
|
-
Leptris::XML::Native.unlink_binding_node(@document, @c_address))
|
|
491
|
+
Leptris::XML::Native.unlink_binding_node(@document, @c_address)
|
|
491
492
|
@parent = nil
|
|
492
493
|
return self
|
|
493
494
|
end
|
|
@@ -17,6 +17,13 @@ class Leptris::XML::ParseOptions
|
|
|
17
17
|
# defaulted attributes.
|
|
18
18
|
DTDATTR = Leptris::XML::FFI::LEPTRIS_PARSE_DTDATTR
|
|
19
19
|
|
|
20
|
+
# Keep &name; entity references unexpanded as first-class
|
|
21
|
+
# EntityReference nodes (libleptris 1.9.176, upstream #1094 /
|
|
22
|
+
# leptris-ruby#212): text runs split around them, character
|
|
23
|
+
# references still expand, and the references serialize back
|
|
24
|
+
# verbatim.
|
|
25
|
+
KEEP_ENTITY_REFS = Leptris::XML::FFI::LEPTRIS_PARSE_KEEP_ENTITY_REFS
|
|
26
|
+
|
|
20
27
|
attr_reader :flags
|
|
21
28
|
|
|
22
29
|
# Recover (libleptris 1.9.0, #547): a parse failure returns an
|
|
@@ -32,6 +39,10 @@ class Leptris::XML::ParseOptions
|
|
|
32
39
|
@recover = recover ? true : false
|
|
33
40
|
end
|
|
34
41
|
|
|
42
|
+
def self.keep_entity_refs
|
|
43
|
+
new(KEEP_ENTITY_REFS)
|
|
44
|
+
end
|
|
45
|
+
|
|
35
46
|
def self.noblanks
|
|
36
47
|
new(NOBLANKS)
|
|
37
48
|
end
|
|
@@ -52,6 +63,10 @@ class Leptris::XML::ParseOptions
|
|
|
52
63
|
@flags & DTDATTR != 0
|
|
53
64
|
end
|
|
54
65
|
|
|
66
|
+
def keep_entity_refs?
|
|
67
|
+
@flags & KEEP_ENTITY_REFS != 0
|
|
68
|
+
end
|
|
69
|
+
|
|
55
70
|
def dtdattr=(value)
|
|
56
71
|
if value then @flags |= DTDATTR else @flags &= ~DTDATTR end
|
|
57
72
|
end
|
|
@@ -55,6 +55,10 @@ module Leptris::XML::Searchable
|
|
|
55
55
|
expr = paths.one? && paths.first.is_a?(String) ? paths.first
|
|
56
56
|
: paths.join(" | ")
|
|
57
57
|
|
|
58
|
+
# TODO.perf/37: the document's Pointer is lazy (materializes
|
|
59
|
+
# once per document lifetime, not per parse); the ffi gem
|
|
60
|
+
# does NOT convert Integers for typedef'd pointer params, so
|
|
61
|
+
# the Pointer object is still what crosses here.
|
|
58
62
|
doc_ptr = is_a?(Leptris::XML::Document) ? c_ptr : document.c_ptr
|
|
59
63
|
context_ptr = is_a?(Leptris::XML::Document) ? nil : c_ptr
|
|
60
64
|
|
|
@@ -100,6 +104,10 @@ module Leptris::XML::Searchable
|
|
|
100
104
|
expr = paths.one? && paths.first.is_a?(String) ? paths.first
|
|
101
105
|
: paths.join(" | ")
|
|
102
106
|
|
|
107
|
+
# TODO.perf/37: the document's Pointer is lazy (materializes
|
|
108
|
+
# once per document lifetime, not per parse); the ffi gem
|
|
109
|
+
# does NOT convert Integers for typedef'd pointer params, so
|
|
110
|
+
# the Pointer object is still what crosses here.
|
|
103
111
|
doc_ptr = is_a?(Leptris::XML::Document) ? c_ptr : document.c_ptr
|
|
104
112
|
context_ptr = is_a?(Leptris::XML::Document) ? nil : c_ptr
|
|
105
113
|
|
|
@@ -32,16 +32,16 @@ module Leptris::XML::Serialization
|
|
|
32
32
|
# cycle lives at the FFI seam (FFI.serialize_into_string); this
|
|
33
33
|
# module owns only options selection and construction.
|
|
34
34
|
def self.to_xml(ffi_function, c_ptr, indent: 0, no_decl: false, encoding: nil)
|
|
35
|
-
# TODO.perf/04: the
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
if defined?(Leptris::XML::NATIVE_FAST)
|
|
35
|
+
# TODO.perf/04+36: the whole sized-buffer cycle in the ext —
|
|
36
|
+
# one C dispatch, no options marshaling, no Ruby buffer
|
|
37
|
+
# management; encoding rides the face (nil -> no override).
|
|
38
|
+
if defined?(Leptris::XML::NATIVE_FAST)
|
|
39
39
|
decl = !no_decl
|
|
40
40
|
return Leptris::XML::Native.fast_element_xml(
|
|
41
|
-
c_ptr.address, indent.to_i, decl) if
|
|
41
|
+
c_ptr.address, indent.to_i, decl, encoding) if
|
|
42
42
|
ffi_function == ELEMENT_SERIALIZE_INTO
|
|
43
43
|
return Leptris::XML::Native.fast_document_xml(
|
|
44
|
-
c_ptr.address, indent.to_i, decl) if
|
|
44
|
+
c_ptr.address, indent.to_i, decl, encoding) if
|
|
45
45
|
ffi_function == DOCUMENT_SERIALIZE_INTO
|
|
46
46
|
end
|
|
47
47
|
opts =
|
|
@@ -59,7 +59,8 @@ module Leptris::XML::Serialization
|
|
|
59
59
|
# loop: no kwargs, no options rebuild, no method allocation —
|
|
60
60
|
# one FFI fast-path dispatch per child.
|
|
61
61
|
def self.element_xml_default(c_ptr)
|
|
62
|
-
return Leptris::XML::Native.fast_element_xml(c_ptr.address, 0, true
|
|
62
|
+
return Leptris::XML::Native.fast_element_xml(c_ptr.address, 0, true,
|
|
63
|
+
nil) if
|
|
63
64
|
defined?(Leptris::XML::NATIVE_FAST)
|
|
64
65
|
Leptris::XML::FFI.serialize_into_string(
|
|
65
66
|
ELEMENT_SERIALIZE_INTO, c_ptr, DEFAULT_OPTIONS.pointer)
|
data/lib/leptris/xml/text.rb
CHANGED
|
@@ -20,9 +20,8 @@ class Leptris::XML::Text < Leptris::XML::Node
|
|
|
20
20
|
|
|
21
21
|
def content=(new_content)
|
|
22
22
|
if @native_fast
|
|
23
|
-
Leptris::XML::
|
|
24
|
-
|
|
25
|
-
@document, @c_address, new_content.to_s))
|
|
23
|
+
Leptris::XML::Native.set_binding_text(
|
|
24
|
+
@document, @c_address, new_content.to_s)
|
|
26
25
|
return new_content
|
|
27
26
|
end
|
|
28
27
|
ensure_writable!
|
data/lib/leptris/xml.rb
CHANGED
|
@@ -9,6 +9,7 @@ module Leptris
|
|
|
9
9
|
autoload :Comment, "leptris/xml/comment"
|
|
10
10
|
autoload :CDATA, "leptris/xml/cdata"
|
|
11
11
|
autoload :ProcessingInstruction, "leptris/xml/processing_instruction"
|
|
12
|
+
autoload :EntityReference, "leptris/xml/entity_reference"
|
|
12
13
|
autoload :Attr, "leptris/xml/attr"
|
|
13
14
|
autoload :Namespace, "leptris/xml/namespace"
|
|
14
15
|
autoload :Document, "leptris/xml/document"
|
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.177.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -104,6 +104,8 @@ files:
|
|
|
104
104
|
- TODO.perf/33-cbound-root-set.md
|
|
105
105
|
- TODO.perf/34-fragment-fast-lane.md
|
|
106
106
|
- TODO.perf/35-parse-default-c-face.md
|
|
107
|
+
- TODO.perf/36-raise-in-c-serializer-encoding.md
|
|
108
|
+
- TODO.perf/37-document-lazy-pointer.md
|
|
107
109
|
- TODO.restructure/01-constraint-compliance-audit.md
|
|
108
110
|
- TODO.restructure/02-deep-copy-seam.md
|
|
109
111
|
- TODO.restructure/03-evaluation-context-seam.md
|
|
@@ -162,6 +164,7 @@ files:
|
|
|
162
164
|
- lib/leptris/xml/document.rb
|
|
163
165
|
- lib/leptris/xml/document_fragment.rb
|
|
164
166
|
- lib/leptris/xml/element.rb
|
|
167
|
+
- lib/leptris/xml/entity_reference.rb
|
|
165
168
|
- lib/leptris/xml/evaluation_context.rb
|
|
166
169
|
- lib/leptris/xml/ffi.rb
|
|
167
170
|
- lib/leptris/xml/iteration_scope.rb
|