leptris 1.9.228.0 → 1.9.231.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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -0
  3. data/Rakefile +1 -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/ppc64le-linux/libleptris.so +0 -0
  10. data/lib/leptris/vendor/s390x-linux-musl/libleptris.so +0 -0
  11. data/lib/leptris/vendor/x86_64-darwin/libleptris.dylib +0 -0
  12. data/lib/leptris/vendor/x86_64-linux/libleptris.so +0 -0
  13. data/lib/leptris/vendor/x86_64-linux-musl/libleptris.so +0 -0
  14. data/lib/leptris/version.rb +1 -1
  15. data/vendor-src/README.md +1 -1
  16. data/vendor-src/libleptris/CHANGELOG.md +72 -0
  17. data/vendor-src/libleptris/CMakeLists.txt +1 -1
  18. data/vendor-src/libleptris/benchmarks/serialization/bench_leptris_serialize.c +4 -4
  19. data/vendor-src/libleptris/benchmarks/workflows/bench_end_to_end.c +1 -1
  20. data/vendor-src/libleptris/src/leptris/dom/compact.c +3 -1
  21. data/vendor-src/libleptris/src/leptris/dom/compact.h +13 -1
  22. data/vendor-src/libleptris/src/leptris/dom/element_modify.c +19 -13
  23. data/vendor-src/libleptris/src/leptris/dom/node_public.c +2 -2
  24. data/vendor-src/libleptris/src/leptris/dom/root_doc_map.c +120 -36
  25. data/vendor-src/libleptris/src/leptris/dom/root_doc_map.h +3 -1
  26. data/vendor-src/libleptris/src/leptris/dom/text.c +15 -5
  27. data/vendor-src/libleptris/src/leptris/dom/text.h +66 -11
  28. data/vendor-src/libleptris/src/leptris/dtd/validator.c +3 -1
  29. data/vendor-src/libleptris/src/leptris/flat/direct_parse.c +8 -7
  30. data/vendor-src/libleptris/src/leptris/html/html_parse.c +11 -11
  31. data/vendor-src/libleptris/src/leptris/serialize/serialize.c +5 -5
  32. data/vendor-src/libleptris/src/leptris/xinclude/xinclude.c +15 -10
  33. data/vendor-src/libleptris/src/leptris/xpath/evaluator_types.c +6 -2
  34. data/vendor-src/libleptris/src/leptris/xpath/functions.c +220 -31
  35. data/vendor-src/libleptris/src/leptris/xslt/xslt_exec.c +13 -6
  36. data/vendor-src/libleptris/test/CMakeLists.txt +3 -0
  37. data/vendor-src/libleptris/test/concurrency/test_root_doc_cross_thread.cpp +104 -0
  38. data/vendor-src/libleptris/test/dom/test_text_borrowed.cpp +36 -35
  39. data/vendor-src/libleptris/test/perf/test_perf.cpp +20 -3
  40. data/vendor-src/libleptris/test/xquery/qt3/fn/boolean.xml +1342 -0
  41. data/vendor-src/libleptris/test/xquery/qt3/fn/normalize-space.xml +429 -0
  42. data/vendor-src/libleptris/test/xquery/qt3/fn/string-length.xml +349 -0
  43. data/vendor-src/libleptris/test/xquery/qt3/fn/substring-after.xml +594 -0
  44. data/vendor-src/libleptris/test/xquery/qt3/fn/substring-before.xml +597 -0
  45. data/vendor-src/libleptris/test/xquery/qt3/fn/translate.xml +410 -0
  46. data/vendor-src/libleptris/test/xquery/test_qt3.cpp +38 -0
  47. data/vendor-src/libleptris/vcpkg.json +1 -1
  48. metadata +8 -22
  49. data/vendor-src/libleptris/benchmarks/results/BENCHMARK_REPORT.md +0 -226
  50. data/vendor-src/libleptris/benchmarks/results/baseline_report.md +0 -219
  51. data/vendor-src/libleptris/benchmarks/results/benchmark_20251223_134550.json +0 -13
  52. data/vendor-src/libleptris/benchmarks/results/benchmark_20260129_225844.json +0 -13
  53. data/vendor-src/libleptris/benchmarks/results/leptris_dom_output.txt +0 -24
  54. data/vendor-src/libleptris/benchmarks/results/leptris_modify.json +0 -36
  55. data/vendor-src/libleptris/benchmarks/results/leptris_modify_optimized.json +0 -36
  56. data/vendor-src/libleptris/benchmarks/results/leptris_modify_session1.json +0 -36
  57. data/vendor-src/libleptris/benchmarks/results/leptris_modify_session2.json +0 -36
  58. data/vendor-src/libleptris/benchmarks/results/leptris_serialize.json +0 -34
  59. data/vendor-src/libleptris/benchmarks/results/leptris_xpath_output.txt +0 -24
  60. data/vendor-src/libleptris/benchmarks/results/libxml2_dom_output.txt +0 -24
  61. data/vendor-src/libleptris/benchmarks/results/libxml2_xpath_output.txt +0 -24
  62. data/vendor-src/libleptris/benchmarks/results/phase16_session2.json +0 -21
  63. data/vendor-src/libleptris/benchmarks/results/phase17.5_session1.json +0 -30
  64. data/vendor-src/libleptris/benchmarks/results/phase17_session1.json +0 -30
  65. data/vendor-src/libleptris/benchmarks/results/pugixml_dom_output.txt +0 -51
  66. data/vendor-src/libleptris/benchmarks/results/xpath_axes_leptris.json +0 -318
  67. data/vendor-src/libleptris/benchmarks/results/xpath_axes_libxml2.json +0 -318
  68. data/vendor-src/libleptris/benchmarks/results/xpath_functions_leptris.json +0 -222
  69. data/vendor-src/libleptris/benchmarks/results/xpath_functions_libxml2.json +0 -222
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fa53c1b21f30aa89e5992390c40deb360eb8d94d0000ca5346e381b35b086f87
4
- data.tar.gz: e667ca695bb8b10ecd251ad03d77521adacf6829cdd5bac7ec9c4f9bcb5b42a0
3
+ metadata.gz: 7788b24a1aa6763d4bc2d101f7a9c50b30f0067e6ca7f84cf9c7335187b2b55a
4
+ data.tar.gz: 8eef3d332d6a5c31e9d9b15d1c453f68485930f026240b8087e662888738ee2a
5
5
  SHA512:
