leptris 1.9.249.0-aarch64-linux → 1.9.255.1-aarch64-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 +19 -0
- data/Rakefile +1 -1
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/plan_value.rb +12 -15
- data/lib/libleptris.so +0 -0
- data/vendor-src/README.md +1 -1
- data/vendor-src/libleptris/CHANGELOG.md +58 -0
- data/vendor-src/libleptris/CMakeLists.txt +1 -1
- data/vendor-src/libleptris/benchmarks/CMakeLists.txt +15 -0
- data/vendor-src/libleptris/benchmarks/modification/bench_append_attr_pugixml.cpp +79 -0
- data/vendor-src/libleptris/src/leptris/flat/direct_parse.c +59 -10
- data/vendor-src/libleptris/src/leptris/html/html_parse.c +292 -316
- data/vendor-src/libleptris/src/leptris/xpath/evaluator_internal.h +14 -0
- data/vendor-src/libleptris/src/leptris/xpath/evaluator_operators.c +388 -10
- data/vendor-src/libleptris/src/leptris/xpath/functions_ext31.c +13 -40
- data/vendor-src/libleptris/src/leptris/xpath/parser.c +202 -2
- data/vendor-src/libleptris/src/leptris/xquery/xquery.c +15 -1
- data/vendor-src/libleptris/test/html/test_html.cpp +16 -0
- data/vendor-src/libleptris/test/xquery/test_qt3.cpp +36 -13
- data/vendor-src/libleptris/test/xquery/test_xquery.cpp +171 -10
- data/vendor-src/libleptris/vcpkg.json +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2efd375773deeb8f143c4963f6626ba293c00340a36f82e5fdcd0d96a1d17775
|
|
4
|
+
data.tar.gz: 493e70329997dca3961969ce7fd427766771d30701a4a72cfd54c6bbbdeb95bc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fdb05dfbc6b2985e86d7b36b79848122d8f4fa9a07d742e521ebda42bca3d7c7f426dea6f0b08a0829ecaf7cb8637b28420ac635a6aaca13fd190bbcd5d459d3
|
|
7
|
+
data.tar.gz: 7e52337d90dbe79e0102d023db14c05017acaef34519a491482e1c5192c5232f5e71b35cdcd90babcb6229e928b70d09b8d02cda5696d481a0bba878d194654c
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,25 @@ 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.255.0] - 2026-09-26
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Vendored libleptris 1.9.255 (riding 1.9.249-1.9.255, seven
|
|
13
|
+
engine syncs). Highlights:
|
|
14
|
+
- xquery: numeric-variable predicates are positional, not EBV —
|
|
15
|
+
(1)[1] keeps the atom, (1)[1] + 1 = 2 (#1392).
|
|
16
|
+
- xquery: document{...} materializes a real node (path steps
|
|
17
|
+
resolve over constructor results), the ! simple map is
|
|
18
|
+
item-preserving, multi-binding for/quantifier clauses parse,
|
|
19
|
+
and keyword constructors run the path continuation.
|
|
20
|
+
- xquery: F&O 3.0 numeric type promotion in value comparisons
|
|
21
|
+
and dedup (QT3 typed-atom lane) + timezone-normalized
|
|
22
|
+
xs:time comparison.
|
|
23
|
+
- html: implied-end pair checks run on tag ids with the tag
|
|
24
|
+
table gaps fixed (rb/rp/rt/rtc, option/optgroup) — round 2 of
|
|
25
|
+
the #1218 parse profile.
|
|
26
|
+
|
|
8
27
|
## [1.9.248.0] - 2026-09-26
|
|
9
28
|
|
|
10
29
|
### Fixed
|
data/Rakefile
CHANGED
|
@@ -8,7 +8,7 @@ RSpec::Core::RakeTask.new(:spec)
|
|
|
8
8
|
# Pin for `rake compile` and the platform-gem builds. Keep in lockstep
|
|
9
9
|
# with .github/workflows/build.yml (which calls `rake compile`) and the
|
|
10
10
|
# CHANGELOG when libleptris releases.
|
|
11
|
-
LIBLEPTRIS_VERSION = "1.9.
|
|
11
|
+
LIBLEPTRIS_VERSION = "1.9.255"
|
|
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/lib/leptris/version.rb
CHANGED
|
@@ -31,33 +31,33 @@ class Leptris::XML::PlanValue
|
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def kind
|
|
34
|
-
|
|
34
|
+
@counter[0] += 1
|
|
35
35
|
VALUE_KINDS.fetch(Leptris::XML::FFI.leptris_plan_value_kind(@ptr))
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
# wire_name of the plan row that produced this value.
|
|
39
39
|
def name
|
|
40
|
-
|
|
40
|
+
@counter[0] += 1
|
|
41
41
|
Leptris::XML::FFI.leptris_plan_value_name(@ptr)
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
# Host type_tag echoed verbatim (0 when the row had none).
|
|
45
45
|
def type_tag
|
|
46
|
-
|
|
46
|
+
@counter[0] += 1
|
|
47
47
|
Leptris::XML::FFI.leptris_plan_value_type_tag(@ptr)
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
# #1273: LEPTRIS_NODE_TYPE_* of this value's source node (0 for
|
|
51
51
|
# ELEMENT/COLLECTION wrappers and synthesized text).
|
|
52
52
|
def node_kind
|
|
53
|
-
|
|
53
|
+
@counter[0] += 1
|
|
54
54
|
Leptris::XML::FFI.leptris_plan_value_node_kind(@ptr)
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
# #1273: dense sibling rank inside the producing element (0 if
|
|
58
58
|
# unranked) — document-order identity without byte offsets.
|
|
59
59
|
def order_index
|
|
60
|
-
|
|
60
|
+
@counter[0] += 1
|
|
61
61
|
Leptris::XML::FFI.leptris_plan_value_order_index(@ptr)
|
|
62
62
|
end
|
|
63
63
|
|
|
@@ -85,26 +85,26 @@ class Leptris::XML::PlanValue
|
|
|
85
85
|
|
|
86
86
|
# SCALAR / RAW / CALLBACK string value.
|
|
87
87
|
def string_value
|
|
88
|
-
|
|
88
|
+
@counter[0] += 1
|
|
89
89
|
Leptris::XML::FFI.leptris_plan_value_string(@ptr)
|
|
90
90
|
end
|
|
91
91
|
|
|
92
92
|
# CALLBACK: document byte offset of the source node (0 unknown).
|
|
93
93
|
def position
|
|
94
|
-
|
|
94
|
+
@counter[0] += 1
|
|
95
95
|
Leptris::XML::FFI.leptris_plan_value_position(@ptr)
|
|
96
96
|
end
|
|
97
97
|
|
|
98
98
|
# ELEMENT: child-value count. COLLECTION: item count.
|
|
99
99
|
def count
|
|
100
|
-
|
|
100
|
+
@counter[0] += 1
|
|
101
101
|
Leptris::XML::FFI.leptris_plan_value_count(@ptr)
|
|
102
102
|
end
|
|
103
103
|
|
|
104
104
|
# ELEMENT child value / COLLECTION item at +i+ (nil out of
|
|
105
105
|
# range). Borrowed: the owning root must stay alive.
|
|
106
106
|
def at(i)
|
|
107
|
-
|
|
107
|
+
@counter[0] += 1
|
|
108
108
|
ptr = Leptris::XML::FFI.leptris_plan_value_at(@ptr, i)
|
|
109
109
|
return nil if ptr.null?
|
|
110
110
|
child_plan = child_plan_for_position(ptr)
|
|
@@ -114,7 +114,7 @@ class Leptris::XML::PlanValue
|
|
|
114
114
|
|
|
115
115
|
# ELEMENT: attribute value by wire_name (nil when absent).
|
|
116
116
|
def attribute(wire_name)
|
|
117
|
-
|
|
117
|
+
@counter[0] += 1
|
|
118
118
|
Leptris::XML::FFI.leptris_plan_value_attribute(@ptr, wire_name.to_s)
|
|
119
119
|
end
|
|
120
120
|
|
|
@@ -149,7 +149,7 @@ class Leptris::XML::PlanValue
|
|
|
149
149
|
# (0 when absent). Collection rows appear as Arrays; callbacks
|
|
150
150
|
# are emitted as +{value:, position:, type_tag:}+ like #to_ruby.
|
|
151
151
|
def as_kwarg_hash
|
|
152
|
-
|
|
152
|
+
@counter[0] += 1 # the kind probe + name + type_tag are one logical fetch
|
|
153
153
|
case kind
|
|
154
154
|
when :element
|
|
155
155
|
{
|
|
@@ -194,9 +194,6 @@ class Leptris::XML::PlanValue
|
|
|
194
194
|
|
|
195
195
|
private
|
|
196
196
|
|
|
197
|
-
def inc_crossing
|
|
198
|
-
@counter[0] += 1
|
|
199
|
-
end
|
|
200
197
|
|
|
201
198
|
def kwarg_attributes
|
|
202
199
|
((@plan && @plan[:attributes]) || []).each_with_object({}) do |row, result|
|
|
@@ -290,7 +287,7 @@ class Leptris::XML::PlanValue
|
|
|
290
287
|
rows = @plan[:children] || []
|
|
291
288
|
row = rows.size == 1 ? rows.first : nil
|
|
292
289
|
unless row
|
|
293
|
-
|
|
290
|
+
@counter[0] += 1
|
|
294
291
|
child_name = Leptris::XML::FFI.leptris_plan_value_name(ptr)
|
|
295
292
|
row = rows.find { |r| r[:name] == child_name }
|
|
296
293
|
end
|
data/lib/libleptris.so
CHANGED
|
Binary file
|
data/vendor-src/README.md
CHANGED
|
@@ -1,5 +1,63 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.9.255] - 2026-09-26
|
|
4
|
+
|
|
5
|
+
<!-- Edit this section with the actual release notes. -->
|
|
6
|
+
<!-- See https://keepachangelog.com for format guidance. -->
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
|
|
10
|
+
- (describe changes here)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## [1.9.254] - 2026-09-26
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- timezone-normalized xs:time comparison and ordering (xquery)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
## [1.9.253] - 2026-09-26
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- F&O numeric promotion in value comparison and distinct-values (xquery)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
## [1.9.252] - 2026-09-26
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- multi-binding for/quantifier clauses (xquery)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
## [1.9.251] - 2026-09-26
|
|
38
|
+
|
|
39
|
+
### Added
|
|
40
|
+
|
|
41
|
+
- document{} materializes a node; simple map preserves items (xquery)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
## [1.9.250] - 2026-09-26
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- keyword ctors run the path continuation (document{...}/M) (xquery)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
## [1.9.249] - 2026-09-26
|
|
54
|
+
|
|
55
|
+
### Performance
|
|
56
|
+
|
|
57
|
+
- classify implied-end pairs on tag ids (#1218) (html)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
3
61
|
## [1.9.248] - 2026-09-26
|
|
4
62
|
|
|
5
63
|
<!-- Edit this section with the actual release notes. -->
|
|
@@ -211,6 +211,21 @@ endif()
|
|
|
211
211
|
leptris_add_benchmark(bench_serialize serialization/bench_leptris_serialize.c)
|
|
212
212
|
leptris_add_benchmark(bench_modify_leptris modification/bench_leptris_modify.c)
|
|
213
213
|
|
|
214
|
+
# Lane 18 (#1179): append + attr-parse vs pugixml (the lane bar is
|
|
215
|
+
# <=1x on both shapes). Needs C++ (pugixml.hpp) and the pugixml
|
|
216
|
+
# target; skipped when the dependency is absent.
|
|
217
|
+
if(PUGIXML_AVAILABLE)
|
|
218
|
+
add_executable(bench_append_attr_pugixml
|
|
219
|
+
modification/bench_append_attr_pugixml.cpp)
|
|
220
|
+
target_link_libraries(bench_append_attr_pugixml PRIVATE leptris::leptris)
|
|
221
|
+
if(PUGIXML_TARGET)
|
|
222
|
+
target_link_libraries(bench_append_attr_pugixml PRIVATE ${PUGIXML_TARGET})
|
|
223
|
+
else()
|
|
224
|
+
target_include_directories(bench_append_attr_pugixml PRIVATE ${PUGIXML_INCLUDE_DIR})
|
|
225
|
+
target_link_libraries(bench_append_attr_pugixml PRIVATE ${PUGIXML_LIBRARY})
|
|
226
|
+
endif()
|
|
227
|
+
endif()
|
|
228
|
+
|
|
214
229
|
# XSLT release scorecard (TODO.xslt-full/13, #682): the transform
|
|
215
230
|
# fixtures the perf lane gates on — best-of protocol, never mean.
|
|
216
231
|
leptris_add_benchmark(bench_xslt_dispatch xslt/bench_dispatch.c utils.c)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// bench_append_attr_pugixml — Lane 18 (#1179): DOM append and
|
|
2
|
+
// attr-parse throughput vs pugixml. The lane bar is append <=1x and
|
|
3
|
+
// attr-parse <=1x against pugixml on these shapes.
|
|
4
|
+
#include "leptris.h"
|
|
5
|
+
#include "pugixml.hpp"
|
|
6
|
+
#include <chrono>
|
|
7
|
+
#include <cstdio>
|
|
8
|
+
#include <cstring>
|
|
9
|
+
#include <string>
|
|
10
|
+
|
|
11
|
+
static double now_ns() {
|
|
12
|
+
return (double)std::chrono::duration_cast<std::chrono::nanoseconds>(
|
|
13
|
+
std::chrono::steady_clock::now().time_since_epoch())
|
|
14
|
+
.count();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
int main(void) {
|
|
18
|
+
const int N_APPEND = 200000;
|
|
19
|
+
const int N_PARSE = 20000;
|
|
20
|
+
|
|
21
|
+
/* ---- append: 200k element children on one root ---- */
|
|
22
|
+
{
|
|
23
|
+
double t0 = now_ns();
|
|
24
|
+
LeptrisDocument d = leptris_document_create();
|
|
25
|
+
LeptrisElement root = leptris_element_create(d, "root");
|
|
26
|
+
leptris_document_set_root(d, root);
|
|
27
|
+
for (int i = 0; i < N_APPEND; i++) {
|
|
28
|
+
LeptrisElement c = leptris_element_create(d, "c");
|
|
29
|
+
leptris_element_append_child(root, c);
|
|
30
|
+
}
|
|
31
|
+
double t1 = now_ns();
|
|
32
|
+
printf("append leptris : %8.1f ns/child\n",
|
|
33
|
+
(t1 - t0) / N_APPEND);
|
|
34
|
+
leptris_document_free(d);
|
|
35
|
+
}
|
|
36
|
+
{
|
|
37
|
+
double t0 = now_ns();
|
|
38
|
+
pugi::xml_document d;
|
|
39
|
+
pugi::xml_node root = d.append_child("root");
|
|
40
|
+
for (int i = 0; i < N_APPEND; i++)
|
|
41
|
+
root.append_child("c");
|
|
42
|
+
double t1 = now_ns();
|
|
43
|
+
printf("append pugixml : %8.1f ns/child\n",
|
|
44
|
+
(t1 - t0) / N_APPEND);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* ---- attr-parse: parse 20k docs, 4 elems x 16 attrs ---- */
|
|
48
|
+
std::string xml = "<r>";
|
|
49
|
+
for (int e = 0; e < 4; e++) {
|
|
50
|
+
xml += "<e";
|
|
51
|
+
for (int a = 0; a < 16; a++)
|
|
52
|
+
xml += " a" + std::to_string(a) + "='value-" +
|
|
53
|
+
std::to_string(a) + "-xyz'";
|
|
54
|
+
xml += "/>";
|
|
55
|
+
}
|
|
56
|
+
xml += "</r>";
|
|
57
|
+
{
|
|
58
|
+
double t0 = now_ns();
|
|
59
|
+
for (int i = 0; i < N_PARSE; i++) {
|
|
60
|
+
LeptrisDocument d =
|
|
61
|
+
leptris_parse_string(xml.data(), xml.size(), NULL);
|
|
62
|
+
leptris_document_free(d);
|
|
63
|
+
}
|
|
64
|
+
double t1 = now_ns();
|
|
65
|
+
printf("attrparse leptris: %7.1f ns/doc (%zu bytes)\n",
|
|
66
|
+
(t1 - t0) / N_PARSE, xml.size());
|
|
67
|
+
}
|
|
68
|
+
{
|
|
69
|
+
double t0 = now_ns();
|
|
70
|
+
for (int i = 0; i < N_PARSE; i++) {
|
|
71
|
+
pugi::xml_document d;
|
|
72
|
+
d.load_buffer((void*)xml.data(), xml.size());
|
|
73
|
+
}
|
|
74
|
+
double t1 = now_ns();
|
|
75
|
+
printf("attrparse pugixml: %7.1f ns/doc\n",
|
|
76
|
+
(t1 - t0) / N_PARSE);
|
|
77
|
+
}
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
@@ -108,6 +108,13 @@ typedef struct {
|
|
|
108
108
|
* (the element cache line only needs touching twice per tag). */
|
|
109
109
|
struct leptris_attribute* attr_cursor;
|
|
110
110
|
struct leptris_attribute* attr_end;
|
|
111
|
+
/* #1200 round 2: per-element attr-name hash prefilter — the
|
|
112
|
+
* dup scan compares uint32 hashes (uint32 loop, no pointer
|
|
113
|
+
* chasing) and only walks the attr chain with memcmp on a hash
|
|
114
|
+
* hit. Pool-backed growth; reset per element. */
|
|
115
|
+
uint32_t* attr_dup_hashes;
|
|
116
|
+
size_t attr_dup_count;
|
|
117
|
+
size_t attr_dup_cap;
|
|
111
118
|
/* Bulk text-node block (round 8): leptris_text_create_borrowed
|
|
112
119
|
* is an out-of-line call per text node — pool_alloc alone costs
|
|
113
120
|
* more than the field stores. Same carve pattern as the elem and
|
|
@@ -454,20 +461,56 @@ static inline int dp_add_attr_inline(DParser* p, LeptrisElement elem,
|
|
|
454
461
|
* (libxml2 recover semantics, and the digest's first-wins
|
|
455
462
|
* becomes official); the duplicate never enters the chain, it
|
|
456
463
|
* costs its already-carved slot. */
|
|
457
|
-
|
|
464
|
+
{
|
|
465
|
+
uint32_t dh = 2166136261u;
|
|
466
|
+
for (size_t i = 0; i < name_len; i++) {
|
|
467
|
+
dh ^= (unsigned char)name[i];
|
|
468
|
+
dh *= 16777619u;
|
|
469
|
+
}
|
|
458
470
|
int dup = 0;
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
if (a->name_view.length == attr->name_view.length &&
|
|
464
|
-
a->name_view.data[0] == attr->name_view.data[0] &&
|
|
465
|
-
memcmp(a->name_view.data, attr->name_view.data,
|
|
466
|
-
attr->name_view.length) == 0) {
|
|
467
|
-
dup = 1;
|
|
471
|
+
int cand = 0;
|
|
472
|
+
for (size_t i = 0; i < p->attr_dup_count; i++) {
|
|
473
|
+
if (p->attr_dup_hashes[i] == dh) {
|
|
474
|
+
cand = 1;
|
|
468
475
|
break;
|
|
469
476
|
}
|
|
470
477
|
}
|
|
478
|
+
if (cand && elem->first_attribute_off != 0) {
|
|
479
|
+
/* hash hit: confirm against the real chain (collision
|
|
480
|
+
* or genuine duplicate) */
|
|
481
|
+
for (struct leptris_attribute* a =
|
|
482
|
+
(struct leptris_attribute*)(
|
|
483
|
+
(char*)elem + elem->first_attribute_off);
|
|
484
|
+
a; a = leptris_attr_next(a)) {
|
|
485
|
+
if (a->name_view.length ==
|
|
486
|
+
attr->name_view.length &&
|
|
487
|
+
a->name_view.data[0] ==
|
|
488
|
+
attr->name_view.data[0] &&
|
|
489
|
+
memcmp(a->name_view.data, attr->name_view.data,
|
|
490
|
+
attr->name_view.length) == 0) {
|
|
491
|
+
dup = 1;
|
|
492
|
+
break;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
if (!dup) {
|
|
497
|
+
if (p->attr_dup_count == p->attr_dup_cap) {
|
|
498
|
+
size_t nc = p->attr_dup_cap ? p->attr_dup_cap * 2
|
|
499
|
+
: 16;
|
|
500
|
+
uint32_t* nh = (uint32_t*)leptris_pool_alloc(
|
|
501
|
+
p->pool, nc * sizeof(uint32_t));
|
|
502
|
+
if (nh) {
|
|
503
|
+
if (p->attr_dup_count)
|
|
504
|
+
memcpy(nh, p->attr_dup_hashes,
|
|
505
|
+
p->attr_dup_count *
|
|
506
|
+
sizeof(uint32_t));
|
|
507
|
+
p->attr_dup_hashes = nh;
|
|
508
|
+
p->attr_dup_cap = nc;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
if (p->attr_dup_count < p->attr_dup_cap)
|
|
512
|
+
p->attr_dup_hashes[p->attr_dup_count++] = dh;
|
|
513
|
+
}
|
|
471
514
|
if (dup) {
|
|
472
515
|
if (p->doc) {
|
|
473
516
|
extern struct LeptrisDiag* leptris_diag_emit(
|
|
@@ -489,6 +532,7 @@ static inline int dp_add_attr_inline(DParser* p, LeptrisElement elem,
|
|
|
489
532
|
* the raw-attr registration so BOTH surfaces agree. */
|
|
490
533
|
return 1;
|
|
491
534
|
}
|
|
535
|
+
if (elem->first_attribute_off != 0) {
|
|
492
536
|
/* Cache should always be valid mid-parse. Fall back to walk
|
|
493
537
|
* only if cache is NULL (defensive — shouldn't happen). */
|
|
494
538
|
struct leptris_attribute* tail = p->current_elem_last_attr;
|
|
@@ -506,6 +550,7 @@ static inline int dp_add_attr_inline(DParser* p, LeptrisElement elem,
|
|
|
506
550
|
* offset is ever needed. */
|
|
507
551
|
elem->first_attribute_off =
|
|
508
552
|
(int32_t)((char*)attr - (char*)elem);
|
|
553
|
+
}
|
|
509
554
|
}
|
|
510
555
|
p->current_elem_last_attr = attr;
|
|
511
556
|
p->cur_attr_count++;
|
|
@@ -723,6 +768,7 @@ static int dp_parse_attrs(DParser* p, LeptrisElement elem) {
|
|
|
723
768
|
p->current_elem_last_ns = NULL;
|
|
724
769
|
p->raw_last = NULL;
|
|
725
770
|
p->cur_attr_count = 0;
|
|
771
|
+
p->attr_dup_count = 0; /* hash array + cap persist across elements */
|
|
726
772
|
/* Sentinel-terminated (parse endgame, third application): buf[len]
|
|
727
773
|
* is NUL and NUL fails every classification below — not '>', not
|
|
728
774
|
* '/', not '=' , not a quote, not IS_NAME_START. Each former
|
|
@@ -1333,6 +1379,9 @@ static struct leptris_document* direct_parse_internal(char* buf, size_t len,
|
|
|
1333
1379
|
p.attr_capacity = attr_capacity;
|
|
1334
1380
|
p.attr_cursor = attr_block;
|
|
1335
1381
|
p.attr_end = attr_block + attr_capacity;
|
|
1382
|
+
p.attr_dup_hashes = NULL;
|
|
1383
|
+
p.attr_dup_count = 0;
|
|
1384
|
+
p.attr_dup_cap = 0;
|
|
1336
1385
|
p.text_cursor = text_block;
|
|
1337
1386
|
p.text_end = text_block + lt_count;
|
|
1338
1387
|
p.cpi_cursor = cpi_block;
|