leptris 1.9.199.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 (124) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +3890 -0
  3. data/CLAUDE.md +115 -0
  4. data/CONTEXT.md +39 -0
  5. data/LICENSE.md +33 -0
  6. data/README.adoc +755 -0
  7. data/Rakefile +407 -0
  8. data/TODO.perf/01-ext-accelerated-default-reads.md +28 -0
  9. data/TODO.perf/02-auto-enable-native-layer.md +19 -0
  10. data/TODO.perf/03-bulk-xpath-result-materialization.md +18 -0
  11. data/TODO.perf/04-ext-bound-serialization.md +15 -0
  12. data/TODO.perf/05-native-surface-completion.md +19 -0
  13. data/TODO.perf/06-perf-battery-native-legs.md +16 -0
  14. data/TODO.perf/07-native-read-floor.md +31 -0
  15. data/TODO.perf/08-version-coherent-native-mutations.md +24 -0
  16. data/TODO.perf/09-adoption-lift-fast-path.md +38 -0
  17. data/TODO.perf/10-bulk-attribute-materialization.md +20 -0
  18. data/TODO.perf/11-moxml-gap-battery.md +18 -0
  19. data/TODO.perf/12-document-lifetime-in-c.md +47 -0
  20. data/TODO.perf/13-structural-memos.md +49 -0
  21. data/TODO.perf/14-insert-family-c-bound.md +31 -0
  22. data/TODO.perf/15-compiled-expression-cache.md +35 -0
  23. data/TODO.perf/16-at-xpath-single-result-seam.md +38 -0
  24. data/TODO.perf/17-precomputed-fast-path-flags.md +35 -0
  25. data/TODO.perf/18-inner-html-one-c-pass.md +32 -0
  26. data/TODO.perf/19-lazy-node-pointer.md +39 -0
  27. data/TODO.perf/20-css-translation-cache.md +24 -0
  28. data/TODO.perf/21-key-memo-consult.md +20 -0
  29. data/TODO.perf/22-eager-nodeset-materialization.md +30 -0
  30. data/TODO.perf/23-cbound-value-mutations.md +27 -0
  31. data/TODO.perf/24-immutable-read-lanes.md +22 -0
  32. data/TODO.perf/25-scope-owned-bulk-path.md +39 -0
  33. data/TODO.perf/26-post-mutation-memo-seeding.md +26 -0
  34. data/TODO.perf/27-c-yield-traversal.md +38 -0
  35. data/TODO.perf/28-address-fills.md +25 -0
  36. data/TODO.perf/29-ns-xpath-compiled.md +23 -0
  37. data/TODO.perf/30-copy-and-element-child-faces.md +31 -0
  38. data/TODO.perf/31-battery-rounds-4-8.md +23 -0
  39. data/TODO.perf/32-clean-host-floor-table.md +29 -0
  40. data/TODO.perf/33-cbound-root-set.md +29 -0
  41. data/TODO.perf/34-fragment-fast-lane.md +41 -0
  42. data/TODO.perf/35-parse-default-c-face.md +24 -0
  43. data/TODO.perf/36-raise-in-c-serializer-encoding.md +34 -0
  44. data/TODO.perf/37-document-lazy-pointer.md +28 -0
  45. data/TODO.perf/38-bulk-walk-and-bench-gate.md +23 -0
  46. data/TODO.restructure/01-constraint-compliance-audit.md +40 -0
  47. data/TODO.restructure/02-deep-copy-seam.md +21 -0
  48. data/TODO.restructure/03-evaluation-context-seam.md +18 -0
  49. data/TODO.restructure/04-spec-mece-restructure.md +20 -0
  50. data/TODO.restructure/05-memory-documentation.md +13 -0
  51. data/TODO.restructure/06-architecture-map-refresh.md +12 -0
  52. data/TODO.restructure/07-external-gates.md +16 -0
  53. data/TODO.restructure/08-new-code-audit-and-ext-memo.md +11 -0
  54. data/TODO.restructure/09-iteration-scope.md +29 -0
  55. data/TODO.restructure/10-cold-attr-read-diet.md +19 -0
  56. data/TODO.restructure/11-construction-factory-diet.md +16 -0
  57. data/TODO.restructure/12-resultattr-merge.md +12 -0
  58. data/TODO.restructure/13-spec-mece-followup.md +9 -0
  59. data/TODO.restructure/14-constraint-audit-refresh.md +15 -0
  60. data/TODO.restructure/15-readme-feature-documentation.md +10 -0
  61. data/TODO.restructure/16-claudemd-map-refresh.md +5 -0
  62. data/TODO.restructure/17-perf-drift-record.md +12 -0
  63. data/TODO.restructure/18-audit-spec-doubles-and-gem-contents.md +12 -0
  64. data/TODO.restructure/19-gitignore-hygiene.md +10 -0
  65. data/TODO.restructure/20-utf8proc-enablement.md +34 -0
  66. data/TODO.restructure/21-typeddata-variant-prototype.md +42 -0
  67. data/TODO.restructure/22-sax-drain-bulk-strip.md +28 -0
  68. data/docs/adr/0001-lockstep-mirror.md +17 -0
  69. data/docs/adr/0002-utf8-at-the-seam.md +19 -0
  70. data/docs/adr/0003-readonly-memoization-pattern.md +34 -0
  71. data/docs/adr/0004-lifetime-guard.md +21 -0
  72. data/docs/adr/0005-autoload-manifest-ordering.md +19 -0
  73. data/docs/adr/0006-ruby-variant-policy.md +25 -0
  74. data/ext/build_windows_native.rb +42 -0
  75. data/ext/leptris/native/extconf.rb +55 -0
  76. data/ext/leptris/native/native.c +2528 -0
  77. data/leptris.gemspec +43 -0
  78. data/lib/leptris/version.rb +5 -0
  79. data/lib/leptris/xml/attr.rb +76 -0
  80. data/lib/leptris/xml/c_string_array.rb +37 -0
  81. data/lib/leptris/xml/cdata.rb +27 -0
  82. data/lib/leptris/xml/comment.rb +27 -0
  83. data/lib/leptris/xml/css_to_xpath.rb +192 -0
  84. data/lib/leptris/xml/descriptor.rb +214 -0
  85. data/lib/leptris/xml/diff.rb +74 -0
  86. data/lib/leptris/xml/doc_type.rb +54 -0
  87. data/lib/leptris/xml/document.rb +716 -0
  88. data/lib/leptris/xml/document_fragment.rb +63 -0
  89. data/lib/leptris/xml/element.rb +771 -0
  90. data/lib/leptris/xml/entity_reference.rb +13 -0
  91. data/lib/leptris/xml/evaluation_context.rb +32 -0
  92. data/lib/leptris/xml/ffi.rb +1527 -0
  93. data/lib/leptris/xml/iteration_scope.rb +56 -0
  94. data/lib/leptris/xml/iterparse.rb +144 -0
  95. data/lib/leptris/xml/namespace.rb +43 -0
  96. data/lib/leptris/xml/native.so +0 -0
  97. data/lib/leptris/xml/native_layer.rb +92 -0
  98. data/lib/leptris/xml/node.rb +686 -0
  99. data/lib/leptris/xml/node_set.rb +212 -0
  100. data/lib/leptris/xml/parse_options.rb +97 -0
  101. data/lib/leptris/xml/plan_value.rb +121 -0
  102. data/lib/leptris/xml/processing_instruction.rb +90 -0
  103. data/lib/leptris/xml/pull.rb +212 -0
  104. data/lib/leptris/xml/relaxng.rb +126 -0
  105. data/lib/leptris/xml/result_attr.rb +39 -0
  106. data/lib/leptris/xml/result_text.rb +36 -0
  107. data/lib/leptris/xml/sax/document.rb +45 -0
  108. data/lib/leptris/xml/sax/dom_dispatch.rb +144 -0
  109. data/lib/leptris/xml/sax/parser.rb +326 -0
  110. data/lib/leptris/xml/sax/recorder.rb +374 -0
  111. data/lib/leptris/xml/sax.rb +14 -0
  112. data/lib/leptris/xml/schematron.rb +88 -0
  113. data/lib/leptris/xml/searchable.rb +361 -0
  114. data/lib/leptris/xml/serialization.rb +180 -0
  115. data/lib/leptris/xml/text.rb +32 -0
  116. data/lib/leptris/xml/xpath.rb +89 -0
  117. data/lib/leptris/xml/xquery.rb +77 -0
  118. data/lib/leptris/xml/xslt.rb +85 -0
  119. data/lib/leptris/xml.rb +108 -0
  120. data/lib/leptris.rb +53 -0
  121. data/lib/libleptris.so +0 -0
  122. data/lib/libutf8proc.so.3 +0 -0
  123. data/scripts/gem_smoke.rb +36 -0
  124. metadata +214 -0