6
- metadata.gz: 2927fc583597d9c607816ad16cb293a73c9ab7647a57b77429b8efbf8124b6e7ff52b871c25825b826d3a70501dbd51d4cc4b628c4992ea916825bd3f42845fa
7
- data.tar.gz: a4a6371fa8e9e461db91e4705308b6ab9bc1920d08a76cee7d32f964f75bf9c5e9d16751359e6159b39fe8c0d3a57f0a6ffe5560806114f5d36b142385cd9204
6
+ metadata.gz: 3a4046fa5c520c295926bdc8857e19255a0e7cc739528a0df26fec89a7a05b014d07b930acca2f0bccdf695c6ce5056c3048d99877cb7b0a54afda9f1e689b23
7
+ data.tar.gz: d9efd5c7e9ee80a638e5210d8aa4e449d052c4e6b967ce3d3f85426b2acb56227e9444d344093ee90d4f6f02917364264abd174e0eb42ef1457b0e5661163974
data/CHANGELOG.md CHANGED
@@ -5,6 +5,39 @@ 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.231.0] - 2026-09-23
9
+
10
+ ### Fixed
11
+
12
+ - Vendored libleptris 1.9.231: the root-doc map is process-global
13
+ now — a document freed on a thread other than the one that
14
+ registered its roots (binding GC finalizers run on arbitrary
15
+ threads) no longer leaves a stale `{root -> doc}` entry in the
16
+ owner thread's map. Pre-fix, address-matched walks dereferenced
17
+ (and rewired) the freed document: the
18
+ `leptris_root_doc_unregister+0x6c` segfault under heavy parse
19
+ churn on macOS Ruby 3.3 (#321), plus silent freed-document
20
+ resolution through lookups and the per-thread memo. The memo
21
+ stays lock-free, validated by a global atomic generation.
22
+ - Also riding 1.9.230's string-length fix (continuation-count
23
+ table, ac6f9738): the old lead-byte table over-advanced past
24
+ the NUL, and the NFD spec expectation enshrined the phantom
25
+ byte (4.0). Re-pinned to the correct contracts — verbatim
26
+ one-argument passthrough (2 codepoints) and two-argument NFD
27
+ (splits the precomposed é, 3 codepoints).
28
+
29
+ ## [1.9.229.0] - 2026-09-23
30
+
31
+ ### Performance — engine sync (1.9.229)
32
+
33
+ - Vendored libleptris 1.9.229: the text node struct shrinks
34
+ 40 → 32 bytes on LP64 and ILP32 alike (#1285 slice 4b) — the
35
+ content pointer became an int32 self-relative offset with
36
+ doc-tagged overflow stamps (0 = NULL, shared sentinel for the
37
+ static empty string, byte offset otherwise; >2 GB deltas spill
38
+ to the overflow table tagged EXPLICIT). No binding-facing
39
+ surface changes; audit 351/351, suite 781/0.
40
+
8
41
  ## [1.9.228.0] - 2026-09-23
9
42
 
10
43
  ### Fixed — Text#content= on created-but-unattached text nodes (#318; engine 1.9.227–228)
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.228"
11
+ LIBLEPTRIS_VERSION = "1.9.231"
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.228.0"
4
+ VERSION = "1.9.231.0"
5
5
  end
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.228
4
+ - libleptris v1.9.231
5
5
  - utf8proc v2.11.0
6
6
 
7
7
  Rebuild by hand:
@@ -1,5 +1,77 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.9.231] - 2026-09-23
4
+
5
+ ### Fixed
6
+
7
+ - root-doc map is process-global — cross-thread document death poisoned owner-thread state (dom)
8
+
9
+
10
+
11
+ ## [1.9.230] - 2026-09-23
12
+
13
+ ### Added
14
+
15
+ - **xquery: QT3 stage-2 corpus slice — 228 newly gated conformance
16
+ cases across six core families** (translate, normalize-space,
17
+ substring-before, substring-after, boolean, string-length),
18
+ vendored from the W3C qt3tests suite with the driver's exact
19
+ `agree == run == pinned` gate. fn:number is deliberately not
20
+ adopted: its string assertions want canonical E-notation doubles,
21
+ which the number formatter deliberately does not print
22
+ (libxml2 xmlXPathFormatNumber parity is load-bearing for the
23
+ libxslt suite).
24
+
25
+ ### Fixed
26
+
27
+ - **xpath: `fn:translate` is now codepoint-correct.** The old
28
+ implementation built a 256-entry byte-level table, so every
29
+ multi-byte UTF-8 character matched byte-by-byte and astral
30
+ codepoints could not be addressed at all. The rewrite decodes
31
+ `from`/`to` into codepoints (first occurrence governs, per
32
+ F&O), supports cross-width replacements, and emits into a
33
+ growable buffer.
34
+ - **xpath: `fn:string-length` counts codepoints, not bytes** —
35
+ `string-length("𐀂")` returned 4; the walk now steps by
36
+ lead-byte length.
37
+ - **xpath: `substring-before`/`substring-after` accept the 3-arg
38
+ collation overload.** The codepoint-collation URI (under which
39
+ the collation is the default byte comparison) and the empty URI
40
+ are accepted; any other URI raises the unsupported-collation
41
+ error.
42
+ - **bench: the CI benchmark legs' fixture/result paths unified
43
+ under one convention** (harnesses read `fixtures/` and write
44
+ `results/` relative to their working directory; CI stages both
45
+ once). SAX and XPath benchmark results that were silently
46
+ skipped or failing to write now produce real data, and 21 stale
47
+ committed result files (including a nine-month-old comparison
48
+ report) are gone from the tree.
49
+
50
+
51
+
52
+ ## [1.9.229] - 2026-09-23
53
+
54
+ ### Performance
55
+
56
+ - **dom: text node 40 → 32 bytes — content pointer became an int32
57
+ self-relative offset, with doc-tagged overflow stamps (#1285
58
+ slice 4b).** The struct is base + five int32s on both LP64 and
59
+ ILP32 (content off, uint32 len, int32 next, int32 parent, int32
60
+ owner doc). Encoding: 0 = NULL, a shared sentinel = the static
61
+ empty string (xsl:strip-space writes land there instead of one
62
+ overflow-table entry per stripped node), any other value a byte
63
+ offset from the node. **Thread safety:** the rare >2 GB delta
64
+ spills to the overflow table tagged with the EXPLICIT owning
65
+ document — the direct-parse lane is overflow-table-free by design
66
+ (it never sets the TLS current-doc), so a TLS-tagged spill leaked
67
+ the entry past document teardown, which Linux CI ASAN/LSan
68
+ surfaced as aborting the two concurrency tests. Every content
69
+ stamp now carries its document; the untagged setter is deleted
70
+ from the codebase. Local gates: 1683/1683 both parse lanes; the
71
+ CI ASAN leg that caught the leak is green.
72
+
73
+
74
+
3
75
  ## [1.9.228] - 2026-09-23
