leptris 1.9.331.0 → 1.9.334.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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -0
  3. data/Rakefile +19 -1
  4. data/lib/leptris/vendor/aarch64-linux/libleptris.so +0 -0
  5. data/lib/leptris/vendor/aarch64-linux-musl/libleptris.so +0 -0
  6. data/lib/leptris/vendor/arm-linux/libleptris.so +0 -0
  7. data/lib/leptris/vendor/arm-linux-musl/libleptris.so +0 -0
  8. data/lib/leptris/vendor/arm64-darwin/libleptris.dylib +0 -0
  9. data/lib/leptris/vendor/arm64-darwin/libutf8proc.3.dylib +0 -0
  10. data/lib/leptris/vendor/ppc64le-linux/libleptris.so +0 -0
  11. data/lib/leptris/vendor/s390x-linux-musl/libleptris.so +0 -0
  12. data/lib/leptris/vendor/x86_64-darwin/libleptris.dylib +0 -0
  13. data/lib/leptris/vendor/x86_64-darwin/libutf8proc.3.dylib +0 -0
  14. data/lib/leptris/vendor/x86_64-linux/libleptris.so +0 -0
  15. data/lib/leptris/vendor/x86_64-linux-musl/libleptris.so +0 -0
  16. data/lib/leptris/version.rb +1 -1
  17. data/lib/leptris/xml/cdata.rb +4 -0
  18. data/lib/leptris/xml/comment.rb +4 -0
  19. data/lib/leptris/xml/descriptor.rb +55 -0
  20. data/lib/leptris/xml/entity_reference.rb +4 -0
  21. data/lib/leptris/xml/ffi.rb +25 -0
  22. data/lib/leptris/xml/node.rb +1 -0
  23. data/lib/leptris/xml/processing_instruction.rb +4 -0
  24. data/lib/leptris/xml/text.rb +4 -0
  25. data/vendor-src/README.md +1 -1
  26. data/vendor-src/libleptris/CHANGELOG.md +65 -0
  27. data/vendor-src/libleptris/CMakeLists.txt +1 -1
  28. data/vendor-src/libleptris/src/include/leptris/descriptor.h +30 -0
  29. data/vendor-src/libleptris/src/include/leptris.h +5 -0
  30. data/vendor-src/libleptris/src/leptris/descriptor/plan.c +144 -0
  31. data/vendor-src/libleptris/src/leptris/dom/element_modify.c +7 -0
  32. data/vendor-src/libleptris/src/leptris/xsd/compile.c +241 -4
  33. data/vendor-src/libleptris/src/leptris/xsd/content_model.c +50 -0
  34. data/vendor-src/libleptris/src/leptris/xsd/validate.c +190 -8
  35. data/vendor-src/libleptris/src/leptris/xsd/xsd_internal.h +34 -0
  36. data/vendor-src/libleptris/test/abi/test_descriptor.cpp +95 -0
  37. data/vendor-src/libleptris/test/xsd/test_xsd_compile.cpp +380 -0
  38. data/vendor-src/libleptris/vcpkg.json +1 -1
  39. metadata +2 -2
@@ -8,6 +8,7 @@
8
8
  * accumulate in the schema-owned error list — the RNG accessor
9
9
  * pattern: validate returns 1/0, errors enumerate every miss.
10
10
  */
11
+ #include <ctype.h>
11
12
  #include <stdio.h>
12
13
  #include <stdlib.h>
13
14
  #include <string.h>
@@ -50,6 +51,7 @@ static void verr(struct leptris_xsd_schema* s, const char* fmt,
50
51
  const char* a, const char* b);
