leptris 1.9.270.0 → 1.9.273.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 +15 -0
- data/Rakefile +1 -1
- 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/arm-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/arm-linux-musl/libleptris.so +0 -0
- data/lib/leptris/vendor/arm64-darwin/libleptris.dylib +0 -0
- data/lib/leptris/vendor/ppc64le-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/s390x-linux-musl/libleptris.so +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/ffi.rb +8 -0
- data/lib/leptris/xml/plan_value.rb +54 -3
- data/vendor-src/README.md +1 -1
- data/vendor-src/libleptris/CHANGELOG.md +37 -0
- data/vendor-src/libleptris/CMakeLists.txt +1 -1
- data/vendor-src/libleptris/src/include/leptris/descriptor.h +16 -0
- data/vendor-src/libleptris/src/include/leptris/types.h +11 -1
- data/vendor-src/libleptris/src/leptris/descriptor/plan.c +35 -0
- data/vendor-src/libleptris/src/leptris/flat/direct_parse.c +28 -13
- data/vendor-src/libleptris/test/dom/test_dom.cpp +69 -0
- data/vendor-src/libleptris/vcpkg.json +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: eac60497cce482f9c8a82023dfe9af7437aca757483c90e2a8af52c0aa9f9f40
|
|
4
|
+
data.tar.gz: aadeb53448aabd955c1ede63cf1b468d50ab575100f0d7f3e57f1675a460f9f0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9d695d666df1ebec848d062cee08f7f35101f872d799ffb18ec2bc2de502b25f410fe086f02f280658c76972511e3d532b42d4bfca78c4fee5ea38d8b94cf1e2
|
|
7
|
+
data.tar.gz: f1704c469043bf964a5b52def67dede9a719a36e42732d0e4abd818e8de6f8ef076c12c30bce82a2d39591c1951e2518fdafbb2af7f821a46eaf5ba9f973a1de
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@ 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.270.0] - 2026-09-29
|
|
9
|
+
|
|
10
|
+
### Performance — engine sync (1.9.269-1.9.270)
|
|
11
|
+
|
|
12
|
+
- Vendored libleptris 1.9.270:
|
|
13
|
+
- A parse+free cycle is exactly ONE system allocation (the arena
|
|
14
|
+
header carved into the span; frozen by an exact-count allocator
|
|
15
|
+
pin) — and the 256-byte error buffer leaves the construction
|
|
16
|
+
memset.
|
|
17
|
+
- The lazy #635 raw-attribute journal was reverted (#1431,
|
|
18
|
+
release-blocking): chain synthesis on stale elements walked
|
|
19
|
+
freed chains — the eager journal is restored, byte-identical to
|
|
20
|
+
1.9.268 on that surface. The lazy form returns only behind a
|
|
21
|
+
real lifetime contract.
|
|
22
|
+
|
|
8
23
|
## [1.9.268.0] - 2026-09-29
|
|
9
24
|
|
|
10
25
|
### Performance — engine sync (1.9.266-1.9.268)
|
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.273"
|
|
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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/lib/leptris/version.rb
CHANGED
data/lib/leptris/xml/ffi.rb
CHANGED
|
@@ -384,6 +384,14 @@ module Leptris
|
|
|
384
384
|
[:leptris_plan_result, :size_t], :leptris_plan_result
|
|
385
385
|
attach_function :leptris_plan_value_attribute,
|
|
386
386
|
[:leptris_plan_result, :string], UTF8_STRING
|
|
387
|
+
# One-call flat child list: names blob with per-child offsets
|
|
388
|
+
# (SIZE_MAX = NULL name, i.e. content runs), type tags, and
|
|
389
|
+
# borrowed child handles. Returns 0 on success or the blob
|
|
390
|
+
# capacity required (buffers untouched) — the host retries at
|
|
391
|
+
# that capacity.
|
|
392
|
+
attach_function :leptris_plan_value_children_snapshot,
|
|
393
|
+
[:leptris_plan_result, :pointer, :size_t, :pointer, :pointer,
|
|
394
|
+
:pointer], :size_t
|
|
387
395
|
# #1254 (leptris 1.9.216): one-call flat attribute read —
|
|
388
396
|
# parallel name/value/handle arrays, capacity-bounded; total
|
|
389
397
|
# count returned (min(total, max_count) copied). Also the
|
|
@@ -112,6 +112,30 @@ class Leptris::XML::PlanValue
|
|
|
112
112
|
counter: @counter)
|
|
113
113
|
end
|
|
114
114
|
|
|
115
|
+
# ELEMENT: the whole child list in one FFI crossing — parallel
|
|
116
|
+
# arrays of name (nil for content runs), type_tag, and the wrapped
|
|
117
|
+
# children. Enumerating through #at/#name/#type_tag costs three
|
|
118
|
+
# crossings per child; this costs one per subtree, with the
|
|
119
|
+
# PlanValue wrapping Ruby-side. Returns [names, tags, children].
|
|
120
|
+
def children_snapshot
|
|
121
|
+
n = Leptris::XML::FFI.leptris_plan_value_count(@ptr)
|
|
122
|
+
return [[], [], []] if n.zero?
|
|
123
|
+
|
|
124
|
+
@counter[0] += 2 # count + the snapshot crossing
|
|
125
|
+
cap = 64 * n
|
|
126
|
+
loop do
|
|
127
|
+
blob = ::FFI::MemoryPointer.new(:char, cap)
|
|
128
|
+
offsets = ::FFI::MemoryPointer.new(:size_t, n + 1)
|
|
129
|
+
tags = ::FFI::MemoryPointer.new(:uint8, n)
|
|
130
|
+
handles = ::FFI::MemoryPointer.new(:pointer, n)
|
|
131
|
+
needed = Leptris::XML::FFI.leptris_plan_value_children_snapshot(
|
|
132
|
+
@ptr, blob, cap, offsets, tags, handles)
|
|
133
|
+
break snapshot_build(blob, offsets, tags, handles, n) if needed.zero?
|
|
134
|
+
|
|
135
|
+
cap = needed
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
115
139
|
# ELEMENT: attribute value by wire_name (nil when absent).
|
|
116
140
|
def attribute(wire_name)
|
|
117
141
|
@counter[0] += 1
|
|
@@ -282,13 +306,40 @@ class Leptris::XML::PlanValue
|
|
|
282
306
|
# row — the FFI name probe is skipped (#298 crossings floor).
|
|
283
307
|
# Multi-row plans probe by wire_name (result positions only
|
|
284
308
|
# cover matched children, so positional mapping is unsafe).
|
|
285
|
-
|
|
309
|
+
SNAPSHOT_NULL_OFFSET = 0xFFFFFFFFFFFFFFFF
|
|
310
|
+
|
|
311
|
+
# FFI get_uint64/get_pointer take BYTE offsets, not indices.
|
|
312
|
+
SIZE_T_SIZE = ::FFI::MemoryPointer.new(:size_t, 2).total / 2
|
|
313
|
+
POINTER_SIZE = ::FFI::Type::POINTER.size
|
|
314
|
+
|
|
315
|
+
def snapshot_build(blob, offsets, tags, handles, n)
|
|
316
|
+
names = ::Array.new(n)
|
|
317
|
+
n.times do |i|
|
|
318
|
+
off = offsets.get_uint64(i * SIZE_T_SIZE)
|
|
319
|
+
names[i] = off == SNAPSHOT_NULL_OFFSET ? nil : blob.get_string(off)
|
|
320
|
+
end
|
|
321
|
+
tag_values = ::Array.new(n) { |i| tags.get_uint8(i) }
|
|
322
|
+
children = ::Array.new(n)
|
|
323
|
+
n.times do |i|
|
|
324
|
+
ptr = handles.get_pointer(i * POINTER_SIZE)
|
|
325
|
+
child_plan = child_plan_for_position(ptr, name: names[i])
|
|
326
|
+
children[i] = Leptris::XML::PlanValue.new(ptr, plans: @plans,
|
|
327
|
+
plan: child_plan,
|
|
328
|
+
counter: @counter)
|
|
329
|
+
end
|
|
330
|
+
[names, tag_values, children]
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def child_plan_for_position(ptr, name: nil)
|
|
286
334
|
return nil unless @plan
|
|
287
335
|
rows = @plan[:children] || []
|
|
288
336
|
row = rows.size == 1 ? rows.first : nil
|
|
289
337
|
unless row
|
|
290
|
-
|
|
291
|
-
child_name
|
|
338
|
+
child_name = name
|
|
339
|
+
if child_name.nil?
|
|
340
|
+
@counter[0] += 1
|
|
341
|
+
child_name = Leptris::XML::FFI.leptris_plan_value_name(ptr)
|
|
342
|
+
end
|
|
292
343
|
row = rows.find { |r| r[:name] == child_name }
|
|
293
344
|
end
|
|
294
345
|
return nil unless row && @plans
|
data/vendor-src/README.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.9.273] - 2026-09-29
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- children snapshot returns child handles (descriptor)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## [1.9.272] - 2026-09-29
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- `LEPTRIS_PARSE_SKIP_DUP_DETECTION` (8u) and `LEPTRIS_PARSE_SKIP_SOURCE_POSITIONS` (16u) parse
|
|
16
|
+
flags for `leptris_parse_string_flags` — leptris#1436 Door A. Opt-out gates for the two remaining
|
|
17
|
+
fixed per-attribute/per-element bookkeeping costs; defaults unchanged (a zero flag word parses
|
|
18
|
+
byte-identically to 1.9.271).
|
|
19
|
+
|
|
20
|
+
### Performance
|
|
21
|
+
|
|
22
|
+
- `SKIP_DUP_DETECTION`: the name-hash serial-multiply chain and duplicate probe are skipped
|
|
23
|
+
(~2.5 ns/attr measured floor). Duplicate attributes are admitted silently — no recover
|
|
24
|
+
diagnostic; both copies stay visible in the chain and the raw view; queries resolve first-wins.
|
|
25
|
+
Measured -11-13% opt-in on the 100-attribute wide parse row (1522-1535 -> 1315-1357 ns/doc).
|
|
26
|
+
- `SKIP_SOURCE_POSITIONS`: the element position journal is not built; element columns degrade
|
|
27
|
+
(col_start falls back to 1, col_end unwritten) while lines continue to resolve via the lazy
|
|
28
|
+
newline table.
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
## [1.9.271] - 2026-09-29
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
- plan-value children snapshot — one crossing per child list (descriptor)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
3
40
|
## [1.9.270] - 2026-09-29
|
|
4
41
|
|
|
5
42
|
### Fixed
|
|
@@ -211,6 +211,22 @@ LEPTRIS_API size_t leptris_plan_value_count(const LeptrisPlanResult v);
|
|
|
211
211
|
/* ELEMENT child value / COLLECTION item at i; NULL out of range. */
|
|
212
212
|
LEPTRIS_API LeptrisPlanResult leptris_plan_value_at(const LeptrisPlanResult v,
|
|
213
213
|
size_t i);
|
|
214
|
+
/* ELEMENT: one crossing for the whole child list. Writes each
|
|
215
|
+
* child's NUL-terminated name into names_blob at the byte offsets
|
|
216
|
+
* recorded in name_offsets (kid_count+1 entries; the final entry is
|
|
217
|
+
* the total blob size), each child's type_tag into type_tags, and
|
|
218
|
+
* each child's borrowed handle into child_handles. A child whose
|
|
219
|
+
* name is NULL (content runs) records SIZE_MAX in its offsets entry
|
|
220
|
+
* and writes nothing to the blob. Returns 0 on success; otherwise
|
|
221
|
+
* returns the blob capacity the child names require and leaves the
|
|
222
|
+
* buffers untouched, so the host retries with the returned
|
|
223
|
+
* capacity. Handles stay borrowed — leptris_plan_value_at is no
|
|
224
|
+
* longer needed for enumeration. */
|
|
225
|
+
LEPTRIS_API size_t leptris_plan_value_children_snapshot(
|
|
226
|
+
const LeptrisPlanResult v,
|
|
227
|
+
char* names_blob, size_t blob_cap,
|
|
228
|
+
size_t* name_offsets, uint8_t* type_tags,
|
|
229
|
+
LeptrisPlanResult* child_handles);
|
|
214
230
|
/* ELEMENT: attribute value by wire_name; NULL when absent. */
|
|
215
231
|
LEPTRIS_API const char* leptris_plan_value_attribute(const LeptrisPlanResult v,
|
|
216
232
|
const char* wire_name);
|
|
@@ -226,7 +226,17 @@ typedef enum {
|
|
|
226
226
|
/* Keep &name; references unexpanded as ENTITY_REF nodes in the
|
|
227
227
|
* tree (#1094): text runs split around them and reads resolve
|
|
228
228
|
* them lazily (libxml2's default, pre-NOENT). */
|
|
229
|
-
LEPTRIS_PARSE_KEEP_ENTITY_REFS = 4u
|
|
229
|
+
LEPTRIS_PARSE_KEEP_ENTITY_REFS = 4u,
|
|
230
|
+
/* Lane-18 round 18 (leptris#1436 Door A): performance opt-outs
|
|
231
|
+
* for callers that do not consume diagnostics features pugixml
|
|
232
|
+
* does not carry. Defaults keep every feature ON — these bits
|
|
233
|
+
* SKIP work:
|
|
234
|
+
* - SKIP_DUP_DETECTION: duplicate attributes are admitted
|
|
235
|
+
* silently (first still wins for queries; no recover diag).
|
|
236
|
+
* - SKIP_SOURCE_POSITIONS: element source columns degrade to
|
|
237
|
+
* zeros (line still resolves from the buffer). */
|
|
238
|
+
LEPTRIS_PARSE_SKIP_DUP_DETECTION = 8u,
|
|
239
|
+
LEPTRIS_PARSE_SKIP_SOURCE_POSITIONS = 16u
|
|
230
240
|
} LeptrisParseFlags;
|
|
231
241
|
|
|
232
242
|
/* ============================================================================
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* recursively by leptris_plan_result_free. */
|
|
13
13
|
#include <errno.h>
|
|
14
14
|
#include <stdint.h>
|
|
15
|
+
#include <stdio.h>
|
|
15
16
|
#include <stdlib.h>
|
|
16
17
|
#include <string.h>
|
|
17
18
|
|
|
@@ -909,6 +910,40 @@ LEPTRIS_API LeptrisPlanResult leptris_plan_value_at(const LeptrisPlanResult v, s
|
|
|
909
910
|
return v->kids[i];
|
|
910
911
|
}
|
|
911
912
|
|
|
913
|
+
LEPTRIS_API size_t leptris_plan_value_children_snapshot(
|
|
914
|
+
const LeptrisPlanResult v,
|
|
915
|
+
char* names_blob, size_t blob_cap,
|
|
916
|
+
size_t* name_offsets, uint8_t* type_tags,
|
|
917
|
+
LeptrisPlanResult* child_handles) {
|
|
918
|
+
if (!v) return 0;
|
|
919
|
+
size_t need = 0;
|
|
920
|
+
for (size_t i = 0; i < v->kid_count; i++) {
|
|
921
|
+
const char* n = v->kids[i]->name;
|
|
922
|
+
need += (n ? strlen(n) : 0) + 1;
|
|
923
|
+
}
|
|
924
|
+
if (need > blob_cap || !names_blob || !name_offsets || !type_tags ||
|
|
925
|
+
!child_handles)
|
|
926
|
+
return need;
|
|
927
|
+
size_t off = 0;
|
|
928
|
+
for (size_t i = 0; i < v->kid_count; i++) {
|
|
929
|
+
const LeptrisPlanResult kid = v->kids[i];
|
|
930
|
+
const char* n = kid->name;
|
|
931
|
+
if (n) {
|
|
932
|
+
size_t len = strlen(n);
|
|
933
|
+
memcpy(names_blob + off, n, len);
|
|
934
|
+
names_blob[off + len] = '\0';
|
|
935
|
+
name_offsets[i] = off;
|
|
936
|
+
off += len + 1;
|
|
937
|
+
} else {
|
|
938
|
+
name_offsets[i] = (size_t)-1;
|
|
939
|
+
}
|
|
940
|
+
type_tags[i] = kid->type_tag;
|
|
941
|
+
child_handles[i] = kid;
|
|
942
|
+
}
|
|
943
|
+
name_offsets[v->kid_count] = off;
|
|
944
|
+
return 0;
|
|
945
|
+
}
|
|
946
|
+
|
|
912
947
|
LEPTRIS_API const char* leptris_plan_value_attribute(const LeptrisPlanResult v,
|
|
913
948
|
const char* wire_name) {
|
|
914
949
|
if (!v || !wire_name) return NULL;
|
|
@@ -184,6 +184,7 @@ typedef struct {
|
|
|
184
184
|
* the first ws-normalized/entity value flips this on and
|
|
185
185
|
* backfills the clean prefix, preserving the source-byte
|
|
186
186
|
* contract exactly where synthesis could not. */
|
|
187
|
+
unsigned skip_flags; /* LEPTRIS_PARSE_SKIP_* (round 18, #1436 Door A) */
|
|
187
188
|
struct leptris_ns_cache* nsc_cursor;
|
|
188
189
|
struct leptris_ns_cache* nsc_end;
|
|
189
190
|
/* DTD parsed from the DOCTYPE internal subset. NULL when the
|
|
@@ -552,7 +553,7 @@ static inline int dp_add_attr_inline(DParser* p, LeptrisElement elem,
|
|
|
552
553
|
* (libxml2 recover semantics, and the digest's first-wins
|
|
553
554
|
* becomes official); the duplicate never enters the chain, it
|
|
554
555
|
* costs its already-carved slot. */
|
|
555
|
-
{
|
|
556
|
+
if (!(p->skip_flags & LEPTRIS_PARSE_SKIP_DUP_DETECTION)) {
|
|
556
557
|
int dup = 0;
|
|
557
558
|
if (dp_dup_seen_or_add(p, name_hash) &&
|
|
558
559
|
elem->first_attribute_off != 0) {
|
|
@@ -594,7 +595,8 @@ static inline int dp_add_attr_inline(DParser* p, LeptrisElement elem,
|
|
|
594
595
|
* the raw-attr registration so BOTH surfaces agree. */
|
|
595
596
|
return 1;
|
|
596
597
|
}
|
|
597
|
-
|
|
598
|
+
}
|
|
599
|
+
if (elem->first_attribute_off != 0) {
|
|
598
600
|
/* Cache should always be valid mid-parse. Fall back to walk
|
|
599
601
|
* only if cache is NULL (defensive — shouldn't happen). */
|
|
600
602
|
struct leptris_attribute* tail = p->current_elem_last_attr;
|
|
@@ -612,7 +614,6 @@ static inline int dp_add_attr_inline(DParser* p, LeptrisElement elem,
|
|
|
612
614
|
* offset is ever needed. */
|
|
613
615
|
elem->first_attribute_off =
|
|
614
616
|
(int32_t)((char*)attr - (char*)elem);
|
|
615
|
-
}
|
|
616
617
|
}
|
|
617
618
|
p->current_elem_last_attr = attr;
|
|
618
619
|
p->cur_attr_count++;
|
|
@@ -902,8 +903,16 @@ static int dp_parse_attrs(DParser* p, LeptrisElement elem) {
|
|
|
902
903
|
* bytes, was the cost. */
|
|
903
904
|
char* name_start = p->pos;
|
|
904
905
|
if (!IS_NAME_START(*p->pos)) return -1;
|
|
905
|
-
|
|
906
|
-
|
|
906
|
+
/* The name hash feeds only the duplicate probe (the dup
|
|
907
|
+
* confirm is bytewise) — SKIP_DUP_DETECTION callers skip the
|
|
908
|
+
* whole serial-multiply chain. */
|
|
909
|
+
const int need_dup_hash =
|
|
910
|
+
(p->skip_flags & LEPTRIS_PARSE_SKIP_DUP_DETECTION) == 0;
|
|
911
|
+
uint32_t dh = 0;
|
|
912
|
+
if (need_dup_hash) {
|
|
913
|
+
dh = 2166136261u ^ (unsigned char)name_start[0];
|
|
914
|
+
dh *= 16777619u;
|
|
915
|
+
}
|
|
907
916
|
const char* attr_colon =
|
|
908
917
|
(name_start[0] == ':') ? name_start : NULL;
|
|
909
918
|
p->pos++;
|
|
@@ -912,19 +921,19 @@ static int dp_parse_attrs(DParser* p, LeptrisElement elem) {
|
|
|
912
921
|
for (;;) {
|
|
913
922
|
char c0 = s[0];
|
|
914
923
|
if (DP_UNLIKELY(!IS_NAME_CHAR(c0))) break;
|
|
915
|
-
dh ^= (unsigned char)c0; dh *= 16777619u;
|
|
924
|
+
if (need_dup_hash) { dh ^= (unsigned char)c0; dh *= 16777619u; }
|
|
916
925
|
if (c0 == ':') attr_colon = s;
|
|
917
926
|
char c1 = s[1];
|
|
918
927
|
if (DP_UNLIKELY(!IS_NAME_CHAR(c1))) { s += 1; break; }
|
|
919
|
-
dh ^= (unsigned char)c1; dh *= 16777619u;
|
|
928
|
+
if (need_dup_hash) { dh ^= (unsigned char)c1; dh *= 16777619u; }
|
|
920
929
|
if (c1 == ':') attr_colon = s + 1;
|
|
921
930
|
char c2 = s[2];
|
|
922
931
|
if (DP_UNLIKELY(!IS_NAME_CHAR(c2))) { s += 2; break; }
|
|
923
|
-
dh ^= (unsigned char)c2; dh *= 16777619u;
|
|
932
|
+
if (need_dup_hash) { dh ^= (unsigned char)c2; dh *= 16777619u; }
|
|
924
933
|
if (c2 == ':') attr_colon = s + 2;
|
|
925
934
|
char c3 = s[3];
|
|
926
935
|
if (DP_UNLIKELY(!IS_NAME_CHAR(c3))) { s += 3; break; }
|
|
927
|
-
dh ^= (unsigned char)c3; dh *= 16777619u;
|
|
936
|
+
if (need_dup_hash) { dh ^= (unsigned char)c3; dh *= 16777619u; }
|
|
928
937
|
if (c3 == ':') attr_colon = s + 3;
|
|
929
938
|
s += 4;
|
|
930
939
|
}
|
|
@@ -1258,6 +1267,7 @@ static inline LeptrisTextNode* dp_text_create_norm(DParser* p,
|
|
|
1258
1267
|
* bearing declaration whose element matches, materialize the value
|
|
1259
1268
|
* when the element does not carry the attribute. */
|
|
1260
1269
|
static struct leptris_document* direct_parse_internal(char* buf, size_t len,
|
|
1270
|
+
unsigned skip_flags,
|
|
1261
1271
|
int owns_buffer,
|
|
1262
1272
|
int drop_ws_text,
|
|
1263
1273
|
int apply_dtd_attrs,
|
|
@@ -1474,6 +1484,7 @@ static struct leptris_document* direct_parse_internal(char* buf, size_t len,
|
|
|
1474
1484
|
p.end = buf + len;
|
|
1475
1485
|
p.pool = pool;
|
|
1476
1486
|
p.doc = doc;
|
|
1487
|
+
p.skip_flags = skip_flags;
|
|
1477
1488
|
p.depth = 0;
|
|
1478
1489
|
p.root = NULL;
|
|
1479
1490
|
p.dc_head = NULL;
|
|
@@ -1823,7 +1834,8 @@ static struct leptris_document* direct_parse_internal(char* buf, size_t len,
|
|
|
1823
1834
|
/* Parse attributes (scans from the delimiter position). */
|
|
1824
1835
|
int self_closing = dp_parse_attrs(&p, elem);
|
|
1825
1836
|
if (self_closing < 0) goto fail;
|
|
1826
|
-
if (p.line_offsets_ok
|
|
1837
|
+
if (p.line_offsets_ok &&
|
|
1838
|
+
!(p.skip_flags & LEPTRIS_PARSE_SKIP_SOURCE_POSITIONS)) {
|
|
1827
1839
|
uint32_t ste = (uint32_t)(p.pos - p.buf);
|
|
1828
1840
|
p.pos_slot_stack[p.depth] = leptris_elem_pos_record(
|
|
1829
1841
|
p.doc, elem, ste, self_closing ? ste : 0u);
|
|
@@ -3080,7 +3092,7 @@ struct leptris_document* direct_parse(const char* xml, size_t len) {
|
|
|
3080
3092
|
struct leptris_document* d = dp_il_try(xml, len, 0, 0, 0);
|
|
3081
3093
|
if (d) return d;
|
|
3082
3094
|
}
|
|
3083
|
-
return direct_parse_internal((char*)xml, len, 2, 0, 0, 0);
|
|
3095
|
+
return direct_parse_internal((char*)xml, len, 0, 2, 0, 0, 0);
|
|
3084
3096
|
}
|
|
3085
3097
|
|
|
3086
3098
|
/* Public flagged entry (LEPTRIS_PARSE_DROP_WS_TEXT et al.). */
|
|
@@ -3095,7 +3107,10 @@ struct leptris_document* direct_parse_flags(const char* xml, size_t len,
|
|
|
3095
3107
|
(parse_flags & LEPTRIS_PARSE_DTDATTR) ? 1 : 0);
|
|
3096
3108
|
if (d) return d;
|
|
3097
3109
|
}
|
|
3098
|
-
return direct_parse_internal((char*)xml, len,
|
|
3110
|
+
return direct_parse_internal((char*)xml, len,
|
|
3111
|
+
parse_flags & (LEPTRIS_PARSE_SKIP_DUP_DETECTION |
|
|
3112
|
+
LEPTRIS_PARSE_SKIP_SOURCE_POSITIONS),
|
|
3113
|
+
2,
|
|
3099
3114
|
(parse_flags & LEPTRIS_PARSE_DROP_WS_TEXT)
|
|
3100
3115
|
? 1 : 0,
|
|
3101
3116
|
(parse_flags & LEPTRIS_PARSE_DTDATTR)
|
|
@@ -3108,5 +3123,5 @@ struct leptris_document* direct_parse_flags(const char* xml, size_t len,
|
|
|
3108
3123
|
struct leptris_document* direct_parse_inplace(char* buf, size_t len) {
|
|
3109
3124
|
if (!buf || len == 0) return NULL;
|
|
3110
3125
|
buf[len] = '\0'; /* Ensure NUL termination */
|
|
3111
|
-
return direct_parse_internal(buf, len, 0, 0, 0, 0);
|
|
3126
|
+
return direct_parse_internal(buf, len, 0, 0, 0, 0, 0);
|
|
3112
3127
|
}
|
|
@@ -2393,6 +2393,75 @@ TEST(DomBasics, RawAttributesBackfillWhenXmlnsArrivesLate) {
|
|
|
2393
2393
|
leptris_document_free(doc);
|
|
2394
2394
|
}
|
|
2395
2395
|
|
|
2396
|
+
/* Lane-18 round 18 (leptris#1436 Door A): the SKIP_ parse flags let
|
|
2397
|
+
* performance-critical callers drop diagnostics features pugixml
|
|
2398
|
+
* does not carry. Defaults keep every feature ON — zero-initialized
|
|
2399
|
+
* flag words parse exactly like today. */
|
|
2400
|
+
TEST(DomBasics, ParseSkipDupDetectionAdmitsBothSilently) {
|
|
2401
|
+
/* Default-lane pin: the interleaved lane always tracks (its
|
|
2402
|
+
* builder is a separate construction path). */
|
|
2403
|
+
if (getenv("LEPTRIS_INTERLEAVED")) GTEST_SKIP();
|
|
2404
|
+
const char xml[] = "<r a='1' a='2'/>";
|
|
2405
|
+
LeptrisDocument d0 =
|
|
2406
|
+
leptris_parse_string_flags(xml, std::strlen(xml),
|
|
2407
|
+
(LeptrisParseFlags)0, nullptr);
|
|
2408
|
+
ASSERT_NE(d0, nullptr);
|
|
2409
|
+
LeptrisElement r0 = leptris_document_root(d0);
|
|
2410
|
+
EXPECT_EQ(leptris_document_parse_diag_count(d0), 1u);
|
|
2411
|
+
EXPECT_EQ(leptris_element_attributes_raw(r0, nullptr, nullptr, 0), 1u);
|
|
2412
|
+
leptris_document_free(d0);
|
|
2413
|
+
|
|
2414
|
+
LeptrisDocument d1 = leptris_parse_string_flags(
|
|
2415
|
+
xml, std::strlen(xml), LEPTRIS_PARSE_SKIP_DUP_DETECTION,
|
|
2416
|
+
nullptr);
|
|
2417
|
+
ASSERT_NE(d1, nullptr);
|
|
2418
|
+
LeptrisElement r1 = leptris_document_root(d1);
|
|
2419
|
+
EXPECT_EQ(leptris_document_parse_diag_count(d1), 0u);
|
|
2420
|
+
EXPECT_EQ(leptris_element_attributes_raw(r1, nullptr, nullptr, 0), 2u);
|
|
2421
|
+
const char* q[2]; const char* v[2];
|
|
2422
|
+
ASSERT_EQ(leptris_element_attributes_raw(r1, q, v, 2), 2u);
|
|
2423
|
+
EXPECT_STREQ(q[0], "a"); EXPECT_STREQ(v[0], "1");
|
|
2424
|
+
EXPECT_STREQ(q[1], "a"); EXPECT_STREQ(v[1], "2");
|
|
2425
|
+
/* Queries still resolve first-wins by chain order. */
|
|
2426
|
+
EXPECT_STREQ(leptris_element_attribute(r1, "a"), "1");
|
|
2427
|
+
leptris_document_free(d1);
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
TEST(DomBasics, ParseSkipPositionsDegradesColumnsToZeros) {
|
|
2431
|
+
if (getenv("LEPTRIS_INTERLEAVED")) GTEST_SKIP();
|
|
2432
|
+
const char xml[] = "<r>\n <e id='7'/>\n</r>";
|
|
2433
|
+
LeptrisDocument d0 =
|
|
2434
|
+
leptris_parse_string_flags(xml, std::strlen(xml),
|
|
2435
|
+
(LeptrisParseFlags)0, nullptr);
|
|
2436
|
+
ASSERT_NE(d0, nullptr);
|
|
2437
|
+
LeptrisElement e0 = leptris_element_child(
|
|
2438
|
+
leptris_document_root(d0), 0);
|
|
2439
|
+
LeptrisSourcePosition p0;
|
|
2440
|
+
memset(&p0, 0, sizeof(p0));
|
|
2441
|
+
leptris_node_source_position((LeptrisNodeRef)e0, &p0);
|
|
2442
|
+
EXPECT_GT(p0.line, 0);
|
|
2443
|
+
EXPECT_GT(p0.col_start, 0);
|
|
2444
|
+
leptris_document_free(d0);
|
|
2445
|
+
|
|
2446
|
+
LeptrisDocument d1 = leptris_parse_string_flags(
|
|
2447
|
+
xml, std::strlen(xml), LEPTRIS_PARSE_SKIP_SOURCE_POSITIONS,
|
|
2448
|
+
nullptr);
|
|
2449
|
+
ASSERT_NE(d1, nullptr);
|
|
2450
|
+
LeptrisElement e1 = leptris_element_child(
|
|
2451
|
+
leptris_document_root(d1), 0);
|
|
2452
|
+
LeptrisSourcePosition p1;
|
|
2453
|
+
memset(&p1, 0, sizeof(p1));
|
|
2454
|
+
leptris_node_source_position((LeptrisNodeRef)e1, &p1);
|
|
2455
|
+
/* Line still resolves (base.line + the lazy newline table).
|
|
2456
|
+
* The journaled columns degrade: col_start falls back to the
|
|
2457
|
+
* generic column 1 (start offset 0), col_end is never written
|
|
2458
|
+
* (element_end 0 -> the setter skips it). */
|
|
2459
|
+
EXPECT_GT(p1.line, 0);
|
|
2460
|
+
EXPECT_EQ(p1.col_start, 1);
|
|
2461
|
+
EXPECT_EQ(p1.col_end, 0);
|
|
2462
|
+
leptris_document_free(d1);
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2396
2465
|
/* Issue #635: the raw attribute view carries xmlns declarations
|
|
2397
2466
|
* interleaved among the attributes in SOURCE order — the mixed
|
|
2398
2467
|
* qname-ordered list the streaming transports deliver, which the
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json",
|
|
3
3
|
"name": "leptris",
|
|
4
|
-
"version": "1.9.
|
|
4
|
+
"version": "1.9.273",
|
|
5
5
|
"description": "High-performance XML parser and XPath 1.0 engine in C (libleptris)",
|
|
6
6
|
"homepage": "https://github.com/leptris/leptris",
|
|
7
7
|
"license": "MIT",
|