leptris 1.9.311.3-arm-linux → 1.9.312.0-arm-linux

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -0
  3. data/Rakefile +1 -1
  4. data/lib/leptris/version.rb +1 -1
  5. data/lib/leptris/xml/descriptor.rb +42 -1
  6. data/lib/leptris/xml/ffi.rb +28 -3
  7. data/lib/leptris/xml/plan_value.rb +7 -0
  8. data/lib/libleptris.so +0 -0
  9. data/vendor-src/README.md +1 -1
  10. data/vendor-src/libleptris/CHANGELOG.md +45 -0
  11. data/vendor-src/libleptris/CMakeLists.txt +1 -1
  12. data/vendor-src/libleptris/bindings/rust/descriptor/src/lib.rs +14 -0
  13. data/vendor-src/libleptris/bindings/rust/tests/descriptor_abi.rs +5 -0
  14. data/vendor-src/libleptris/src/include/leptris/descriptor.h +45 -1
  15. data/vendor-src/libleptris/src/leptris/descriptor/plan.c +529 -23
  16. data/vendor-src/libleptris/src/leptris/dom/element_modify.c +50 -0
  17. data/vendor-src/libleptris/test/abi/test_descriptor.cpp +736 -1
  18. data/vendor-src/libleptris/test/abi/test_dom_mutation_bugs.cpp +67 -0
  19. data/vendor-src/libleptris/test/perf/test_perf.cpp +7 -3
  20. data/vendor-src/libleptris/test/xslt/libxslt_suite_general/bug-111.xml +17 -17
  21. data/vendor-src/libleptris/test/xslt/libxslt_suite_general/bug-111.xsl +45 -45
  22. data/vendor-src/libleptris/test/xslt/libxslt_suite_general/bug-115.xsl +19 -19
  23. data/vendor-src/libleptris/test/xslt/libxslt_suite_general/bug-156.imp1.imp +16 -16
  24. data/vendor-src/libleptris/test/xslt/libxslt_suite_general/bug-156.imp2.imp +10 -10
  25. data/vendor-src/libleptris/test/xslt/libxslt_suite_general/bug-156.xsl +12 -12
  26. data/vendor-src/libleptris/test/xslt/libxslt_suite_general/bug-165.xml +10 -10
  27. data/vendor-src/libleptris/test/xslt/libxslt_suite_general/bug-80.xml +6 -6
  28. data/vendor-src/libleptris/test/xslt/libxslt_suite_general/bug-80.xsl +29 -29
  29. data/vendor-src/libleptris/vcpkg.json +1 -1
  30. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0c602909069d653cf84a6f3f070c7da010fa57098de4c5b4fc99357f5e4ae50e
4
- data.tar.gz: 562138bda8f3ed62c677eb795aedc4e6d602432ca6b45da5743df0e1a39605f8
3
+ metadata.gz: 24a440011e4a6851d5061d8f705b893ba373225704f5598843e71dc3312af4cd
4
+ data.tar.gz: de6359b3f0966e2e0c0af65ecf6bc200d95f2c77c20c08c447c6c2a352cc70c2
5
5
  SHA512:
6
- metadata.gz: b342e2e96a9b744a5273f9cfe8844d43f089b6af63df52a23822980652f7992cd70d667974091873c27fc7017bb98b4a2af97bb5bc8f759945fc92f9164c486e
7
- data.tar.gz: 48d87fd8760043ae1bfe7c7874bb6ee3884c294264d739dc779ba9aaba80dc760371454aba014f431afeabda42938cd2b82128e8322633f88972fa2d20890126
6
+ metadata.gz: bc2d4c618ff6ac5be9014fd9e1924de7179e3dadd5556fecccbf9c0a44c526bb1945cc0f7fa703d90143f06a75c30c6e240f4f8914dc95fe10063eec18fce24e
7
+ data.tar.gz: 96d259bd4e4e526404e4483478bc619ed1116be358dcf1615e75c79c625e8e502c49b1c4960eda377b5ab99dd3313d8554e1e4cec6d59c84dd7f74f485ecd69d
data/CHANGELOG.md CHANGED
@@ -1,3 +1,34 @@
1
+ ## [1.9.312.0] - 2026-10-07
2
+
3
+ ### Added — engine sync (libleptris 1.9.312)
4
+
5
+ - **Descriptor#serialize — plan-guided serialization (#1551)**:
6
+ `leptris_plan_serialize` round-trips a walk result back to XML —
7
+ the plan supplies element wrappers (row wire_name + the new
8
+ `ns_prefix:` field on element/child/attr rows), the result the
9
+ content. Children emit in document order; rows with ns EXACT +
10
+ `ns_prefix:` emit prefixed names and every distinct (prefix,
11
+ uri) pair is declared exactly once, on the output root, in
12
+ first-encounter order; text/attr values escape; RAW members are
13
+ verbatim. The three plan structs take the additive trailing
14
+ `ns_prefix` field (attr row 40 → 48B; the row-size skew gate
15
+ holds). `PlanValue#result_ptr` exposes the whole-result handle
16
+ the face reads.
17
+ - **Wildcard child rows (#1552)**: `kind: :wildcard` binds every
18
+ element child no named sibling row bound — named rows win
19
+ regardless of row order; the row emits exactly one COLLECTION
20
+ (echoing wire_name/type_tag, even when empty); members keep
21
+ document order; an explicit `ns:` filters the remainder
22
+ (`ns: :none` binds no-namespace children alone; unset stays the
23
+ catch-all ANY default). PLAN_KIND_WILDCARD = 7.
24
+
25
+ ### Fixed — engine sync (1.9.312)
26
+
27
+ - **Root-level leaf splices adopt single-node (#1539)**: a leaf
28
+ directly on a scratch document's children chain (prolog
29
+ comment / PI) no longer reports LEPTRIS_ERROR_MEMORY on
30
+ cross-document append — spec-pinned in the adoption suite.
31
+
1
32
  ## [1.9.311.3] - 2026-10-06
2
33
 
3
34
  ### Performance
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.311"
11
+ LIBLEPTRIS_VERSION = "1.9.312"
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.311.3"
4
+ VERSION = "1.9.312.0"
5
5
  end
@@ -74,6 +74,12 @@ class Leptris::XML::Descriptor
74
74
  raw: Leptris::XML::FFI::PLAN_KIND_RAW,
75
75
  content: Leptris::XML::FFI::PLAN_KIND_CONTENT,
76
76
  callback: Leptris::XML::FFI::PLAN_KIND_CALLBACK,
77
+ # libleptris 1.9.312 (#1552): the remainder bucket — binds
78
+ # every element child no named sibling row bound. Emits one
79
+ # COLLECTION (even when empty); named rows win regardless of
80
+ # row order. child_plan_index >= 0 walks members through that
81
+ # plan; -1 (the default) emits RAW serialized subtrees.
82
+ wildcard: Leptris::XML::FFI::PLAN_KIND_WILDCARD,
77
83
  }.freeze
78
84
  private_constant :KINDS
79
85
 
@@ -177,6 +183,10 @@ class Leptris::XML::Descriptor
177
183
  end
178
184
  ep[:ns_form] = ns_form
179
185
  ep[:ns_uri] = ns_uri
186
+ # libleptris 1.9.312 (#1551): the root plan's serialization
187
+ # prefix (ns_form EXACT rows emit "prefix:local" and the
188
+ # xmlns declaration lands once on the output root).
189
+ ep[:ns_prefix] = anchor_string(anchors, plan[:ns_prefix]) if plan[:ns_prefix]
180
190
  attrs = plan[:attributes] || []
181
191
  attr_memory = attrs.empty? ? nil :
182
192
  ::FFI::MemoryPointer.new(Leptris::XML::FFI::AttrPlan, attrs.size)
@@ -198,6 +208,9 @@ class Leptris::XML::Descriptor
198
208
  ap[:ns_uri] =
199
209
  row[:ns].is_a?(Hash) ?
200
210
  anchor_string(anchors, row[:ns].fetch(:exact)) : nil