51
52
  struct xsd_validator {
52
53
  struct leptris_xsd_schema* s;
54
+ LeptrisDocument doc; /* the instance (assertion evaluation) */
53
55
  struct xsd_error* errors;
54
56
  struct xsd_error* tail;
55
57
  size_t count;
@@ -86,16 +88,20 @@ static void verrf(struct xsd_validator* v, const char* what,
86
88
 
87
89
  /* ---- lexical helpers ---------------------------------------------- */
88
90
 
91
+ static int simple_is_numeric(struct leptris_xsd_schema* s,
92
+ const XsdSimple* st);
93
+
89
94
  static int text_valid(struct xsd_validator* v,
90
95
  struct leptris_xsd_schema* s, const char* type,
91
96
  const char* lexical, const char* what,
92
- const char* name) {
97
+ const char* name, LeptrisElement elem) {
93
98
  if (!type) return 1;
94
99
  int r;
100
+ const XsdSimple* st = NULL;
95
101
  if (strncmp(type, "xs:", 3) == 0)
96
102
  r = xsd_builtin_valid(type, lexical);
97
103
  else {
98
- const XsdSimple* st = xsd_find_simple_pub(s, type);
104
+ st = xsd_find_simple_pub(s, type);
99
105
  if (!st) return 1; /* unresolved: accept */
100
106
  r = xsd_simple_valid_chain(s, st, lexical);
101
107
  }
@@ -103,6 +109,59 @@ static int text_valid(struct xsd_validator* v,
103
109
  verrf(v, what, name, "lexical value does not match its type");
104
110
  return 0;
105
111
  }
112
+ /* XSD 1.1 xs:assertion facet (document validation only):
113
+ * $value binds the lexical — numeric lexicals bind bare,
114
+ * everything else as a string — and the context is the
115
+ * owning element. */
116
+ if (st && st->assertion && elem && v->doc) {
117
+ XsdSimple* mut = (XsdSimple*)st;
118
+ if (!mut->assertion_tried) {
119
+ mut->assertion_tried = 1;
120
+ if (xsd_schema_version_11(s)) {
121
+ /* eval_params binds DECLARED external variables —
122
+ * the test compiles under a $value declaration */
123
+ char wrapped[1024];
124
+ int w = snprintf(wrapped, sizeof(wrapped),
125
+ "declare variable $value external; %s",
126
+ mut->assertion);
127
+ if (w > 0 && (size_t)w < sizeof(wrapped))
128
+ mut->assertion_q =
129
+ leptris_xquery_parse(wrapped, (size_t)w);
130
+ }
131
+ }
132
+ if (mut->assertion_q) {
133
+ char sel[256];
134
+ int numeric =
135
+ simple_is_numeric(s, st) && *lexical &&
136
+ strspn(lexical, "0123456789.+-eE") == strlen(lexical);
137
+ if (numeric)
138
+ snprintf(sel, sizeof(sel), "%s", lexical);
139
+ else {
140
+ size_t w = 0;
141
+ sel[w++] = '\'';
142
+ for (const char* p = lexical; *p && w < sizeof(sel) - 3;
143
+ p++) {
144
+ if (*p == '\'') sel[w++] = '\'';
145
+ sel[w++] = *p;
146
+ }
147
+ sel[w++] = '\'';
148
+ sel[w] = 0;
149
+ }
150
+ const char* names[1] = {"value"};
151
+ const char* sels[1] = {sel};
152
+ LeptrisXPathResult r2 = leptris_xquery_eval_params(
153
+ mut->assertion_q, v->doc, elem, names, sels, 1);
154
+ int ebv = 0;
155
+ if (r2) {
156
+ ebv = leptris_xpath_result_boolean(r2);
157
+ leptris_xpath_result_free(r2);
158
+ }
159
+ if (!ebv) {
160
+ verrf(v, what, name, "assertion failed");
161
+ return 0;
162
+ }
163
+ }
164
+ }
106
165
  return 1;
107
166
  }
108
167
 
@@ -201,6 +260,59 @@ static void validate_children(struct xsd_validator* v,
201
260
  }
202
261
  }
203
262
 
263
+ /* $value binds typed: a numeric-derived simple type binds the
264
+ * lexical as a number literal, everything else as a string — the
265
+ * TYPE decides, not the lexical's shape ("5" under xs:string is
266
+ * the string '5'). */
267
+ static int numeric_builtin(const char* b) {
268
+ static const char* nums[] = {
269
+ "xs:integer", "xs:decimal", "xs:double", "xs:float",
270
+ "xs:int", "xs:long", "xs:short", "xs:byte",
271
+ "xs:nonNegativeInteger", "xs:positiveInteger",
272
+ "xs:negativeInteger", "xs:nonPositiveInteger",
273
+ "xs:unsignedLong", "xs:unsignedInt",
274
+ "xs:unsignedShort", "xs:unsignedByte", NULL};
275
+ for (int i = 0; nums[i]; i++)
276
+ if (strcmp(b, nums[i]) == 0) return 1;
277
+ return 0;
278
+ }
279
+
280
+ static int simple_is_numeric(struct leptris_xsd_schema* s,
281
+ const XsdSimple* st) {
282
+ for (int d = 0; st && d < 32; d++) {
283
+ if (!st->base) return 0;
284
+ if (strncmp(st->base, "xs:", 3) == 0)
285
+ return numeric_builtin(st->base);
286
+ st = xsd_find_simple_pub(s, st->base);
287
+ }
288
+ return 0;
289
+ }
290
+
291
+ /* xs:anyAttribute grammar: ##any (or an absent attribute),
292
+ * ##other, ##targetNamespace, ##local, or a space-separated URI
293
+ * list. */
294
+ static int any_attr_matches(const char* grammar, const char* uri,
295
+ const char* target_ns) {
296
+ if (!grammar || strcmp(grammar, "##any") == 0) return 1;
297
+ if (strcmp(grammar, "##other") == 0)
298
+ return uri && target_ns && strcmp(uri, target_ns) != 0;
299
+ if (strcmp(grammar, "##targetNamespace") == 0)
300
+ return uri && target_ns && strcmp(uri, target_ns) == 0;
301
+ if (strcmp(grammar, "##local") == 0) return uri == NULL;
302
+ /* URI list */
303
+ const char* p = grammar;
304
+ while (*p) {
305
+ while (*p == ' ' || *p == '\t') p++;
306
+ if (!*p) break;
307
+ const char* start = p;
308
+ while (*p && *p != ' ' && *p != '\t') p++;
309
+ size_t len = (size_t)(p - start);
310
+ if (uri && strlen(uri) == len && strncmp(uri, start, len) == 0)
311
+ return 1;
312
+ }
313
+ return 0;
314
+ }
315
+
204
316
  static void validate_attributes(struct xsd_validator* v,
205
317
  struct leptris_xsd_schema* s,
206
318
  LeptrisElement elem,
@@ -231,11 +343,37 @@ static void validate_attributes(struct xsd_validator* v,
231
343
  verrf(v, "attribute", a->name, "required but absent");
232
344
  continue;
233
345
  }
234
- text_valid(v, s, a->type, val, "attribute", a->name);
346
+ text_valid(v, s, a->type, val, "attribute", a->name, elem);
235
347
  if (a->fixed && strcmp(val, a->fixed) != 0)
236
348
  verrf(v, "attribute", a->name,
237
349
  "value does not match the fixed value");
238
350
  }
351
+ /* strict: every instance attribute must be declared (or pass
352
+ * the type's xs:anyAttribute grammar). Namespace declarations
353
+ * are not attributes. */
354
+ for (LeptrisAttribute at = leptris_element_first_attribute(elem);
355
+ at; at = leptris_attribute_next(at)) {
356
+ const char* qn = leptris_attribute_get_name(at);
357
+ if (!qn || strncmp(qn, "xmlns", 5) == 0) continue;
358
+ const char* colon = strchr(qn, ':');
359
+ const char* local = colon ? colon + 1 : qn;
360
+ int declared = 0;
361
+ for (XsdAttrDecl* a = ta->attrs; a && !declared; a = a->next)
362
+ if (strcmp(a->name, local) == 0 ||
363
+ strcmp(a->name, qn) == 0)
364
+ declared = 1;
365
+ if (declared) continue;
366
+ if (ta->any_attr_ns) {
367
+ /* namespace of the attribute: engine-side attributes
368
+ * carry their uri through the expanded form */
369
+ const char* auri =
370
+ leptris_attribute_namespace_uri(at);
371
+ if (any_attr_matches(ta->any_attr_ns, auri,
372
+ xsd_target_ns(s)))
373
+ continue;
374
+ }
375
+ verrf(v, "attribute", local, "not declared (strict)");
376
+ }
239
377
  }
