leptris 1.0.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 (70) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +8 -0
  4. data/CHANGELOG.md +529 -0
  5. data/CLAUDE.md +104 -0
  6. data/LICENSE.md +33 -0
  7. data/README.adoc +405 -0
  8. data/Rakefile +7 -0
  9. data/TODO.impl/01-architecture.md +217 -0
  10. data/TODO.impl/02-ffi-declarations.md +236 -0
  11. data/TODO.impl/03-document-node-element-nodeset.md +382 -0
  12. data/TODO.impl/04-sax-parser.md +203 -0
  13. data/TODO.impl/05-serialize-c14n-memory-specs-css.md +276 -0
  14. data/benchmark/README.md +168 -0
  15. data/benchmark/leptris_vs_nokogiri.rb +105 -0
  16. data/docs/ARCHITECTURE.adoc +559 -0
  17. data/docs/BUILD.md +395 -0
  18. data/docs/ERROR_MESSAGES.md +458 -0
  19. data/docs/FFI_ARCHITECTURE.md +439 -0
  20. data/docs/FUTURE_VISION.md +303 -0
  21. data/docs/GITHUB_ACTIONS.md +293 -0
  22. data/docs/OPTIMIZATIONS_IMPLEMENTED.adoc +459 -0
  23. data/docs/PERFORMANCE.adoc +668 -0
  24. data/docs/PERFORMANCE.md +448 -0
  25. data/docs/RELEASE_NOTES_v1.0.0.md +515 -0
  26. data/docs/XPATH_SPEC_COMPLIANCE.md +298 -0
  27. data/docs/completion/leptris.bash +86 -0
  28. data/docs/completion/leptris.zsh +74 -0
  29. data/docs/man/leptris-format.1 +227 -0
  30. data/docs/man/leptris-parse.1 +178 -0
  31. data/docs/man/leptris-xpath.1 +312 -0
  32. data/docs/man/leptris.1 +160 -0
  33. data/docs/v0.9.0_PERFORMANCE_IMPROVEMENTS.md +217 -0
  34. data/docs/v0.9.0_RELEASE_SUMMARY.md +281 -0
  35. data/docs/v1.0.0_CONTINUATION_PLAN.md +172 -0
  36. data/docs/v1.0.0_CONTINUATION_PROMPT.md +382 -0
  37. data/docs/v1.0.0_SESSION_6_CONTINUATION.md +434 -0
  38. data/docs/v1.0.0_SESSION_6_PROMPT.md +231 -0
  39. data/docs/v1.0.0_STATUS_TRACKER.md +224 -0
  40. data/docs/v1.1.0_CONTINUATION_PLAN.md +299 -0
  41. data/docs/v1.1.0_FINAL_CONTINUATION_PLAN.md +201 -0
  42. data/docs/v1.1.0_SESSION_3_PROMPT.md +223 -0
  43. data/docs/v1.1.0_STATUS_TRACKER.md +355 -0
  44. data/docs/xml-performance.adoc +115 -0
  45. data/docs/xpath-performance.adoc +379 -0
  46. data/leptris.gemspec +42 -0
  47. data/lib/leptris/version.rb +5 -0
  48. data/lib/leptris/xml/attr.rb +43 -0
  49. data/lib/leptris/xml/c14n.rb +23 -0
  50. data/lib/leptris/xml/cdata.rb +16 -0
  51. data/lib/leptris/xml/comment.rb +16 -0
  52. data/lib/leptris/xml/css_to_xpath.rb +177 -0
  53. data/lib/leptris/xml/doc_type.rb +54 -0
  54. data/lib/leptris/xml/document.rb +202 -0
  55. data/lib/leptris/xml/document_fragment.rb +42 -0
  56. data/lib/leptris/xml/element.rb +278 -0
  57. data/lib/leptris/xml/ffi.rb +420 -0
  58. data/lib/leptris/xml/namespace.rb +43 -0
  59. data/lib/leptris/xml/node.rb +221 -0
  60. data/lib/leptris/xml/node_set.rb +143 -0
  61. data/lib/leptris/xml/parse_options.rb +19 -0
  62. data/lib/leptris/xml/processing_instruction.rb +26 -0
  63. data/lib/leptris/xml/sax/document.rb +45 -0
  64. data/lib/leptris/xml/sax/parser.rb +148 -0
  65. data/lib/leptris/xml/sax.rb +12 -0
  66. data/lib/leptris/xml/searchable.rb +93 -0
  67. data/lib/leptris/xml/text.rb +16 -0
  68. data/lib/leptris/xml.rb +39 -0
  69. data/lib/leptris.rb +7 -0
  70. metadata +157 -0