211
+ # libleptris 1.9.312 (#1551): serialization prefix — with
212
+ # ns_form EXACT the attribute emits as "prefix:local".
213
+ ap[:ns_prefix] = anchor_string(anchors, row[:ns_prefix]) if row[:ns_prefix]
201
214
  pack_predicates(ap, row, anchors)
202
215
  end
203
216
  ep[:attribute_count] = attrs.size
@@ -214,16 +227,24 @@ class Leptris::XML::Descriptor
214
227
  cp[:child_plan_index] = row[:child_plan_index] || -1
215
228
  # Rule-level ns form (libleptris 1.9.178, #1115): siblings
216
229
  # under one parent can require different URIs when set;
217
- # defaults to NONE for backward compatibility.
230
+ # defaults to NONE for backward compatibility. WILDCARD
231
+ # rows (#1552) read an explicit :ns through pad0 (an
232
+ # explicit :none filters no-namespace remainder alone;
233
+ # unset stays the catch-all ANY default).
218
234
  cp[:ns_form] =
219
235
  case row[:ns]
220
236
  when :any then Leptris::XML::FFI::PLAN_NS_ANY
221
237
  when Hash then Leptris::XML::FFI::PLAN_NS_EXACT
222
238
  else Leptris::XML::FFI::PLAN_NS_NONE
223
239
  end
240
+ cp[:pad0] = 1 if row[:kind] == :wildcard && row.key?(:ns)
224
241
  cp[:ns_uri] =
225
242
  row[:ns].is_a?(Hash) ?
226
243
  anchor_string(anchors, row[:ns].fetch(:exact)) : nil
244
+ # libleptris 1.9.312 (#1551): serialization prefix — with
245
+ # ns_form EXACT the element emits as "prefix:local" and
246
+ # its declaration lands once on the output root.
247
+ cp[:ns_prefix] = anchor_string(anchors, row[:ns_prefix]) if row[:ns_prefix]
227
248
  pack_predicates(cp, row, anchors)
228
249
  end
229
250
  ep[:child_count] = children.size
@@ -318,6 +339,26 @@ class Leptris::XML::Descriptor
318
339
  owner: true, plans: @plans, plan: @plans[0])
319
340
  end
320
341
 
342
+ # libleptris 1.9.312 (#1551): serialize a walk result back to
343
+ # XML guided by this plan — the plan supplies element wrappers
344
+ # (row wire_name + ns_prefix), the result the content; children
345
+ # emit in document order. Rows with ns_form EXACT + ns_prefix
346
+ # emit prefixed names and every distinct (prefix, uri) pair is
347
+ # declared exactly once, on the output root, first-encounter
348
+ # order. Text and attribute values are escaped; RAW members are
349
+ # verbatim.
350
+ def serialize(plan_value)
351
+ result_ptr = plan_value.result_ptr
352
+ status = ::FFI::MemoryPointer.new(:int)
353
+ raw = Leptris::XML::FFI.leptris_plan_serialize(
354
+ @handle, result_ptr, status)
355
+ if raw.null?
356
+ raise Leptris::XML::Error,
357
+ "plan serialize failed (status=#{status.read_int})"
358
+ end
359
+ Leptris::XML::FFI.read_owned_string(raw)
360
+ end
361
+
321
362
  # The fused loop (#1269b): source bytes → typed rows in ONE C
322
363
  # call — leptris_plan_materialize runs parse, walk, and free
323
364
  # natively; the returned PlanValue tree is standalone and the
@@ -306,7 +306,12 @@ module Leptris
306
306
  # unset = the historical wire-name lookup.
307
307
  :ns_form, :uint8,
308
308
  :pad_ns, :uint8,
309
- :ns_uri, :pointer
309
+ :ns_uri, :pointer,
310
+ # libleptris 1.9.312 (#1551): intended wire prefix
311
+ # for plan-path serialization (ns_form EXACT emits
312
+ # "ns_prefix:wire_name"). NULL keeps matching-only
313
+ # behavior.
314
+ :ns_prefix, :pointer
310
315
  end
311
316
  class ChildPlan < ::FFI::Struct
312
317
  layout :wire_name, :pointer,
@@ -321,7 +326,13 @@ module Leptris
321
326
  # #1272: element-side predicates (same semantics).