240
378
 
241
379
  static void validate_element(struct xsd_validator* v,
@@ -246,16 +384,39 @@ static void validate_element(struct xsd_validator* v,
246
384
  leptris_element_expanded_name(elem, &local, &prefix, &uri);
247
385
  if (!local) return;
248
386
  const XsdElementDecl* d = xsd_find_element(s, local);
249
- if (!d) return; /* undeclared: lax at this depth (strict later) */
387
+ if (!d) {
388
+ /* strict: the instance uses an element the schema never
389
+ * declared (a local particle would have typed it) */
390
+ verrf(v, "element", local, "not declared (strict)");
391
+ return;
392
+ }
250
393
 
251
- if (!d->type) return; /* anyType */
394
+ /* XSD 1.1: the first alternative whose test passes types the
395
+ * element for this instance (context = the element). */
396
+ const char* eff_type = d->type;
397
+ for (XsdAlternative* alt = d->alternatives; alt; alt = alt->next) {
398
+ int ebv = 0;
399
+ if (alt->compiled && v->doc) {
400
+ LeptrisXPathResult r =
401
+ leptris_xquery_eval(alt->compiled, v->doc, elem);
402
+ if (r) {
403
+ ebv = leptris_xpath_result_boolean(r);
404
+ leptris_xpath_result_free(r);
405
+ }
406
+ }
407
+ if (ebv) {
408
+ eff_type = alt->type;
409
+ break;
410
+ }
411
+ }
412
+ if (!eff_type) return; /* anyType */
252
413
  if (d->fixed) {
253
414
  const char* text = leptris_element_text(elem);
254
415
  if (text && *text && strcmp(text, d->fixed) != 0)
255
416
  verrf(v, "element", local,
256
417
  "content does not match the fixed value");
257
418
  }
258
- validate_local(v, s, elem, local, d->type);
419
+ validate_local(v, s, elem, local, eff_type);
259
420
  }
260
421
 
261
422
  /* Validate one element against a type name that comes from a
@@ -270,11 +431,27 @@ static void validate_local(struct xsd_validator* v,
270
431
  if (ct) {
271
432
  validate_attributes(v, s, elem, xsd_find_type_attrs(s, type));
272
433
  validate_children(v, s, elem, ct);
434
+ /* XSD 1.1: the type's xs:assert list — context is the
435
+ * element; evaluation errors are failures */
436
+ for (XsdAssert* a = ct->asserts; a; a = a->next) {
437
+ int ebv = 0;
438
+ if (a->compiled && v->doc) {
439
+ LeptrisXPathResult r =
440
+ leptris_xquery_eval(a->compiled, v->doc, elem);
441
+ if (r) {
442
+ ebv = leptris_xpath_result_boolean(r);
443
+ leptris_xpath_result_free(r);
444
+ }
445
+ }
446
+ if (!ebv)
447
+ verrf(v, "assertion", local, a->test ? a->test
448
+ : "(invalid)");
449
+ }
273
450
  if (ct->text_type) {
274
451
  const char* text = leptris_element_text(elem);
275
452
  if (text && *text)
276
453
  text_valid(v, s, ct->text_type, text, "element",
277
- local);
454
+ local, elem);
278
455
  }