@@ -0,0 +1,420 @@
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
+ ffi_lib [
11
+ ENV["LEPTRIS_LIB_PATH"],
12
+ File.expand_path("../../libleptris.dylib", __dir__),
13
+ File.expand_path("../../libleptris.so", __dir__),
14
+ "/usr/local/lib/libleptris.dylib",
15
+ "/usr/local/lib/libleptris.so",
16
+ "leptris",
17
+ ].compact
18
+
19
+ typedef :pointer, :leptris_document
20
+ typedef :pointer, :leptris_element
21
+ typedef :pointer, :leptris_node_ref
22
+ typedef :pointer, :leptris_attribute
23
+ typedef :pointer, :leptris_doctype
24
+ typedef :pointer, :leptris_xpath_result
25
+ typedef :pointer, :leptris_xpath_var_set
26
+ typedef :pointer, :leptris_sax_parser
27
+ typedef :int, :leptris_status
28
+
29
+ class SAXHandler < ::FFI::Struct
30
+ layout \
31
+ :start_document, :pointer,
32
+ :end_document, :pointer,
33
+ :start_element, :pointer,
34
+ :end_element, :pointer,
35
+ :characters, :pointer,
36
+ :comment, :pointer,
37
+ :cdata, :pointer,
38
+ :processing_instruction, :pointer,
39
+ :start_prefix_mapping, :pointer,
40
+ :end_prefix_mapping, :pointer,
41
+ :error, :pointer
42
+ end
43
+
44
+ class SerializeOptions < ::FFI::Struct
45
+ layout \
46
+ :indent, :int,
47
+ :xml_declaration, :int,
48
+ :encoding, :pointer
49
+ end
50
+
51
+ attach_function :leptris_version, [], :string
52
+ attach_function :leptris_version_components, [:pointer, :pointer, :pointer], :void
53
+
54
+ attach_function :leptris_parse_string,
55
+ [:string, :size_t, :pointer], :leptris_document
56
+ attach_function :leptris_parse_string_inplace,
57
+ [:pointer, :size_t, :pointer], :leptris_document
58
+ attach_function :leptris_parse_string_with_encoding,
59
+ [:string, :size_t, :pointer], :leptris_document
60
+ attach_function :leptris_parse_file,
61
+ [:string, :pointer], :leptris_document
62
+ attach_function :leptris_load_file,
63
+ [:string, :pointer], :pointer
64
+ attach_function :leptris_document_free,
65
+ [:leptris_document], :void
66
+ attach_function :leptris_document_root,
67
+ [:leptris_document], :leptris_element
68
+ attach_function :leptris_document_encoding,
69
+ [:leptris_document], :string
70
+ attach_function :leptris_document_finalize_strings,
71
+ [:leptris_document], :int
72
+ attach_function :leptris_document_adopt_child,
73
+ [:leptris_document, :leptris_document], :void
74
+ attach_function :leptris_document_set_strict,
75
+ [:leptris_document, :int], :leptris_status
76
+ attach_function :leptris_document_get_strict,
77
+ [:leptris_document], :int
78
+ attach_function :leptris_document_freeze,
79
+ [:leptris_document], :leptris_status
80
+ attach_function :leptris_document_is_frozen,
81
+ [:leptris_document], :int
82
+ attach_function :leptris_set_strict_mode, [:int], :void
83
+ attach_function :leptris_get_strict_mode, [], :int
84
+ attach_function :leptris_set_max_depth, [:int], :void
85
+ attach_function :leptris_get_max_depth, [], :int
86
+ attach_function :leptris_xinclude_process,
87
+ [:leptris_document, :string], :leptris_status
88
+ attach_function :leptris_xinclude_is_include_element,
89
+ [:leptris_element], :int
90
+ attach_function :leptris_xinclude_is_fallback_element,
91
+ [:leptris_element], :int
92
+ attach_function :leptris_xinclude_get_href, [:leptris_element], :string
93
+ attach_function :leptris_xinclude_get_parse, [:leptris_element], :string
94
+ attach_function :leptris_xinclude_get_xpointer, [:leptris_element], :string
95
+ # leptris_xinclude_get_encoding is declared in leptris.h but not exported
96
+ # from the v0.4.4 dylib (likely same visibility bug as the serialize
97
+ # functions; see upstream issue #166). Re-add once #173 / v0.4.5 ships.
98
+
99
+ attach_function :leptris_node_get_type,
100
+ [:leptris_node_ref], :int
101
+ attach_function :leptris_node_first_child,
102
+ [:leptris_node_ref], :leptris_node_ref
103
+ attach_function :leptris_node_last_child,
104
+ [:leptris_node_ref], :leptris_node_ref
105
+ attach_function :leptris_node_next_sibling,
106
+ [:leptris_node_ref], :leptris_node_ref
107
+ attach_function :leptris_node_previous_sibling,
108
+ [:leptris_node_ref], :leptris_node_ref
109
+ attach_function :leptris_node_child_count,
110
+ [:leptris_node_ref], :size_t
111
+ attach_function :leptris_node_as_element,
112
+ [:leptris_node_ref], :leptris_element
113
+ attach_function :leptris_element_as_node,
114
+ [:leptris_element], :leptris_node_ref
115
+ attach_function :leptris_node_get_binding_wrapper,
116
+ [:leptris_node_ref], :pointer
117
+ attach_function :leptris_node_set_binding_wrapper,
118
+ [:leptris_node_ref, :pointer], :void
119
+ attach_function :leptris_node_parent,
120
+ [:leptris_node_ref], :leptris_element
121
+ attach_function :leptris_node_unlink,
122
+ [:leptris_node_ref], :leptris_status
123
+ attach_function :leptris_node_line,
124
+ [:leptris_node_ref], :int
125
+ attach_function :leptris_node_compare,
126
+ [:leptris_node_ref, :leptris_node_ref], :int
127
+ attach_function :leptris_node_traverse,
128
+ [:leptris_node_ref, :int, :pointer, :pointer], :int
129
+
130
+ attach_function :leptris_text_node_get_content,
131
+ [:leptris_node_ref], :string
132
+ attach_function :leptris_comment_node_get_content,
133
+ [:leptris_node_ref], :string
134
+ attach_function :leptris_cdata_node_get_content,
135
+ [:leptris_node_ref], :string
136
+ attach_function :leptris_pi_node_get_target,
137
+ [:leptris_node_ref], :string
138
+ attach_function :leptris_pi_node_get_data,
139
+ [:leptris_node_ref], :string
140
+ attach_function :leptris_text_node_create,
141
+ [:leptris_document, :string], :leptris_node_ref
142
+ attach_function :leptris_comment_node_create,
143
+ [:leptris_document, :string], :leptris_node_ref
144
+ attach_function :leptris_cdata_node_create,
145
+ [:leptris_document, :string], :leptris_node_ref
146
+ attach_function :leptris_pi_node_create,
147
+ [:leptris_document, :string, :string], :leptris_node_ref
148
+ attach_function :leptris_text_node_set_content,
149
+ [:leptris_node_ref, :string], :leptris_status
150
+ attach_function :leptris_comment_node_set_content,
151
+ [:leptris_node_ref, :string], :leptris_status
152
+ attach_function :leptris_cdata_node_set_content,
153
+ [:leptris_node_ref, :string], :leptris_status
154
+ attach_function :leptris_pi_node_set_target,
155
+ [:leptris_node_ref, :string], :leptris_status
156
+ attach_function :leptris_pi_node_set_data,
157
+ [:leptris_node_ref, :string], :leptris_status
158
+
159
+ attach_function :leptris_element_name,
160
+ [:leptris_element], :string
161
+ attach_function :leptris_element_text,
162
+ [:leptris_element], :string
163
+ attach_function :leptris_element_text_int,
164
+ [:leptris_element, :int], :int
165
+ attach_function :leptris_element_text_uint,
166
+ [:leptris_element, :uint], :uint
167
+ attach_function :leptris_element_text_double,
168
+ [:leptris_element, :double], :double
169
+ attach_function :leptris_element_text_float,
170
+ [:leptris_element, :float], :float
171
+ attach_function :leptris_element_text_bool,
172
+ [:leptris_element, :int], :int
173
+ attach_function :leptris_element_attribute,
174
+ [:leptris_element, :string], :string
175
+ attach_function :leptris_element_attribute_string,
176
+ [:leptris_element, :string, :string], :string
177
+ attach_function :leptris_element_attribute_int,
178
+ [:leptris_element, :string, :int], :int
179
+ attach_function :leptris_element_attribute_uint,
180
+ [:leptris_element, :string, :uint], :uint
181
+ attach_function :leptris_element_attribute_double,
182
+ [:leptris_element, :string, :double], :double
183
+ attach_function :leptris_element_attribute_float,
184
+ [:leptris_element, :string, :float], :float
185
+ attach_function :leptris_element_attribute_bool,
186
+ [:leptris_element, :string, :int], :int
187
+ # leptris_element_has_attribute is declared in leptris.h but not exported
188
+ # from the v0.4.4 dylib (likely visibility bug; see upstream #166).
189
+ # Ruby-level Element#key? will emulate via attribute lookup.
190
+ attach_function :leptris_element_attribute_count,
191
+ [:leptris_element], :size_t
192
+ attach_function :leptris_element_attribute_name_at,
193
+ [:leptris_element, :size_t], :string
194
+ attach_function :leptris_element_attribute_value_at,
195
+ [:leptris_element, :size_t], :string
196
+ attach_function :leptris_element_remove_all_attributes,
197
+ [:leptris_element], :leptris_status
198
+
199
+ attach_function :leptris_element_child_count,
200
+ [:leptris_element], :size_t
201
+ attach_function :leptris_element_child,
202
+ [:leptris_element, :size_t], :leptris_element
203
+ attach_function :leptris_element_parent,
204
+ [:leptris_element], :leptris_element
205
+ attach_function :leptris_element_root,
206
+ [:leptris_element], :leptris_element
207
+ attach_function :leptris_element_child_value,
208
+ [:leptris_element], :string
209
+ attach_function :leptris_element_hash_value,
210
+ [:leptris_element], :size_t
211
+ attach_function :leptris_element_find_child,
212
+ [:leptris_element, :string], :leptris_element
213
+ attach_function :leptris_element_find_child_by_attr,
214
+ [:leptris_element, :string, :string, :string], :leptris_element
215
+ attach_function :leptris_element_next_sibling,
216
+ [:leptris_element, :string], :leptris_element
217
+ attach_function :leptris_element_previous_sibling,
218
+ [:leptris_element, :string], :leptris_element
219
+ attach_function :leptris_element_first_child,
220
+ [:leptris_element, :string], :leptris_element
221
+ attach_function :leptris_element_last_child,
222
+ [:leptris_element, :string], :leptris_element
223
+ attach_function :leptris_element_first_child_any,
224
+ [:leptris_element], :leptris_element
225
+ attach_function :leptris_element_last_child_any,
226
+ [:leptris_element], :leptris_element
227
+ attach_function :leptris_element_next_sibling_any,
228
+ [:leptris_element], :leptris_element
229
+ attach_function :leptris_element_previous_sibling_any,
230
+ [:leptris_element], :leptris_element
231
+
232
+ attach_function :leptris_element_create,
233
+ [:leptris_document, :string], :leptris_element
234
+ attach_function :leptris_element_set_name,
235
+ [:leptris_element, :string], :leptris_status
236
+ attach_function :leptris_element_set_text,
237
+ [:leptris_element, :string], :leptris_status
238
+ attach_function :leptris_element_set_attribute,
239
+ [:leptris_element, :string, :string], :leptris_status
240
+ attach_function :leptris_element_set_attribute_bool,
241
+ [:leptris_element, :string, :int], :leptris_status
242
+ attach_function :leptris_element_set_attribute_double,
243
+ [:leptris_element, :string, :double], :leptris_status
244
+ attach_function :leptris_element_set_attribute_float,
245
+ [:leptris_element, :string, :float], :leptris_status
246
+ attach_function :leptris_element_set_attribute_int,
247
+ [:leptris_element, :string, :int], :leptris_status
248
+ attach_function :leptris_element_set_attribute_uint,
249
+ [:leptris_element, :string, :uint], :leptris_status
250
+ attach_function :leptris_element_remove_attribute,
251
+ [:leptris_element, :string], :leptris_status
252
+ attach_function :leptris_element_append_child,
253
+ [:leptris_element, :leptris_element], :leptris_status
254
+ attach_function :leptris_element_prepend_child,
255
+ [:leptris_element, :leptris_element], :leptris_status
256
+ attach_function :leptris_element_insert_before,
257
+ [:leptris_element, :leptris_element], :leptris_status
258
+ attach_function :leptris_element_insert_after,
259
+ [:leptris_element, :leptris_element], :leptris_status
260
+ attach_function :leptris_element_remove_child,
261
+ [:leptris_element, :leptris_element], :leptris_status
262
+ attach_function :leptris_element_remove_children,
263
+ [:leptris_element], :leptris_status
264
+ attach_function :leptris_element_append_copy,
265
+ [:leptris_element, :leptris_element], :leptris_element
266
+ attach_function :leptris_element_prepend_copy,
267
+ [:leptris_element, :leptris_element], :leptris_element
268
+ attach_function :leptris_element_insert_copy_before,
269
+ [:leptris_element, :leptris_element], :leptris_element
270
+ attach_function :leptris_element_insert_copy_after,
271
+ [:leptris_element, :leptris_element], :leptris_element
272
+
273
+ attach_function :leptris_element_copy,
274
+ [:leptris_element, :leptris_document], :leptris_element
275
+ attach_function :leptris_document_copy,
276
+ [:leptris_document], :leptris_document
277
+ attach_function :leptris_node_get_xpath,
278
+ [:leptris_node_ref], :pointer
279
+ attach_function :leptris_parse_fragment,
280
+ [:string, :size_t, :leptris_document, :pointer], :leptris_element
281
+
282
+ attach_function :leptris_element_namespace,
283
+ [:leptris_element], :string
284
+ attach_function :leptris_element_namespace_for_prefix,
285
+ [:leptris_element, :string], :string
286
+ attach_function :leptris_element_namespace_count,
287
+ [:leptris_element], :size_t
288
+ attach_function :leptris_element_namespace_decl_prefix,
289
+ [:leptris_element, :size_t], :string
290
+ attach_function :leptris_element_namespace_decl_uri,
291
+ [:leptris_element, :size_t], :string
292
+ attach_function :leptris_element_add_namespace_definition,
293
+ [:leptris_element, :string, :string], :leptris_status
294
+ attach_function :leptris_element_set_default_namespace,
295
+ [:leptris_element, :string], :leptris_status
296
+ attach_function :leptris_element_remove_namespace_definition,
297
+ [:leptris_element, :string], :leptris_status
298
+ attach_function :leptris_namespace_uri, [:string], :string
299
+ attach_function :leptris_namespace_prefix, [:string], :string
300
+
301
+ attach_function :leptris_xpath_eval,
302
+ [:leptris_document, :leptris_element, :string], :leptris_xpath_result
303
+ attach_function :leptris_xpath_eval_with_vars,
304
+ [:leptris_document, :string, :leptris_xpath_var_set], :leptris_xpath_result
305
+ attach_function :leptris_xpath_eval_with_vars_context,
306
+ [:leptris_document, :leptris_element, :string, :leptris_xpath_var_set],
307
+ :leptris_xpath_result
308
+ attach_function :leptris_xpath_result_type,
309
+ [:leptris_xpath_result], :int
310
+ attach_function :leptris_xpath_result_count,
311
+ [:leptris_xpath_result], :size_t
312
+ attach_function :leptris_xpath_result_get,
313
+ [:leptris_xpath_result, :size_t], :leptris_element
314
+ attach_function :leptris_xpath_result_get_nodes,
315
+ [:leptris_xpath_result, :pointer, :size_t], :size_t
316
+ attach_function :leptris_xpath_result_boolean,
317
+ [:leptris_xpath_result], :int
318
+ attach_function :leptris_xpath_result_number,
319
+ [:leptris_xpath_result], :double
320
+ attach_function :leptris_xpath_result_string,
321
+ [:leptris_xpath_result], :pointer
322
+ attach_function :leptris_xpath_result_free,
323
+ [:leptris_xpath_result], :void
324
+ attach_function :leptris_xpath_function_supported, [:string], :int
325
+ attach_function :leptris_xpath_supported_functions, [], :pointer
326
+
327
+ attach_function :leptris_xpath_variable_set_new,
328
+ [], :leptris_xpath_var_set
329
+ attach_function :leptris_xpath_variable_set_free,
330
+ [:leptris_xpath_var_set], :void
331
+ attach_function :leptris_xpath_variable_set_boolean,
332
+ [:leptris_xpath_var_set, :string, :int], :leptris_status
333
+ attach_function :leptris_xpath_variable_set_number,
334
+ [:leptris_xpath_var_set, :string, :double], :leptris_status
335
+ attach_function :leptris_xpath_variable_set_string,
336
+ [:leptris_xpath_var_set, :string, :string], :leptris_status
337
+
338
+ attach_function :leptris_sax_parse,
339
+ [:string, :size_t, :pointer, :pointer], :int
340
+ attach_function :leptris_sax_parser_create,
341
+ [:pointer, :pointer], :leptris_sax_parser
342
+ attach_function :leptris_sax_parser_feed,
343
+ [:leptris_sax_parser, :string, :size_t, :int], :int
344
+ attach_function :leptris_sax_parser_free,
345
+ [:leptris_sax_parser], :void
346
+ attach_function :leptris_sax_parser_set_streaming,
347
+ [:leptris_sax_parser, :int], :int
348
+
349
+ attach_function :leptris_document_serialize,
350
+ [:leptris_document, :pointer], :pointer
351
+ attach_function :leptris_element_serialize,
352
+ [:leptris_element, :pointer], :pointer
353
+ attach_function :leptris_document_save_file,
354
+ [:leptris_document, :string, :pointer], :leptris_status
355
+ attach_function :leptris_c14n_canonicalize,
356
+ [:leptris_document, :int, :int], :pointer
357
+ attach_function :leptris_c14n_canonicalize_subtree,
358
+ [:leptris_element, :int, :int], :pointer
359
+ attach_function :leptris_c14n_canonicalize_ex,
360
+ [:leptris_document, :int, :int, :pointer, :int], :pointer
361
+ attach_function :leptris_c14n_canonicalize_subtree_ex,
362
+ [:leptris_element, :int, :int, :pointer, :int], :pointer
363
+
364
+ attach_function :leptris_document_internal_subset,
365
+ [:leptris_document], :leptris_doctype
366
+ attach_function :leptris_doctype_get_name,
367
+ [:leptris_doctype], :string
368
+ attach_function :leptris_doctype_get_root_name,
369
+ [:leptris_doctype], :string
370
+ attach_function :leptris_doctype_get_public_id,
371
+ [:leptris_doctype], :string
372
+ attach_function :leptris_doctype_get_system_id,
373
+ [:leptris_doctype], :string
374
+ attach_function :leptris_doctype_get_internal_subset,
375
+ [:leptris_doctype], :string
376
+
377
+ attach_function :leptris_free_string, [:pointer], :void
378
+ attach_function :leptris_explicit_cleanup, [], :void
379
+ attach_function :leptris_set_memory_management_functions,
380
+ [:pointer, :pointer], :void
381
+ attach_function :leptris_get_memory_allocation_function, [], :pointer
382
+ attach_function :leptris_get_memory_deallocation_function, [], :pointer
383
+ attach_function :leptris_document_set_allocators,
384
+ [:leptris_document, :pointer, :pointer], :leptris_status
385
+ attach_function :leptris_status_string, [:leptris_status], :string
386
+
387
+ LEPTRIS_OK = 0
388
+ LEPTRIS_ERROR_MEMORY = -1
389
+ LEPTRIS_ERROR_PARSE = -2
390
+ LEPTRIS_ERROR_XPATH = -3
391
+ LEPTRIS_ERROR_NULL_ARG = -4
392
+ LEPTRIS_ERROR_INVALID_ARG = -5
393
+ LEPTRIS_ERROR_NOT_FOUND = -6
394
+ LEPTRIS_ERROR_IO = -7
395
+ LEPTRIS_ERROR_NOT_IMPLEMENTED = -8
396
+
397
+ XPATH_NODESET = 0
398
+ XPATH_BOOLEAN = 1
399
+ XPATH_NUMBER = 2
400
+ XPATH_STRING = 3
401
+
402
+ NODE_ELEMENT = 0
403
+ NODE_TEXT = 1
404
+ NODE_COMMENT = 2
405
+ NODE_CDATA = 3
406
+ NODE_PI = 4
407
+ NODE_DOCTYPE = 5
408
+ NODE_ATTRIBUTE = 6
409
+
410
+ C14N_1_0 = 0
411
+ C14N_1_1 = 1
412
+
413
+ C14N_MODE_CANONICAL = 0
414
+ C14N_MODE_EXCLUSIVE = 1
415
+
416
+ TRAVERSE_PRE_ORDER = 0
417
+ TRAVERSE_POST_ORDER = 1
418
+ end
419
+ end
420
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ # libleptris's LeptrisNamespace is `const char*` (the URI directly). The Ruby
4
+ # Namespace wrapper holds (element, uri, optional prefix). When the prefix
5
+ # isn't supplied (e.g. via Element#namespace), it can be derived by walking
6
+ # the element's namespace declarations via leptris_element_namespace_decl_*.
7
+
8
+ class Leptris::XML::Namespace
9
+ attr_reader :element, :href, :prefix
10
+
11
+ def initialize(element, href, prefix: :derive)
12
+ @element = element
13
+ @href = href
14
+ @prefix = prefix == :derive ? derive_prefix : prefix
15
+ end
16
+
17
+ def document
18
+ @element&.document
19
+ end
20
+
21
+ def ==(other)
22
+ other.is_a?(Leptris::XML::Namespace) &&
23
+ @href == other.href && @prefix == other.prefix
24
+ end
25
+
26
+ def inspect
27
+ "#<#{self.class.name} prefix=#{@prefix.inspect} href=#{@href.inspect}>"
28
+ end
29
+
30
+ private
31
+
32
+ def derive_prefix
33
+ return nil if @element.nil?
34
+ count = Leptris::XML::FFI.leptris_element_namespace_count(@element.c_ptr)
35
+ count.times do |i|
36
+ uri = Leptris::XML::FFI.leptris_element_namespace_decl_uri(@element.c_ptr, i)
37
+ next unless uri == @href
38
+ prefix = Leptris::XML::FFI.leptris_element_namespace_decl_prefix(@element.c_ptr, i)
39
+ return prefix.nil? || prefix.empty? ? nil : prefix
40
+ end
41
+ nil
42
+ end
43
+ end
@@ -0,0 +1,221 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Leptris::XML::Node
4
+ attr_reader :c_ptr, :document
5
+
6
+ def initialize(c_ptr, document, parent: nil)
7
+ @c_ptr = c_ptr
8
+ @document = document
9
+ @parent = parent
10
+ end
11
+
12
+ def self.wrap(c_ptr, document, parent: nil)
13
+ # Per-document weak-ref cache. Returns the existing wrapper when the
14
+ # same c_ptr is wrapped twice (common in children/sibling walks,
15
+ # repeated xpath queries, traverse-then-access patterns). The cache
16
+ # dies with the document so no stale entries.
17
+ if document && (cached = document.wrapper_cache[c_ptr.address])
18
+ return cached
19
+ end
20
+
21
+ node =
22
+ case Leptris::XML::FFI.leptris_node_get_type(c_ptr)
23
+ when Leptris::XML::FFI::NODE_ELEMENT
24
+ Leptris::XML::Element.new(c_ptr, document, parent: parent)
25
+ when Leptris::XML::FFI::NODE_TEXT
26
+ Leptris::XML::Text.new(c_ptr, document, parent: parent)
27
+ when Leptris::XML::FFI::NODE_COMMENT
28
+ Leptris::XML::Comment.new(c_ptr, document, parent: parent)
29
+ when Leptris::XML::FFI::NODE_CDATA
30
+ Leptris::XML::CDATA.new(c_ptr, document, parent: parent)
31
+ when Leptris::XML::FFI::NODE_PI
32
+ Leptris::XML::ProcessingInstruction.new(c_ptr, document, parent: parent)
33
+ else
34
+ new(c_ptr, document, parent: parent)
35
+ end
36
+
37
+ document.wrapper_cache[c_ptr.address] = node if document
38
+ node
39
+ end
40
+
41
+ def name
42
+ raise NotImplementedError, "#{self.class}#name not implemented"
43
+ end
44
+
45
+ def content
46
+ raise NotImplementedError, "#{self.class}#content not implemented"
47
+ end
48
+ alias_method :text, :content
49
+ alias_method :inner_text, :content
50
+
51
+ def type
52
+ Leptris::XML::FFI.leptris_node_get_type(@c_ptr)
53
+ end
54
+ alias_method :node_type, :type
55
+
56
+ def element?; type == Leptris::XML::FFI::NODE_ELEMENT; end
57
+ def text?; type == Leptris::XML::FFI::NODE_TEXT; end
58
+ def comment?; type == Leptris::XML::FFI::NODE_COMMENT; end
59
+ def cdata?; type == Leptris::XML::FFI::NODE_CDATA; end
60
+ def processing_instruction?
61
+ type == Leptris::XML::FFI::NODE_PI
62
+ end
63
+ alias_method :pi?, :processing_instruction?
64
+
65
+ def parent
66
+ return @parent if @parent
67
+ ptr = Leptris::XML::FFI.leptris_node_parent(@c_ptr)
68
+ return nil if ptr.null?
69
+ Leptris::XML::Element.new(ptr, @document)
70
+ end
71
+
72
+ def line
73
+ Leptris::XML::FFI.leptris_node_line(@c_ptr)
74
+ end
75
+
76
+ def <=>(other)
77
+ return nil unless other.is_a?(Leptris::XML::Node)
78
+ return nil unless @document == other.document
79
+ Leptris::XML::FFI.leptris_node_compare(@c_ptr, other.c_ptr)
80
+ end
81
+
82
+ def child
83
+ ptr = Leptris::XML::FFI.leptris_node_first_child(@c_ptr)
84
+ return nil if ptr.null?
85
+ Leptris::XML::Node.wrap(ptr, @document, parent: as_element_or_self)
86
+ end
87
+
88
+ def children
89
+ nodes = []
90
+ ptr = Leptris::XML::FFI.leptris_node_first_child(@c_ptr)
91
+ until ptr.nil? || ptr.null?
92
+ nodes << Leptris::XML::Node.wrap(ptr, @document, parent: as_element_or_self)
93
+ ptr = Leptris::XML::FFI.leptris_node_next_sibling(ptr)
94
+ end
95
+ Leptris::XML::NodeSet.new(@document, nodes)
96
+ end
97
+
98
+ def next_sibling
99
+ ptr = Leptris::XML::FFI.leptris_node_next_sibling(@c_ptr)
100
+ return nil if ptr.null?
101
+ Leptris::XML::Node.wrap(ptr, @document, parent: @parent)
102
+ end
103
+ alias_method :next, :next_sibling
104
+
105
+ def previous_sibling
106
+ ptr = Leptris::XML::FFI.leptris_node_previous_sibling(@c_ptr)
107
+ return nil if ptr.null?
108
+ Leptris::XML::Node.wrap(ptr, @document, parent: @parent)
109
+ end
110
+ alias_method :previous, :previous_sibling
111
+
112
+ def first_element_child
113
+ ptr = Leptris::XML::FFI.leptris_node_first_child(@c_ptr)
114
+ until ptr.nil? || ptr.null?
115
+ node = Leptris::XML::Node.wrap(ptr, @document, parent: as_element_or_self)
116
+ return node if node.element?
117
+ ptr = Leptris::XML::FFI.leptris_node_next_sibling(ptr)
118
+ end
119
+ nil
120
+ end
121
+
122
+ def last_element_child
123
+ children.reverse_each.find(&:element?)
124
+ end
125
+
126
+ def element_children
127
+ children.select(&:element?)
128
+ end
129
+ alias_method :elements, :element_children
130
+
131
+ def next_element
132
+ sibling = next_sibling
133
+ sibling = sibling.next_sibling until sibling.nil? || sibling.element?
134
+ sibling
135
+ end
136
+
137
+ def previous_element
138
+ sibling = previous_sibling
139
+ sibling = sibling.previous_sibling until sibling.nil? || sibling.element?
140
+ sibling
141
+ end
142
+
143
+ def unlink
144
+ status = Leptris::XML::FFI.leptris_node_unlink(@c_ptr)
145
+ raise Leptris::XML::Error,
146
+ Leptris::XML::FFI.leptris_status_string(status) unless status == Leptris::XML::FFI::LEPTRIS_OK
147
+ @parent = nil
148
+ self
149
+ end
150
+ alias_method :remove, :unlink
151
+
152
+ # Walks the subtree in post-order DFS (matches Nokogiri's semantics).
153
+ #
154
+ # Specialized hot path: skips the intermediate NodeSet allocation that
155
+ # Element#children would create, walking via raw FFI calls and wrapping
156
+ # nodes directly. Saves one Array + one NodeSet allocation per parent
157
+ # node. For a tree of N nodes that's ~N fewer allocations on a full
158
+ # traversal.
159
+ #
160
+ # Still pays ~2 FFI calls per visited node (first_child + next_sibling).
161
+ # Beating Nokogiri on this benchmark needs C-side traverse with a
162
+ # callback (libleptris #273); the per-node FFI cost is the floor.
163
+ def traverse
164
+ return enum_for(:traverse) unless block_given?
165
+ callback = ::FFI::Function.new(:int, [:pointer, :pointer], blocking: true) do |node_ptr, _|
166
+ yield Leptris::XML::Node.wrap(node_ptr, @document)
167
+ 0
168
+ end
169
+ Leptris::XML::FFI.leptris_node_traverse(
170
+ @c_ptr, Leptris::XML::FFI::TRAVERSE_POST_ORDER, callback, nil)
171
+ end
172
+
173
+ def path
174
+ str_ptr = Leptris::XML::FFI.leptris_node_get_xpath(@c_ptr)
175
+ return nil if str_ptr.null?
176
+ str_ptr.read_string.tap { Leptris::XML::FFI.leptris_free_string(str_ptr) }
177
+ end
178
+
179
+ def css_path
180
+ return nil if path.nil?
181
+ path.split("/").filter_map do |part|
182
+ next nil if part.empty?
183
+ part.gsub(/\[(\d+)\]/, ':nth-of-type(\1)')
184
+ end.join(" > ")
185
+ end
186
+
187
+ def dup
188
+ elem_ptr = Leptris::XML::FFI.leptris_node_as_element(@c_ptr)
189
+ raise Leptris::XML::Error, "dup is only supported for element nodes" if elem_ptr.null?
190
+ copy_ptr = Leptris::XML::FFI.leptris_element_copy(elem_ptr, @document.c_ptr)
191
+ raise Leptris::XML::Error, "leptris_element_copy failed" if copy_ptr.null?
192
+ Leptris::XML::Element.new(copy_ptr, @document)
193
+ end
194
+ alias_method :clone, :dup
195
+
196
+ def ==(other)
197
+ return false unless other.is_a?(Leptris::XML::Node)
198
+ @c_ptr == other.c_ptr
199
+ end
200
+
201
+ def inspect
202
+ "#<#{self.class.name} ptr=#{c_ptr}>"
203
+ end
204
+
205
+ protected
206
+
207
+ def as_element_or_self
208
+ is_a?(Leptris::XML::Element) ? self : nil
209
+ end
210
+
211
+ private
212
+
213
+ # Walks the subtree in post-order DFS (matches Nokogiri's semantics).
214
+ #
215
+ # Specialized hot path for #traverse: skips the intermediate NodeSet
216
+ # allocation that Element#children would create, walking via raw FFI
217
+ # calls and wrapping nodes directly. Saves one Array + one NodeSet
218
+ # allocation per parent node.
219
+ #
220
+ include Leptris::XML::Searchable
221
+ end