4
76
 
5
77
  ### Fixed
@@ -3,7 +3,7 @@
3
3
  cmake_minimum_required(VERSION 3.20)
4
4
 
5
5
  project(leptris
6
- VERSION 1.9.228
6
+ VERSION 1.9.231
7
7
  DESCRIPTION "Fast XML parser and XPath evaluator in pure C"
8
8
  LANGUAGES C CXX
9
9
  )
@@ -35,7 +35,7 @@ static char* load_file(const char* path, size_t* size) {
35
35
  // Benchmark: Serialize small document
36
36
  static void bench_serialize_small(void) {
37
37
  size_t size;
38
- char* xml = load_file("benchmarks/fixtures/small.xml", &size);
38
+ char* xml = load_file("fixtures/small.xml", &size);
39
39
  if (!xml) {
40
40
  printf("ERROR: Could not load small.xml\n");
41
41
  return;
@@ -68,7 +68,7 @@ static void bench_serialize_small(void) {
68
68
  // Benchmark: Serialize medium document
69
69
  static void bench_serialize_medium(void) {
70
70
  size_t size;
71
- char* xml = load_file("benchmarks/fixtures/medium.xml", &size);
71
+ char* xml = load_file("fixtures/medium.xml", &size);
72
72
  if (!xml) {
73
73
  printf("ERROR: Could not load medium.xml\n");
74
74
  return;
@@ -101,7 +101,7 @@ static void bench_serialize_medium(void) {
101
101
  // Benchmark: Serialize large document
102
102
  static void bench_serialize_large(void) {
103
103
  size_t size;
104
- char* xml = load_file("benchmarks/fixtures/large.xml", &size);
104
+ char* xml = load_file("fixtures/large.xml", &size);
105
105
  if (!xml) {
106
106
  printf("ERROR: Could not load large.xml\n");
107
107
  return;
@@ -134,7 +134,7 @@ static void bench_serialize_large(void) {
134
134
  // Benchmark: Pretty-print vs compact
135
135
  static void bench_format_comparison(void) {
136
136
  size_t size;
137
- char* xml = load_file("benchmarks/fixtures/small.xml", &size);
137
+ char* xml = load_file("fixtures/small.xml", &size);
138
138
  if (!xml) {
139
139
  printf("ERROR: Could not load small.xml\n");
140
140
  return;
@@ -513,7 +513,7 @@ int main(int argc, char** argv) {
513
513
  snprintf(size_medium, sizeof(size_medium), "%.1fKB", medium_len / 1024.0);
514
514
  snprintf(size_large, sizeof(size_large), "%.1fKB", large_len / 1024.0);
515
515
 
516
- write_json_results("benchmarks/results/phase17.5_session1.json",
516
+ write_json_results("results/phase17.5_session1.json",
517
517
  size_small, size_medium, size_large,
518
518
  &small_results, &medium_results, &large_results,
519
519
  iterations);
@@ -286,7 +286,9 @@ int32_t leptris_compact_int32_encode(void* base, void* target,
286
286
  const int32_t* field_addr) {
287
287
  if (!target) return 0;
288
288
  ptrdiff_t d = (char*)target - (char*)base;
289
- if (d < INT32_MIN || d > INT32_MAX) {
289
+ /* INT32_MAX is the EMPTY sentinel (compact.h) — spill exact
290
+ * deltas there too so raw offsets never collide with it. */
291
+ if (d < INT32_MIN || d >= INT32_MAX) {
290
292
  LeptrisCompactOverflowTable* table = get_overflow_table();
291
293
  struct leptris_document* doc = get_current_document();
292
294
  if (table && leptris_compact_overflow_set(table, field_addr,
@@ -127,6 +127,15 @@ int32_t leptris_compact_int32_encode_doc(void* base, void* target,
127
127
  * value is INT32_MIN). */
128
128
  #define LEPTRIS_INT32_OVERFLOW_SENTINEL_H ((int32_t)(-2147483647 - 1))
129
129
 
130
+ /* int32 sentinel for a non-NULL static "" pointer (#1285 slice 4b).
131
+ * Text content can be set to the empty string from far-away
132
+ * read-only storage (xsl:strip-space field writes); encoding that
133
+ * address would register one overflow-table entry per stripped
134
+ * node. Decoding returns a shared "" instead. Collision-free: the
135
+ * encode fast path below spills exact-INT32_MAX deltas to the
136
+ * overflow table, so no raw offset ever equals this value. */
137
+ #define LEPTRIS_COMPACT_INT32_EMPTY ((int32_t)2147483647)
138
+
130
139
  /* Inline fast paths (lane 18): the shared encode/decode live in
131
140
  * compact.c, so every tree-edge store or sibling/parent read paid an
132
141
  * out-of-line call — two per node in the parse loop and one per edge
@@ -140,7 +149,10 @@ static inline int32_t leptris_compact_int32_encode_inline(
140
149
  void* base, void* target, const int32_t* field_addr) {
141
150
  if (!target) return 0;
142
151
  ptrdiff_t d = (char*)target - (char*)base;
143
- if (d < INT32_MIN || d > INT32_MAX)
152
+ /* INT32_MAX is the EMPTY sentinel — a real delta of exactly
153
+ * that size spills to the overflow table (correct, just not
154
+ * inline). */
155
+ if (d < INT32_MIN || d >= INT32_MAX)
144
156
  return leptris_compact_int32_encode(base, target, field_addr);
145
157
  return (int32_t)d;
146
158
  }
@@ -677,7 +677,8 @@ LeptrisStatus leptris_element_set_text(LeptrisElement elem, const char* text) {
677
677
  if (text) {
678
678
  /* Create new text node (even if empty to match pugixml behavior) */
679
679
  LeptrisMemoryPool* pool = leptris_element_get_pool(elem);
680
- LeptrisTextNode* text_node = leptris_text_create(text, strlen(text), pool);
680
+ LeptrisTextNode* text_node = leptris_text_create(text, strlen(text), pool,
681
+ leptris_element_get_document(elem));
681
682
  if (!text_node) {
682
683
  return LEPTRIS_ERROR_MEMORY;
683
684
  }
@@ -1341,9 +1342,10 @@ LeptrisElement leptris_element_append_copy(LeptrisElement parent, LeptrisElement
1341
1342
  LeptrisTextNode* text = (LeptrisTextNode*)child;
1342
1343
  /* SAFETY: Verify text pointer is valid before accessing content */
1343
1344
  if ((uintptr_t)text > 0x1000) {
1344
- LeptrisTextNode* text_copy = leptris_text_create(text->content,
1345
+ LeptrisTextNode* text_copy = leptris_text_create(leptris_textnode_content(text),
1345
1346
  text->content_len,
1346
- leptris_element_get_pool(copy));
1347
+ leptris_element_get_pool(copy),
1348
+ leptris_element_get_document(copy));
1347
1349
  if (text_copy) {
1348
1350
  leptris_element_append_child_internal(copy, (LeptrisNode*)text_copy);
1349
1351
  }
@@ -1507,9 +1509,10 @@ LeptrisElement leptris_element_prepend_copy(LeptrisElement parent, LeptrisElemen
1507
1509
  leptris_element_append_copy(copy, child);
1508
1510
  } else if (child_node->type == LEPTRIS_NODE_TYPE_TEXT) {
1509
1511
  LeptrisTextNode* text = (LeptrisTextNode*)child;
1510
- LeptrisTextNode* text_copy = leptris_text_create(text->content,
1512
+ LeptrisTextNode* text_copy = leptris_text_create(leptris_textnode_content(text),
1511
1513
  text->content_len,
1512
- leptris_element_get_pool(copy));
1514
+ leptris_element_get_pool(copy),
1515
+ leptris_element_get_document(copy));
1513
1516
  if (text_copy) {
1514
1517
  leptris_element_append_child_internal(copy, (LeptrisNode*)text_copy);
1515
1518
  }
@@ -1622,9 +1625,10 @@ LeptrisElement leptris_element_insert_copy_after(LeptrisElement sibling, Leptris
1622
1625
  leptris_element_append_copy(copy, child);
1623
1626
  } else if (child_node->type == LEPTRIS_NODE_TYPE_TEXT) {
1624
1627
  LeptrisTextNode* text = (LeptrisTextNode*)child;
1625
- LeptrisTextNode* text_copy = leptris_text_create(text->content,
1628
+ LeptrisTextNode* text_copy = leptris_text_create(leptris_textnode_content(text),
1626
1629
  text->content_len,
1627
- leptris_element_get_pool(copy));
1630
+ leptris_element_get_pool(copy),
1631
+ leptris_element_get_document(copy));
1628
1632
  if (text_copy) {
1629
1633
  leptris_element_append_child_internal(copy, (LeptrisNode*)text_copy);
1630
1634
  }
@@ -1737,9 +1741,10 @@ LeptrisElement leptris_element_insert_copy_before(LeptrisElement sibling, Leptri
1737
1741
  leptris_element_append_copy(copy, child);
1738
1742
  } else if (child_node->type == LEPTRIS_NODE_TYPE_TEXT) {
1739
1743
  LeptrisTextNode* text = (LeptrisTextNode*)child;
1740
- LeptrisTextNode* text_copy = leptris_text_create(text->content,
1744
+ LeptrisTextNode* text_copy = leptris_text_create(leptris_textnode_content(text),
1741
1745
  text->content_len,
1742
- leptris_element_get_pool(copy));
1746
+ leptris_element_get_pool(copy),
1747
+ leptris_element_get_document(copy));
1743
1748
  if (text_copy) {
1744
1749
  leptris_element_append_child_internal(copy, (LeptrisNode*)text_copy);
1745
1750
  }
@@ -1925,9 +1930,10 @@ static LeptrisElement leptris_element_copy_subtree_bulk_internal(
1925
1930
  } else if (child_node->type == LEPTRIS_NODE_TYPE_TEXT) {
1926
1931
  /* Copy text node (not bulk-allocated) */
1927
1932
  LeptrisTextNode* text = (LeptrisTextNode*)child;
1928
- LeptrisTextNode* text_copy = leptris_text_create(text->content,
1933
+ LeptrisTextNode* text_copy = leptris_text_create(leptris_textnode_content(text),
1929
1934
  text->content_len,
1930
- leptris_element_get_pool(copy));
1935
+ leptris_element_get_pool(copy),
1936
+ leptris_element_get_document(copy));
1931
1937
  if (text_copy) {
1932
1938
  /* Link as child using internal function */
1933
1939
  leptris_element_append_child_internal(copy, (LeptrisNode*)text_copy);
@@ -2066,8 +2072,8 @@ static LeptrisElement copy_subtree_detached(LeptrisElement source,
2066
2072
  copy, pool, doc);
2067
2073
  } else if (ty == LEPTRIS_NODE_TYPE_TEXT) {
2068
2074
  LeptrisTextNode* t = (LeptrisTextNode*)c;
2069
- cc = (LeptrisNodeRef)leptris_text_create(t->content,
2070
- t->content_len, pool);
2075
+ cc = (LeptrisNodeRef)leptris_text_create(leptris_textnode_content(t),
2076
+ t->content_len, pool, doc);
2071
2077
  if (cc) leptris_textnode_set_parent((LeptrisTextNode*)cc, copy);
2072
2078
  } else if (ty == LEPTRIS_NODE_TYPE_CDATA) {
2073
2079
  LeptrisCDATANode* cd = (LeptrisCDATANode*)c;
@@ -333,7 +333,7 @@ LEPTRIS_API LeptrisNodeRef leptris_text_node_create(LeptrisDocument doc,
333
333
  if (!doc->pool) return NULL;
334
334
  if (!content) content = "";
335
335
  size_t len = strlen(content);
336
- LeptrisTextNode* n = leptris_text_create(content, len, doc->pool);
336
+ LeptrisTextNode* n = leptris_text_create(content, len, doc->pool, doc);
337
337
  if (n) {
338
338
  /* #1320: stamp the owner so detached mutations can reach the
339
339
  * doc's pool (PI/comment/CDATA do this via owner_doc fields;
@@ -416,7 +416,7 @@ LEPTRIS_API LeptrisStatus leptris_text_node_set_content(LeptrisNodeRef node,
416
416
  if (!doc || !doc->pool) return LEPTRIS_ERROR_INVALID_ARG;
417
417
  char* copy = node_public_pool_strdup(doc->pool, content, len);
418
418
  if (!copy) return LEPTRIS_ERROR_MEMORY;
419
- t->content = copy;
419
+ leptris_textnode_set_content_ptr_doc(t, copy, doc);
420
420
  t->content_len = (uint32_t)len;
421
421
  return LEPTRIS_OK;
422
422
  }