makiri 0.2.0 → 0.4.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.
- checksums.yaml +4 -4
- data/.github/workflows/conformance.yml +22 -0
- data/.github/workflows/libfuzzer.yml +83 -0
- data/.github/workflows/release.yml +12 -7
- data/.github/workflows/security.yml +88 -3
- data/.github/workflows/valgrind.yml +135 -0
- data/CHANGELOG.md +152 -15
- data/README.md +183 -13
- data/Rakefile +294 -7
- data/ext/makiri/bridge/bridge.h +28 -0
- data/ext/makiri/bridge/ruby_string.c +282 -12
- data/ext/makiri/core/mkr_alloc.c +40 -3
- data/ext/makiri/core/mkr_alloc.h +28 -5
- data/ext/makiri/core/mkr_buf.c +47 -3
- data/ext/makiri/core/mkr_buf.h +112 -3
- data/ext/makiri/core/mkr_core.c +143 -0
- data/ext/makiri/core/mkr_core.h +11 -2
- data/ext/makiri/core/mkr_hash.h +1 -1
- data/ext/makiri/core/mkr_span.h +186 -0
- data/ext/makiri/core/mkr_text.h +8 -8
- data/ext/makiri/core/mkr_utf8.c +101 -0
- data/ext/makiri/core/mkr_utf8.h +88 -0
- data/ext/makiri/extconf.rb +123 -10
- data/ext/makiri/fuzz/Makefile +95 -0
- data/ext/makiri/fuzz/check_fuzzer.cc +4 -0
- data/ext/makiri/fuzz/xml_fuzz.c +24 -0
- data/ext/makiri/fuzz/xpath_fuzz.c +109 -0
- data/ext/makiri/glue/glue.h +55 -11
- data/ext/makiri/glue/ruby_doc.c +129 -59
- data/ext/makiri/glue/ruby_html_css.c +292 -0
- data/ext/makiri/glue/{ruby_mutate.c → ruby_html_mutate.c} +248 -52
- data/ext/makiri/glue/ruby_html_node.c +859 -0
- data/ext/makiri/glue/ruby_html_serialize.c +154 -0
- data/ext/makiri/glue/ruby_node.c +74 -729
- data/ext/makiri/glue/ruby_node_set.c +167 -32
- data/ext/makiri/glue/ruby_xml.c +602 -0
- data/ext/makiri/glue/ruby_xml_node.c +1373 -0
- data/ext/makiri/glue/ruby_xpath.c +63 -30
- data/ext/makiri/glue/ruby_xpath.h +19 -0
- data/ext/makiri/lexbor_compat/compat.h +42 -9
- data/ext/makiri/lexbor_compat/compat_internal.h +1 -1
- data/ext/makiri/lexbor_compat/dom_index.c +2 -2
- data/ext/makiri/lexbor_compat/post_parse.c +100 -10
- data/ext/makiri/lexbor_compat/source_loc.c +15 -13
- data/ext/makiri/lexbor_compat/text_index.c +14 -8
- data/ext/makiri/lexbor_compat/utf8_input.c +19 -33
- data/ext/makiri/makiri.c +184 -6
- data/ext/makiri/makiri.h +43 -2
- data/ext/makiri/xml/mkr_xml.h +125 -0
- data/ext/makiri/xml/mkr_xml_chars.c +195 -0
- data/ext/makiri/xml/mkr_xml_index.c +169 -0
- data/ext/makiri/xml/mkr_xml_index.h +48 -0
- data/ext/makiri/xml/mkr_xml_mutate.c +817 -0
- data/ext/makiri/xml/mkr_xml_mutate.h +139 -0
- data/ext/makiri/xml/mkr_xml_node.c +399 -0
- data/ext/makiri/xml/mkr_xml_node.h +184 -0
- data/ext/makiri/xml/mkr_xml_tree.c +1515 -0
- data/ext/makiri/xpath/mkr_css.c +1023 -0
- data/ext/makiri/xpath/mkr_css.h +65 -0
- data/ext/makiri/xpath/mkr_xpath.c +96 -32
- data/ext/makiri/xpath/mkr_xpath.h +109 -4
- data/ext/makiri/xpath/mkr_xpath_engine_html.c +17 -0
- data/ext/makiri/xpath/mkr_xpath_engine_xml.c +12 -0
- data/ext/makiri/xpath/{mkr_xpath_eval.c → mkr_xpath_eval_body.h} +551 -241
- data/ext/makiri/xpath/{mkr_xpath_funcs.c → mkr_xpath_funcs_body.h} +318 -276
- data/ext/makiri/xpath/mkr_xpath_internal.h +177 -206
- data/ext/makiri/xpath/mkr_xpath_lex.c +95 -125
- data/ext/makiri/xpath/mkr_xpath_node_access_html.h +138 -0
- data/ext/makiri/xpath/mkr_xpath_node_access_xml.h +145 -0
- data/ext/makiri/xpath/mkr_xpath_number.c +109 -0
- data/ext/makiri/xpath/mkr_xpath_parse.c +83 -94
- data/ext/makiri/xpath/mkr_xpath_prelude_html.h +30 -0
- data/ext/makiri/xpath/mkr_xpath_prelude_xml.h +28 -0
- data/ext/makiri/xpath/mkr_xpath_shared.c +609 -0
- data/ext/makiri/xpath/mkr_xpath_value_body.h +801 -0
- data/ext/makiri/xpath/mkr_xpath_xml_selftest.c +76 -0
- data/lib/makiri/{attribute.rb → attr.rb} +7 -3
- data/lib/makiri/cdata_section.rb +19 -0
- data/lib/makiri/comment.rb +10 -0
- data/lib/makiri/compat_aliases.rb +30 -0
- data/lib/makiri/document.rb +9 -73
- data/lib/makiri/document_fragment.rb +14 -9
- data/lib/makiri/element.rb +4 -4
- data/lib/makiri/html/document.rb +106 -0
- data/lib/makiri/html/node_methods.rb +19 -0
- data/lib/makiri/html.rb +12 -0
- data/lib/makiri/node.rb +58 -15
- data/lib/makiri/node_set.rb +8 -0
- data/lib/makiri/processing_instruction.rb +10 -0
- data/lib/makiri/text.rb +1 -1
- data/lib/makiri/version.rb +1 -1
- data/lib/makiri/xml/builder.rb +263 -0
- data/lib/makiri/xml/document.rb +24 -0
- data/lib/makiri/xml/node_methods.rb +84 -0
- data/lib/makiri/xml.rb +10 -0
- data/lib/makiri/xpath_context.rb +1 -1
- data/lib/makiri.rb +24 -5
- data/script/build_native_gem.rb +2 -2
- data/script/check_alloc_failures.rb +266 -0
- data/script/check_c_safety.rb +77 -2
- data/script/check_c_safety_allowlist.yml +102 -0
- data/script/check_leaks.rb +64 -0
- data/script/leaks_harness.rb +64 -0
- data/vendor/lexbor/CMakeLists.txt +6 -0
- data/vendor/lexbor/README.md +12 -0
- data/vendor/lexbor/config.cmake +1 -1
- data/vendor/lexbor/source/lexbor/core/base.h +1 -1
- data/vendor/lexbor/source/lexbor/core/config.cmake +9 -1
- data/vendor/lexbor/source/lexbor/css/selectors/pseudo_state.c +2 -3
- data/vendor/lexbor/source/lexbor/css/selectors/state.c +3 -0
- data/vendor/lexbor/source/lexbor/dom/interfaces/element.c +21 -0
- data/vendor/lexbor/source/lexbor/dom/interfaces/element.h +5 -0
- data/vendor/lexbor/source/lexbor/encoding/decode.c +33 -4
- data/vendor/lexbor/source/lexbor/html/base.h +1 -1
- data/vendor/lexbor/source/lexbor/html/interfaces/select_element.c +4 -0
- data/vendor/lexbor/source/lexbor/html/serialize.c +545 -41
- data/vendor/lexbor/source/lexbor/html/serialize.h +2 -1
- data/vendor/lexbor/source/lexbor/html/tokenizer.h +2 -2
- data/vendor/lexbor/source/lexbor/html/tree/insertion_mode/in_body.c +1 -1
- data/vendor/lexbor/source/lexbor/html/tree.c +6 -6
- data/vendor/lexbor/source/lexbor/selectors/selectors.c +12 -3
- data/vendor/lexbor/source/lexbor/url/base.h +1 -1
- data/vendor/lexbor/source/lexbor/url/url.c +5 -2
- data/vendor/lexbor/source/lexbor/url/url.h +9 -0
- data/vendor/lexbor/version +1 -1
- metadata +53 -9
- data/ext/makiri/glue/ruby_css.c +0 -185
- data/ext/makiri/glue/ruby_serialize.c +0 -92
- data/ext/makiri/xpath/mkr_xpath_value.c +0 -1286
- data/lib/makiri/cdata.rb +0 -6
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#ifndef MKR_CSS_H
|
|
2
|
+
#define MKR_CSS_H
|
|
3
|
+
|
|
4
|
+
#include "mkr_xpath.h" /* mkr_node_t, mkr_xpath_limits_t, mkr_xpath_error_t, mkr_verified_text_t */
|
|
5
|
+
|
|
6
|
+
#ifdef __cplusplus
|
|
7
|
+
extern "C" {
|
|
8
|
+
#endif
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
* CSS selector front-end for the native XPath engine.
|
|
12
|
+
*
|
|
13
|
+
* Rather than convert CSS to an XPath *string* and re-parse it, this lowers a
|
|
14
|
+
* CSS selector directly into the engine's compiled AST (the same mkr_node_t that
|
|
15
|
+
* mkr_parse produces), which is then run by mkr_xpath_eval_compiled[_first].
|
|
16
|
+
* The whole evaluation - per-evaluate budgets, document order, dedup, namespace
|
|
17
|
+
* resolution - is therefore shared with XPath, and CSS gains no new evaluator
|
|
18
|
+
* opcodes: every supported selector lowers to existing PATH/step/predicate nodes
|
|
19
|
+
* (name tests + contains/starts-with/substring/count/position/not + arithmetic).
|
|
20
|
+
*
|
|
21
|
+
* The CSS *parsing* reuses Lexbor's selector parser (which, unlike its matcher,
|
|
22
|
+
* preserves name case), so this layer only walks the parsed selector list and
|
|
23
|
+
* builds AST. The result AST is owned by the caller and freed with mkr_node_free,
|
|
24
|
+
* exactly like an mkr_parse result.
|
|
25
|
+
*
|
|
26
|
+
* Namespaces (Nokogiri-compatible): a bare type selector binds to the document's
|
|
27
|
+
* DEFAULT namespace when one is in scope. The glue collects the in-scope
|
|
28
|
+
* namespaces (Ruby side, mirroring Nokogiri's `document.namespaces`), registers
|
|
29
|
+
* them on the context so the engine resolves prefixes at eval, and registers the
|
|
30
|
+
* default namespace under the synthetic prefix "xmlns" (Nokogiri's convention).
|
|
31
|
+
* +default_prefix+ is that synthetic prefix ("xmlns") when a default namespace is
|
|
32
|
+
* in scope, or NULL otherwise; lowering uses it to emit name tests:
|
|
33
|
+
* - bare `el` -> prefix = default_prefix (when set), else no-namespace
|
|
34
|
+
* - `p|el` -> prefix = "p" (resolved by the engine; unbound -> error)
|
|
35
|
+
* - `*|el` -> any namespace (nodetest wildcard + local-name() predicate)
|
|
36
|
+
* - `|el` -> no namespace
|
|
37
|
+
*/
|
|
38
|
+
/* The synthetic prefix bound to the document's default namespace (Nokogiri's
|
|
39
|
+
* convention). The glue registers this prefix -> default-ns URI on the context. */
|
|
40
|
+
#define MKR_CSS_DEFAULT_NS_PREFIX "xmlns"
|
|
41
|
+
|
|
42
|
+
typedef struct {
|
|
43
|
+
const char *default_prefix; /* MKR_CSS_DEFAULT_NS_PREFIX when a default ns is in scope, else NULL */
|
|
44
|
+
} mkr_css_ns_t;
|
|
45
|
+
|
|
46
|
+
/*
|
|
47
|
+
* Compile +selector+ into a freshly allocated AST (caller frees with
|
|
48
|
+
* mkr_node_free). Returns NULL on error with *err filled:
|
|
49
|
+
* MKR_XPATH_ERR_SYNTAX - malformed selector or an unsupported construct
|
|
50
|
+
* (jQuery extensions, pseudo-elements, the case
|
|
51
|
+
* modifier)
|
|
52
|
+
* MKR_XPATH_ERR_OOM / _LIMIT - allocation failure / selector-complexity cap
|
|
53
|
+
* +ns+ may be NULL (bare selectors then match no-namespace). +limits+ bounds the
|
|
54
|
+
* AST node count.
|
|
55
|
+
*/
|
|
56
|
+
mkr_node_t *mkr_css_compile(mkr_verified_text_t selector,
|
|
57
|
+
const mkr_css_ns_t *ns,
|
|
58
|
+
mkr_xpath_limits_t *limits,
|
|
59
|
+
mkr_xpath_error_t *err);
|
|
60
|
+
|
|
61
|
+
#ifdef __cplusplus
|
|
62
|
+
}
|
|
63
|
+
#endif
|
|
64
|
+
|
|
65
|
+
#endif /* MKR_CSS_H */
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
#include <string.h>
|
|
10
10
|
|
|
11
11
|
/*
|
|
12
|
-
* Native XPath engine
|
|
12
|
+
* Native XPath engine - top-level: context lifetime, namespace + variable
|
|
13
13
|
* registries, the eval entry point that drives parser + evaluator.
|
|
14
14
|
*
|
|
15
15
|
* Phase 1 builds the engine up to a working subset (see plan file).
|
|
@@ -27,8 +27,8 @@ typedef struct {
|
|
|
27
27
|
} mkr_var_entry_t;
|
|
28
28
|
|
|
29
29
|
struct mkr_xpath_context_s {
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
MKR_DOM_DOCUMENT *doc;
|
|
31
|
+
MKR_DOM_NODE *node;
|
|
32
32
|
|
|
33
33
|
mkr_ns_entry_t *ns;
|
|
34
34
|
size_t ns_count;
|
|
@@ -60,6 +60,14 @@ struct mkr_xpath_context_s {
|
|
|
60
60
|
mkr_tag_index_lookup_t tag_lookup;
|
|
61
61
|
mkr_tag_index_foreign_t tag_has_foreign;
|
|
62
62
|
|
|
63
|
+
/* XML element-name index: the owning document plus a lazy getter and a
|
|
64
|
+
* string-keyed lookup, injected by the XML glue. NULL disables the
|
|
65
|
+
* descendant-name fast path (the engine then walks). HTML uses the tag-id
|
|
66
|
+
* index above instead. */
|
|
67
|
+
void *name_index_owner;
|
|
68
|
+
mkr_name_index_get_t name_index_get;
|
|
69
|
+
mkr_name_index_lookup_t name_index_lookup;
|
|
70
|
+
|
|
63
71
|
/* Namespace-matching policy for UNPREFIXED name tests. 0 (default) =
|
|
64
72
|
* strict/HTML5-faithful: an unprefixed name resolves in the HTML namespace
|
|
65
73
|
* (matches HTML-namespace or no-namespace nodes only; foreign SVG/MathML
|
|
@@ -67,8 +75,36 @@ struct mkr_xpath_context_s {
|
|
|
67
75
|
* tests and wildcards are unaffected. Set by the glue from the
|
|
68
76
|
* namespace_matching: option. */
|
|
69
77
|
int unprefixed_lax;
|
|
78
|
+
|
|
79
|
+
/* Which monomorphized engine instance walks this context's nodes: 0 = HTML
|
|
80
|
+
* (lxb_dom), 1 = XML (custom node). Set by the glue at context creation. Only
|
|
81
|
+
* the two node-dereferencing entries (eval, first-match) dispatch on it; all
|
|
82
|
+
* other per-evaluate machinery is representation-neutral (pointer/string only),
|
|
83
|
+
* so it runs the HTML copy for both. */
|
|
84
|
+
int engine_kind;
|
|
70
85
|
};
|
|
71
86
|
|
|
87
|
+
/* The two node-dereferencing engine ENTRY points, one pair per monomorphized
|
|
88
|
+
* instance (mkr_xpath_engine_{html,xml}.c - the bodies suffix them _html / _xml
|
|
89
|
+
* via the prelude). The driver holds them as void node pointers and dispatches
|
|
90
|
+
* on ctx->engine_kind; every other per-evaluate helper is representation-neutral
|
|
91
|
+
* and shared (mkr_xpath_shared.c), called by its bare name. The signatures are
|
|
92
|
+
* node-pointer-only, hence ABI-identical across the two instances. */
|
|
93
|
+
int mkr_eval_ast_html(mkr_xpath_context_t *ctx, const mkr_node_t *ast,
|
|
94
|
+
mkr_val_t *out, mkr_xpath_error_t *err);
|
|
95
|
+
int mkr_eval_ast_xml (mkr_xpath_context_t *ctx, const mkr_node_t *ast,
|
|
96
|
+
mkr_val_t *out, mkr_xpath_error_t *err);
|
|
97
|
+
int mkr_try_first_match_html(mkr_xpath_context_t *ctx, const mkr_node_t *ast,
|
|
98
|
+
MKR_DOM_NODE **out_node, mkr_xpath_error_t *err);
|
|
99
|
+
int mkr_try_first_match_xml (mkr_xpath_context_t *ctx, const mkr_node_t *ast,
|
|
100
|
+
MKR_DOM_NODE **out_node, mkr_xpath_error_t *err);
|
|
101
|
+
|
|
102
|
+
void
|
|
103
|
+
mkr_xpath_set_engine_kind(mkr_xpath_context_t *ctx, int kind)
|
|
104
|
+
{
|
|
105
|
+
if (ctx) ctx->engine_kind = kind ? 1 : 0;
|
|
106
|
+
}
|
|
107
|
+
|
|
72
108
|
mkr_doc_order_index_t *
|
|
73
109
|
mkr_ctx_order_index(mkr_xpath_context_t *ctx)
|
|
74
110
|
{
|
|
@@ -104,6 +140,35 @@ mkr_ctx_tag_has_foreign(mkr_xpath_context_t *ctx)
|
|
|
104
140
|
return ctx ? ctx->tag_has_foreign : NULL;
|
|
105
141
|
}
|
|
106
142
|
|
|
143
|
+
void
|
|
144
|
+
mkr_xpath_context_set_name_index(mkr_xpath_context_t *ctx, void *owner,
|
|
145
|
+
mkr_name_index_get_t get,
|
|
146
|
+
mkr_name_index_lookup_t lookup)
|
|
147
|
+
{
|
|
148
|
+
if (ctx == NULL) return;
|
|
149
|
+
ctx->name_index_owner = owner;
|
|
150
|
+
ctx->name_index_get = get;
|
|
151
|
+
ctx->name_index_lookup = lookup;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
void *
|
|
155
|
+
mkr_ctx_name_index_owner(mkr_xpath_context_t *ctx)
|
|
156
|
+
{
|
|
157
|
+
return ctx ? ctx->name_index_owner : NULL;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
mkr_name_index_get_t
|
|
161
|
+
mkr_ctx_name_index_get(mkr_xpath_context_t *ctx)
|
|
162
|
+
{
|
|
163
|
+
return ctx ? ctx->name_index_get : NULL;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
mkr_name_index_lookup_t
|
|
167
|
+
mkr_ctx_name_index_lookup(mkr_xpath_context_t *ctx)
|
|
168
|
+
{
|
|
169
|
+
return ctx ? ctx->name_index_lookup : NULL;
|
|
170
|
+
}
|
|
171
|
+
|
|
107
172
|
mkr_str_cache_t *
|
|
108
173
|
mkr_ctx_str_cache(mkr_xpath_context_t *ctx)
|
|
109
174
|
{
|
|
@@ -163,7 +228,7 @@ mkr_xpath_limits_init_defaults(mkr_xpath_limits_t *L)
|
|
|
163
228
|
L->max_steps = 256; /* path step count */
|
|
164
229
|
L->max_predicates = 64; /* per-step predicates */
|
|
165
230
|
L->max_function_args = 64;
|
|
166
|
-
L->max_nodeset_size = 10 * 1000 * 1000; /* 10M nodes
|
|
231
|
+
L->max_nodeset_size = 10 * 1000 * 1000; /* 10M nodes - large but bounded */
|
|
167
232
|
L->max_eval_ops = 50 * 1000 * 1000; /* 50M evaluator steps */
|
|
168
233
|
L->max_string_bytes = 64 * 1024 * 1024; /* 64 MB string-value */
|
|
169
234
|
L->max_recursion_depth = 256;
|
|
@@ -272,29 +337,10 @@ mkr_limit_check_expr_bytes(mkr_xpath_limits_t *L, size_t bytes, mkr_xpath_error_
|
|
|
272
337
|
|
|
273
338
|
/* ---------- node qualified name ---------- */
|
|
274
339
|
|
|
275
|
-
/*
|
|
276
|
-
* Borrowed qualified name for any node. HTML elements report their
|
|
277
|
-
* lowercase local name (lxb_dom_element_qualified_name), matching
|
|
278
|
-
* Makiri::Node#name and the lowercase-tag HTML data model the engine
|
|
279
|
-
* assumes; every other node kind defers to lxb_dom_node_name.
|
|
280
|
-
*/
|
|
281
|
-
const lxb_char_t *
|
|
282
|
-
mkr_dom_node_name_qualified(lxb_dom_node_t *node, size_t *len)
|
|
283
|
-
{
|
|
284
|
-
if (node == NULL) {
|
|
285
|
-
if (len) *len = 0;
|
|
286
|
-
return NULL;
|
|
287
|
-
}
|
|
288
|
-
if (node->type == LXB_DOM_NODE_TYPE_ELEMENT) {
|
|
289
|
-
return lxb_dom_element_qualified_name(lxb_dom_interface_element(node), len);
|
|
290
|
-
}
|
|
291
|
-
return lxb_dom_node_name(node, len);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
340
|
/* ---------- context ---------- */
|
|
295
341
|
|
|
296
342
|
mkr_xpath_context_t *
|
|
297
|
-
mkr_xpath_context_new(
|
|
343
|
+
mkr_xpath_context_new(MKR_DOM_DOCUMENT *doc, MKR_DOM_NODE *node)
|
|
298
344
|
{
|
|
299
345
|
mkr_xpath_context_t *ctx = mkr_callocarray(1, sizeof(*ctx));
|
|
300
346
|
if (ctx == NULL) {
|
|
@@ -447,19 +493,19 @@ mkr_ctx_lookup_variable_text(mkr_xpath_context_t *ctx, const char *prefix,
|
|
|
447
493
|
return 0;
|
|
448
494
|
}
|
|
449
495
|
|
|
450
|
-
|
|
496
|
+
MKR_DOM_NODE *
|
|
451
497
|
mkr_ctx_node(mkr_xpath_context_t *ctx)
|
|
452
498
|
{
|
|
453
499
|
return ctx ? ctx->node : NULL;
|
|
454
500
|
}
|
|
455
501
|
|
|
456
502
|
void
|
|
457
|
-
mkr_ctx_set_node(mkr_xpath_context_t *ctx,
|
|
503
|
+
mkr_ctx_set_node(mkr_xpath_context_t *ctx, MKR_DOM_NODE *node)
|
|
458
504
|
{
|
|
459
505
|
if (ctx) ctx->node = node;
|
|
460
506
|
}
|
|
461
507
|
|
|
462
|
-
|
|
508
|
+
MKR_DOM_DOCUMENT *
|
|
463
509
|
mkr_ctx_document(mkr_xpath_context_t *ctx)
|
|
464
510
|
{
|
|
465
511
|
return ctx ? ctx->doc : NULL;
|
|
@@ -563,7 +609,7 @@ mkr_xpath_eval_compiled(mkr_xpath_context_t *ctx, mkr_node_t *ast,
|
|
|
563
609
|
|
|
564
610
|
/* String-value cache snapshot. Nested eval (handler that calls back
|
|
565
611
|
* into XPath on the same context) sees outer entries but anything it
|
|
566
|
-
* adds is discarded on return
|
|
612
|
+
* adds is discarded on return - outer borrowed pointers stay valid. */
|
|
567
613
|
size_t str_cache_snapshot = ctx->str_cache.count;
|
|
568
614
|
/* Document-order index: outermost evaluate owns the lifecycle. If the
|
|
569
615
|
* outer wasn't built, the inner's build (if any) gets cleared at this
|
|
@@ -572,13 +618,14 @@ mkr_xpath_eval_compiled(mkr_xpath_context_t *ctx, mkr_node_t *ast,
|
|
|
572
618
|
int order_was_built = ctx->order_index.built;
|
|
573
619
|
|
|
574
620
|
mkr_val_t v = {0};
|
|
575
|
-
int eval_rc =
|
|
621
|
+
int eval_rc = ctx->engine_kind ? mkr_eval_ast_xml(ctx, ast, &v, &err)
|
|
622
|
+
: mkr_eval_ast_html(ctx, ast, &v, &err);
|
|
576
623
|
mkr_str_cache_truncate(&ctx->str_cache, str_cache_snapshot);
|
|
577
624
|
if (!order_was_built && ctx->order_index.built) {
|
|
578
625
|
mkr_doc_order_index_clear(&ctx->order_index);
|
|
579
626
|
}
|
|
580
627
|
/* Clear memoized values regardless of success: they are valid only
|
|
581
|
-
* within a single evaluate scope. Defensive
|
|
628
|
+
* within a single evaluate scope. Defensive - also clears on error. */
|
|
582
629
|
mkr_node_clear_memos(ast);
|
|
583
630
|
if (eval_rc != 0) {
|
|
584
631
|
if (out_error) *out_error = err; else mkr_xpath_error_clear(&err);
|
|
@@ -600,8 +647,25 @@ mkr_xpath_eval_compiled_first(mkr_xpath_context_t *ctx, mkr_node_t *ast,
|
|
|
600
647
|
}
|
|
601
648
|
return -1;
|
|
602
649
|
}
|
|
603
|
-
|
|
604
|
-
|
|
650
|
+
/* Reset the per-evaluate counters before the first-match fast path: its
|
|
651
|
+
* descendant walk charges every visited node to max_eval_ops, so it is
|
|
652
|
+
* bounded fail-closed exactly like the full evaluator (which resets these in
|
|
653
|
+
* mkr_xpath_eval_compiled). The not-recognised fallback below resets them
|
|
654
|
+
* again; the walk only runs for recognised shapes, so nothing double-counts. */
|
|
655
|
+
ctx->limits.eval_ops = 0;
|
|
656
|
+
ctx->limits.recursion_depth = 0;
|
|
657
|
+
|
|
658
|
+
MKR_DOM_NODE *node = NULL;
|
|
659
|
+
mkr_xpath_error_t err = {0};
|
|
660
|
+
int matched = ctx->engine_kind ? mkr_try_first_match_xml(ctx, ast, &node, &err)
|
|
661
|
+
: mkr_try_first_match_html(ctx, ast, &node, &err);
|
|
662
|
+
if (matched < 0) {
|
|
663
|
+
/* Op budget exceeded while walking - fail closed (do NOT fall back to the
|
|
664
|
+
* full evaluator, which would hit the same wall). */
|
|
665
|
+
if (out_error) *out_error = err; else mkr_xpath_error_clear(&err);
|
|
666
|
+
return -1;
|
|
667
|
+
}
|
|
668
|
+
if (matched) {
|
|
605
669
|
/* Recognised first-match shape: return a 0-or-1-node node-set without
|
|
606
670
|
* building (or sorting) the full descendant set. */
|
|
607
671
|
mkr_val_t v = {0};
|
|
@@ -17,7 +17,7 @@ extern "C" {
|
|
|
17
17
|
*
|
|
18
18
|
* Security: every evaluate enforces per-call budgets (op count, recursion
|
|
19
19
|
* depth, node-set / string caps); any overrun fails closed with
|
|
20
|
-
* MKR_XPATH_ERR_LIMIT
|
|
20
|
+
* MKR_XPATH_ERR_LIMIT - never a truncated or wrong result.
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
23
|
typedef enum {
|
|
@@ -45,7 +45,10 @@ typedef struct {
|
|
|
45
45
|
mkr_xpath_type_t type;
|
|
46
46
|
union {
|
|
47
47
|
struct {
|
|
48
|
-
lxb_dom_node_t
|
|
48
|
+
/* Representation-agnostic node pointers: lxb_dom_node_t* for an HTML
|
|
49
|
+
* document, mkr_xml_node_t* for an XML one. The glue knows the kind and
|
|
50
|
+
* casts; the engine boundary stays neutral. */
|
|
51
|
+
void **nodes;
|
|
49
52
|
size_t count;
|
|
50
53
|
} nodeset;
|
|
51
54
|
mkr_owned_text_t string; /* owned; valid when type == MKR_XPATH_TYPE_STRING */
|
|
@@ -59,7 +62,10 @@ typedef struct {
|
|
|
59
62
|
char *message; /* heap-allocated, freed with mkr_xpath_error_free */
|
|
60
63
|
} mkr_xpath_error_t;
|
|
61
64
|
|
|
62
|
-
|
|
65
|
+
/* Create a context for +node+ in +doc+ (representation-agnostic void* - the glue
|
|
66
|
+
* passes lxb_dom_* for HTML, mkr_xml_* for XML; the engine kind is selected via
|
|
67
|
+
* mkr_xpath_set_engine_kind). */
|
|
68
|
+
mkr_xpath_context_t *mkr_xpath_context_new(void *doc, void *node);
|
|
63
69
|
void mkr_xpath_context_free(mkr_xpath_context_t *ctx);
|
|
64
70
|
|
|
65
71
|
int mkr_xpath_register_ns(mkr_xpath_context_t *ctx, mkr_verified_text_t prefix, mkr_verified_text_t uri);
|
|
@@ -108,7 +114,7 @@ void mkr_xpath_set_func_resolver (mkr_xpath_context_t *ctx, mkr_func_resolver
|
|
|
108
114
|
|
|
109
115
|
/*
|
|
110
116
|
* Element-index hooks, injected by the glue layer alongside an opaque index
|
|
111
|
-
* pointer. The engine never sees the index's concrete type
|
|
117
|
+
* pointer. The engine never sees the index's concrete type - it only calls
|
|
112
118
|
* back through these, exactly as it calls Ruby only through the func resolver
|
|
113
119
|
* above. This keeps the engine free of the lexbor_compat headers.
|
|
114
120
|
*
|
|
@@ -129,10 +135,109 @@ void mkr_xpath_context_set_element_index(mkr_xpath_context_t *ctx, void *index,
|
|
|
129
135
|
mkr_tag_index_lookup_t lookup,
|
|
130
136
|
mkr_tag_index_foreign_t has_foreign);
|
|
131
137
|
|
|
138
|
+
/* XML element-name index hooks (string-keyed; the HTML tag-id index above is
|
|
139
|
+
* integer-keyed). get(owner) lazily builds + caches the index on the owning
|
|
140
|
+
* document and returns it (or NULL on OOM -> the engine walks); lookup returns
|
|
141
|
+
* the document-ordered bucket for (local, ns_uri). Node pointers are void*. */
|
|
142
|
+
typedef void *(*mkr_name_index_get_t)(void *owner);
|
|
143
|
+
typedef void *const *(*mkr_name_index_lookup_t)(const void *index,
|
|
144
|
+
const char *local, size_t local_len,
|
|
145
|
+
const char *ns_uri, size_t ns_uri_len,
|
|
146
|
+
size_t *count);
|
|
147
|
+
void mkr_xpath_context_set_name_index(mkr_xpath_context_t *ctx, void *owner,
|
|
148
|
+
mkr_name_index_get_t get,
|
|
149
|
+
mkr_name_index_lookup_t lookup);
|
|
150
|
+
|
|
132
151
|
|
|
133
152
|
void mkr_xpath_value_clear(mkr_xpath_value_t *v);
|
|
134
153
|
void mkr_xpath_error_clear(mkr_xpath_error_t *e);
|
|
135
154
|
|
|
155
|
+
/* ------------------------------------------------------------------ *
|
|
156
|
+
* Engine-client API: parser, compiled-AST evaluator, context accessors
|
|
157
|
+
* and the internal value model. The Ruby glue (the engine's client) drives the
|
|
158
|
+
* engine through these and bridges custom-function handlers, so they live on
|
|
159
|
+
* this public boundary -- representation-neutral (node pointers are void*; the
|
|
160
|
+
* glue casts to the document's kind) and free of the MKR_DOM_* monomorphization
|
|
161
|
+
* machinery in mkr_xpath_internal.h, which the glue never includes. The engine
|
|
162
|
+
* itself reaches them through internal.h, which includes this header.
|
|
163
|
+
* ------------------------------------------------------------------ */
|
|
164
|
+
|
|
165
|
+
/* Per-evaluate budgets + live counters. Filled by
|
|
166
|
+
* mkr_xpath_limits_init_defaults; every overrun fails closed with
|
|
167
|
+
* MKR_XPATH_ERR_LIMIT. */
|
|
168
|
+
typedef struct {
|
|
169
|
+
/* Static budgets. */
|
|
170
|
+
size_t max_expr_bytes;
|
|
171
|
+
size_t max_ast_nodes;
|
|
172
|
+
size_t max_steps;
|
|
173
|
+
size_t max_predicates;
|
|
174
|
+
size_t max_function_args;
|
|
175
|
+
size_t max_nodeset_size;
|
|
176
|
+
size_t max_eval_ops;
|
|
177
|
+
size_t max_string_bytes;
|
|
178
|
+
size_t max_recursion_depth;
|
|
179
|
+
/* Live counters. */
|
|
180
|
+
size_t ast_nodes;
|
|
181
|
+
size_t eval_ops;
|
|
182
|
+
size_t recursion_depth;
|
|
183
|
+
} mkr_xpath_limits_t;
|
|
184
|
+
|
|
185
|
+
/* The compiled AST, opaque to clients (built by mkr_parse, freed by
|
|
186
|
+
* mkr_node_free, run by the evaluator). */
|
|
187
|
+
typedef struct mkr_node_s mkr_node_t;
|
|
188
|
+
|
|
189
|
+
/* The engine's internal value -- the type a custom-function resolver receives
|
|
190
|
+
* as its void* args / out (distinct from mkr_xpath_value_t, the result-boundary
|
|
191
|
+
* type, which names the node array `nodes`). Node pointers are void*; the glue
|
|
192
|
+
* handler bridge reads/writes them and casts to the document's representation. */
|
|
193
|
+
typedef struct {
|
|
194
|
+
void **items;
|
|
195
|
+
size_t count;
|
|
196
|
+
size_t capacity;
|
|
197
|
+
} mkr_nodeset_t;
|
|
198
|
+
|
|
199
|
+
typedef struct {
|
|
200
|
+
mkr_xpath_type_t type;
|
|
201
|
+
union {
|
|
202
|
+
mkr_nodeset_t nodeset;
|
|
203
|
+
mkr_owned_text_t string;
|
|
204
|
+
double number;
|
|
205
|
+
int boolean;
|
|
206
|
+
} u;
|
|
207
|
+
} mkr_val_t;
|
|
208
|
+
|
|
209
|
+
/* Parse an expression into a compiled AST (caller frees with mkr_node_free);
|
|
210
|
+
* NULL on error (*err filled). 'limits' bounds the AST node count. */
|
|
211
|
+
mkr_node_t *mkr_parse(mkr_verified_text_t expr, mkr_xpath_limits_t *limits, mkr_xpath_error_t *err);
|
|
212
|
+
void mkr_node_free(mkr_node_t *n);
|
|
213
|
+
|
|
214
|
+
/* Evaluate a compiled AST; fills *out_value / *out_error. _first is the
|
|
215
|
+
* Node#at_xpath variant (a 0-or-1-node result without building the full set). */
|
|
216
|
+
int mkr_xpath_eval_compiled (mkr_xpath_context_t *ctx, mkr_node_t *ast,
|
|
217
|
+
mkr_xpath_value_t *out_value, mkr_xpath_error_t *out_error);
|
|
218
|
+
int mkr_xpath_eval_compiled_first(mkr_xpath_context_t *ctx, mkr_node_t *ast,
|
|
219
|
+
mkr_xpath_value_t *out_value, mkr_xpath_error_t *out_error);
|
|
220
|
+
|
|
221
|
+
/* Select the monomorphized engine instance: 0 = HTML (lxb_dom), 1 = XML. */
|
|
222
|
+
void mkr_xpath_set_engine_kind(mkr_xpath_context_t *ctx, int kind);
|
|
223
|
+
|
|
224
|
+
/* Context accessors used by the glue (node pointers are void*). */
|
|
225
|
+
mkr_xpath_limits_t *mkr_ctx_limits (mkr_xpath_context_t *ctx);
|
|
226
|
+
void *mkr_ctx_document (mkr_xpath_context_t *ctx);
|
|
227
|
+
void mkr_ctx_set_node (mkr_xpath_context_t *ctx, void *node);
|
|
228
|
+
void mkr_ctx_set_unprefixed_lax(mkr_xpath_context_t *ctx, int lax);
|
|
229
|
+
|
|
230
|
+
/* Error helpers (fill *err; never raise). */
|
|
231
|
+
void mkr_err_set (mkr_xpath_error_t *err, mkr_xpath_status_t status, const char *msg);
|
|
232
|
+
void mkr_err_setf(mkr_xpath_error_t *err, mkr_xpath_status_t status, const char *fmt, ...);
|
|
233
|
+
|
|
234
|
+
/* Node-set builder + value setter, for the custom-function handler bridge. */
|
|
235
|
+
void mkr_nodeset_init (mkr_nodeset_t *ns);
|
|
236
|
+
int mkr_nodeset_push (mkr_nodeset_t *ns, void *node, mkr_xpath_limits_t *limits, mkr_xpath_error_t *err);
|
|
237
|
+
void mkr_nodeset_clear(mkr_nodeset_t *ns);
|
|
238
|
+
int mkr_val_set_borrowed_text_copy(mkr_val_t *v, mkr_borrowed_text_t text,
|
|
239
|
+
mkr_xpath_error_t *err, const char *what);
|
|
240
|
+
|
|
136
241
|
#ifdef __cplusplus
|
|
137
242
|
}
|
|
138
243
|
#endif
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/* mkr_xpath_engine_html.c - the HTML monomorphization of the XPath engine.
|
|
2
|
+
*
|
|
3
|
+
* One translation unit holding the whole per-instance engine: the value model,
|
|
4
|
+
* the built-in function library, and the evaluator. The prelude binds the
|
|
5
|
+
* MKR_NODE_* / MKR_DOM_* contract to Lexbor's lxb_dom (mkr_xpath_engine_xml.c is
|
|
6
|
+
* the identical build for the custom XML node). Because all three bodies share
|
|
7
|
+
* this one TU, every helper they pass between each other is file-static - the
|
|
8
|
+
* engine internals never collide with the XML instance and need no per-symbol
|
|
9
|
+
* renaming. Only the two node-dereferencing ENTRY points the driver dispatches
|
|
10
|
+
* on (mkr_eval_ast, mkr_try_first_match) are external, suffixed _html by the
|
|
11
|
+
* prelude. The representation-independent primitives both instances call live
|
|
12
|
+
* once in mkr_xpath_shared.c. */
|
|
13
|
+
#include "mkr_xpath_prelude_html.h"
|
|
14
|
+
|
|
15
|
+
#include "mkr_xpath_value_body.h"
|
|
16
|
+
#include "mkr_xpath_funcs_body.h"
|
|
17
|
+
#include "mkr_xpath_eval_body.h"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/* mkr_xpath_engine_xml.c - the XML monomorphization of the XPath engine.
|
|
2
|
+
*
|
|
3
|
+
* Byte-for-byte the same bodies as mkr_xpath_engine_html.c, but the prelude
|
|
4
|
+
* binds the MKR_NODE_* / MKR_DOM_* contract to the custom mkr_xml_node_t and
|
|
5
|
+
* selects the engine's XML host-policy branches (MKR_HOST_XML). See
|
|
6
|
+
* mkr_xpath_engine_html.c for the one-TU / file-static rationale; here the two
|
|
7
|
+
* external entries are suffixed _xml. */
|
|
8
|
+
#include "mkr_xpath_prelude_xml.h"
|
|
9
|
+
|
|
10
|
+
#include "mkr_xpath_value_body.h"
|
|
11
|
+
#include "mkr_xpath_funcs_body.h"
|
|
12
|
+
#include "mkr_xpath_eval_body.h"
|