279
456
  return;
280
457
  }
@@ -284,8 +461,12 @@ static void validate_local(struct xsd_validator* v,
284
461
  xsd_find_simple_pub(s, type)) {
285
462
  const char* text = leptris_element_text(elem);
286
463
  if (text && *text)
287
- text_valid(v, s, type, text, "element", local);
464
+ text_valid(v, s, type, text, "element", local, elem);
465
+ return;
288
466
  }
467
+ /* neither complex nor simple nor builtin: the schema is
468
+ * incomplete — loud, never a silent accept (#1615 class) */
469
+ verrf(v, "element", local, "type not found in the schema");
289
470
  }
290
471
 
291
472
  /* ---- slice 6: identity constraints -------------------------------- */
@@ -509,6 +690,7 @@ void xsd_validator_free(struct xsd_validator* v) {
509
690
 
510
691
  int xsd_validator_run(struct xsd_validator* v, LeptrisDocument doc) {
511
692
  if (!v || !doc) return -1;
693
+ v->doc = doc; /* XSD 1.1: assertion evaluation context */
512
694
  LeptrisElement root = leptris_document_root(doc);
513
695
  if (!root) return -1;
514
696
  validate_element(v, v->s, root, 0);
@@ -5,6 +5,8 @@
5
5
 
6
6
  #include <stddef.h>
7
7
 
8
+ #include "../../include/leptris/xquery/xquery.h"
9
+
8
10
  #ifdef __cplusplus
9
11
  extern "C" {
10
12
  #endif
@@ -57,6 +59,9 @@ typedef struct xsd_simple {
57
59
  long total_digits;
58
60
  long fraction_digits;
59
61
  char* whitespace; /* "preserve" | "replace" | "collapse" */
62
+ char* assertion; /* XSD 1.1: the xs:assertion test text */
63
+ LeptrisXQuery assertion_q; /* compiled lazily at first use */
64
+ int assertion_tried; /* lazy-compile guard */
60
65
  struct xsd_simple* next;
61
66
  } XsdSimple;
62
67
 
@@ -65,6 +70,15 @@ typedef struct xsd_simple {
65
70
  int xsd_simple_valid_chain(struct leptris_xsd_schema* s,
66
71
  const XsdSimple* t, const char* v);
67
72
 
73
+ /* XSD 1.1 (#1075 tier 2): one xs:assert bound to a complexType —
74
+ * the compiled XPath 2 test plus its source text. Evaluation
75
+ * errors count as assertion failure. */
76
+ typedef struct xsd_assert {
77
+ char* test; /* the wire expression, for reporting */
78
+ LeptrisXQuery compiled; /* NULL when compilation failed */
79
+ struct xsd_assert* next;
80
+ } XsdAssert;
81
+
68
82
  /* Content-model node: a particle (element ref / wildcard) or a
69
83
  * group (sequence | choice | all). Owned by the schema; freed
70
84
  * with the schema. */
@@ -88,8 +102,14 @@ typedef struct xsd_cm {
88
102
  struct xsd_cm* first_child; /* group children list */
89
103
  struct xsd_cm* next; /* sibling link within the parent group */
90
104
  char* text_type; /* simpleContent wrapper: the text's type ref */
105
+ XsdAssert* asserts; /* XSD 1.1: the type's xs:assert list */
106
+ struct xsd_cm* open_any; /* XSD 1.1: xs:openContent wildcard */
107
+ int oc_mode; /* 0 none/unset, 1 suffix, 2 interleave */
108
+ int oc_owned; /* the wildcard node is this model's own */
91
109
  } XsdCm;
92
110
 
111
+
112
+
93
113
  /* A top-level xs:group definition: name + the model its refs
94
114
  * splice in (the definition's single sequence/choice/all). */
95
115
  typedef struct xsd_group_def {
@@ -98,15 +118,28 @@ typedef struct xsd_group_def {
98
118
  struct xsd_group_def* next;
99
119
  } XsdGroupDef;
100
120
 
121
+ /* XSD 1.1 (xs:schema @version="1.1") gates 1.1 enforcement. */
122
+ int xsd_schema_version_11(struct leptris_xsd_schema* s);
123
+
101
124
  /* Group refs resolve through the schema at NFA-build time —
102
125
  * forward references need no capture order. */
103
126
  XsdGroupDef* xsd_find_group(struct leptris_xsd_schema* s,
104
127
  const char* name);
105
128
 
129
+ /* XSD 1.1 (#1075 tier 2): one xs:alternative on an element
130
+ * declaration — the test plus the type it selects. */
131
+ typedef struct xsd_alternative {
132
+ char* test; /* wire text, for reporting */
133
+ LeptrisXQuery compiled; /* NULL when compilation failed */
134
+ char* type; /* the selected type name */
135
+ struct xsd_alternative* next;
136
+ } XsdAlternative;
137
+
106
138
  /* A top-level xs:element declaration (slice 3: name + type ref). */
107
139
  typedef struct xsd_element_decl {
108
140
  char* name;
109
141
  char* type; /* "xs:string"-style builtin or a local type name */
142
+ XsdAlternative* alternatives; /* XSD 1.1 conditional types */
110
143
  char* fixed; /* fixed element content: text must string-equal it */
111
144
  char* sub_head; /* substitutionGroup head (local name after ':') */
112
145
  struct xsd_element_decl* next;
@@ -157,6 +190,7 @@ typedef struct xsd_attrgroup_def {
157
190
 
158
191
  typedef struct xsd_type_attrs {
159
192
  char* type_name;
193
+ char* any_attr_ns; /* xs:anyAttribute grammar (NULL = absent) */
160
194
  XsdAttrDecl* attrs;
161
195
  struct xsd_type_attrs* next;
162
196
  } XsdTypeAttrs;
@@ -2131,3 +2131,98 @@ TEST(Plan1586, ExactUriRowStillWinsAndUnqualifiedStillBinds) {
2131
2131
  leptris_document_free(doc);
2132
2132
  leptris_plan_free(plan);
2133
2133
  }
2134
+
2135
+ // ---- #408: native result builder + address attach -------------------
2136
+ namespace {
2137
+
2138
+ /* leptris_plan_result_build: a flat op program produces the same
2139
+ * standalone result tree a walk would — wrappers, ns bindings, kid
2140
+ * order — so leptris_plan_serialize serves standalone
2141
+ * serialization with no DOM mint on the host side. */
2142
+ TEST(Plan408, ResultBuildFeedsSerialize) {
2143
+ LeptrisStatus st = LEPTRIS_OK;
2144
+ leptris_child_plan kids[1] = {};
2145
+ kids[0].wire_name = "p";
2146
+ kids[0].kind = LEPTRIS_PLAN_KIND_SCALAR;
2147
+ kids[0].type_tag = 1;
2148
+ kids[0].child_plan_index = -1;
2149
+ leptris_element_plan plans[1] = {};
2150
+ plans[0].element_name = "b";
2151
+ plans[0].ns_form = LEPTRIS_PLAN_NS_ANY;
2152
+ plans[0].child_count = 1;
2153
+ plans[0].child_plans = kids;
2154
+ leptris_plan_spec spec = {};
2155
+ spec.abi_version = leptris_plan_abi_version();
2156
+ spec.plan_count = 1;
2157
+ spec.plans = plans;
2158
+ LeptrisPlan plan = leptris_plan_build(&spec, &st);
2159
+ ASSERT_NE(plan, nullptr);
2160
+
2161
+ /* a SCALAR op IS the row's value (name and binding come from
2162
+ * the plan row) — no ELEMENT wrapper needed for scalar rows */
2163
+ leptris_plan_row_op ops[] = {
2164
+ {LEPTRIS_PLAN_OP_ELEMENT, 0, UINT32_MAX, NULL, NULL, 0},
2165
+ {LEPTRIS_PLAN_OP_ATTR, 0, UINT32_MAX, "id", "7", 1},
2166
+ {LEPTRIS_PLAN_OP_SCALAR, 0, 0, NULL, "first", 5},
2167
+ {LEPTRIS_PLAN_OP_END, 0, UINT32_MAX, NULL, NULL, 0},
2168
+ };
2169
+ LeptrisPlanResult r =
2170
+ leptris_plan_result_build(plan, ops, 4, &st);
2171
+ ASSERT_NE(r, nullptr);
2172
+ char* out = leptris_plan_serialize(plan, r, &st);
2173
+ ASSERT_NE(out, nullptr);
2174
+ EXPECT_STREQ(out, "<b id=\"7\"><p>first</p></b>");
2175
+ leptris_free_string(out);
2176
+ leptris_plan_result_free(r);
2177
+ leptris_plan_free(plan);
2178
+ }
2179
+
2180
+ TEST(Plan408, ResultBuildRejectsBadPrograms) {
2181
+ LeptrisStatus st = LEPTRIS_OK;
2182
+ leptris_element_plan plans[1] = {};
2183
+ plans[0].element_name = "b";
2184
+ plans[0].ns_form = LEPTRIS_PLAN_NS_ANY;
2185
+ leptris_plan_spec spec = {};
2186
+ spec.abi_version = leptris_plan_abi_version();
2187
+ spec.plan_count = 1;
2188
+ spec.plans = plans;
2189
+ LeptrisPlan plan = leptris_plan_build(&spec, &st);
2190
+ ASSERT_NE(plan, nullptr);
2191
+
2192
+ /* END without an open element */
2193
+ leptris_plan_row_op bad1[] = {
2194
+ {LEPTRIS_PLAN_OP_END, 0, UINT32_MAX, NULL, NULL, 0}};
2195
+ EXPECT_EQ(leptris_plan_result_build(plan, bad1, 1, &st), nullptr);
2196
+ /* unclosed element */
2197
+ leptris_plan_row_op bad2[] = {
2198
+ {LEPTRIS_PLAN_OP_ELEMENT, 0, UINT32_MAX, NULL, NULL, 0}};
2199
+ EXPECT_EQ(leptris_plan_result_build(plan, bad2, 1, &st), nullptr);
2200
+ /* NULL/empty args */
2201
+ EXPECT_EQ(leptris_plan_result_build(NULL, bad2, 1, &st), nullptr);
2202
+ EXPECT_EQ(leptris_plan_result_build(plan, NULL, 0, &st), nullptr);
2203
+ leptris_plan_free(plan);
2204
+ }
2205
+
2206
+ /* leptris_element_add_child_addr: attach by raw addresses — the
2207
+ * binding's per-node FFI::Pointer mint (#408 ask 1) disappears. */
2208
+ TEST(Plan408, AddChildByAddress) {
2209
+ LeptrisStatus st = LEPTRIS_OK;
2210
+ LeptrisDocument doc = leptris_parse_string("<r/>", 4, &st);
2211
+ ASSERT_NE(doc, nullptr);
2212
+ LeptrisElement parent = leptris_document_root(doc);
2213
+ LeptrisElement child = leptris_element_create(doc, "c");
2214
+ ASSERT_NE(child, nullptr);
2215
+ EXPECT_EQ(leptris_element_add_child_addr(
2216
+ (uintptr_t)parent, (uintptr_t)child),
2217
+ LEPTRIS_OK);
2218
+ EXPECT_EQ(leptris_element_child_count(parent), 1u);
2219
+ LeptrisElement got = leptris_element_first_child_any(parent);
2220
+ ASSERT_NE(got, nullptr);
2221
+ EXPECT_STREQ(leptris_element_name(got), "c");
2222
+ /* NULL-shaped addresses are clean errors */
2223
+ EXPECT_NE(leptris_element_add_child_addr(0, (uintptr_t)child),
2224
+ LEPTRIS_OK);
2225
+ leptris_document_free(doc);
2226
+ }
2227
+
2228
+ } // namespace