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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cb2a0e979362ca73a36edb1e16e47d1e69db7e3ce0f4dc1efc3fe012eeadfd53
4
- data.tar.gz: ba0d63880b1060c14dda81a33b835c88c37a294dd093dd0bf9801d9ebea8070c
3
+ metadata.gz: eac60497cce482f9c8a82023dfe9af7437aca757483c90e2a8af52c0aa9f9f40
4
+ data.tar.gz: aadeb53448aabd955c1ede63cf1b468d50ab575100f0d7f3e57f1675a460f9f0
5
5
  SHA512:
6
- metadata.gz: f1ec1c35f4d191afdaee7e5b5e4c799532d1ea58f4ad388f63b4ebfc3b7fbce759022f1a6be01b341d167c6998b6adfe72619e7e90ba2c261a3f26ca6f5b577f
7
- data.tar.gz: 9c0d192018890d531cd645df8866164093baa9dbd9974fd0500b4bdc32ed566906fcb8d2bf3d7c748c223fb54df88124c6476f373d04fa21350b3739598ac7c4
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.270"
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leptris
4
- VERSION = "1.9.270.0"
4
+ VERSION = "1.9.273.0"
5
5
  end
@@ -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
- def child_plan_for_position(ptr)
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
- @counter[0] += 1
291
- child_name = Leptris::XML::FFI.leptris_plan_value_name(ptr)
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,7 +1,7 @@
1
1
  Sources vendored in this gem (recompile rights; the ruby
2
2
  variant builds them at install via extconf.rb):
3
3
 
4
- - libleptris v1.9.270
4
+ - libleptris v1.9.273
5
5
  - utf8proc v2.11.0
6
6
 
7
7
  Rebuild by hand:
@@ -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
@@ -3,7 +3,7 @@
3
3
  cmake_minimum_required(VERSION 3.20)
4
4
 
5
5
  project(leptris
6
- VERSION 1.9.270
6
+ VERSION 1.9.273
7
7
  DESCRIPTION "Fast XML parser and XPath evaluator in pure C"
8
8
  LANGUAGES C CXX
9
9
  )
@@ -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
- if (elem->first_attribute_off != 0) {
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
- uint32_t dh = 2166136261u ^ (unsigned char)name_start[0];
906
- dh *= 16777619u;
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, 2,
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.270",
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",
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.270.0
4
+ version: 1.9.273.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.