@@ -0,0 +1,1527 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ffi"
4
+
5
+ module Leptris
6
+ module XML
7
+ module FFI
8
+ extend ::FFI::Library
9
+
10
+ # The per-OS vendor directory of the ruby-platform gem
11
+ # (zero-setup TruffleRuby/JRuby, #160): binaries for the
12
+ # common engines' OSes under lib/leptris/vendor/<platform>/.
13
+ # Returns [] in platform gems (no vendor tree) and on
14
+ # OSes the variant does not carry.
15
+ # Dir-name candidates for a RUBY_PLATFORM string, in
16
+ # preference order (glibc before musl — dlopen falls through
17
+ # on musl hosts).
18
+ def self.vendor_platforms_for(ruby_platform)
19
+ case ruby_platform
20
+ when /arm64.*darwin|aarch64.*darwin/ then %w[arm64-darwin]
21
+ when /x86_64.*darwin/ then %w[x86_64-darwin]
22
+ when /aarch64.*linux/ then %w[aarch64-linux aarch64-linux-musl]
23
+ when /x86_64.*linux/ then %w[x86_64-linux x86_64-linux-musl]
24
+ # 32-bit ARM (armv[567l]: "arm-linux", "armv7l-linux",
25
+ # "armeb-linux", ...). Never matches aarch64 ("aarch" has
26
+ # no "arm" substring).
27
+ when /arm.*linux/ then %w[arm-linux arm-linux-musl]
28
+ # POWER8+ little-endian and IBM Z (qemu-built platform gems)
29
+ when /ppc64le.*linux/ then %w[ppc64le-linux]
30
+ when /s390x.*linux/ then %w[s390x-linux s390x-linux-musl]
31
+ else []
32
+ end
33
+ end
34
+
35
+ def self.vendor_dirs
36
+ vendor_platforms_for(RUBY_PLATFORM)
37
+ .map { |d| File.expand_path("../../leptris/vendor/#{d}", __dir__) }
38
+ .select { |dir| File.directory?(dir) }
39
+ end
40
+
41
+ # THE libleptris candidate list, in order — shared with the
42
+ # native read layer's resolve!. Single source of truth is
43
+ # load-bearing: two differently-pathed copies of the library
44
+ # load as two images with split per-document state.
45
+ def self.libleptris_candidates
46
+ [
47
+ ENV["LEPTRIS_LIB_PATH"],
48
+ *vendor_dirs.flat_map { |d|
49
+ %w[libleptris.dylib libleptris.so libleptris.dll]
50
+ .map { |n| File.join(d, n) }
51
+ },
52
+ File.expand_path("../../libleptris.dylib", __dir__),
53
+ File.expand_path("../../libleptris.so", __dir__),
54
+ File.expand_path("../../libleptris.dll", __dir__),
55
+ "/usr/local/lib/libleptris.dylib",
56
+ "/usr/local/lib/libleptris.so",
57
+ "leptris",
58
+ ].compact
59
+ end
60
+
61
+ # libleptris links utf8proc via @rpath (TODO.restructure/20
62
+ # — fn:normalize-unicode). dlopen the VENDORED utf8proc
63
+ # FIRST: once its install name is loaded, dyld resolves
64
+ # libleptris's dependent image from this image instead of
65
+ # searching system paths. Absent (pure-ruby fallback gem,
66
+ # UTF8PROC-less custom builds) is not an error — the loader
67
+ # tries and moves on, and libleptris without the dependency
68
+ # never consults it.
69
+ begin
70
+ ffi_lib [
71
+ *Leptris::XML::FFI.vendor_dirs.flat_map { |d|
72
+ %w[libutf8proc.3.dylib libutf8proc.so.3 libutf8proc.so
73
+ utf8proc.dll].map { |n| File.join(d, n) }
74
+ },
75
+ File.expand_path("../../libutf8proc.3.dylib", __dir__),
76
+ File.expand_path("../../libutf8proc.so.3", __dir__),
77
+ File.expand_path("../../libutf8proc.so", __dir__),
78
+ File.expand_path("../../utf8proc.dll", __dir__),
79
+ ].compact
80
+ rescue LoadError
81
+ nil
82
+ end
83
+
84
+ # Issue leptris-ruby#49: name the remedy when the library is
85
+ # missing (ruby-platform gem without a vendored libleptris).
86
+ begin
87
+ ffi_lib Leptris::XML::FFI.libleptris_candidates
88
+ rescue LoadError => e
89
+ raise LoadError, <<~MSG
90
+ leptris: cannot load the vendored libleptris library.
91
+ You may have installed the ruby-platform variant of this
92
+ gem (#{Gem.loaded_specs["leptris"]&.full_name rescue "unknown"}),
93
+ which does not vendor the native library. Fix:
94
+
95
+ bundle update leptris # force platform gems
96
+ # or: gem install leptris --platform ruby is unsupported
97
+ # or: set LEPTRIS_LIB_PATH=/path/to/libleptris.so
98
+
99
+ Original error: #{e.message}
100
+ MSG
101
+ end
102
+
103
+ typedef :pointer, :leptris_document
104
+ typedef :pointer, :leptris_element
105
+ typedef :pointer, :leptris_node_ref
106
+ typedef :pointer, :leptris_attribute
107
+ typedef :pointer, :leptris_doctype
108
+ typedef :pointer, :leptris_xpath_result
109
+ typedef :pointer, :leptris_xpath_ns_set
110
+ typedef :pointer, :leptris_xpath_compiled
111
+ typedef :pointer, :leptris_relaxng
112
+ typedef :pointer, :leptris_xslt
113
+ typedef :pointer, :leptris_xquery
114
+ typedef :pointer, :leptris_schematron
115
+ typedef :pointer, :leptris_diff
116
+ typedef :pointer, :leptris_plan
117
+ typedef :pointer, :leptris_plan_result
118
+ typedef :pointer, :leptris_sax_recorder
119
+ typedef :pointer, :leptris_pull_parser
120
+ typedef :pointer, :leptris_iterparse
121
+ typedef :pointer, :leptris_xpath_var_set
122
+ typedef :pointer, :leptris_sax_parser
123
+ typedef :int, :leptris_status
124
+
125
+ class SAXHandler < ::FFI::Struct
126
+ layout \
127
+ :start_document, :pointer,
128
+ :end_document, :pointer,
129
+ :start_element, :pointer,
130
+ :end_element, :pointer,
131
+ :characters, :pointer,
132
+ :comment, :pointer,
133
+ :cdata, :pointer,
134
+ :processing_instruction, :pointer,
135
+ :start_prefix_mapping, :pointer,
136
+ :end_prefix_mapping, :pointer,
137
+ :error, :pointer
138
+ end
139
+
140
+ class SerializeOptions < ::FFI::Struct
141
+ layout \
142
+ :indent, :int,
143
+ :xml_declaration, :int,
144
+ :encoding, :pointer
145
+ end
146
+
147
+ # LeptrisSerializeExtOptions (1.9.9 #129 display form; 1.9.22
148
+ # #633/#109 indent unit): knobs beyond the ABI-frozen options
149
+ # struct. MAY grow — always pass its size through the sized
150
+ # entry.
151
+ class SerializeExtStruct < ::FFI::Struct
152
+ layout :indent_text, :int,
153
+ :indent_unit, :pointer,
154
+ :expand_empty, :int
155
+ end
156
+
157
+ # Mirrors LeptrisParseOptions (libleptris >= 1.9.0 carries the
158
+ # recover field; earlier fields unchanged since v1.6.0).
159
+ class ParseOptionsStruct < ::FFI::Struct
160
+ layout \
161
+ :flags, :int,
162
+ :strict_mode, :int,
163
+ :max_depth, :int,
164
+ :recover, :int
165
+ end
166
+
167
+ # LeptrisSaxEventRecord (types.h, issue #585): fixed layout the
168
+ # header explicitly freezes for FFI mirrors. Strings are NOT
169
+ # inline — name/text/attrs slice the recorder's arena.
170
+ class SaxEventRecord < ::FFI::Struct
171
+ layout :kind, :uint8,
172
+ :reserved, [:uint8, 7],
173
+ :name_off, :uint32, :name_len, :uint32,
174
+ :text_off, :uint32, :text_len, :uint32,
175
+ :attrs_off, :uint32, :attr_count, :uint32,
176
+ :line, :uint32, :column, :uint32
177
+ end
178
+
179
+ SAX_EVENT_START_DOCUMENT = 0
180
+ SAX_EVENT_END_DOCUMENT = 1
181
+ SAX_EVENT_START_ELEMENT = 2
182
+ SAX_EVENT_END_ELEMENT = 3
183
+ SAX_EVENT_CHARACTERS = 4
184
+ SAX_EVENT_COMMENT = 5
185
+ SAX_EVENT_CDATA = 6
186
+ SAX_EVENT_PI = 7
187
+ SAX_EVENT_START_PREFIX = 8
188
+ SAX_EVENT_END_PREFIX = 9
189
+ SAX_EVENT_ERROR = 10
190
+
191
+ ITERPARSE_TOP_LEVEL = 0
192
+ ITERPARSE_FULL_DOCUMENT = 1
193
+
194
+ # struct LeptrisPullEvent (leptris/sax.h): FFI::Struct derives
195
+ # the field offsets from the layout, so ABI changes are a
196
+ # one-line edit here instead of silent offset drift in callers.
197
+ class PullEventStruct < ::FFI::Struct
198
+ layout \
199
+ :type, :int,
200
+ :name, :pointer,
201
+ :text, :pointer,
202
+ :text_len, :size_t
203
+ end
204
+
205
+ # Tree-shaped schema-descriptor materialization (1.9.162,
206
+ # upstream #1039): hosts compile a plan tree once, then
207
+ # leptris_plan_walk materializes a whole subtree in one
208
+ # native pass. The plan/result structs mirror descriptor.h
209
+ # — POD, ABI-frozen at v1 (leptris_plan_abi_version).
210
+ class AttrPlan < ::FFI::Struct
211
+ layout :wire_name, :pointer,
212
+ :kind, :uint8,
213
+ :type_tag, :uint8
214
+ end
215
+ class ChildPlan < ::FFI::Struct
216
+ layout :wire_name, :pointer,
217
+ :kind, :uint8,
218
+ :type_tag, :uint8,
219
+ :child_plan_index, :int32,
220
+ # libleptris 1.9.178 (#1114/#1115): rule-level ns
221
+ # forms on ChildPlan (additive to the frozen v1 ABI).
222
+ :ns_form, :uint8,
223
+ :pad0, :uint8,
224
+ :ns_uri, :pointer
225
+ end
226
+ class ElementPlan < ::FFI::Struct
227
+ layout :element_name, :pointer,
228
+ :ns_form, :uint8,
229
+ :pad0, :uint8,
230
+ :ns_uri, :pointer,
231
+ :attribute_count, :uint32,
232
+ :child_count, :uint32,
233
+ :attribute_plans, :pointer,
234
+ :child_plans, :pointer,
235
+ :flags, :uint16,
236
+ :pad1, :uint16
237
+ end
238
+ class PlanSpec < ::FFI::Struct
239
+ layout :abi_version, :uint32,
240
+ :plan_count, :uint32,
241
+ :plans, :pointer
242
+ end
243
+
244
+ PLAN_ABI_VERSION = 1
245
+
246
+ PLAN_KIND_SCALAR = 1
247
+ PLAN_KIND_COLLECTION = 2
248
+ PLAN_KIND_NESTED = 3
249
+ PLAN_KIND_RAW = 4
250
+ PLAN_KIND_CONTENT = 5
251
+ PLAN_KIND_CALLBACK = 6
252
+
253
+ PLAN_FLAG_MIXED_CONTENT = 0x1
254
+ PLAN_FLAG_ORDERED = 0x2
255
+ PLAN_FLAG_CDATA = 0x4
256
+ PLAN_FLAG_NS_LENIENT = 0x8
257
+
258
+ PLAN_NS_NONE = 0
259
+ PLAN_NS_EXACT = 1
260
+ PLAN_NS_ANY = 2
261
+
262
+ PLAN_VALUE_ELEMENT = 0
263
+ PLAN_VALUE_SCALAR = 1
264
+ PLAN_VALUE_COLLECTION = 2
265
+ PLAN_VALUE_RAW = 3
266
+ PLAN_VALUE_CALLBACK = 4
267
+
268
+ # Byte offset of parse-created nodes (1.9.162, #1039): the
269
+ # position descriptor CALLBACK rows echo; 0 when unknown.
270
+ attach_function :leptris_node_byte_offset,
271
+ [:leptris_node_ref], :size_t
272
+
273
+ attach_function :leptris_plan_abi_version, [], :uint32
274
+ attach_function :leptris_plan_build,
275
+ [:pointer, :pointer], :leptris_plan
276
+ attach_function :leptris_plan_free, [:leptris_plan], :void
277
+ attach_function :leptris_plan_walk,
278
+ [:leptris_document, :leptris_element, :leptris_plan, :pointer],
279
+ :leptris_plan_result
280
+ attach_function :leptris_plan_result_free,
281
+ [:leptris_plan_result], :void
282
+ attach_function :leptris_plan_value_kind,
283
+ [:leptris_plan_result], :int
284
+ attach_function :leptris_plan_value_name,
285
+ [:leptris_plan_result], :string
286
+ attach_function :leptris_plan_value_type_tag,
287
+ [:leptris_plan_result], :uint8
288
+ attach_function :leptris_plan_value_string,
289
+ [:leptris_plan_result], :string
290
+ attach_function :leptris_plan_value_length,
291
+ [:leptris_plan_result], :size_t
292
+ attach_function :leptris_plan_value_position,
293
+ [:leptris_plan_result], :size_t
294
+ attach_function :leptris_plan_value_count,
295
+ [:leptris_plan_result], :size_t
296
+ attach_function :leptris_plan_value_at,
297
+ [:leptris_plan_result, :size_t], :leptris_plan_result
298
+ attach_function :leptris_plan_value_attribute,
299
+ [:leptris_plan_result, :string], :string
300
+
301
+ attach_function :leptris_version, [], :string
302
+ attach_function :leptris_version_components, [:pointer, :pointer, :pointer], :void
303
+
304
+ # Tolerant HTML4/5 parse into the STANDARD DOM (libleptris
305
+ # 1.9.75, #659): implied end tags, void elements, raw-text
306
+ # script/style, case-insensitive (lowercased) names, the HTML
307
+ # named-entity table. Document shape — html/head/body
308
+ # synthesized; no implied tbody. Malformed input never fails
309
+ # the parse; only an entirely empty result is an error.
310
+ attach_function :leptris_parse_html_string,
311
+ [:string, :size_t, :pointer], :leptris_document
312
+ # HTML4/libxml2-compatibility mode (1.9.104, #659): the same
313
+ # tolerant tokenizer and Nokogiri document shape, except
314
+ # leading script/style stay in <body> (libxml2's shape —
315
+ # title/meta/link/base still lift). Bindings matching
316
+ # Nokogiri byte-for-byte pin this; _html_string is the
317
+ # WHATWG-conformant engine (implied-head full set, foster
318
+ # parenting from 1.9.105).
319
+ attach_function :leptris_parse_html4_string,
320
+ [:string, :size_t, :pointer], :leptris_document
321
+
322
+ attach_function :leptris_parse_string,
323
+ [:string, :size_t, :pointer], :leptris_document
324
+ attach_function :leptris_parse_string_flags,
325
+ [:string, :size_t, :uint, :pointer], :leptris_document
326
+ attach_function :leptris_parse_string_inplace,
327
+ [:pointer, :size_t, :pointer], :leptris_document
328
+ attach_function :leptris_parse_string_with_encoding,
329
+ [:string, :size_t, :pointer], :leptris_document
330
+ attach_function :leptris_parse_file,
331
+ [:string, :pointer], :leptris_document
332
+ attach_function :leptris_load_file,
333
+ [:string, :pointer], :pointer
334
+ attach_function :leptris_document_free,
335
+ [:leptris_document], :void
336
+ attach_function :leptris_document_create,
337
+ [], :leptris_document
338
+ attach_function :leptris_document_set_root,
339
+ [:leptris_document, :leptris_element], :leptris_status
340
+ attach_function :leptris_document_root,
341
+ [:leptris_document], :leptris_element
342
+ # The document node — navigation head over the whole tree
343
+ # chain [prolog comments/PIs, root, epilog...] (libleptris
344
+ # 1.9.7, upstream #580; the libxml2 model).
345
+ attach_function :leptris_document_node,
346
+ [:leptris_document], :leptris_node_ref
347
+ # First node of the document child chain in one call — the
348
+ # prolog head (libleptris 1.9.174; document_node +
349
+ # node_first_child without the intermediate handle).
350
+ attach_function :leptris_document_first_child,
351
+ [:leptris_document], :leptris_node_ref
352
+ # Node-surface parity (libleptris 1.9.176, upstream #1094,
353
+ # leptris-ruby#212): entity references, the XML declaration,
354
+ # programmatic DOCTYPE, and document-PI identity/removal.
355
+ attach_function :leptris_entity_ref_node_create,
356
+ [:leptris_document, :string], :leptris_node_ref
357
+ attach_function :leptris_entity_ref_node_name,
358
+ [:leptris_node_ref], :string
359
+ attach_function :leptris_document_version,
360
+ [:leptris_document], :string
361
+ attach_function :leptris_document_standalone,
362
+ [:leptris_document], :int
363
+ attach_function :leptris_document_set_version,
364
+ [:leptris_document, :string], :leptris_status
365
+ attach_function :leptris_document_set_encoding,
366
+ [:leptris_document, :string], :leptris_status
367
+ attach_function :leptris_document_set_standalone,
368
+ [:leptris_document, :int], :leptris_status
369
+ attach_function :leptris_document_set_doctype,
370
+ [:leptris_document, :string, :string, :string], :leptris_doctype
371
+ attach_function :leptris_document_append_pi,
372
+ [:leptris_document, :string, :string], :leptris_node_ref
373
+ attach_function :leptris_document_remove_child,
374
+ [:leptris_document, :leptris_node_ref], :leptris_status
375
+ # Wrap-free subtree visitation (1.9.20, upstream #645a):
376
+ # enter/leave pairs for elements, depth from the walk's root,
377
+ # one C call — no NodeSet/Array churn per level. The document
378
+ # node walks the document child chain.
379
+ attach_function :leptris_node_visit,
380
+ [:leptris_node_ref, :pointer, :pointer], :void
381
+ # Document-level PIs (v1.6.0): not tree nodes — enumerate via
382
+ # these accessors only.
383
+ attach_function :leptris_document_pi_count,
384
+ [:leptris_document], :size_t
385
+ attach_function :leptris_document_pi_target,
386
+ [:leptris_document, :size_t], :string
387
+ attach_function :leptris_document_pi_data,
388
+ [:leptris_document, :size_t], :string
389
+ # Document-level PI removal by target or index (1.9.9, #612):
390
+ # unlinks from the child chain; the node is pool-owned and
391
+ # stays valid until document free.
392
+ attach_function :leptris_document_remove_pi,
393
+ [:leptris_document, :string, :size_t], :leptris_node_ref
394
+ attach_function :leptris_document_add_pi,
395
+ [:leptris_document, :string, :string], :pointer
396
+ # Document-level comment writer (libleptris 1.9.160, #1032):
397
+ # epilog append, the add_pi shape.
398
+ attach_function :leptris_document_add_comment,
399
+ [:leptris_document, :string], :leptris_node_ref
400
+ # Document-level comments (libleptris 1.9.3, #578): parsed
401
+ # <!-- ... --> outside the root, prolog then epilog, in
402
+ # document order. Read-only surface (no add_comment yet).
403
+ attach_function :leptris_document_comment_count,
404
+ [:leptris_document], :size_t
405
+ attach_function :leptris_document_comment_content,
406
+ [:leptris_document, :size_t], :string
407
+ # Options-struct parse (v1.6.0): supersedes the flags variants;
408
+ # the flags path remains bound for compatibility.
409
+ attach_function :leptris_parse_string_ex,
410
+ [:string, :size_t, :pointer, :pointer], :leptris_document
411
+ attach_function :leptris_document_encoding,
412
+ [:leptris_document], :string
413
+ attach_function :leptris_document_finalize_strings,
414
+ [:leptris_document], :int
415
+ attach_function :leptris_document_adopt_child,
416
+ [:leptris_document, :leptris_document], :void
417
+ attach_function :leptris_document_set_strict,
418
+ [:leptris_document, :int], :leptris_status
419
+ attach_function :leptris_document_get_strict,
420
+ [:leptris_document], :int
421
+ attach_function :leptris_document_freeze,
422
+ [:leptris_document], :leptris_status
423
+ attach_function :leptris_document_is_frozen,
424
+ [:leptris_document], :int
425
+ attach_function :leptris_set_strict_mode, [:int], :void
426
+ attach_function :leptris_get_strict_mode, [], :int
427
+ attach_function :leptris_set_max_depth, [:int], :void
428
+ attach_function :leptris_get_max_depth, [], :int
429
+ attach_function :leptris_xinclude_process,
430
+ [:leptris_document, :string], :leptris_status
431
+ attach_function :leptris_xinclude_is_include_element,
432
+ [:leptris_element], :int
433
+ attach_function :leptris_xinclude_is_fallback_element,
434
+ [:leptris_element], :int
435
+ attach_function :leptris_xinclude_get_href, [:leptris_element], :string
436
+ attach_function :leptris_xinclude_get_parse, [:leptris_element], :string
437
+ attach_function :leptris_xinclude_get_xpointer, [:leptris_element], :string
438
+ attach_function :leptris_xinclude_get_encoding, [:leptris_element], :string
439
+
440
+ attach_function :leptris_node_get_type,
441
+ [:leptris_node_ref], :int
442
+ attach_function :leptris_node_first_child,
443
+ [:leptris_node_ref], :leptris_node_ref
444
+ attach_function :leptris_node_last_child,
445
+ [:leptris_node_ref], :leptris_node_ref
446
+ attach_function :leptris_node_next_sibling,
447
+ [:leptris_node_ref], :leptris_node_ref
448
+ attach_function :leptris_node_previous_sibling,
449
+ [:leptris_node_ref], :leptris_node_ref
450
+ attach_function :leptris_node_child_count,
451
+ [:leptris_node_ref], :size_t
452
+ # libleptris 1.7.0: all-kind child batch — out_nodes=NULL is a
453
+ # count-only query returning the TOTAL across every child kind.
454
+ attach_function :leptris_node_children,
455
+ [:leptris_node_ref, :pointer, :size_t], :size_t
456
+ # Children with out_kinds (1.9.18, upstream #617): the kind
457
+ # rides the batch, so wrappers skip the per-child get_type.
458
+ attach_function :leptris_node_children_ex,
459
+ [:leptris_node_ref, :pointer, :pointer, :size_t], :size_t
460
+ # Element-only batch; mirror-only — fetch_children rides the
461
+ # all-kind node_children above.
462
+ # Raw qname-ordered attribute list INCLUDING xmlns
463
+ # declarations at their byte positions (1.9.18, upstream
464
+ # #635) — the streaming SAX contract's mixed view.
465
+ attach_function :leptris_element_attributes_raw,
466
+ [:leptris_element, :pointer, :pointer, :size_t], :size_t
467
+ attach_function :leptris_element_children,
468
+ [:leptris_element, :pointer, :size_t], :size_t
469
+ attach_function :leptris_node_as_element,
470
+ [:leptris_node_ref], :leptris_element
471
+ attach_function :leptris_element_as_node,
472
+ [:leptris_element], :leptris_node_ref
473
+ attach_function :leptris_node_get_binding_wrapper,
474
+ [:leptris_node_ref], :pointer
475
+ attach_function :leptris_node_set_binding_wrapper,
476
+ [:leptris_node_ref, :pointer], :void
477
+ attach_function :leptris_node_parent,
478
+ [:leptris_node_ref], :leptris_element
479
+ attach_function :leptris_node_unlink,
480
+ [:leptris_node_ref], :leptris_status
481
+ attach_function :leptris_node_line,
482
+ [:leptris_node_ref], :int
483
+ # Parser-recorded source position (libleptris 1.9.180,
484
+ # #1124): {line, col_start, col_end} — Jing's diagnostic
485
+ # convention (column after the start tag's '>' / the
486
+ # element's final '>').
487
+ class SourcePosition < ::FFI::Struct
488
+ layout :line, :int,
489
+ :col_start, :int,
490
+ :col_end, :int
491
+ end
492
+ attach_function :leptris_node_source_position,
493
+ [:leptris_node_ref, :pointer], :void
494
+ attach_function :leptris_node_compare,
495
+ [:leptris_node_ref, :leptris_node_ref], :int
496
+ attach_function :leptris_node_traverse,
497
+ [:leptris_node_ref, :int, :pointer, :pointer], :int
498
+
499
+ attach_function :leptris_text_node_get_content,
500
+ [:leptris_node_ref], :string
501
+ attach_function :leptris_comment_node_get_content,
502
+ [:leptris_node_ref], :string
503
+ attach_function :leptris_cdata_node_get_content,
504
+ [:leptris_node_ref], :string
505
+ attach_function :leptris_pi_node_get_target,
506
+ [:leptris_node_ref], :string
507
+ attach_function :leptris_pi_node_get_data,
508
+ [:leptris_node_ref], :string
509
+ attach_function :leptris_text_node_create,
510
+ [:leptris_document, :string], :leptris_node_ref
511
+ attach_function :leptris_comment_node_create,
512
+ [:leptris_document, :string], :leptris_node_ref
513
+ attach_function :leptris_cdata_node_create,
514
+ [:leptris_document, :string], :leptris_node_ref
515
+ attach_function :leptris_pi_node_create,
516
+ [:leptris_document, :string, :string], :leptris_node_ref
517
+ attach_function :leptris_text_node_set_content,
518
+ [:leptris_node_ref, :string], :leptris_status
519
+ attach_function :leptris_comment_node_set_content,
520
+ [:leptris_node_ref, :string], :leptris_status
521
+ attach_function :leptris_cdata_node_set_content,
522
+ [:leptris_node_ref, :string], :leptris_status
523
+ attach_function :leptris_pi_node_set_target,
524
+ [:leptris_node_ref, :string], :leptris_status
525
+ attach_function :leptris_pi_node_set_data,
526
+ [:leptris_node_ref, :string], :leptris_status
527
+
528
+ attach_function :leptris_element_name,
529
+ [:leptris_element], :string
530
+ attach_function :leptris_element_text,
531
+ [:leptris_element], :string
532
+ attach_function :leptris_element_text_int,
533
+ [:leptris_element, :int], :int
534
+ attach_function :leptris_element_text_uint,
535
+ [:leptris_element, :uint], :uint
536
+ attach_function :leptris_element_text_double,
537
+ [:leptris_element, :double], :double
538
+ attach_function :leptris_element_text_float,
539
+ [:leptris_element, :float], :float
540
+ attach_function :leptris_element_text_bool,
541
+ [:leptris_element, :int], :int
542
+ attach_function :leptris_element_attribute,
543
+ [:leptris_element, :string], :string
544
+ attach_function :leptris_element_attribute_string,
545
+ [:leptris_element, :string, :string], :string
546
+ attach_function :leptris_element_attribute_int,
547
+ [:leptris_element, :string, :int], :int
548
+ attach_function :leptris_element_attribute_uint,
549
+ [:leptris_element, :string, :uint], :uint
550
+ attach_function :leptris_element_attribute_double,
551
+ [:leptris_element, :string, :double], :double
552
+ attach_function :leptris_element_attribute_float,
553
+ [:leptris_element, :string, :float], :float
554
+ attach_function :leptris_element_attribute_bool,
555
+ [:leptris_element, :string, :int], :int
556
+ attach_function :leptris_element_has_attribute,
557
+ [:leptris_element, :string], :int
558
+ # libleptris 1.8.0: expanded-name lookup — (uri, local) with
559
+ # XML Namespaces 1.0 semantics; NULL/"" uri matches only
560
+ # no-namespace attributes, xmlns declarations never match.
561
+ attach_function :leptris_element_attribute_ns,
562
+ [:leptris_element, :string, :string], :string
563
+ attach_function :leptris_element_has_attribute_ns,
564
+ [:leptris_element, :string, :string], :int
565
+ # libleptris 1.8.0: per-attribute prefix (name-derived) and
566
+ # namespace URI (resolved through the owning element's
567
+ # in-scope declarations at read time).
568
+ attach_function :leptris_attribute_prefix,
569
+ [:leptris_attribute], :string
570
+ attach_function :leptris_attribute_namespace_uri,
571
+ [:leptris_attribute], :string
572
+ attach_function :leptris_element_first_attribute,
573
+ [:leptris_element], :leptris_attribute
574
+ attach_function :leptris_attribute_next,
575
+ [:leptris_attribute], :leptris_attribute
576
+ attach_function :leptris_attribute_get_name,
577
+ [:leptris_attribute], :string
578
+ attach_function :leptris_attribute_get_value,
579
+ [:leptris_element, :leptris_attribute], :string
580
+ attach_function :leptris_element_prefix,
581
+ [:leptris_element], :string
582
+ attach_function :leptris_element_attribute_count,
583
+ [:leptris_element], :size_t
584
+ attach_function :leptris_element_attribute_name_at,
585
+ [:leptris_element, :size_t], :string
586
+ attach_function :leptris_element_attribute_value_at,
587
+ [:leptris_element, :size_t], :string
588
+ attach_function :leptris_element_remove_all_attributes,
589
+ [:leptris_element], :leptris_status
590
+
591
+ attach_function :leptris_element_child_count,
592
+ [:leptris_element], :size_t
593
+ attach_function :leptris_element_child,
594
+ [:leptris_element, :size_t], :leptris_element
595
+ attach_function :leptris_element_parent,
596
+ [:leptris_element], :leptris_element
597
+ attach_function :leptris_element_root,
598
+ [:leptris_element], :leptris_element
599
+ attach_function :leptris_element_child_value,
600
+ [:leptris_element], :string
601
+ attach_function :leptris_element_hash_value,
602
+ [:leptris_element], :size_t
603
+ attach_function :leptris_element_find_child,
604
+ [:leptris_element, :string], :leptris_element
605
+ attach_function :leptris_element_find_child_by_attr,
606
+ [:leptris_element, :string, :string, :string], :leptris_element
607
+ attach_function :leptris_element_next_sibling,
608
+ [:leptris_element, :string], :leptris_element
609
+ attach_function :leptris_element_previous_sibling,
610
+ [:leptris_element, :string], :leptris_element
611
+ attach_function :leptris_element_first_child,
612
+ [:leptris_element, :string], :leptris_element
613
+ attach_function :leptris_element_last_child,
614
+ [:leptris_element, :string], :leptris_element
615
+ attach_function :leptris_element_first_child_any,
616
+ [:leptris_element], :leptris_element
617
+ attach_function :leptris_element_last_child_any,
618
+ [:leptris_element], :leptris_element
619
+ attach_function :leptris_element_next_sibling_any,
620
+ [:leptris_element], :leptris_element
621
+ attach_function :leptris_element_previous_sibling_any,
622
+ [:leptris_element], :leptris_element
623
+
624
+ attach_function :leptris_element_create,
625
+ [:leptris_document, :string], :leptris_element
626
+ attach_function :leptris_element_set_name,
627
+ [:leptris_element, :string], :leptris_status
628
+ attach_function :leptris_element_set_text,
629
+ [:leptris_element, :string], :leptris_status
630
+ attach_function :leptris_element_set_attribute,
631
+ [:leptris_element, :string, :string], :leptris_status
632
+ attach_function :leptris_element_set_attribute_bool,
633
+ [:leptris_element, :string, :int], :leptris_status
634
+ attach_function :leptris_element_set_attribute_double,
635
+ [:leptris_element, :string, :double], :leptris_status
636
+ attach_function :leptris_element_set_attribute_float,
637
+ [:leptris_element, :string, :float], :leptris_status
638
+ attach_function :leptris_element_set_attribute_int,
639
+ [:leptris_element, :string, :int], :leptris_status
640
+ attach_function :leptris_element_set_attribute_uint,
641
+ [:leptris_element, :string, :uint], :leptris_status
642
+ attach_function :leptris_element_remove_attribute,
643
+ [:leptris_element, :string], :leptris_status
644
+ attach_function :leptris_element_append_child,
645
+ [:leptris_element, :leptris_element], :leptris_status
646
+ # Fused create+append (libleptris 1.9.153): one document
647
+ # resolution, identical tree semantics to the two-call pair.
648
+ attach_function :leptris_element_create_child,
649
+ [:leptris_element, :string], :leptris_element
650
+ attach_function :leptris_element_prepend_child,
651
+ [:leptris_element, :leptris_element], :leptris_status
652
+ attach_function :leptris_element_insert_before,
653
+ [:leptris_element, :leptris_element], :leptris_status
654
+ attach_function :leptris_element_insert_after,
655
+ [:leptris_element, :leptris_element], :leptris_status
656
+ attach_function :leptris_element_remove_child,
657
+ [:leptris_element, :leptris_element], :leptris_status
658
+ attach_function :leptris_element_remove_children,
659
+ [:leptris_element], :leptris_status
660
+ attach_function :leptris_element_append_copy,
661
+ [:leptris_element, :leptris_element], :leptris_element
662
+ attach_function :leptris_element_prepend_copy,
663
+ [:leptris_element, :leptris_element], :leptris_element
664
+ attach_function :leptris_element_insert_copy_before,
665
+ [:leptris_element, :leptris_element], :leptris_element
666
+ attach_function :leptris_element_insert_copy_after,
667
+ [:leptris_element, :leptris_element], :leptris_element
668
+
669
+ attach_function :leptris_element_copy,
670
+ [:leptris_element, :leptris_document], :leptris_element
671
+ attach_function :leptris_document_copy,
672
+ [:leptris_document], :leptris_document
673
+ attach_function :leptris_node_get_xpath,
674
+ [:leptris_node_ref], :pointer
675
+ attach_function :leptris_parse_fragment,
676
+ [:string, :size_t, :leptris_document, :pointer], :leptris_element
677
+
678
+ # Nokogiri node.namespace= parity (libleptris 1.9.76, #817):
679
+ # NULL (or "") detaches — link + prefix clear, xmlns=""
680
+ # undeclaration added; a URI rebinds to an IN-SCOPE
681
+ # declaration carrying it (NOT_FOUND if there is none — this
682
+ # entry does not invent declarations).
683
+ attach_function :leptris_element_set_namespace,
684
+ [:leptris_element, :string], :leptris_status
685
+ attach_function :leptris_element_namespace,
686
+ [:leptris_element], :string
687
+ attach_function :leptris_element_namespace_for_prefix,
688
+ [:leptris_element, :string], :string
689
+ attach_function :leptris_element_namespace_count,
690
+ [:leptris_element], :size_t
691
+ attach_function :leptris_element_namespace_decl_prefix,
692
+ [:leptris_element, :size_t], :string
693
+ attach_function :leptris_element_namespace_decl_uri,
694
+ [:leptris_element, :size_t], :string
695
+ attach_function :leptris_element_add_namespace_definition,
696
+ [:leptris_element, :string, :string], :leptris_status
697
+ attach_function :leptris_element_set_default_namespace,
698
+ [:leptris_element, :string], :leptris_status
699
+ attach_function :leptris_element_remove_namespace_definition,
700
+ [:leptris_element, :string], :leptris_status
701
+ attach_function :leptris_namespace_uri, [:string], :string
702
+ attach_function :leptris_namespace_prefix, [:string], :string
703
+
704
+ attach_function :leptris_xpath_eval,
705
+ [:leptris_document, :leptris_element, :string], :leptris_xpath_result
706
+ attach_function :leptris_xpath_eval_with_vars,
707
+ [:leptris_document, :string, :leptris_xpath_var_set], :leptris_xpath_result
708
+ # Union semantics (NodeSet#xpath): N contexts, ONE call — the
709
+ # engine merges the per-context results into a de-duplicated,
710
+ # document-ordered nodeset (libleptris 1.9.7, upstream #589).
711
+ attach_function :leptris_xpath_eval_nodeset,
712
+ [:leptris_document, :pointer, :size_t, :string], :leptris_xpath_result
713
+ attach_function :leptris_xpath_compiled_eval_nodeset,
714
+ [:leptris_document, :pointer, :size_t, :leptris_xpath_compiled],
715
+ :leptris_xpath_result
716
+ attach_function :leptris_xpath_eval_with_vars_context,
717
+ [:leptris_document, :leptris_element, :string, :leptris_xpath_var_set],
718
+ :leptris_xpath_result
719
+ attach_function :leptris_xpath_result_type,
720
+ [:leptris_xpath_result], :int
721
+ attach_function :leptris_xpath_result_count,
722
+ [:leptris_xpath_result], :size_t
723
+ # Mixed nodesets (v1.1.0): unlike result_get (elements only),
724
+ # get_node returns the node whatever its kind; node_kind reports
725
+ # which (element / synthetic attribute / text / other).
726
+ attach_function :leptris_xpath_result_node_kind,
727
+ [:leptris_xpath_result, :size_t], :int
728
+ # Pre-_ex variants, superseded by xpath_result_get_nodes_ex /
729
+ # xpath_result_get_node; mirror-only (ADR 0001).
730
+ attach_function :leptris_xpath_result_get,
731
+ [:leptris_xpath_result, :size_t], :leptris_element
732
+ attach_function :leptris_xpath_result_get_nodes,
733
+ [:leptris_xpath_result, :pointer, :size_t], :size_t
734
+ attach_function :leptris_xpath_result_get_node,
735
+ [:leptris_xpath_result, :size_t], :leptris_node_ref
736
+ attach_function :leptris_xpath_result_node_name,
737
+ [:leptris_xpath_result, :size_t], :string
738
+ attach_function :leptris_xpath_result_node_value,
739
+ [:leptris_xpath_result, :size_t], :string
740
+ # Document-scoped custom XPath functions. The callback receives
741
+ # (const char* const* args, int argc, void* user_data) and returns
742
+ # a heap string the library frees.
743
+ attach_function :leptris_xpath_register_function,
744
+ [:leptris_document, :string, :pointer, :pointer], :leptris_status
745
+ # v1.3.0: copies ALL node kinds in one call, optionally
746
+ # reporting per-entry kinds.
747
+ attach_function :leptris_xpath_result_get_nodes_ex,
748
+ [:leptris_xpath_result, :pointer, :pointer, :size_t], :size_t
749
+ attach_function :leptris_xpath_result_boolean,
750
+ [:leptris_xpath_result], :int
751
+ attach_function :leptris_xpath_result_number,
752
+ [:leptris_xpath_result], :double
753
+ attach_function :leptris_xpath_result_string,
754
+ [:leptris_xpath_result], :pointer
755
+ attach_function :leptris_xpath_result_free,
756
+ [:leptris_xpath_result], :void
757
+ attach_function :leptris_xpath_function_supported, [:string], :int
758
+ attach_function :leptris_xpath_supported_functions, [], :pointer
759
+
760
+ attach_function :leptris_xpath_variable_set_new,
761
+ [], :leptris_xpath_var_set
762
+ attach_function :leptris_xpath_variable_set_free,
763
+ [:leptris_xpath_var_set], :void
764
+ attach_function :leptris_xpath_variable_set_boolean,
765
+ [:leptris_xpath_var_set, :string, :int], :leptris_status
766
+ attach_function :leptris_xpath_variable_set_number,
767
+ [:leptris_xpath_var_set, :string, :double], :leptris_status
768
+ attach_function :leptris_xpath_variable_set_string,
769
+ [:leptris_xpath_var_set, :string, :string], :leptris_status
770
+
771
+ # Namespace-bound XPath (v1.2.0): expression prefixes resolve to
772
+ # caller-supplied URIs regardless of the document's declarations.
773
+ attach_function :leptris_xpath_ns_set_new,
774
+ [], :leptris_xpath_ns_set
775
+ attach_function :leptris_xpath_ns_set_free,
776
+ [:leptris_xpath_ns_set], :void
777
+ # One-call constructor: flat array of 2*pair_count alternating
778
+ # prefix/URI strings.
779
+ # Per-pair add; superseded by ns_set_new_from_pairs (one call);
780
+ # mirror-only (ADR 0001).
781
+ attach_function :leptris_xpath_ns_set_add,
782
+ [:leptris_xpath_ns_set, :string, :string], :leptris_status
783
+ attach_function :leptris_xpath_ns_set_new_from_pairs,
784
+ [:pointer, :size_t], :leptris_xpath_ns_set
785
+ attach_function :leptris_xpath_eval_ns,
786
+ [:leptris_document, :leptris_element, :string, :leptris_xpath_ns_set],
787
+ :leptris_xpath_result
788
+ # Compiled expressions (v1.6.0): parse once, evaluate many.
789
+ attach_function :leptris_xpath_compile,
790
+ [:string], :leptris_xpath_compiled
791
+ attach_function :leptris_xpath_compiled_eval,
792
+ [:leptris_xpath_compiled, :leptris_document, :leptris_element],
793
+ :leptris_xpath_result
794
+ attach_function :leptris_xpath_compiled_eval_ns,
795
+ [:leptris_xpath_compiled, :leptris_document, :leptris_element,
796
+ :leptris_xpath_ns_set], :leptris_xpath_result
797
+ attach_function :leptris_xpath_compiled_eval_vars,
798
+ [:leptris_xpath_compiled, :leptris_document, :leptris_element,
799
+ :leptris_xpath_var_set], :leptris_xpath_result
800
+ # Combined namespaces + variables (1.9.18, upstream #608).
801
+ attach_function :leptris_xpath_compiled_eval_ns_vars,
802
+ [:leptris_xpath_compiled, :leptris_document, :leptris_element,
803
+ :leptris_xpath_ns_set, :leptris_xpath_var_set],
804
+ :leptris_xpath_result
805
+ # Source text of a compiled expression (owned by the handle).
806
+ attach_function :leptris_xpath_compiled_text,
807
+ [:leptris_xpath_compiled], :string
808
+ attach_function :leptris_xpath_compiled_free,
809
+
810
+ [:leptris_xpath_compiled], :void
811
+ attach_function :leptris_sax_parse,
812
+ [:string, :size_t, :pointer, :pointer], :int
813
+ attach_function :leptris_sax_parser_create,
814
+ [:pointer, :pointer], :leptris_sax_parser
815
+ attach_function :leptris_sax_parser_feed,
816
+ [:leptris_sax_parser, :string, :size_t, :int], :int
817
+ # Chunked event recorder (libleptris 1.9.4, #585): buffers
818
+ # events C-side (fixed records + packed arena); the host
819
+ # drains in bulk — callbacks become O(chunks), not O(events).
820
+ attach_function :leptris_sax_recorder_new,
821
+ [], :leptris_sax_recorder
822
+ attach_function :leptris_sax_recorder_feed,
823
+ [:leptris_sax_recorder, :string, :size_t, :int], :int
824
+ attach_function :leptris_sax_recorder_records,
825
+ [:leptris_sax_recorder, :pointer], :pointer
826
+ attach_function :leptris_sax_recorder_arena,
827
+ [:leptris_sax_recorder, :pointer], :pointer
828
+ # Reuse across documents (1.9.10, #594): swaps in fresh parser
829
+ # state, RETAINS the record/arena buffers — a
830
+ # one-document-per-parse loop stops paying new/free churn.
831
+ attach_function :leptris_sax_recorder_reset,
832
+ [:leptris_sax_recorder], :int
833
+ attach_function :leptris_sax_recorder_free,
834
+ [:leptris_sax_recorder], :void
835
+ attach_function :leptris_sax_parser_free,
836
+ [:leptris_sax_parser], :void
837
+ attach_function :leptris_sax_parser_set_streaming,
838
+ [:leptris_sax_parser, :int], :int
839
+
840
+ # Pull parsing (v1.6.0): StAX-style cursor over a document.
841
+ attach_function :leptris_pull_new,
842
+ [:string, :size_t], :leptris_pull_parser
843
+ attach_function :leptris_pull_new_file,
844
+ [:string], :leptris_pull_parser
845
+ attach_function :leptris_pull_next,
846
+ [:leptris_pull_parser], :pointer
847
+ attach_function :leptris_pull_next_batch,
848
+ [:leptris_pull_parser, :pointer, :size_t], :size_t
849
+ attach_function :leptris_pull_attr_count,
850
+ [:leptris_pull_parser], :size_t
851
+ attach_function :leptris_pull_attr_name,
852
+ [:leptris_pull_parser, :size_t], :string
853
+ attach_function :leptris_pull_attr_value,
854
+ [:leptris_pull_parser, :size_t], :string
855
+ attach_function :leptris_pull_attrs,
856
+ [:leptris_pull_parser, :pointer, :size_t], :size_t
857
+ attach_function :leptris_pull_free,
858
+ [:leptris_pull_parser], :void
859
+
860
+ # Iterparse (v1.6.0): yields each completed top-level child
861
+ # element; the previous subtree is released on each next call.
862
+ attach_function :leptris_iterparse_new,
863
+ [:string, :size_t], :leptris_iterparse
864
+ attach_function :leptris_iterparse_new_file,
865
+ [:string], :leptris_iterparse
866
+ attach_function :leptris_iterparse_next,
867
+ [:leptris_iterparse], :leptris_element
868
+ # iterparse v2 (libleptris 1.9.4, #586): explicit yield mode
869
+ # (top-level children or full-document post-order), namespace
870
+ # resolution on the last yielded element, error channel.
871
+ attach_function :leptris_iterparse_new_ex,
872
+ [:string, :size_t, :int], :leptris_iterparse
873
+ attach_function :leptris_iterparse_new_file_ex,
874
+ [:string, :int], :leptris_iterparse
875
+ attach_function :leptris_iterparse_ns_count,
876
+ [:leptris_iterparse], :size_t
877
+ attach_function :leptris_iterparse_ns_uri,
878
+ [:leptris_iterparse, :string], :string
879
+ attach_function :leptris_iterparse_error,
880
+ [:leptris_iterparse], :string
881
+ attach_function :leptris_iterparse_free,
882
+ [:leptris_iterparse], :void
883
+
884
+ attach_function :leptris_document_serialize,
885
+ [:leptris_document, :pointer], :pointer
886
+ # Deprecated automatic-options alias (header: prefer
887
+ # leptris_document_serialize); mirror-only.
888
+ attach_function :leptris_serialize_document,
889
+ [:leptris_document], :pointer
890
+ # libleptris >= 1.9.0: caller-buffer serialization with options
891
+ # (leptris#541). buf=NULL is a size query; the size-query +
892
+ # fill pair reuses one serialization through a per-document
893
+ # cache invalidated on mutation.
894
+ # Display-form variant (1.9.9, #129): the ext struct carries
895
+ # indent_text; returns an OWNED string — read via
896
+ # read_owned_string (the seam frees it).
897
+ attach_function :leptris_document_serialize_ext,
898
+ [:leptris_document, :pointer, :pointer], :pointer
899
+ # Sized variant (1.9.19, upstream #644 — leptris-ruby PR #107):
900
+ # passing our ext-struct size keeps the engine from reading
901
+ # past this binding's allocation (the MSVC segfault).
902
+ attach_function :leptris_document_serialize_ext_sized,
903
+ [:leptris_document, :pointer, :pointer, :size_t], :pointer
904
+ attach_function :leptris_document_serialize_into,
905
+ [:leptris_document, :pointer, :size_t, :pointer, :pointer], :size_t
906
+ attach_function :leptris_document_get_dtd,
907
+ [:leptris_document], :pointer
908
+ # Element-level ext serialization (libleptris 1.9.95, #882):
909
+ # the document ext option surface (indent_text, indent_unit,
910
+ # expand_empty) at element level. The sized twin is the
911
+ # FFI-safe form (older ext allocations read within bounds).
912
+ attach_function :leptris_element_serialize_ext,
913
+ [:leptris_element, :pointer, :pointer], :pointer
914
+ attach_function :leptris_element_serialize_ext_sized,
915
+ [:leptris_element, :pointer, :pointer, :size_t], :pointer
916
+
917
+ # Content-defined Merkle digest of a subtree (libleptris
918
+ # 1.9.99, #869): equal flags + equal digests imply structural
919
+ # equivalence (descend to decide on inequality); flags bit 1
920
+ # (DROP_WS_TEXT) skips whitespace-only text nodes.
921
+ attach_function :leptris_node_digest,
922
+ [:leptris_node_ref, :int], :uint64
923
+
924
+ attach_function :leptris_element_serialize,
925
+ [:leptris_element, :pointer], :pointer
926
+ attach_function :leptris_element_serialize_into,
927
+ [:leptris_element, :pointer, :size_t, :pointer, :pointer], :size_t
928
+ attach_function :leptris_document_save_file,
929
+ [:leptris_document, :string, :pointer], :leptris_status
930
+ # XSLT 1.0 engine (libleptris 1.9.1): compile once, apply many.
931
+ # XQuery 1.0 core (libleptris 1.9.64-1.9.66): orchestration
932
+ # over the XPath engine — prolog (declare variable/namespace/
933
+ # function local:*), FLWOR, computed and direct constructors.
934
+ # Results reuse the XPath result handle (free with
935
+ # leptris_xpath_result_free); context_node NULL = document.
936
+ attach_function :leptris_xquery_parse,
937
+ [:string, :size_t], :leptris_xquery
938
+ attach_function :leptris_xquery_eval,
939
+ [:leptris_xquery, :leptris_document, :leptris_element],
940
+ :leptris_xpath_result
941
+ # External-variable XQuery evaluation (libleptris 1.9.133):
942
+ # names[i]/selects[i] bind `declare variable $name external`
943
+ # — each select is an XPath expression in an empty context
944
+ # (QT3 <param select> semantics), overriding any default
945
+ # initializer. An external with neither binding nor default
946
+ # fails evaluation.
947
+ attach_function :leptris_xquery_eval_params,
948
+ [:leptris_xquery, :leptris_document, :leptris_element,
949
+ :pointer, :pointer, :size_t], :leptris_xpath_result
950
+ attach_function :leptris_xquery_free, [:leptris_xquery], :void
951
+
952
+ # Schematron validation (libleptris >= 1.9.126 family):
953
+ # parse once (optionally selecting a PHASE), validate any
954
+ # number of documents — #validate returns the SVRL report as
955
+ # a Document; #valid? answers through the SVRL root name.
956
+ attach_function :leptris_schematron_parse,
957
+ [:string, :size_t, :pointer], :leptris_schematron
958
+ attach_function :leptris_schematron_parse_file,
959
+ [:string, :pointer], :leptris_schematron
960
+ attach_function :leptris_schematron_parse_phase,
961
+ [:string, :size_t, :string, :pointer], :leptris_schematron
962
+ attach_function :leptris_schematron_free,
963
+ [:leptris_schematron], :void
964
+ attach_function :leptris_schematron_valid,
965
+ [:leptris_schematron, :leptris_document], :int
966
+ attach_function :leptris_schematron_validate,
967
+ [:leptris_schematron, :leptris_document], :leptris_document
968
+ attach_function :leptris_schematron_error,
969
+ [:leptris_schematron], :string
970
+
971
+ # Tree diff (libleptris >= 1.9.126 family): equal subtrees
972
+ # prune in O(1) by the #869 Merkle digest; diverging regions
973
+ # align via LCS on child digests, then recurse by name.
974
+ attach_function :leptris_diff,
975
+ [:leptris_document, :leptris_document, :int, :pointer],
976
+ :leptris_diff
977
+ attach_function :leptris_diff_free, [:leptris_diff], :void
978
+ attach_function :leptris_diff_op_count, [:leptris_diff], :size_t
979
+ attach_function :leptris_diff_op_type,
980
+ [:leptris_diff, :size_t], :int
981
+ attach_function :leptris_diff_op_name,
982
+ [:leptris_diff, :size_t], :string
983
+ attach_function :leptris_diff_op_path,
984
+ [:leptris_diff, :size_t], :string
985
+ attach_function :leptris_diff_op_before,
986
+ [:leptris_diff, :size_t], :string
987
+ attach_function :leptris_diff_op_after,
988
+ [:leptris_diff, :size_t], :string
989
+ attach_function :leptris_diff_serialize,
990
+ [:leptris_diff], :pointer
991
+
992
+ # One-call expanded name (libleptris 1.9.144): local name +
993
+ # prefix + resolved namespace URI in a single entry — the
994
+ # FFI-adapter fan-out read.
995
+ attach_function :leptris_element_expanded_name,
996
+ [:leptris_element, :pointer, :pointer, :pointer], :void
997
+
998
+ # Versioned XPath evaluation (libleptris lane 15): XPATH_10
999
+ # keeps the strict 1.0 surface (3.x syntax rejected with
1000
+ # INVALID_ARG); XPATH_31 evaluates the full grammar.
1001
+ attach_function :leptris_xpath_eval_versioned,
1002
+ [:leptris_document, :leptris_element, :string, :int, :pointer],
1003
+ :leptris_xpath_result
1004
+ XPATH_10 = 1 # strict XPath 1.0: the 3.x surface is rejected
1005
+ XPATH_31 = 3 # the full grammar (the default entry's surface)
1006
+
1007
+ # One-pass entity pre-scan (libleptris 1.9.62, leptris#745):
1008
+ # 1 when the buffer holds a named entity that is not one of
1009
+ # the five predefined XML entities (nor numeric) — the moxml
1010
+ # pre-scan at C speed instead of a Ruby regex.
1011
+ attach_function :leptris_str_has_nonstandard_entity,
1012
+ [:string, :size_t], :int
1013
+
1014
+ attach_function :leptris_rng_parse,
1015
+ [:string, :size_t, :pointer], :leptris_relaxng
1016
+ attach_function :leptris_rng_parse_file,
1017
+ [:string, :pointer], :leptris_relaxng
1018
+ attach_function :leptris_rng_free,
1019
+ [:leptris_relaxng], :void
1020
+ # 1 valid; 0 invalid (detail via leptris_rng_error)
1021
+ attach_function :leptris_rng_validate,
1022
+ [:leptris_relaxng, :leptris_document], :int
1023
+ attach_function :leptris_rng_error,
1024
+ [:leptris_relaxng], :string
1025
+ # Accumulated validation errors (libleptris 1.9.179, #878):
1026
+ # the validator no longer stops at the first failure;
1027
+ # leptris_rng_error keeps returning the first (back-compat).
1028
+ attach_function :leptris_rng_error_count,
1029
+ [:leptris_relaxng], :size_t
1030
+ attach_function :leptris_rng_error_message,
1031
+ [:leptris_relaxng, :size_t], :string
1032
+ attach_function :leptris_rng_error_line,
1033
+ [:leptris_relaxng, :size_t], :int
1034
+ attach_function :leptris_rng_error_column,
1035
+ [:leptris_relaxng, :size_t], :int
1036
+
1037
+ # Whole-report face (libleptris 1.9.190): one call returns
1038
+ # every record — kind (the #1126 failure-class taxonomy) and
1039
+ # offender were unreachable through the per-index accessors.
1040
+ # Record array is handle-owned; strings live until the next
1041
+ # validate call or leptris_rng_free.
1042
+ class RngErrorRecord < ::FFI::Struct
1043
+ layout :kind, :string,
1044
+ :message, :string,
1045
+ :offender, :string,
1046
+ :line, :uint,
1047
+ :column, :uint
1048
+ end
1049
+ attach_function :leptris_rng_error_report,
1050
+ [:leptris_relaxng, :pointer], :size_t
1051
+
1052
+ attach_function :leptris_xslt_parse,
1053
+ [:string, :size_t], :leptris_xslt
1054
+ attach_function :leptris_xslt_parse_file,
1055
+ [:string], :leptris_xslt
1056
+ attach_function :leptris_xslt_free,
1057
+ [:leptris_xslt], :void
1058
+ # Result tree is a document (free with document_free); errors
1059
+ # surface via document_last_error on NULL.
1060
+ attach_function :leptris_xslt_apply,
1061
+ [:leptris_xslt, :leptris_document], :leptris_document
1062
+ # apply + serialize in one call, keeping top-level text nodes
1063
+ # and result fragments the tree API would flatten.
1064
+ attach_function :leptris_xslt_apply_string,
1065
+ [:leptris_xslt, :leptris_document], :pointer
1066
+ attach_function :leptris_c14n_canonicalize,
1067
+
1068
+ [:leptris_document, :int, :int], :pointer
1069
+ attach_function :leptris_c14n_canonicalize_subtree,
1070
+ [:leptris_element, :int, :int], :pointer
1071
+ attach_function :leptris_c14n_canonicalize_ex,
1072
+ [:leptris_document, :int, :int, :pointer, :int], :pointer
1073
+ attach_function :leptris_c14n_canonicalize_subtree_ex,
1074
+ [:leptris_element, :int, :int, :pointer, :int], :pointer
1075
+
1076
+ attach_function :leptris_document_internal_subset,
1077
+ [:leptris_document], :leptris_doctype
1078
+ attach_function :leptris_doctype_get_name,
1079
+ [:leptris_doctype], :string
1080
+ attach_function :leptris_doctype_get_root_name,
1081
+ [:leptris_doctype], :string
1082
+ attach_function :leptris_doctype_get_public_id,
1083
+ [:leptris_doctype], :string
1084
+ attach_function :leptris_doctype_get_system_id,
1085
+ [:leptris_doctype], :string
1086
+ attach_function :leptris_doctype_get_internal_subset,
1087
+ [:leptris_doctype], :string
1088
+
1089
+ attach_function :leptris_free_string, [:pointer], :void
1090
+ attach_function :leptris_explicit_cleanup, [], :void
1091
+ attach_function :leptris_set_memory_management_functions,
1092
+ [:pointer, :pointer], :void
1093
+ attach_function :leptris_get_memory_allocation_function, [], :pointer
1094
+ attach_function :leptris_get_memory_deallocation_function, [], :pointer
1095
+ attach_function :leptris_document_set_allocators,
1096
+ [:leptris_document, :pointer, :pointer], :leptris_status
1097
+ attach_function :leptris_status_string, [:leptris_status], :string
1098
+ attach_function :leptris_error_message, [:leptris_status], :string
1099
+ # Thread-global last-failure position (error.h): companion to
1100
+ # leptris_last_error; populated by recover parses (#547).
1101
+ attach_function :leptris_last_error_position,
1102
+ [:pointer, :pointer], :void
1103
+ # Thread-local since v1.3.0; reliable under the
1104
+ # one-document-per-thread contract.
1105
+ attach_function :leptris_last_error, [], :string
1106
+ attach_function :leptris_document_last_error,
1107
+ [:leptris_document], :string
1108
+ # Optional: release per-thread registry entries when a worker
1109
+ # thread exits (long-lived threads never need it).
1110
+ attach_function :leptris_thread_cleanup, [], :void
1111
+ # First-party EXSLT-style extension pack (str:/set:/math:),
1112
+ # registered natively on one document.
1113
+ attach_function :leptris_exslt_enable,
1114
+ [:leptris_document], :leptris_status
1115
+
1116
+ LEPTRIS_OK = 0
1117
+ LEPTRIS_ERROR_MEMORY = -1
1118
+ LEPTRIS_ERROR_PARSE = -2
1119
+ LEPTRIS_ERROR_XPATH = -3
1120
+ LEPTRIS_ERROR_NULL_ARG = -4
1121
+ LEPTRIS_ERROR_INVALID_ARG = -5
1122
+ LEPTRIS_ERROR_NOT_FOUND = -6
1123
+ LEPTRIS_ERROR_IO = -7
1124
+ LEPTRIS_ERROR_NOT_IMPLEMENTED = -8
1125
+
1126
+ XPATH_NODESET = 0
1127
+ XPATH_BOOLEAN = 1
1128
+ XPATH_NUMBER = 2
1129
+ XPATH_STRING = 3
1130
+ # XPath 3.0 function item (libleptris TODO 07 lane): a closure
1131
+ # or named function reference. Not callable across the FFI
1132
+ # boundary yet — wrap_xpath_result raises explicitly.
1133
+ XPATH_FUNCTION = 4
1134
+
1135
+ XPATH_NODE_ELEMENT = 0
1136
+ XPATH_NODE_ATTRIBUTE = 1
1137
+ XPATH_NODE_TEXT = 2
1138
+ XPATH_NODE_OTHER = 3
1139
+
1140
+ NODE_ELEMENT = 0
1141
+ NODE_TEXT = 1
1142
+ NODE_COMMENT = 2
1143
+ NODE_CDATA = 3
1144
+ NODE_PI = 4
1145
+ NODE_ENTITY_REF = 10
1146
+ # Internal XPath-synthetic text node (leptris/types.h): the
1147
+ # carrier for sequence/map/array items in result nodesets —
1148
+ # readable only through the result handle, so the binding
1149
+ # captures the value at materialization (ResultText).
1150
+ NODE_SYNTHETIC_TEXT = 8
1151
+ # #1094: &name; references stay unexpanded first-class nodes
1152
+ # when this flag parses (character references still expand).
1153
+ LEPTRIS_PARSE_KEEP_ENTITY_REFS = 4
1154
+ NODE_DOCTYPE = 5
1155
+ NODE_ATTRIBUTE = 6
1156
+
1157
+ C14N_1_0 = 0
1158
+ C14N_1_1 = 1
1159
+
1160
+ C14N_MODE_CANONICAL = 0
1161
+ C14N_MODE_EXCLUSIVE = 1
1162
+
1163
+ TRAVERSE_PRE_ORDER = 0
1164
+ TRAVERSE_POST_ORDER = 1
1165
+
1166
+ PULL_START_ELEMENT = 0
1167
+ PULL_END_ELEMENT = 1
1168
+ PULL_TEXT = 2
1169
+ PULL_COMMENT = 3
1170
+ PULL_CDATA = 4
1171
+ PULL_PI = 5
1172
+ PULL_END_DOCUMENT = 6
1173
+ PULL_ERROR = 7
1174
+ # Prefix events (types.h LeptrisPullEventType 8/9): the
1175
+ # default namespace's prefix is legitimately "".
1176
+ PULL_START_PREFIX = 8
1177
+ PULL_END_PREFIX = 9
1178
+
1179
+ LEPTRIS_PARSE_DEFAULT = 0
1180
+ LEPTRIS_PARSE_DROP_WS_TEXT = 1
1181
+ # libleptris >= 1.9.8 (#606): apply DTD ATTLIST default (and
1182
+ # #FIXED) attribute values at parse time — libxml2
1183
+ # XML_PARSE_DTDATTR opt-in parity.
1184
+ LEPTRIS_PARSE_DTDATTR = 2
1185
+
1186
+ # The headers' contract is UTF-8 for every C string, but FFI's
1187
+ # read_string hands back ASCII-8BIT — the platform default
1188
+ # leaks through the seam. Every string-returning attached
1189
+ # function this binding CALLS is wrapped here so its result
1190
+ # arrives as UTF-8; mirror-only attachments stay raw (add the
1191
+ # name here when a call site starts using its string).
1192
+ UTF8_RETURNS = %i[
1193
+ leptris_version
1194
+ leptris_status_string leptris_last_error
1195
+ leptris_document_last_error leptris_document_encoding
1196
+ leptris_document_pi_target leptris_document_pi_data
1197
+ leptris_document_comment_content
1198
+ leptris_element_name leptris_element_text leptris_element_prefix
1199
+ leptris_element_namespace
1200
+ leptris_element_attribute leptris_element_attribute_ns
1201
+ leptris_attribute_get_name leptris_attribute_get_value
1202
+ leptris_attribute_namespace_uri
1203
+ leptris_element_namespace_decl_prefix
1204
+ leptris_element_namespace_decl_uri
1205
+ leptris_text_node_get_content leptris_comment_node_get_content
1206
+ leptris_cdata_node_get_content
1207
+ leptris_pi_node_get_target leptris_pi_node_get_data
1208
+ leptris_pull_attr_name leptris_pull_attr_value
1209
+ ].freeze
1210
+ private_constant :UTF8_RETURNS
1211
+
1212
+ UTF8_RETURNS.each do |name|
1213
+ raw = method(name)
1214
+ define_singleton_method(name) do |*args|
1215
+ str = raw.call(*args)
1216
+ str.nil? ? nil : str.force_encoding(Encoding::UTF_8)
1217
+ end
1218
+ end
1219
+
1220
+ # Reads an libleptris-owned char* result and frees it as one unit,
1221
+ # so a call site can neither leak nor double-free.
1222
+ def self.read_owned_string(ptr)
1223
+ return "" if ptr.nil? || ptr.null?
1224
+ ptr.read_string.tap { leptris_free_string(ptr) }
1225
+ .force_encoding(Encoding::UTF_8)
1226
+ end
1227
+
1228
+ # Single status seam: turns a C status code into a Ruby error,
1229
+ # appending the library's last-error detail when present. The
1230
+ # detail is a library-global string, so it can only be treated
1231
+ # as best-effort context.
1232
+ def self.check_status(status)
1233
+ unless status == LEPTRIS_OK
1234
+ raise Leptris::XML::Error, status_message(status)
1235
+ end
1236
+ status
1237
+ end
1238
+
1239
+ def self.status_message(status)
1240
+ base = leptris_status_string(status).to_s
1241
+ detail = leptris_last_error.to_s
1242
+ detail.empty? ? base : "#{base} (#{detail})"
1243
+ end
1244
+
1245
+ # Namespace-binding lifecycle, written once: flatten the
1246
+ # prefix/URI hash into the alternating CStringArray wire
1247
+ # format and build the C set. Sets are cached per distinct
1248
+ # vocabulary: the XPath VM reads the set as a const map
1249
+ # during evaluation (vm.c) and never mutates it, so one set
1250
+ # serves the process across queries and threads — building
1251
+ # the wire format plus a C set per query cost ~6.7 µs, 2.6×
1252
+ # a no-namespace query on a small document. Cached sets are
1253
+ # never freed (bounded by the process's distinct namespace
1254
+ # vocabularies — the same trade the CSS translation cache
1255
+ # makes). Failed builds raise before caching.
1256
+ NS_SET_CACHE = {}
1257
+
1258
+ def self.with_ns_set(hash)
1259
+ yield ns_set_for(hash)
1260
+ end
1261
+
1262
+ def self.ns_set_for(hash)
1263
+ pairs = hash.map { |prefix, uri| [prefix.to_s, uri.to_s] }
1264
+ key = if pairs.size == 1
1265
+ "#{pairs[0][0]}\x00#{pairs[0][1]}"
1266
+ else
1267
+ pairs.sort.join("\x00")
1268
+ end
1269
+ NS_SET_CACHE.fetch(key) do
1270
+ flat = pairs.flatten
1271
+ buffer, _anchors = Leptris::XML::CStringArray.to_c(flat)
1272
+ set = leptris_xpath_ns_set_new_from_pairs(buffer, flat.length / 2)
1273
+ if set.null?
1274
+ raise Leptris::XML::Error,
1275
+ "leptris_xpath_ns_set_new_from_pairs failed"
1276
+ end
1277
+ NS_SET_CACHE[key] = set
1278
+ end
1279
+ end
1280
+
1281
+ # Caller-buffer serialization cycle: size query (buf=NULL),
1282
+ # allocate exactly, fill, read. Since libleptris 1.9.0 the
1283
+ # pair reuses one serialization through the per-document
1284
+ # cache. +ffi_function+ is leptris_document_serialize_into or
1285
+ # leptris_element_serialize_into. The buffer holds the
1286
+ # serialization + NUL and XML output is NUL-free, so the
1287
+ # bounded read is exact.
1288
+ # snprintf contract (leptris.h #535): the return is ALWAYS the
1289
+ # bytes needed incl. the NUL; the copy happens only when the
1290
+ # buffer fits. So the size probe is paid only when the child
1291
+ # outgrows the grow-only scratch — one dispatch per call on
1292
+ # the fast path instead of two (inner_html serializes each
1293
+ # child; the probe was most of the per-child FFI cost).
1294
+ SERIALIZE_INITIAL = 256
1295
+ private_constant :SERIALIZE_INITIAL
1296
+
1297
+ def self.serialize_into_string(ffi_function, c_ptr, options)
1298
+ scratch = (Thread.current[:leptris_scratch] ||= {})
1299
+ buffer = scratch[:bytes]
1300
+ if buffer.nil?
1301
+ buffer = scratch[:bytes] =
1302
+ ::FFI::MemoryPointer.new(:char, SERIALIZE_INITIAL)
1303
+ end
1304
+ needed = ffi_function.call(c_ptr, buffer, buffer.size, nil, options)
1305
+ if needed > buffer.size
1306
+ buffer = scratch_bytes(needed)
1307
+ needed = ffi_function.call(c_ptr, buffer, buffer.size, nil, options)
1308
+ end
1309
+ return "" if needed <= 1
1310
+ buffer.read_string(needed - 1).force_encoding(Encoding::UTF_8)
1311
+ end
1312
+
1313
+ # Byte scratch for serialize-into cycles: the size+fill pair
1314
+ # allocated and freed a buffer per call — inner_html
1315
+ # serializes each child, and the allocation was most of the
1316
+ # per-child cost. Grows to the largest serialization seen;
1317
+ # no Ruby reentrancy during a serialize call.
1318
+ def self.scratch_bytes(need)
1319
+ scratch = (Thread.current[:leptris_scratch] ||= {})
1320
+ ptr = scratch[:bytes]
1321
+ if ptr.nil? || ptr.size < need
1322
+ ptr&.free
1323
+ ptr = scratch[:bytes] = ::FFI::MemoryPointer.new(:char, need)
1324
+ end
1325
+ ptr
1326
+ end
1327
+
1328
+ # All-kind child handle batch (libleptris 1.7.0). Opportunistic
1329
+ # single dispatch: the C call copies up to the capacity handed
1330
+ # it and returns what fit, so the count query is paid only when
1331
+ # the buffer fills exactly (a possible truncation) — families
1332
+ # that fit the scratch buffer cost ONE dispatch, not two.
1333
+ FETCH_INITIAL = 32
1334
+ private_constant :FETCH_INITIAL
1335
+
1336
+ # Returns [pointers, kinds] — the kinds ride the batch
1337
+ # (leptris_node_children_ex, 1.9.18 #617) so wrappers skip
1338
+ # the per-child get_type dispatch.
1339
+ def self.fetch_children(c_ptr)
1340
+ # One thread-local access serves both buffers (the helper
1341
+ # re-looked it up); the kinds buffer sizes from the POINTERS
1342
+ # buffer's capacity — it may have grown past FETCH_INITIAL on
1343
+ # earlier use, and C writes one kind per copied child.
1344
+ scratch = (Thread.current[:leptris_scratch] ||= {})
1345
+ buffer = scratch[:pointers]
1346
+ if buffer.nil?
1347
+ buffer = scratch[:pointers] =
1348
+ ::FFI::MemoryPointer.new(:pointer, FETCH_INITIAL)
1349
+ end
1350
+ cap = buffer.size / PTR_BYTES
1351
+ kinds = scratch[:ints]
1352
+ if kinds.nil? || kinds.size / INT_BYTES < cap
1353
+ kinds&.free
1354
+ kinds = scratch[:ints] = ::FFI::MemoryPointer.new(:int, cap)
1355
+ end
1356
+ copied = leptris_node_children_ex(c_ptr, buffer, kinds, cap)
1357
+ if copied == cap
1358
+ total = leptris_node_children_ex(c_ptr, nil, nil, 0)
1359
+ if total > cap
1360
+ buffer.free
1361
+ buffer = scratch[:pointers] =
1362
+ ::FFI::MemoryPointer.new(:pointer, total)
1363
+ kinds.free
1364
+ kinds = scratch[:ints] =
1365
+ ::FFI::MemoryPointer.new(:int, total)
1366
+ copied = leptris_node_children_ex(c_ptr, buffer, kinds, total)
1367
+ end
1368
+ end
1369
+ [buffer.get_array_of_pointer(0, copied),
1370
+ kinds.get_array_of_int(0, copied)]
1371
+ end
1372
+
1373
+ # Raw qname-ordered attribute pairs INCLUDING xmlns at their
1374
+ # byte positions (leptris_element_attributes_raw, 1.9.18
1375
+ # #635) — the streaming SAX contract's mixed view.
1376
+ def self.fetch_attributes_raw(el_ptr)
1377
+ total = leptris_element_attributes_raw(el_ptr, nil, nil, 0)
1378
+ return [] if total.zero?
1379
+ scratch = (Thread.current[:leptris_scratch] ||= {})
1380
+ names = scratch[:attr_names]
1381
+ if names.nil? || names.size / PTR_BYTES < total
1382
+ names&.free
1383
+ names = scratch[:attr_names] =
1384
+ ::FFI::MemoryPointer.new(:pointer, total)
1385
+ end
1386
+ values = scratch[:attr_values]
1387
+ if values.nil? || values.size / PTR_BYTES < total
1388
+ values&.free
1389
+ values = scratch[:attr_values] =
1390
+ ::FFI::MemoryPointer.new(:pointer, total)
1391
+ end
1392
+ copied = leptris_element_attributes_raw(
1393
+ el_ptr, names, values, total)
1394
+ Array.new(copied) do |i|
1395
+ [utf8(names.get_pointer(i * PTR_BYTES).read_string),
1396
+ utf8(values.get_pointer(i * PTR_BYTES).read_string)]
1397
+ end
1398
+ end
1399
+
1400
+ # Attribute strings cross the seam UTF-8 on both faces
1401
+ # (ADR-0002).
1402
+ def self.utf8(str)
1403
+ str.nil? ? nil : str.force_encoding(Encoding::UTF_8)
1404
+ end
1405
+
1406
+ # Element-only child batch (the mirror of fetch_children over
1407
+ # leptris_element_children): element receivers skip wrapping —
1408
+ # and paying for — the text/comment children they will filter
1409
+ # out anyway; every wrap carries the ELEMENT hint so the
1410
+ # per-child get_type dispatch disappears too.
1411
+ def self.fetch_element_children(el_ptr)
1412
+ scratch = (Thread.current[:leptris_scratch] ||= {})
1413
+ buffer = scratch[:pointers]
1414
+ if buffer.nil?
1415
+ buffer = scratch[:pointers] =
1416
+ ::FFI::MemoryPointer.new(:pointer, FETCH_INITIAL)
1417
+ end
1418
+ cap = buffer.size / PTR_BYTES
1419
+ copied = leptris_element_children(el_ptr, buffer, cap)
1420
+ if copied == cap
1421
+ # leptris_element_children has NO count-only mode (NULL
1422
+ # returns 0, unlike leptris_node_children) — size the
1423
+ # regrow from the dedicated counter.
1424
+ total = leptris_element_child_count(el_ptr)
1425
+ if total > cap
1426
+ buffer.free
1427
+ buffer = scratch[:pointers] =
1428
+ ::FFI::MemoryPointer.new(:pointer, total)
1429
+ copied = leptris_element_children(el_ptr, buffer, total)
1430
+ end
1431
+ end
1432
+ buffer.get_array_of_pointer(0, copied)
1433
+ end
1434
+
1435
+ # XPath result-set batch (leptris_xpath_result_get_nodes_ex):
1436
+ # the call fills out_kinds in the 4-value XPATH_NODE space
1437
+ # (ELEMENT/ATTRIBUTE/TEXT/OTHER). ELEMENT is the only value
1438
+ # that maps unambiguously — XPath's data model reports CDATA
1439
+ # as TEXT, so a TEXT hint cannot distinguish Text from CDATA
1440
+ # (different content getters) — everything else falls back to
1441
+ # get_type in the wrapper. Returns [pointers, element_hints].
1442
+ XPATH_KIND_HINT = { XPATH_NODE_ELEMENT => NODE_ELEMENT }.freeze
1443
+
1444
+ # Returns [pointers, element_hints, raw_kinds] — the raw
1445
+ # XPATH_NODE-space kinds ride along so the materializer can
1446
+ # spot TEXT items (synthetic sequence carriers) and capture
1447
+ # their values while the result handle is alive.
1448
+ def self.fetch_result_nodes(result_ptr, count)
1449
+ return [[], nil, nil] if count.zero?
1450
+ buffer = scratch_pointers(count)
1451
+ kinds = scratch_ints(count)
1452
+ copied = leptris_xpath_result_get_nodes_ex(
1453
+ result_ptr, buffer, kinds, count)
1454
+ raw = kinds.get_array_of_int(0, copied)
1455
+ hints = raw.map { |k| XPATH_KIND_HINT[k] }
1456
+ [buffer.get_array_of_pointer(0, copied), hints, raw]
1457
+ end
1458
+
1459
+ # Fragment parse with the status out-param read: returns
1460
+ # [document-or-fragment pointer, status].
1461
+ def self.parse_fragment_with_status(xml, document_ptr)
1462
+ status = ::FFI::MemoryPointer.new(:int)
1463
+ raw = leptris_parse_fragment(
1464
+ xml, xml.bytesize, document_ptr, status)
1465
+ [raw, status.read_int]
1466
+ end
1467
+
1468
+ # libxml2 consumes the whole whitespace run following the PI
1469
+ # target when parsing; the engine retains it as data
1470
+ # (leptris-ruby#85). Every Ruby-facing PI-data read normalizes
1471
+ # here — whitespace-only data becomes "" so
1472
+ # "pi-without-data" PIs report empty consistently. Read-time
1473
+ # only: PI#data= stores verbatim.
1474
+ def self.read_pi_data(data)
1475
+ data&.sub(/\A[ \t\r\n]+/, "")
1476
+ end
1477
+
1478
+ # Thread-local scratch buffers for batch handle fetches (round
1479
+ # XX): the per-call MemoryPointer new/free was pure cold-path
1480
+ # overhead — two allocations and a GC visit per children() /
1481
+ # result-set batch, ~15% of a cold full-tree walk between
1482
+ # 1.9.1 and 1.9.24. The buffers grow to the largest request
1483
+ # seen (bounded by the widest element or result set) and are
1484
+ # reused. Safe: no Ruby reentrancy during the C call, and
1485
+ # per-thread storage keeps concurrent fetches independent.
1486
+ # Never freed — a few KB pinned per thread.
1487
+ PTR_BYTES = ::FFI.type_size(:pointer)
1488
+ private_constant :PTR_BYTES
1489
+ INT_BYTES = ::FFI.type_size(:int)
1490
+ private_constant :INT_BYTES
1491
+
1492
+ def self.scratch_pointers(count)
1493
+ scratch = (Thread.current[:leptris_scratch] ||= {})
1494
+ ptr = scratch[:pointers]
1495
+ if ptr.nil? || ptr.size / PTR_BYTES < count
1496
+ ptr&.free
1497
+ ptr = scratch[:pointers] = ::FFI::MemoryPointer.new(:pointer, count)
1498
+ end
1499
+ ptr
1500
+ end
1501
+
1502
+ def self.scratch_ints(count)
1503
+ scratch = (Thread.current[:leptris_scratch] ||= {})
1504
+ ptr = scratch[:ints]
1505
+ if ptr.nil? || ptr.size / INT_BYTES < count
1506
+ ptr&.free
1507
+ ptr = scratch[:ints] = ::FFI::MemoryPointer.new(:int, count)
1508
+ end
1509
+ ptr
1510
+ end
1511
+
1512
+ # Pull-event staging block for leptris_pull_next_batch (the
1513
+ # #589 bulk transport): LeptrisPullEvent records, stride from
1514
+ # the struct layout that mirrors the ABI.
1515
+ def self.scratch_events(count)
1516
+ need = count * PullEventStruct.size
1517
+ scratch = (Thread.current[:leptris_scratch] ||= {})
1518
+ ptr = scratch[:pull_events]
1519
+ if ptr.nil? || ptr.size < need
1520
+ ptr&.free
1521
+ ptr = scratch[:pull_events] = ::FFI::MemoryPointer.new(need)
1522
+ end
1523
+ ptr
1524
+ end
1525
+ end
1526
+ end
1527
+ end