322
327
  :predicate_count, :uint16,
323
328
  :pad_pred, :uint16,
324
- :predicates, :pointer
329
+ :predicates, :pointer,
330
+ # libleptris 1.9.312 (#1551): serialization prefix,
331
+ # same contract as AttrPlan's. For WILDCARD rows
332
+ # (libleptris 1.9.312, #1552) pad0 marks an EXPLICIT
333
+ # ns_form (0 then filters no-namespace remainder);
334
+ # unset keeps the catch-all ANY default.
335
+ :ns_prefix, :pointer
325
336
  end
326
337
  class ElementPlan < ::FFI::Struct
327
338
  layout :element_name, :pointer,
@@ -333,7 +344,10 @@ module Leptris
333
344
  :attribute_plans, :pointer,
334
345
  :child_plans, :pointer,
335
346
  :flags, :uint16,
336
- :pad1, :uint16
347
+ :pad1, :uint16,
348
+ # libleptris 1.9.312 (#1551): the root plan's
349
+ # serialization prefix.
350
+ :ns_prefix, :pointer
337
351
  end
338
352
  class PlanSpec < ::FFI::Struct
339
353
  layout :abi_version, :uint32,
@@ -349,6 +363,11 @@ module Leptris
349
363
  PLAN_KIND_RAW = 4
350
364
  PLAN_KIND_CONTENT = 5
351
365
  PLAN_KIND_CALLBACK = 6
366
+ # libleptris 1.9.312 (#1552): catch-all row — binds every
367
+ # element child no named sibling row bound (named rows win
368
+ # regardless of order); emits one COLLECTION echoing the
369
+ # row's wire_name/type_tag, members in document order.
370
+ PLAN_KIND_WILDCARD = 7
352
371
 
353
372
  PLAN_FLAG_MIXED_CONTENT = 0x1
354
373
  PLAN_FLAG_ORDERED = 0x2
@@ -391,6 +410,12 @@ module Leptris
391
410
  :leptris_plan_result
392
411
  attach_function :leptris_plan_result_free,
393
412
  [:leptris_plan_result], :void
413
+ # libleptris 1.9.312 (#1551): serialize a walk result back
414
+ # to XML guided by its plan — the plan supplies element
415
+ # wrappers (row wire_name + ns_prefix), the result the
416
+ # content. Caller-freed string (leptris_free_string).
417
+ attach_function :leptris_plan_serialize,
418
+ [:leptris_plan, :leptris_plan_result, :pointer], :pointer
394
419
  attach_function :leptris_plan_value_kind,
395
420
  [:leptris_plan_result], :int
396
421
  attach_function :leptris_plan_value_name,
@@ -30,6 +30,13 @@ class Leptris::XML::PlanValue
30
30
  @counter = counter || [0]
31
31
  end
32
32
 
33
+ # The engine value handle — the ROOT value's is the whole-result
34
+ # handle whole-result faces (leptris_plan_serialize) read. Pass
35
+ # the value Descriptor#walk/#materialize returned.
36
+ def result_ptr
37
+ @ptr
38
+ end
39
+
33
40
  def kind
34
41
  @counter[0] += 1
35
42
  VALUE_KINDS.fetch(Leptris::XML::FFI.leptris_plan_value_kind(@ptr))
data/lib/libleptris.so CHANGED
Binary file
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.311
4
+ - libleptris v1.9.312
5
5
  - utf8proc v2.11.0
6
6
 
7
7
  Rebuild by hand:
@@ -1,5 +1,50 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.9.312] - 2026-10-06
4
+
5
+ ### Added
6
+
7
+ - **Plan-path serialization emits namespace declarations (#1551,
8
+ PR #1558).** New `leptris_plan_serialize(plan, result)`: the plan
9
+ supplies element wrappers, the result the content — children emit
10
+ in document order, collections are transparent, RAW members
11
+ verbatim, text and attributes escaped. Rows carrying
12
+ `ns_form = EXACT` plus the new trailing `ns_prefix` field emit
13
+ prefixed wire names, and every distinct (prefix, uri) pair is
14
+ declared exactly once on the output root in first-encounter
15
+ order. `leptris_attr_plan` takes the same field, so #1486-matched
16
+ attributes emit the plan-chosen spelling. Byte-parity gates on a
17
+ `w:document` corpus; additive trailing fields keep
18
+ `PLAN_ABI_VERSION` at 1 (64-bit attr row 40 -> 48 bytes). Rust
19
+ mirror (`leptris-descriptor`) updated in lockstep.
20
+ - **Wildcard (catch-all) child rows (#1552, PR #1555).**
21
+ `LEPTRIS_PLAN_KIND_WILDCARD` binds every element child no named
22
+ sibling row bound — named rows take precedence regardless of row
23
+ order (a two-pass walk over a completed `bound[]` map; a named
24
+ row that rejected a child does not reserve it). Namespace-form
25
+ aware (`pad0` marks the form explicit so `NS_NONE` is
26
+ expressible; unset keeps the catch-all ANY default), emits one
27
+ COLLECTION echoing the row wire_name/type_tag even when empty,
28
+ members in document order; `child_plan_index >= 0` walks members
29
+ through that plan. Rust mirror gains `PLAN_KIND_WILDCARD = 7`.
30
+
31
+ ### Fixed
32
+
33
+ - **Root-level leaf splice arguments adopt single-node (#1539,
34
+ PR #1554).** A leaf directly on a scratch document's children
35
+ chain (prolog/epilog comment or PI) reported
36
+ `LEPTRIS_ERROR_MEMORY` on cross-document append: the chain
37
+ copier hit the root element and misread it as OOM. The seam now
38
+ copies exactly the argument for the doc-level shape; in-element
39
+ leaves keep the #540 chain shape.
40
+ - **Emulated-i386 perf-gate stability (PR #1561).** The
41
+ indexed-child-access shape budget widens 6x -> 7.5x: the qemu
42
+ timer noise of the ILP32 leg flaked past 6x three times in one
43
+ day while every native leg measured ~3x. Linear ~3x, quadratic
44
+ ~9x, O(N^3) ~27x — the gate keeps its discrimination.
45
+
46
+
47
+
3
48
  ## [1.9.311] - 2026-10-06
4
49
 
5
50
  ### Added
@@ -3,7 +3,7 @@
3
3
  cmake_minimum_required(VERSION 3.20)
4
4
 
5
5
  project(leptris
6
- VERSION 1.9.311
6
+ VERSION 1.9.312
7
7
  DESCRIPTION "Fast XML parser and XPath evaluator in pure C"
8
8
  LANGUAGES C CXX
9
9
  )
@@ -31,6 +31,11 @@ pub const PLAN_KIND_NESTED: PlanKind = 3;
31
31
  pub const PLAN_KIND_RAW: PlanKind = 4;
32
32
  pub const PLAN_KIND_CONTENT: PlanKind = 5;
33
33
  pub const PLAN_KIND_CALLBACK: PlanKind = 6;
34
+ /// #1552: catch-all row — binds every element child no named
35
+ /// sibling row bound; emits one COLLECTION echoing the row
36
+ /// wire_name/type_tag; `child_plan_index >= 0` walks members
37
+ /// through that plan, else members are RAW serialized subtrees.
38
+ pub const PLAN_KIND_WILDCARD: PlanKind = 7;
34
39
 
35
40
  /// Element plan flags.
36
41
  pub const PLAN_FLAG_MIXED_CONTENT: u32 = 0x1;
@@ -83,6 +88,9 @@ pub struct AttrPlan {
83
88
  pub pad_ns: u8,
84
89
  /// `PLAN_NS_EXACT` only.
85
90
  pub ns_uri: *const c_char,
91
+ /// #1551: intended wire prefix for serialized output
92
+ /// (`leptris_plan_serialize`); meaningful with EXACT.
93
+ pub ns_prefix: *const c_char,
86
94
  }
87
95
 
88
96
  /// Child binding row.
@@ -107,6 +115,9 @@ pub struct ChildPlan {
107
115
  pub predicate_count: u16,
108
116
  pub pad_pred: u16,
109
117
  pub predicates: *const AttrPredicate,
118
+ /// #1551: intended wire prefix for serialized output
119
+ /// (`leptris_plan_serialize`); meaningful with EXACT.
120
+ pub ns_prefix: *const c_char,
110
121
  }
111
122
 
112
123
  /// One element plan: the attribute and child rows bound to a name.
@@ -127,6 +138,9 @@ pub struct ElementPlan {
127
138
  /// `PLAN_FLAG_*`.
128
139
  pub flags: u16,
129
140
  pub pad1: u16,
141
+ /// #1551: the root plan's intended wire prefix for serialized
142
+ /// output (`leptris_plan_serialize`); meaningful with EXACT.
143
+ pub ns_prefix: *const c_char,
130
144
  }
131
145
 
132
146
  /// Top-level plan specification handed to the engine.
@@ -86,6 +86,7 @@ fn plan_build_and_walk_round_trip() {
86
86
  ns_form: descriptor::PLAN_NS_NONE,
87
87
  pad_ns: 0,
88
88
  ns_uri: ptr::null(),
89
+ ns_prefix: ptr::null(),
89
90
  };
90
91
  let child_row = descriptor::ChildPlan {
91
92
  wire_name: title.as_ptr(),
@@ -98,6 +99,7 @@ fn plan_build_and_walk_round_trip() {
98
99
  predicate_count: 0,
99
100
  pad_pred: 0,
100
101
  predicates: ptr::null(),
102
+ ns_prefix: ptr::null(),
101
103
  };
102
104
  let book_plan = descriptor::ElementPlan {
103
105
  element_name: book.as_ptr(),
@@ -110,6 +112,7 @@ fn plan_build_and_walk_round_trip() {
110
112
  child_plans: &child_row,
111
113
  flags: 0,
112
114
  pad1: 0,
115
+ ns_prefix: ptr::null(),
113
116
  };
114
117
  let root_child = descriptor::ChildPlan {
115
118
  wire_name: book.as_ptr(),
@@ -124,6 +127,7 @@ fn plan_build_and_walk_round_trip() {
124
127
  predicate_count: 0,
125
128
  pad_pred: 0,
126
129
  predicates: ptr::null(),
130
+ ns_prefix: ptr::null(),
127
131
  };
128
132
  let root_plan = descriptor::ElementPlan {
129
133
  element_name: catalog.as_ptr(),
@@ -136,6 +140,7 @@ fn plan_build_and_walk_round_trip() {
136
140
  child_plans: &root_child,
137
141
  flags: 0,
138
142
  pad1: 0,
143
+ ns_prefix: ptr::null(),
139
144
  };
140
145
  let plans = [root_plan, book_plan];
141
146
  let spec = descriptor::PlanSpec {
@@ -65,7 +65,18 @@ typedef enum {
65
65
  LEPTRIS_PLAN_KIND_NESTED = 3, /* recurse into plans[child_plan_index] */
66
66
  LEPTRIS_PLAN_KIND_RAW = 4, /* verbatim serialized subtree */
67
67
  LEPTRIS_PLAN_KIND_CONTENT = 5, /* mixed-content text runs, doc order */
68
- LEPTRIS_PLAN_KIND_CALLBACK = 6 /* escape hatch: raw value + position */
68
+ LEPTRIS_PLAN_KIND_CALLBACK = 6, /* escape hatch: raw value + position */
69
+ /* #1552: catch-all row. Binds every element child that no
70
+ * named sibling row bound — named rows take precedence
71
+ * regardless of row-list position. Emits exactly one
72
+ * COLLECTION echoing the row's wire_name/type_tag (even when
73
+ * the remainder is empty). Members keep document order and
74
+ * echo the row type_tag. The row's ns_form filters members;
75
+ * an unset form means ANY namespace (catch-all), unlike
76
+ * named rows' historical NONE. child_plan_index >= 0 walks
77
+ * each member through that plan (ELEMENT members); -1 emits
78
+ * RAW serialized subtrees. */
79
+ LEPTRIS_PLAN_KIND_WILDCARD = 7 /* remainder bucket, named rows win */
69
80
  } LeptrisPlanKind;
70
81
 
71
82
  /* Element plan flags. */
@@ -118,6 +129,12 @@ typedef struct {
118
129
  uint8_t ns_form; /* LeptrisPlanNsForm */
119
130
  uint8_t pad_ns;
120
131
  const char* ns_uri; /* LEPTRIS_PLAN_NS_EXACT only */
132
+ /* #1551 (additive trailing field, the #1115 pattern): the
133
+ * intended wire prefix for serialized output. With ns_form
134
+ * EXACT, plan-path serialization emits the attribute as
135
+ * "ns_prefix:wire_name" (wire_name is the LOCAL name). NULL
136
+ * keeps matching-only behavior. */
137
+ const char* ns_prefix;
121
138
  } leptris_attr_plan;
122
139
 
123
140
  typedef struct {
@@ -137,6 +154,10 @@ typedef struct {
137
154
  * zero value) keeps the historical no-namespace+NS_LENIENT
138
155
  * behavior — the fields are consulted only when set. */
139
156
  uint8_t ns_form; /* LeptrisPlanNsForm */
157
+ /* #1552 WILDCARD rows only: nonzero marks ns_form as
158
+ * explicitly set — ns_form 0 then binds no-namespace
159
+ * remainder alone. Unset (0), the row is the catch-all ANY
160
+ * default. Ignored by named rows. */
140
161
  uint8_t pad0;
141
162
  const char* ns_uri; /* LEPTRIS_PLAN_NS_EXACT only */
142
163
  /* #1272: element-side predicates. Same semantics as attr_predicate
@@ -144,6 +165,12 @@ typedef struct {
144
165
  uint16_t predicate_count;
145
166
  uint16_t pad_pred;
146
167
  const leptris_attr_predicate* predicates;
168
+ /* #1551 (additive trailing field): the intended wire prefix
169
+ * for serialized output. With ns_form EXACT, the walk stamps
170
+ * the binding on values and plan-path serialization emits
171
+ * "ns_prefix:local" and the xmlns declaration (once, on the
172
+ * output root). NULL keeps matching-only behavior. */
173
+ const char* ns_prefix;
147
174
  } leptris_child_plan;
148
175
 
149
176
  typedef struct {
@@ -159,6 +186,9 @@ typedef struct {
159
186
  const leptris_child_plan* child_plans;
160
187
  uint16_t flags; /* LEPTRIS_PLAN_FLAG_* */
161
188
  uint16_t pad1;
189
+ /* #1551 (additive trailing field): the root plan's intended
190
+ * wire prefix, same contract as the child-row field. */
191
+ const char* ns_prefix;
162
192
  } leptris_element_plan;
163
193
 
164
194
  typedef struct {
@@ -222,6 +252,20 @@ LEPTRIS_API LeptrisPlanResult leptris_plan_walk(LeptrisDocument doc,
222
252
  LeptrisStatus* status);
223
253
  LEPTRIS_API void leptris_plan_result_free(LeptrisPlanResult result);
224
254
 
255
+ /* #1551: serialize a walk result back to XML, guided by its plan.
256
+ * The plan supplies element wrappers (row wire_name + ns_prefix)
257
+ * and the result supplies content; children emit in document order
258
+ * (the walk's order_index). Rows carrying ns_form EXACT +
259
+ * ns_prefix emit prefixed names, and every distinct (prefix, uri)
260
+ * pair used anywhere in the tree is declared exactly once, on the
261
+ * output root element, in first-encounter order. Attribute rows
262
+ * with ns_prefix emit "prefix:local". Text and attribute values
263
+ * are escaped; RAW members are verbatim. Returns a caller-freed
264
+ * string (leptris_free_string); NULL + status on failure. */
265
+ LEPTRIS_API char* leptris_plan_serialize(LeptrisPlan plan,
266
+ const LeptrisPlanResult result,
267
+ LeptrisStatus* status);
268
+
225
269
  /* ---- Result accessors (NULL-tolerant: NULL in, 0/NULL out) ------- */
226
270
 
227
271
  LEPTRIS_API LeptrisPlanValueKind leptris_plan_value_kind(const LeptrisPlanResult v);