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
|
@@ -14,48 +14,10 @@
|
|
|
14
14
|
* functions are added in Phase 2.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
/*
|
|
18
|
-
* and substring
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
static size_t
|
|
22
|
-
utf8_char_count(const char *s)
|
|
23
|
-
{
|
|
24
|
-
if (s == NULL) return 0;
|
|
25
|
-
size_t n = 0;
|
|
26
|
-
for (const unsigned char *p = (const unsigned char *)s; *p; ++p) {
|
|
27
|
-
if ((*p & 0xC0) != 0x80) ++n;
|
|
28
|
-
}
|
|
29
|
-
return n;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/* Step forward by n UTF-8 characters from s; returns byte pointer.
|
|
33
|
-
* Returns the trailing NUL if n exceeds the string length. */
|
|
34
|
-
static const char *
|
|
35
|
-
utf8_advance(const char *s, size_t n)
|
|
36
|
-
{
|
|
37
|
-
const unsigned char *p = (const unsigned char *)s;
|
|
38
|
-
while (n > 0 && *p) {
|
|
39
|
-
do { ++p; } while ((*p & 0xC0) == 0x80);
|
|
40
|
-
--n;
|
|
41
|
-
}
|
|
42
|
-
return (const char *)p;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
static const char *
|
|
46
|
-
mkr_text_find(const char *hay, size_t hay_len, const char *needle, size_t needle_len)
|
|
47
|
-
{
|
|
48
|
-
if (hay == NULL || needle == NULL) return NULL;
|
|
49
|
-
if (needle_len == 0) return hay;
|
|
50
|
-
if (needle_len > hay_len) return NULL;
|
|
51
|
-
size_t last = hay_len - needle_len;
|
|
52
|
-
for (size_t i = 0; i <= last; ++i) {
|
|
53
|
-
if (hay[i] == needle[0] && memcmp(hay + i, needle, needle_len) == 0) {
|
|
54
|
-
return hay + i;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return NULL;
|
|
58
|
-
}
|
|
17
|
+
/* Character-counting (mkr_utf8_count_chars), character-advance
|
|
18
|
+
* (mkr_utf8_advance_chars) and substring search (mkr_bytes_find) are
|
|
19
|
+
* length-bounded core primitives - see core/mkr_utf8.h and core/mkr_span.h.
|
|
20
|
+
* The byte-equality compares below go through mkr_bytes_eq (core/mkr_span.h). */
|
|
59
21
|
|
|
60
22
|
static int
|
|
61
23
|
arity_check(size_t got, size_t want_min, size_t want_max, mkr_xpath_error_t *err, const char *name)
|
|
@@ -71,52 +33,71 @@ arity_check(size_t got, size_t want_min, size_t want_max, mkr_xpath_error_t *err
|
|
|
71
33
|
return 0;
|
|
72
34
|
}
|
|
73
35
|
|
|
36
|
+
/* Require +arg+ to be a node-set; returns it, or NULL with a TYPE error naming
|
|
37
|
+
* +fname+. The single front for the "argument must be a node-set" check that
|
|
38
|
+
* count()/sum()/the name functions all share. */
|
|
39
|
+
static const mkr_nodeset_t *
|
|
40
|
+
require_nodeset(const mkr_val_t *arg, const char *fname, mkr_xpath_error_t *err)
|
|
41
|
+
{
|
|
42
|
+
if (arg->type != MKR_XPATH_TYPE_NODESET) {
|
|
43
|
+
mkr_err_setf(err, MKR_XPATH_ERR_TYPE, "%s(): argument must be a node-set", fname);
|
|
44
|
+
return NULL;
|
|
45
|
+
}
|
|
46
|
+
return &arg->u.nodeset;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* string-value of args[0], or of the context node when there is no argument -
|
|
50
|
+
* the "optional node-set arg defaults to self, as a string" idiom shared by
|
|
51
|
+
* string() / string-length() / normalize-space(). */
|
|
52
|
+
static int
|
|
53
|
+
fn_string_arg_or_self(MKR_DOM_NODE *self_node, const mkr_val_t *args, size_t nargs,
|
|
54
|
+
mkr_xpath_limits_t *L, mkr_xpath_error_t *err, mkr_owned_text_t *out)
|
|
55
|
+
{
|
|
56
|
+
return (nargs == 0)
|
|
57
|
+
? mkr_node_to_owned_text_or_fail(self_node, L, err, out)
|
|
58
|
+
: mkr_val_to_owned_text_or_fail(&args[0], L, err, out);
|
|
59
|
+
}
|
|
60
|
+
|
|
74
61
|
static int
|
|
75
|
-
fn_last(mkr_xpath_context_t *ctx,
|
|
76
|
-
size_t self_pos, size_t self_size,
|
|
62
|
+
fn_last(mkr_xpath_context_t *ctx, const mkr_focus_t *focus,
|
|
77
63
|
mkr_val_t *args, size_t nargs, mkr_val_t *out, mkr_xpath_error_t *err)
|
|
78
64
|
{
|
|
79
|
-
(void)ctx; (void)
|
|
65
|
+
(void)ctx; (void)args;
|
|
80
66
|
if (arity_check(nargs, 0, 0, err, "last") != 0) return -1;
|
|
81
67
|
out->type = MKR_XPATH_TYPE_NUMBER;
|
|
82
|
-
out->u.number = (double)
|
|
68
|
+
out->u.number = (double)focus->size;
|
|
83
69
|
return 0;
|
|
84
70
|
}
|
|
85
71
|
|
|
86
72
|
static int
|
|
87
|
-
fn_position(mkr_xpath_context_t *ctx,
|
|
88
|
-
size_t self_pos, size_t self_size,
|
|
73
|
+
fn_position(mkr_xpath_context_t *ctx, const mkr_focus_t *focus,
|
|
89
74
|
mkr_val_t *args, size_t nargs, mkr_val_t *out, mkr_xpath_error_t *err)
|
|
90
75
|
{
|
|
91
|
-
(void)ctx; (void)
|
|
76
|
+
(void)ctx; (void)args;
|
|
92
77
|
if (arity_check(nargs, 0, 0, err, "position") != 0) return -1;
|
|
93
78
|
out->type = MKR_XPATH_TYPE_NUMBER;
|
|
94
|
-
out->u.number = (double)
|
|
79
|
+
out->u.number = (double)focus->pos;
|
|
95
80
|
return 0;
|
|
96
81
|
}
|
|
97
82
|
|
|
98
83
|
static int
|
|
99
|
-
fn_count(mkr_xpath_context_t *ctx,
|
|
100
|
-
size_t self_pos, size_t self_size,
|
|
84
|
+
fn_count(mkr_xpath_context_t *ctx, const mkr_focus_t *focus,
|
|
101
85
|
mkr_val_t *args, size_t nargs, mkr_val_t *out, mkr_xpath_error_t *err)
|
|
102
86
|
{
|
|
103
|
-
(void)ctx; (void)
|
|
87
|
+
(void)ctx; (void)focus;
|
|
104
88
|
if (arity_check(nargs, 1, 1, err, "count") != 0) return -1;
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
return -1;
|
|
108
|
-
}
|
|
89
|
+
const mkr_nodeset_t *ns = require_nodeset(&args[0], "count", err);
|
|
90
|
+
if (ns == NULL) return -1;
|
|
109
91
|
out->type = MKR_XPATH_TYPE_NUMBER;
|
|
110
|
-
out->u.number = (double)
|
|
92
|
+
out->u.number = (double)ns->count;
|
|
111
93
|
return 0;
|
|
112
94
|
}
|
|
113
95
|
|
|
114
96
|
static int
|
|
115
|
-
fn_not(mkr_xpath_context_t *ctx,
|
|
116
|
-
size_t self_pos, size_t self_size,
|
|
97
|
+
fn_not(mkr_xpath_context_t *ctx, const mkr_focus_t *focus,
|
|
117
98
|
mkr_val_t *args, size_t nargs, mkr_val_t *out, mkr_xpath_error_t *err)
|
|
118
99
|
{
|
|
119
|
-
(void)ctx; (void)
|
|
100
|
+
(void)ctx; (void)focus;
|
|
120
101
|
if (arity_check(nargs, 1, 1, err, "not") != 0) return -1;
|
|
121
102
|
out->type = MKR_XPATH_TYPE_BOOLEAN;
|
|
122
103
|
out->u.boolean = !mkr_val_to_boolean(&args[0]);
|
|
@@ -124,11 +105,10 @@ fn_not(mkr_xpath_context_t *ctx, lxb_dom_node_t *self_node,
|
|
|
124
105
|
}
|
|
125
106
|
|
|
126
107
|
static int
|
|
127
|
-
fn_true(mkr_xpath_context_t *ctx,
|
|
128
|
-
size_t self_pos, size_t self_size,
|
|
108
|
+
fn_true(mkr_xpath_context_t *ctx, const mkr_focus_t *focus,
|
|
129
109
|
mkr_val_t *args, size_t nargs, mkr_val_t *out, mkr_xpath_error_t *err)
|
|
130
110
|
{
|
|
131
|
-
(void)ctx; (void)
|
|
111
|
+
(void)ctx; (void)focus; (void)args;
|
|
132
112
|
if (arity_check(nargs, 0, 0, err, "true") != 0) return -1;
|
|
133
113
|
out->type = MKR_XPATH_TYPE_BOOLEAN;
|
|
134
114
|
out->u.boolean = 1;
|
|
@@ -136,91 +116,113 @@ fn_true(mkr_xpath_context_t *ctx, lxb_dom_node_t *self_node,
|
|
|
136
116
|
}
|
|
137
117
|
|
|
138
118
|
static int
|
|
139
|
-
fn_false(mkr_xpath_context_t *ctx,
|
|
140
|
-
size_t self_pos, size_t self_size,
|
|
119
|
+
fn_false(mkr_xpath_context_t *ctx, const mkr_focus_t *focus,
|
|
141
120
|
mkr_val_t *args, size_t nargs, mkr_val_t *out, mkr_xpath_error_t *err)
|
|
142
121
|
{
|
|
143
|
-
(void)ctx; (void)
|
|
122
|
+
(void)ctx; (void)focus; (void)args;
|
|
144
123
|
if (arity_check(nargs, 0, 0, err, "false") != 0) return -1;
|
|
145
124
|
out->type = MKR_XPATH_TYPE_BOOLEAN;
|
|
146
125
|
out->u.boolean = 0;
|
|
147
126
|
return 0;
|
|
148
127
|
}
|
|
149
128
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
129
|
+
#ifndef MKR_HOST_XML
|
|
130
|
+
/* id() helpers are HTML-only: the XML instance's id() is the empty node-set
|
|
131
|
+
* (no DTD-declared IDs, §8.6), so it never walks the tree by id attribute.
|
|
132
|
+
* Guarded so the XML engine TU does not carry (and warn about) dead code. */
|
|
133
|
+
|
|
134
|
+
/* id(string|nodeset) - looks up by HTML id attribute. Walks the whole tree per
|
|
135
|
+
* token, so it charges each visited node to the op budget and fails closed on
|
|
136
|
+
* overrun (returns -1, *out untouched-meaningful); a hit sets *out, a miss
|
|
137
|
+
* leaves *out NULL. Without this, id() over a large node-set (a token per node,
|
|
138
|
+
* a tree walk per token) drives O(nodes^2) work as ~0 ops. */
|
|
139
|
+
static int
|
|
140
|
+
find_by_id(MKR_DOM_NODE *root, const char *id, size_t id_len,
|
|
141
|
+
mkr_xpath_limits_t *L, mkr_xpath_error_t *err, MKR_DOM_NODE **out)
|
|
153
142
|
{
|
|
154
|
-
|
|
155
|
-
|
|
143
|
+
*out = NULL;
|
|
144
|
+
if (root == NULL || id == NULL || id_len == 0) return 0;
|
|
145
|
+
MKR_DOM_NODE *n = root;
|
|
156
146
|
while (n) {
|
|
157
|
-
if (
|
|
158
|
-
|
|
147
|
+
if (mkr_limit_eval_op(L, err) != 0) return -1;
|
|
148
|
+
if (MKR_NODE_TYPE(n) == MKR_NTYPE_ELEMENT) {
|
|
149
|
+
MKR_DOM_ELEMENT *el = MKR_NODE_AS_ELEMENT(n);
|
|
159
150
|
size_t vlen = 0;
|
|
160
|
-
const lxb_char_t *v =
|
|
161
|
-
if (v &&
|
|
162
|
-
|
|
151
|
+
const lxb_char_t *v = MKR_ELEM_GET_ATTRIBUTE(el, "id", 2, &vlen);
|
|
152
|
+
if (v && mkr_bytes_eq(v, vlen, id, id_len)) {
|
|
153
|
+
*out = n;
|
|
154
|
+
return 0;
|
|
163
155
|
}
|
|
164
156
|
}
|
|
165
|
-
if (n
|
|
166
|
-
n = n
|
|
157
|
+
if (MKR_NODE_FIRST_CHILD(n)) {
|
|
158
|
+
n = MKR_NODE_FIRST_CHILD(n);
|
|
167
159
|
} else {
|
|
168
|
-
while (n && n != root && n
|
|
160
|
+
while (n && n != root && MKR_NODE_NEXT(n) == NULL) n = MKR_NODE_PARENT(n);
|
|
169
161
|
if (n == root || n == NULL) break;
|
|
170
|
-
n = n
|
|
162
|
+
n = MKR_NODE_NEXT(n);
|
|
171
163
|
}
|
|
172
164
|
}
|
|
173
|
-
return
|
|
165
|
+
return 0;
|
|
174
166
|
}
|
|
175
167
|
|
|
176
168
|
/*
|
|
177
|
-
* Look up each whitespace-separated token in 's'
|
|
178
|
-
* every hit into 'out'. Duplicates are pushed unconditionally
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
169
|
+
* Look up each whitespace-separated token in the borrowed text 's' and push
|
|
170
|
+
* every hit into 'out'. Duplicates are pushed unconditionally - the caller (fn_id) deduplicates
|
|
171
|
+
* the entire result via sort + adjacent pass after all tokens have been
|
|
172
|
+
* processed, which is O(n log n) versus O(n^2) per-insert contains() checks.
|
|
173
|
+
*
|
|
174
|
+
* Length-bounded over an mkr_span_t: each token is captured as a borrowed slice
|
|
175
|
+
* (mark/since) and handed to find_by_id by ptr+len, so the buffer is never
|
|
176
|
+
* mutated (the earlier temp-NUL splice was vestigial - find_by_id already
|
|
177
|
+
* compares by explicit length, never as a C string).
|
|
182
178
|
*/
|
|
183
179
|
static int
|
|
184
|
-
id_collect_from_string(
|
|
185
|
-
|
|
180
|
+
id_collect_from_string(mkr_borrowed_text_t s, MKR_DOM_NODE *root,
|
|
181
|
+
mkr_nodeset_t *out, mkr_xpath_context_t *ctx,
|
|
182
|
+
mkr_xpath_error_t *err)
|
|
186
183
|
{
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
size_t tok_len = (
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
184
|
+
mkr_span_t sp = mkr_span(s.ptr, s.len);
|
|
185
|
+
for (;;) {
|
|
186
|
+
mkr_span_skip_xpath_ws(&sp);
|
|
187
|
+
if (mkr_span_peek(&sp) < 0) break;
|
|
188
|
+
const char *tok = mkr_span_mark(&sp);
|
|
189
|
+
while (mkr_span_peek(&sp) >= 0 && !mkr_xpath_is_ws(mkr_span_peek(&sp)))
|
|
190
|
+
mkr_span_skip(&sp, 1);
|
|
191
|
+
size_t tok_len = mkr_span_since(&sp, tok);
|
|
192
|
+
MKR_DOM_NODE *hit;
|
|
193
|
+
if (find_by_id(root, tok, tok_len, mkr_ctx_limits(ctx), err, &hit) != 0) {
|
|
194
|
+
return -1;
|
|
195
|
+
}
|
|
196
|
+
if (hit && mkr_nodeset_push(out, hit, mkr_ctx_limits(ctx), err) != 0) {
|
|
197
|
+
return -1;
|
|
202
198
|
}
|
|
203
|
-
if (saved == '\0') break;
|
|
204
|
-
*p++ = saved;
|
|
205
199
|
}
|
|
206
200
|
return 0;
|
|
207
201
|
}
|
|
202
|
+
#endif /* !MKR_HOST_XML */
|
|
208
203
|
|
|
209
204
|
static int
|
|
210
|
-
fn_id(mkr_xpath_context_t *ctx,
|
|
211
|
-
size_t self_pos, size_t self_size,
|
|
205
|
+
fn_id(mkr_xpath_context_t *ctx, const mkr_focus_t *focus,
|
|
212
206
|
mkr_val_t *args, size_t nargs, mkr_val_t *out, mkr_xpath_error_t *err)
|
|
213
207
|
{
|
|
214
|
-
(void)
|
|
208
|
+
(void)focus;
|
|
215
209
|
if (arity_check(nargs, 1, 1, err, "id") != 0) return -1;
|
|
216
210
|
out->type = MKR_XPATH_TYPE_NODESET;
|
|
217
211
|
mkr_nodeset_init(&out->u.nodeset);
|
|
218
212
|
|
|
219
|
-
|
|
213
|
+
#ifdef MKR_HOST_XML
|
|
214
|
+
/* Host policy (§8.6): in XML an ID is an attribute DECLARED ID-typed by the
|
|
215
|
+
* DTD/schema - not any attribute named "id". DTDs are rejected at parse
|
|
216
|
+
* (§9.4), so a document read here carries no ID-typed attributes and id() is
|
|
217
|
+
* the empty node-set. (xml:id is a separate, optional spec, not implemented.) */
|
|
218
|
+
(void)ctx; (void)args;
|
|
219
|
+
return 0;
|
|
220
|
+
#else
|
|
221
|
+
MKR_DOM_NODE *root = (MKR_DOM_NODE *)mkr_ctx_document(ctx);
|
|
220
222
|
if (root == NULL) return 0;
|
|
221
223
|
|
|
222
224
|
/* XPath 1.0 §4.1: id() argument may be a node-set; in that case each
|
|
223
|
-
* node's string-value is treated as IDREFS
|
|
225
|
+
* node's string-value is treated as IDREFS - whitespace-separated tokens.
|
|
224
226
|
* For non-node-set, the value is converted to string and split likewise. */
|
|
225
227
|
if (args[0].type == MKR_XPATH_TYPE_NODESET) {
|
|
226
228
|
for (size_t i = 0; i < args[0].u.nodeset.count; ++i) {
|
|
@@ -229,7 +231,7 @@ fn_id(mkr_xpath_context_t *ctx, lxb_dom_node_t *self_node,
|
|
|
229
231
|
mkr_nodeset_clear(&out->u.nodeset);
|
|
230
232
|
return -1;
|
|
231
233
|
}
|
|
232
|
-
int rc = id_collect_from_string(text
|
|
234
|
+
int rc = id_collect_from_string(mkr_borrowed_text_from_owned(text), root, &out->u.nodeset, ctx, err);
|
|
233
235
|
mkr_owned_text_clear(&text);
|
|
234
236
|
if (rc != 0) { mkr_nodeset_clear(&out->u.nodeset); return -1; }
|
|
235
237
|
}
|
|
@@ -239,13 +241,14 @@ fn_id(mkr_xpath_context_t *ctx, lxb_dom_node_t *self_node,
|
|
|
239
241
|
mkr_nodeset_clear(&out->u.nodeset);
|
|
240
242
|
return -1;
|
|
241
243
|
}
|
|
242
|
-
int rc = id_collect_from_string(text
|
|
244
|
+
int rc = id_collect_from_string(mkr_borrowed_text_from_owned(text), root, &out->u.nodeset, ctx, err);
|
|
243
245
|
mkr_owned_text_clear(&text);
|
|
244
246
|
if (rc != 0) { mkr_nodeset_clear(&out->u.nodeset); return -1; }
|
|
245
247
|
}
|
|
246
248
|
/* Result is in document order with duplicates removed (XPath 1.0 §4.1). */
|
|
247
249
|
mkr_nodeset_unique_sorted(ctx, &out->u.nodeset);
|
|
248
250
|
return 0;
|
|
251
|
+
#endif /* MKR_HOST_XML */
|
|
249
252
|
}
|
|
250
253
|
|
|
251
254
|
/* Forward decl for two_owned_texts() defined later. */
|
|
@@ -256,69 +259,68 @@ static int two_owned_texts(mkr_xpath_context_t *ctx, mkr_val_t *args,
|
|
|
256
259
|
/* ---------- nokogiri-builtin namespace ---------- */
|
|
257
260
|
|
|
258
261
|
/*
|
|
259
|
-
* css-class(haystack, needle)
|
|
262
|
+
* css-class(haystack, needle) - true iff 'needle' is a whitespace-
|
|
260
263
|
* separated token of 'haystack'. The libxml2 version of this function
|
|
261
264
|
* ships in nl_xpath_context.c (builtin_css_class); we keep behavior
|
|
262
265
|
* identical.
|
|
263
266
|
*/
|
|
264
267
|
static int
|
|
265
|
-
ws_token_match(
|
|
268
|
+
ws_token_match(mkr_borrowed_text_t hay, mkr_borrowed_text_t val)
|
|
266
269
|
{
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
270
|
+
/* NULL ordering preserved from the original: a NULL haystack/needle is a
|
|
271
|
+
* non-match BEFORE the empty-needle short-circuit (so empty val + NULL hay
|
|
272
|
+
* stays 0, not 1). */
|
|
273
|
+
if (hay.ptr == NULL || val.ptr == NULL) return 0;
|
|
274
|
+
if (val.len == 0) return 1; /* libxml2 returns non-NULL for empty val */
|
|
275
|
+
|
|
276
|
+
mkr_span_t sp = mkr_span(hay.ptr, hay.len);
|
|
277
|
+
while (mkr_span_peek(&sp) >= 0) {
|
|
278
|
+
const char *tok = mkr_span_mark(&sp);
|
|
279
|
+
while (mkr_span_peek(&sp) >= 0 && !mkr_xpath_is_ws(mkr_span_peek(&sp)))
|
|
280
|
+
mkr_span_skip(&sp, 1);
|
|
281
|
+
if (mkr_bytes_eq(tok, mkr_span_since(&sp, tok), val.ptr, val.len)) {
|
|
277
282
|
return 1;
|
|
278
283
|
}
|
|
279
|
-
|
|
280
|
-
while (*p && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) p++;
|
|
284
|
+
mkr_span_skip_xpath_ws(&sp);
|
|
281
285
|
}
|
|
282
286
|
return 0;
|
|
283
287
|
}
|
|
284
288
|
|
|
285
289
|
static int
|
|
286
|
-
fn_nokogiri_css_class(mkr_xpath_context_t *ctx,
|
|
287
|
-
size_t self_pos, size_t self_size,
|
|
290
|
+
fn_nokogiri_css_class(mkr_xpath_context_t *ctx, const mkr_focus_t *focus,
|
|
288
291
|
mkr_val_t *args, size_t nargs, mkr_val_t *out, mkr_xpath_error_t *err)
|
|
289
292
|
{
|
|
290
|
-
(void)
|
|
293
|
+
(void)focus;
|
|
291
294
|
if (arity_check(nargs, 2, 2, err, "nokogiri-builtin:css-class") != 0) return -1;
|
|
292
295
|
mkr_owned_text_t hay, needle;
|
|
293
296
|
if (two_owned_texts(ctx, args, &hay, &needle, err) != 0) return -1;
|
|
294
297
|
out->type = MKR_XPATH_TYPE_BOOLEAN;
|
|
295
|
-
out->u.boolean = ws_token_match(hay
|
|
298
|
+
out->u.boolean = ws_token_match(mkr_borrowed_text_from_owned(hay),
|
|
299
|
+
mkr_borrowed_text_from_owned(needle));
|
|
296
300
|
mkr_owned_text_clear(&hay);
|
|
297
301
|
mkr_owned_text_clear(&needle);
|
|
298
302
|
return 0;
|
|
299
303
|
}
|
|
300
304
|
|
|
301
305
|
/*
|
|
302
|
-
* local-name-is(elementName)
|
|
306
|
+
* local-name-is(elementName) - true iff the context node's qualified
|
|
303
307
|
* name (for HTML this is the lowercase local name) equals the argument.
|
|
304
308
|
*/
|
|
305
309
|
static int
|
|
306
|
-
fn_nokogiri_local_name_is(mkr_xpath_context_t *ctx,
|
|
307
|
-
size_t self_pos, size_t self_size,
|
|
310
|
+
fn_nokogiri_local_name_is(mkr_xpath_context_t *ctx, const mkr_focus_t *focus,
|
|
308
311
|
mkr_val_t *args, size_t nargs, mkr_val_t *out, mkr_xpath_error_t *err)
|
|
309
312
|
{
|
|
310
|
-
(void)self_pos; (void)self_size;
|
|
311
313
|
if (arity_check(nargs, 1, 1, err, "nokogiri-builtin:local-name-is") != 0) return -1;
|
|
312
314
|
mkr_owned_text_t want;
|
|
313
315
|
if (mkr_val_to_owned_text_or_fail(&args[0], mkr_ctx_limits(ctx), err, &want) != 0) return -1;
|
|
314
316
|
|
|
315
317
|
out->type = MKR_XPATH_TYPE_BOOLEAN;
|
|
316
318
|
out->u.boolean = 0;
|
|
317
|
-
if (
|
|
319
|
+
if (focus->node != NULL) {
|
|
318
320
|
size_t got_len = 0;
|
|
319
|
-
const lxb_char_t *got =
|
|
321
|
+
const lxb_char_t *got = MKR_ELEM_QUALIFIED_NAME(focus->node, &got_len);
|
|
320
322
|
if (got != NULL) {
|
|
321
|
-
out->u.boolean = (
|
|
323
|
+
out->u.boolean = mkr_bytes_eq(got, got_len, want.ptr, want.len);
|
|
322
324
|
}
|
|
323
325
|
}
|
|
324
326
|
mkr_owned_text_clear(&want);
|
|
@@ -336,12 +338,65 @@ static const fn_entry_t fn_table_nokogiri_builtin[] = {
|
|
|
336
338
|
{ NULL, NULL },
|
|
337
339
|
};
|
|
338
340
|
|
|
341
|
+
#ifdef MKR_HOST_XML
|
|
342
|
+
/* ---------- internal: untyped :*-of-type position (XML, CSS-only) ---------- *
|
|
343
|
+
* Two elements are the same "type" iff they share an expanded name (local name
|
|
344
|
+
* + namespace URI). These back the CSS-lowered untyped of-type pseudo-classes;
|
|
345
|
+
* see MKR_FN_OF_TYPE_POS in mkr_xpath_internal.h for why they cannot be a pure
|
|
346
|
+
* XPath expression. focus->node is the context element being filtered. */
|
|
339
347
|
static int
|
|
340
|
-
|
|
341
|
-
|
|
348
|
+
mkr_xml_same_type(MKR_DOM_NODE *a, MKR_DOM_NODE *b)
|
|
349
|
+
{
|
|
350
|
+
size_t al = 0, bl = 0;
|
|
351
|
+
const lxb_char_t *an = MKR_ELEM_LOCAL_NAME(a, &al);
|
|
352
|
+
const lxb_char_t *bn = MKR_ELEM_LOCAL_NAME(b, &bl);
|
|
353
|
+
if (!mkr_bytes_eq(an, al, bn, bl)) return 0;
|
|
354
|
+
size_t au = 0, bu = 0;
|
|
355
|
+
const char *ap = MKR_NODE_NS_URI(a, NULL, &au);
|
|
356
|
+
const char *bp = MKR_NODE_NS_URI(b, NULL, &bu);
|
|
357
|
+
return mkr_bytes_eq(ap, au, bp, bu);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/* 1-based position of +self+ among its same-type element siblings; forward walks
|
|
361
|
+
* preceding siblings (position from the start), else following (from the end). */
|
|
362
|
+
static double
|
|
363
|
+
mkr_xml_of_type_pos(MKR_DOM_NODE *self, int forward)
|
|
364
|
+
{
|
|
365
|
+
if (self == NULL || MKR_NODE_TYPE(self) != MKR_NTYPE_ELEMENT) return 0.0;
|
|
366
|
+
long pos = 1;
|
|
367
|
+
for (MKR_DOM_NODE *s = forward ? MKR_NODE_PREV(self) : MKR_NODE_NEXT(self);
|
|
368
|
+
s != NULL; s = forward ? MKR_NODE_PREV(s) : MKR_NODE_NEXT(s)) {
|
|
369
|
+
if (MKR_NODE_TYPE(s) == MKR_NTYPE_ELEMENT && mkr_xml_same_type(self, s)) pos++;
|
|
370
|
+
}
|
|
371
|
+
return (double)pos;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
static int
|
|
375
|
+
fn_xml_of_type_pos(mkr_xpath_context_t *ctx, const mkr_focus_t *focus,
|
|
376
|
+
mkr_val_t *args, size_t nargs, mkr_val_t *out, mkr_xpath_error_t *err)
|
|
377
|
+
{
|
|
378
|
+
(void)ctx; (void)args; (void)nargs; (void)err;
|
|
379
|
+
out->type = MKR_XPATH_TYPE_NUMBER;
|
|
380
|
+
out->u.number = mkr_xml_of_type_pos(focus->node, 1);
|
|
381
|
+
return 0;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
static int
|
|
385
|
+
fn_xml_of_type_pos_last(mkr_xpath_context_t *ctx, const mkr_focus_t *focus,
|
|
386
|
+
mkr_val_t *args, size_t nargs, mkr_val_t *out, mkr_xpath_error_t *err)
|
|
387
|
+
{
|
|
388
|
+
(void)ctx; (void)args; (void)nargs; (void)err;
|
|
389
|
+
out->type = MKR_XPATH_TYPE_NUMBER;
|
|
390
|
+
out->u.number = mkr_xml_of_type_pos(focus->node, 0);
|
|
391
|
+
return 0;
|
|
392
|
+
}
|
|
393
|
+
#endif /* MKR_HOST_XML */
|
|
394
|
+
|
|
395
|
+
static int
|
|
396
|
+
fn_boolean(mkr_xpath_context_t *ctx, const mkr_focus_t *focus,
|
|
342
397
|
mkr_val_t *args, size_t nargs, mkr_val_t *out, mkr_xpath_error_t *err)
|
|
343
398
|
{
|
|
344
|
-
(void)ctx; (void)
|
|
399
|
+
(void)ctx; (void)focus;
|
|
345
400
|
if (arity_check(nargs, 1, 1, err, "boolean") != 0) return -1;
|
|
346
401
|
out->type = MKR_XPATH_TYPE_BOOLEAN;
|
|
347
402
|
out->u.boolean = mkr_val_to_boolean(&args[0]);
|
|
@@ -351,29 +406,22 @@ fn_boolean(mkr_xpath_context_t *ctx, lxb_dom_node_t *self_node,
|
|
|
351
406
|
/* ---------- string functions ---------- */
|
|
352
407
|
|
|
353
408
|
static int
|
|
354
|
-
fn_string(mkr_xpath_context_t *ctx,
|
|
355
|
-
size_t self_pos, size_t self_size,
|
|
409
|
+
fn_string(mkr_xpath_context_t *ctx, const mkr_focus_t *focus,
|
|
356
410
|
mkr_val_t *args, size_t nargs, mkr_val_t *out, mkr_xpath_error_t *err)
|
|
357
411
|
{
|
|
358
|
-
(void)self_pos; (void)self_size;
|
|
359
412
|
if (arity_check(nargs, 0, 1, err, "string") != 0) return -1;
|
|
360
413
|
out->type = MKR_XPATH_TYPE_STRING;
|
|
361
414
|
mkr_owned_text_t text;
|
|
362
|
-
if (nargs
|
|
363
|
-
if (mkr_node_to_owned_text_or_fail(self_node, mkr_ctx_limits(ctx), err, &text) != 0) return -1;
|
|
364
|
-
} else {
|
|
365
|
-
if (mkr_val_to_owned_text_or_fail(&args[0], mkr_ctx_limits(ctx), err, &text) != 0) return -1;
|
|
366
|
-
}
|
|
415
|
+
if (fn_string_arg_or_self(focus->node, args, nargs, mkr_ctx_limits(ctx), err, &text) != 0) return -1;
|
|
367
416
|
mkr_val_set_owned_text(out, text);
|
|
368
417
|
return 0;
|
|
369
418
|
}
|
|
370
419
|
|
|
371
420
|
static int
|
|
372
|
-
fn_concat(mkr_xpath_context_t *ctx,
|
|
373
|
-
size_t self_pos, size_t self_size,
|
|
421
|
+
fn_concat(mkr_xpath_context_t *ctx, const mkr_focus_t *focus,
|
|
374
422
|
mkr_val_t *args, size_t nargs, mkr_val_t *out, mkr_xpath_error_t *err)
|
|
375
423
|
{
|
|
376
|
-
(void)
|
|
424
|
+
(void)focus;
|
|
377
425
|
if (nargs < 2) {
|
|
378
426
|
mkr_err_set(err, MKR_XPATH_ERR_RUNTIME, "concat(): expected at least 2 arguments");
|
|
379
427
|
return -1;
|
|
@@ -439,48 +487,49 @@ two_owned_texts(mkr_xpath_context_t *ctx, mkr_val_t *args,
|
|
|
439
487
|
}
|
|
440
488
|
|
|
441
489
|
static int
|
|
442
|
-
fn_starts_with(mkr_xpath_context_t *ctx,
|
|
443
|
-
size_t self_pos, size_t self_size,
|
|
490
|
+
fn_starts_with(mkr_xpath_context_t *ctx, const mkr_focus_t *focus,
|
|
444
491
|
mkr_val_t *args, size_t nargs, mkr_val_t *out, mkr_xpath_error_t *err)
|
|
445
492
|
{
|
|
446
|
-
(void)
|
|
493
|
+
(void)focus;
|
|
447
494
|
if (arity_check(nargs, 2, 2, err, "starts-with") != 0) return -1;
|
|
448
495
|
mkr_owned_text_t s, t;
|
|
449
496
|
if (two_owned_texts(ctx, args, &s, &t, err) != 0) return -1;
|
|
450
497
|
out->type = MKR_XPATH_TYPE_BOOLEAN;
|
|
451
|
-
|
|
498
|
+
mkr_span_t ss = mkr_span(s.ptr, s.len);
|
|
499
|
+
out->u.boolean = mkr_span_starts(&ss, t.ptr, t.len);
|
|
452
500
|
mkr_owned_text_clear(&s);
|
|
453
501
|
mkr_owned_text_clear(&t);
|
|
454
502
|
return 0;
|
|
455
503
|
}
|
|
456
504
|
|
|
457
505
|
static int
|
|
458
|
-
fn_contains(mkr_xpath_context_t *ctx,
|
|
459
|
-
size_t self_pos, size_t self_size,
|
|
506
|
+
fn_contains(mkr_xpath_context_t *ctx, const mkr_focus_t *focus,
|
|
460
507
|
mkr_val_t *args, size_t nargs, mkr_val_t *out, mkr_xpath_error_t *err)
|
|
461
508
|
{
|
|
462
|
-
(void)
|
|
509
|
+
(void)focus;
|
|
463
510
|
if (arity_check(nargs, 2, 2, err, "contains") != 0) return -1;
|
|
464
511
|
mkr_owned_text_t s, t;
|
|
465
512
|
if (two_owned_texts(ctx, args, &s, &t, err) != 0) return -1;
|
|
466
513
|
out->type = MKR_XPATH_TYPE_BOOLEAN;
|
|
467
|
-
|
|
514
|
+
size_t idx;
|
|
515
|
+
out->u.boolean = mkr_bytes_find(s.ptr, s.len, t.ptr, t.len, &idx);
|
|
468
516
|
mkr_owned_text_clear(&s);
|
|
469
517
|
mkr_owned_text_clear(&t);
|
|
470
518
|
return 0;
|
|
471
519
|
}
|
|
472
520
|
|
|
473
521
|
static int
|
|
474
|
-
fn_substring_before(mkr_xpath_context_t *ctx,
|
|
475
|
-
size_t self_pos, size_t self_size,
|
|
522
|
+
fn_substring_before(mkr_xpath_context_t *ctx, const mkr_focus_t *focus,
|
|
476
523
|
mkr_val_t *args, size_t nargs, mkr_val_t *out, mkr_xpath_error_t *err)
|
|
477
524
|
{
|
|
478
|
-
(void)
|
|
525
|
+
(void)focus;
|
|
479
526
|
if (arity_check(nargs, 2, 2, err, "substring-before") != 0) return -1;
|
|
480
527
|
mkr_owned_text_t s, t;
|
|
481
528
|
if (two_owned_texts(ctx, args, &s, &t, err) != 0) return -1;
|
|
482
|
-
|
|
483
|
-
|
|
529
|
+
/* substring-before(s, t): bytes of s up to the first occurrence of t, or ""
|
|
530
|
+
* when t is empty or absent. */
|
|
531
|
+
size_t n = 0, idx;
|
|
532
|
+
if (t.len != 0 && mkr_bytes_find(s.ptr, s.len, t.ptr, t.len, &idx)) n = idx;
|
|
484
533
|
char *p = mkr_strndup(s.ptr, n); /* n == 0 yields an owned "" */
|
|
485
534
|
mkr_owned_text_clear(&s);
|
|
486
535
|
mkr_owned_text_clear(&t);
|
|
@@ -493,11 +542,10 @@ fn_substring_before(mkr_xpath_context_t *ctx, lxb_dom_node_t *self_node,
|
|
|
493
542
|
}
|
|
494
543
|
|
|
495
544
|
static int
|
|
496
|
-
fn_substring_after(mkr_xpath_context_t *ctx,
|
|
497
|
-
size_t self_pos, size_t self_size,
|
|
545
|
+
fn_substring_after(mkr_xpath_context_t *ctx, const mkr_focus_t *focus,
|
|
498
546
|
mkr_val_t *args, size_t nargs, mkr_val_t *out, mkr_xpath_error_t *err)
|
|
499
547
|
{
|
|
500
|
-
(void)
|
|
548
|
+
(void)focus;
|
|
501
549
|
if (arity_check(nargs, 2, 2, err, "substring-after") != 0) return -1;
|
|
502
550
|
mkr_owned_text_t s, t;
|
|
503
551
|
if (two_owned_texts(ctx, args, &s, &t, err) != 0) return -1;
|
|
@@ -507,9 +555,14 @@ fn_substring_after(mkr_xpath_context_t *ctx, lxb_dom_node_t *self_node,
|
|
|
507
555
|
out_len = s.len;
|
|
508
556
|
p = mkr_strndup(s.ptr, s.len);
|
|
509
557
|
} else {
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
558
|
+
size_t idx;
|
|
559
|
+
if (mkr_bytes_find(s.ptr, s.len, t.ptr, t.len, &idx)) {
|
|
560
|
+
out_len = s.len - (idx + t.len);
|
|
561
|
+
p = mkr_strndup(s.ptr + idx + t.len, out_len);
|
|
562
|
+
} else {
|
|
563
|
+
out_len = 0;
|
|
564
|
+
p = mkr_strdup("");
|
|
565
|
+
}
|
|
513
566
|
}
|
|
514
567
|
mkr_owned_text_clear(&s);
|
|
515
568
|
mkr_owned_text_clear(&t);
|
|
@@ -521,21 +574,20 @@ fn_substring_after(mkr_xpath_context_t *ctx, lxb_dom_node_t *self_node,
|
|
|
521
574
|
return 0;
|
|
522
575
|
}
|
|
523
576
|
|
|
524
|
-
/* substring(s, start[, length])
|
|
577
|
+
/* substring(s, start[, length]) - XPath positions are 1-based and round
|
|
525
578
|
* to nearest; out-of-range positions clip to the string. */
|
|
526
579
|
static int
|
|
527
|
-
fn_substring(mkr_xpath_context_t *ctx,
|
|
528
|
-
size_t self_pos, size_t self_size,
|
|
580
|
+
fn_substring(mkr_xpath_context_t *ctx, const mkr_focus_t *focus,
|
|
529
581
|
mkr_val_t *args, size_t nargs, mkr_val_t *out, mkr_xpath_error_t *err)
|
|
530
582
|
{
|
|
531
|
-
(void)
|
|
583
|
+
(void)focus;
|
|
532
584
|
if (arity_check(nargs, 2, 3, err, "substring") != 0) return -1;
|
|
533
585
|
mkr_xpath_limits_t *L = mkr_ctx_limits(ctx);
|
|
534
586
|
mkr_owned_text_t s;
|
|
535
587
|
if (mkr_val_to_owned_text_or_fail(&args[0], L, err, &s) != 0) return -1;
|
|
536
588
|
double start_d, end_d;
|
|
537
589
|
if (mkr_val_to_number_or_fail(&args[1], L, err, &start_d) != 0) { mkr_owned_text_clear(&s); return -1; }
|
|
538
|
-
size_t s_chars =
|
|
590
|
+
size_t s_chars = mkr_utf8_count_chars(s.ptr, s.len);
|
|
539
591
|
if (nargs == 3) {
|
|
540
592
|
double len_d;
|
|
541
593
|
if (mkr_val_to_number_or_fail(&args[2], L, err, &len_d) != 0) { mkr_owned_text_clear(&s); return -1; }
|
|
@@ -567,10 +619,12 @@ fn_substring(mkr_xpath_context_t *ctx, lxb_dom_node_t *self_node,
|
|
|
567
619
|
if (end <= start) {
|
|
568
620
|
p = mkr_strdup("");
|
|
569
621
|
} else {
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
622
|
+
size_t start_off = mkr_utf8_advance_chars(s.ptr, s.len, (size_t)(start - 1));
|
|
623
|
+
size_t end_off = start_off + mkr_utf8_advance_chars(s.ptr + start_off,
|
|
624
|
+
s.len - start_off,
|
|
625
|
+
(size_t)(end - start));
|
|
626
|
+
out_len = end_off - start_off;
|
|
627
|
+
p = mkr_strndup(s.ptr + start_off, out_len);
|
|
574
628
|
}
|
|
575
629
|
}
|
|
576
630
|
mkr_owned_text_clear(&s);
|
|
@@ -583,38 +637,26 @@ fn_substring(mkr_xpath_context_t *ctx, lxb_dom_node_t *self_node,
|
|
|
583
637
|
}
|
|
584
638
|
|
|
585
639
|
static int
|
|
586
|
-
fn_string_length(mkr_xpath_context_t *ctx,
|
|
587
|
-
size_t self_pos, size_t self_size,
|
|
640
|
+
fn_string_length(mkr_xpath_context_t *ctx, const mkr_focus_t *focus,
|
|
588
641
|
mkr_val_t *args, size_t nargs, mkr_val_t *out, mkr_xpath_error_t *err)
|
|
589
642
|
{
|
|
590
|
-
(void)self_pos; (void)self_size;
|
|
591
643
|
if (arity_check(nargs, 0, 1, err, "string-length") != 0) return -1;
|
|
592
|
-
mkr_xpath_limits_t *L = mkr_ctx_limits(ctx);
|
|
593
644
|
mkr_owned_text_t s;
|
|
594
|
-
|
|
595
|
-
? mkr_node_to_owned_text_or_fail(self_node, L, err, &s)
|
|
596
|
-
: mkr_val_to_owned_text_or_fail(&args[0], L, err, &s);
|
|
597
|
-
if (rc != 0) return -1;
|
|
645
|
+
if (fn_string_arg_or_self(focus->node, args, nargs, mkr_ctx_limits(ctx), err, &s) != 0) return -1;
|
|
598
646
|
out->type = MKR_XPATH_TYPE_NUMBER;
|
|
599
|
-
out->u.number = (double)
|
|
647
|
+
out->u.number = (double)mkr_utf8_count_chars(s.ptr, s.len);
|
|
600
648
|
mkr_owned_text_clear(&s);
|
|
601
649
|
return 0;
|
|
602
650
|
}
|
|
603
651
|
|
|
604
652
|
/* normalize-space: collapse runs of whitespace, trim leading/trailing */
|
|
605
653
|
static int
|
|
606
|
-
fn_normalize_space(mkr_xpath_context_t *ctx,
|
|
607
|
-
size_t self_pos, size_t self_size,
|
|
654
|
+
fn_normalize_space(mkr_xpath_context_t *ctx, const mkr_focus_t *focus,
|
|
608
655
|
mkr_val_t *args, size_t nargs, mkr_val_t *out, mkr_xpath_error_t *err)
|
|
609
656
|
{
|
|
610
|
-
(void)self_pos; (void)self_size;
|
|
611
657
|
if (arity_check(nargs, 0, 1, err, "normalize-space") != 0) return -1;
|
|
612
|
-
mkr_xpath_limits_t *L = mkr_ctx_limits(ctx);
|
|
613
658
|
mkr_owned_text_t s;
|
|
614
|
-
|
|
615
|
-
? mkr_node_to_owned_text_or_fail(self_node, L, err, &s)
|
|
616
|
-
: mkr_val_to_owned_text_or_fail(&args[0], L, err, &s);
|
|
617
|
-
if (rc != 0) return -1;
|
|
659
|
+
if (fn_string_arg_or_self(focus->node, args, nargs, mkr_ctx_limits(ctx), err, &s) != 0) return -1;
|
|
618
660
|
char *buf = mkr_str_alloc(s.len);
|
|
619
661
|
if (buf == NULL) { mkr_owned_text_clear(&s); mkr_err_set(err, MKR_XPATH_ERR_OOM, "out of memory in normalize-space"); return -1; }
|
|
620
662
|
size_t out_i = 0;
|
|
@@ -638,11 +680,10 @@ fn_normalize_space(mkr_xpath_context_t *ctx, lxb_dom_node_t *self_node,
|
|
|
638
680
|
}
|
|
639
681
|
|
|
640
682
|
static int
|
|
641
|
-
fn_translate(mkr_xpath_context_t *ctx,
|
|
642
|
-
size_t self_pos, size_t self_size,
|
|
683
|
+
fn_translate(mkr_xpath_context_t *ctx, const mkr_focus_t *focus,
|
|
643
684
|
mkr_val_t *args, size_t nargs, mkr_val_t *out, mkr_xpath_error_t *err)
|
|
644
685
|
{
|
|
645
|
-
(void)
|
|
686
|
+
(void)focus;
|
|
646
687
|
if (arity_check(nargs, 3, 3, err, "translate") != 0) return -1;
|
|
647
688
|
mkr_xpath_limits_t *L = mkr_ctx_limits(ctx);
|
|
648
689
|
mkr_owned_text_t s, from, to;
|
|
@@ -675,7 +716,7 @@ fn_translate(mkr_xpath_context_t *ctx, lxb_dom_node_t *self_node,
|
|
|
675
716
|
}
|
|
676
717
|
|
|
677
718
|
/* The string-literal lexer validates UTF-8 and DOM string-values are valid
|
|
678
|
-
* UTF-8, so a decode error here should not happen
|
|
719
|
+
* UTF-8, so a decode error here should not happen - but if it does, fail
|
|
679
720
|
* closed (RUNTIME error) rather than silently truncating the result. */
|
|
680
721
|
size_t from_n = 0;
|
|
681
722
|
for (const lxb_char_t *p = (const lxb_char_t *)from.ptr, *e = p + from.len; p < e;) {
|
|
@@ -750,18 +791,16 @@ decode_fail:
|
|
|
750
791
|
/* ---------- number functions ---------- */
|
|
751
792
|
|
|
752
793
|
static int
|
|
753
|
-
fn_number(mkr_xpath_context_t *ctx,
|
|
754
|
-
size_t self_pos, size_t self_size,
|
|
794
|
+
fn_number(mkr_xpath_context_t *ctx, const mkr_focus_t *focus,
|
|
755
795
|
mkr_val_t *args, size_t nargs, mkr_val_t *out, mkr_xpath_error_t *err)
|
|
756
796
|
{
|
|
757
|
-
(void)self_pos; (void)self_size;
|
|
758
797
|
if (arity_check(nargs, 0, 1, err, "number") != 0) return -1;
|
|
759
798
|
out->type = MKR_XPATH_TYPE_NUMBER;
|
|
760
799
|
mkr_xpath_limits_t *L = mkr_ctx_limits(ctx);
|
|
761
800
|
if (nargs == 0) {
|
|
762
801
|
mkr_val_t self_v = { .type = MKR_XPATH_TYPE_NODESET };
|
|
763
802
|
mkr_nodeset_init(&self_v.u.nodeset);
|
|
764
|
-
if (
|
|
803
|
+
if (focus->node && mkr_nodeset_push(&self_v.u.nodeset, focus->node, L, err) != 0) {
|
|
765
804
|
mkr_nodeset_clear(&self_v.u.nodeset);
|
|
766
805
|
return -1;
|
|
767
806
|
}
|
|
@@ -773,22 +812,20 @@ fn_number(mkr_xpath_context_t *ctx, lxb_dom_node_t *self_node,
|
|
|
773
812
|
}
|
|
774
813
|
|
|
775
814
|
static int
|
|
776
|
-
fn_sum(mkr_xpath_context_t *ctx,
|
|
777
|
-
size_t self_pos, size_t self_size,
|
|
815
|
+
fn_sum(mkr_xpath_context_t *ctx, const mkr_focus_t *focus,
|
|
778
816
|
mkr_val_t *args, size_t nargs, mkr_val_t *out, mkr_xpath_error_t *err)
|
|
779
817
|
{
|
|
780
|
-
(void)
|
|
818
|
+
(void)focus;
|
|
781
819
|
if (arity_check(nargs, 1, 1, err, "sum") != 0) return -1;
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
return -1;
|
|
785
|
-
}
|
|
820
|
+
const mkr_nodeset_t *ns = require_nodeset(&args[0], "sum", err);
|
|
821
|
+
if (ns == NULL) return -1;
|
|
786
822
|
/* mkr_get_cached_node_text consults ctx->limits internally for the
|
|
787
823
|
* cache-miss path; no need to thread max_string_bytes here. */
|
|
788
824
|
double total = 0.0;
|
|
789
|
-
for (size_t i = 0; i <
|
|
825
|
+
for (size_t i = 0; i < ns->count; ++i) {
|
|
826
|
+
if (mkr_limit_eval_op(mkr_ctx_limits(ctx), err) != 0) return -1;
|
|
790
827
|
mkr_borrowed_text_t s;
|
|
791
|
-
if (mkr_get_cached_node_text(ctx,
|
|
828
|
+
if (mkr_get_cached_node_text(ctx, ns->items[i], &s, err) != 0) {
|
|
792
829
|
return -1;
|
|
793
830
|
}
|
|
794
831
|
total += mkr_borrowed_text_to_number(s);
|
|
@@ -799,11 +836,10 @@ fn_sum(mkr_xpath_context_t *ctx, lxb_dom_node_t *self_node,
|
|
|
799
836
|
}
|
|
800
837
|
|
|
801
838
|
static int
|
|
802
|
-
fn_floor(mkr_xpath_context_t *ctx,
|
|
803
|
-
size_t self_pos, size_t self_size,
|
|
839
|
+
fn_floor(mkr_xpath_context_t *ctx, const mkr_focus_t *focus,
|
|
804
840
|
mkr_val_t *args, size_t nargs, mkr_val_t *out, mkr_xpath_error_t *err)
|
|
805
841
|
{
|
|
806
|
-
(void)
|
|
842
|
+
(void)focus;
|
|
807
843
|
if (arity_check(nargs, 1, 1, err, "floor") != 0) return -1;
|
|
808
844
|
double d;
|
|
809
845
|
if (mkr_val_to_number_or_fail(&args[0], mkr_ctx_limits(ctx), err, &d) != 0) return -1;
|
|
@@ -813,11 +849,10 @@ fn_floor(mkr_xpath_context_t *ctx, lxb_dom_node_t *self_node,
|
|
|
813
849
|
}
|
|
814
850
|
|
|
815
851
|
static int
|
|
816
|
-
fn_ceiling(mkr_xpath_context_t *ctx,
|
|
817
|
-
size_t self_pos, size_t self_size,
|
|
852
|
+
fn_ceiling(mkr_xpath_context_t *ctx, const mkr_focus_t *focus,
|
|
818
853
|
mkr_val_t *args, size_t nargs, mkr_val_t *out, mkr_xpath_error_t *err)
|
|
819
854
|
{
|
|
820
|
-
(void)
|
|
855
|
+
(void)focus;
|
|
821
856
|
if (arity_check(nargs, 1, 1, err, "ceiling") != 0) return -1;
|
|
822
857
|
double d;
|
|
823
858
|
if (mkr_val_to_number_or_fail(&args[0], mkr_ctx_limits(ctx), err, &d) != 0) return -1;
|
|
@@ -828,11 +863,10 @@ fn_ceiling(mkr_xpath_context_t *ctx, lxb_dom_node_t *self_node,
|
|
|
828
863
|
|
|
829
864
|
/* XPath 1.0 round(): round to nearest integer, .5 rounds toward +inf. */
|
|
830
865
|
static int
|
|
831
|
-
fn_round(mkr_xpath_context_t *ctx,
|
|
832
|
-
size_t self_pos, size_t self_size,
|
|
866
|
+
fn_round(mkr_xpath_context_t *ctx, const mkr_focus_t *focus,
|
|
833
867
|
mkr_val_t *args, size_t nargs, mkr_val_t *out, mkr_xpath_error_t *err)
|
|
834
868
|
{
|
|
835
|
-
(void)
|
|
869
|
+
(void)focus;
|
|
836
870
|
if (arity_check(nargs, 1, 1, err, "round") != 0) return -1;
|
|
837
871
|
double d;
|
|
838
872
|
if (mkr_val_to_number_or_fail(&args[0], mkr_ctx_limits(ctx), err, &d) != 0) return -1;
|
|
@@ -846,16 +880,14 @@ fn_round(mkr_xpath_context_t *ctx, lxb_dom_node_t *self_node,
|
|
|
846
880
|
|
|
847
881
|
/* Helper: pull the first node of a nodeset arg, or the self node when
|
|
848
882
|
* the arg list is empty. */
|
|
849
|
-
static
|
|
850
|
-
name_func_target(mkr_val_t *args, size_t nargs,
|
|
883
|
+
static MKR_DOM_NODE *
|
|
884
|
+
name_func_target(mkr_val_t *args, size_t nargs, MKR_DOM_NODE *self_node, mkr_xpath_error_t *err, const char *fname)
|
|
851
885
|
{
|
|
852
886
|
if (nargs == 0) return self_node;
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
if (args[0].u.nodeset.count == 0) return NULL;
|
|
858
|
-
return args[0].u.nodeset.items[0];
|
|
887
|
+
const mkr_nodeset_t *ns = require_nodeset(&args[0], fname, err);
|
|
888
|
+
if (ns == NULL) return NULL;
|
|
889
|
+
if (ns->count == 0) return NULL;
|
|
890
|
+
return ns->items[0];
|
|
859
891
|
}
|
|
860
892
|
|
|
861
893
|
/* Set out to an empty string and report OOM if strdup fails. */
|
|
@@ -889,82 +921,79 @@ set_bytes_string(mkr_val_t *out, const lxb_char_t *src, size_t len,
|
|
|
889
921
|
}
|
|
890
922
|
|
|
891
923
|
static int
|
|
892
|
-
fn_local_name(mkr_xpath_context_t *ctx,
|
|
893
|
-
size_t self_pos, size_t self_size,
|
|
924
|
+
fn_local_name(mkr_xpath_context_t *ctx, const mkr_focus_t *focus,
|
|
894
925
|
mkr_val_t *args, size_t nargs, mkr_val_t *out, mkr_xpath_error_t *err)
|
|
895
926
|
{
|
|
896
|
-
(void)ctx;
|
|
927
|
+
(void)ctx;
|
|
897
928
|
if (arity_check(nargs, 0, 1, err, "local-name") != 0) return -1;
|
|
898
929
|
/* Every return path below sets out via set_empty_string / set_bytes_string;
|
|
899
930
|
* an error return leaves the caller's zero-initialised (clearable) out. */
|
|
900
|
-
|
|
931
|
+
MKR_DOM_NODE *n = name_func_target(args, nargs, focus->node, err, "local-name");
|
|
901
932
|
if (err->status != MKR_XPATH_OK) return -1;
|
|
902
933
|
if (n == NULL ||
|
|
903
|
-
(n
|
|
904
|
-
n
|
|
905
|
-
n
|
|
934
|
+
(MKR_NODE_TYPE(n) != MKR_NTYPE_ELEMENT &&
|
|
935
|
+
MKR_NODE_TYPE(n) != MKR_NTYPE_ATTRIBUTE &&
|
|
936
|
+
MKR_NODE_TYPE(n) != MKR_NTYPE_PI)) {
|
|
906
937
|
return set_empty_string(out, err, "local-name");
|
|
907
938
|
}
|
|
908
939
|
size_t len = 0;
|
|
909
940
|
const lxb_char_t *name;
|
|
910
|
-
if (n
|
|
911
|
-
name =
|
|
912
|
-
} else if (n
|
|
913
|
-
name =
|
|
941
|
+
if (MKR_NODE_TYPE(n) == MKR_NTYPE_ATTRIBUTE) {
|
|
942
|
+
name = MKR_ATTR_LOCAL_NAME(n, &len);
|
|
943
|
+
} else if (MKR_NODE_TYPE(n) == MKR_NTYPE_ELEMENT) {
|
|
944
|
+
name = MKR_ELEM_LOCAL_NAME(n, &len);
|
|
914
945
|
} else {
|
|
915
|
-
name =
|
|
946
|
+
name = MKR_NODE_PI_NAME(n, &len);
|
|
916
947
|
}
|
|
917
948
|
return set_bytes_string(out, name, len, err, "local-name");
|
|
918
949
|
}
|
|
919
950
|
|
|
920
951
|
static int
|
|
921
|
-
fn_namespace_uri(mkr_xpath_context_t *ctx,
|
|
922
|
-
size_t self_pos, size_t self_size,
|
|
952
|
+
fn_namespace_uri(mkr_xpath_context_t *ctx, const mkr_focus_t *focus,
|
|
923
953
|
mkr_val_t *args, size_t nargs, mkr_val_t *out, mkr_xpath_error_t *err)
|
|
924
954
|
{
|
|
925
|
-
(void)self_pos; (void)self_size;
|
|
926
955
|
if (arity_check(nargs, 0, 1, err, "namespace-uri") != 0) return -1;
|
|
927
|
-
|
|
956
|
+
MKR_DOM_NODE *n = name_func_target(args, nargs, focus->node, err, "namespace-uri");
|
|
928
957
|
if (err->status != MKR_XPATH_OK) return -1;
|
|
929
958
|
if (n == NULL ||
|
|
930
|
-
(n
|
|
959
|
+
(MKR_NODE_TYPE(n) != MKR_NTYPE_ELEMENT && MKR_NODE_TYPE(n) != MKR_NTYPE_ATTRIBUTE)) {
|
|
931
960
|
return set_empty_string(out, err, "namespace-uri");
|
|
932
961
|
}
|
|
933
|
-
if (n
|
|
962
|
+
if (MKR_NODE_NS_ID(n) == 0) {
|
|
934
963
|
return set_empty_string(out, err, "namespace-uri");
|
|
935
964
|
}
|
|
936
|
-
|
|
937
|
-
|
|
965
|
+
size_t len = 0;
|
|
966
|
+
const char *uri = MKR_NODE_NS_URI(n, mkr_ctx_document(ctx), &len);
|
|
967
|
+
if (uri == NULL) {
|
|
938
968
|
return set_empty_string(out, err, "namespace-uri");
|
|
939
969
|
}
|
|
940
|
-
|
|
941
|
-
const lxb_char_t *uri = lxb_ns_by_id(doc->ns, n->ns, &len);
|
|
942
|
-
return set_bytes_string(out, uri, len, err, "namespace-uri");
|
|
970
|
+
return set_bytes_string(out, (const lxb_char_t *)uri, len, err, "namespace-uri");
|
|
943
971
|
}
|
|
944
972
|
|
|
945
973
|
static int
|
|
946
|
-
fn_name(mkr_xpath_context_t *ctx,
|
|
947
|
-
size_t self_pos, size_t self_size,
|
|
974
|
+
fn_name(mkr_xpath_context_t *ctx, const mkr_focus_t *focus,
|
|
948
975
|
mkr_val_t *args, size_t nargs, mkr_val_t *out, mkr_xpath_error_t *err)
|
|
949
976
|
{
|
|
950
|
-
(void)ctx;
|
|
977
|
+
(void)ctx;
|
|
951
978
|
if (arity_check(nargs, 0, 1, err, "name") != 0) return -1;
|
|
952
|
-
|
|
979
|
+
MKR_DOM_NODE *n = name_func_target(args, nargs, focus->node, err, "name");
|
|
953
980
|
if (err->status != MKR_XPATH_OK) return -1;
|
|
954
981
|
if (n == NULL ||
|
|
955
|
-
(n
|
|
956
|
-
n
|
|
957
|
-
n
|
|
982
|
+
(MKR_NODE_TYPE(n) != MKR_NTYPE_ELEMENT &&
|
|
983
|
+
MKR_NODE_TYPE(n) != MKR_NTYPE_ATTRIBUTE &&
|
|
984
|
+
MKR_NODE_TYPE(n) != MKR_NTYPE_PI)) {
|
|
958
985
|
return set_empty_string(out, err, "name");
|
|
959
986
|
}
|
|
960
987
|
/* In HTML the qualified name matches the local name; this also avoids
|
|
961
988
|
* surfacing the LXB_NS_HTML prefix that would otherwise confuse users. */
|
|
962
989
|
size_t len = 0;
|
|
963
990
|
const lxb_char_t *name;
|
|
964
|
-
if (n
|
|
965
|
-
name =
|
|
991
|
+
if (MKR_NODE_TYPE(n) == MKR_NTYPE_ATTRIBUTE) {
|
|
992
|
+
name = MKR_ATTR_QUALIFIED_NAME(n, &len);
|
|
993
|
+
} else if (MKR_NODE_TYPE(n) == MKR_NTYPE_ELEMENT) {
|
|
994
|
+
name = MKR_ELEM_QUALIFIED_NAME(n, &len);
|
|
966
995
|
} else {
|
|
967
|
-
name =
|
|
996
|
+
name = MKR_NODE_PI_NAME(n, &len); /* a PI's expanded-name is (null, target) */
|
|
968
997
|
}
|
|
969
998
|
return set_bytes_string(out, name, len, err, "name");
|
|
970
999
|
}
|
|
@@ -972,25 +1001,30 @@ fn_name(mkr_xpath_context_t *ctx, lxb_dom_node_t *self_node,
|
|
|
972
1001
|
/* ---------- boolean / language ---------- */
|
|
973
1002
|
|
|
974
1003
|
static int
|
|
975
|
-
fn_lang(mkr_xpath_context_t *ctx,
|
|
976
|
-
size_t self_pos, size_t self_size,
|
|
1004
|
+
fn_lang(mkr_xpath_context_t *ctx, const mkr_focus_t *focus,
|
|
977
1005
|
mkr_val_t *args, size_t nargs, mkr_val_t *out, mkr_xpath_error_t *err)
|
|
978
1006
|
{
|
|
979
|
-
(void)ctx;
|
|
1007
|
+
(void)ctx;
|
|
980
1008
|
if (arity_check(nargs, 1, 1, err, "lang") != 0) return -1;
|
|
981
1009
|
mkr_owned_text_t want;
|
|
982
1010
|
if (mkr_val_to_owned_text_or_fail(&args[0], mkr_ctx_limits(ctx), err, &want) != 0) return -1;
|
|
983
1011
|
out->type = MKR_XPATH_TYPE_BOOLEAN;
|
|
984
1012
|
out->u.boolean = 0;
|
|
985
|
-
/* Walk up the ancestor chain looking for
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1013
|
+
/* Walk up the ancestor chain looking for the host's language attribute.
|
|
1014
|
+
Host policy (§8.6): XPath 1.0 lang() is xml:lang based; HTML uses the `lang`
|
|
1015
|
+
attribute (with xml:lang accepted as a fallback). */
|
|
1016
|
+
for (MKR_DOM_NODE *p = focus->node; p != NULL; p = MKR_NODE_PARENT(p)) {
|
|
1017
|
+
if (MKR_NODE_TYPE(p) != MKR_NTYPE_ELEMENT) continue;
|
|
1018
|
+
MKR_DOM_ELEMENT *el = MKR_NODE_AS_ELEMENT(p);
|
|
989
1019
|
size_t vlen = 0;
|
|
990
|
-
|
|
1020
|
+
#ifdef MKR_HOST_XML
|
|
1021
|
+
const lxb_char_t *v = MKR_ELEM_GET_ATTRIBUTE(el, "xml:lang", 8, &vlen);
|
|
1022
|
+
#else
|
|
1023
|
+
const lxb_char_t *v = MKR_ELEM_GET_ATTRIBUTE(el, "lang", 4, &vlen);
|
|
991
1024
|
if (v == NULL) {
|
|
992
|
-
v =
|
|
1025
|
+
v = MKR_ELEM_GET_ATTRIBUTE(el, "xml:lang", 8, &vlen);
|
|
993
1026
|
}
|
|
1027
|
+
#endif /* MKR_HOST_XML */
|
|
994
1028
|
if (v == NULL) continue;
|
|
995
1029
|
/* Compare prefix (case-insensitive) up to '-'. */
|
|
996
1030
|
if (vlen >= want.len) {
|
|
@@ -1043,16 +1077,24 @@ static const fn_entry_t fn_table[] = {
|
|
|
1043
1077
|
{ "floor", fn_floor },
|
|
1044
1078
|
{ "ceiling", fn_ceiling },
|
|
1045
1079
|
{ "round", fn_round },
|
|
1080
|
+
#ifdef MKR_HOST_XML
|
|
1081
|
+
/* CSS-only internal hooks for untyped :*-of-type; the \x01-prefixed names are
|
|
1082
|
+
* unreachable from a user XPath expression (the lexer cannot produce them). */
|
|
1083
|
+
{ MKR_FN_OF_TYPE_POS, fn_xml_of_type_pos },
|
|
1084
|
+
{ MKR_FN_OF_TYPE_POS_LAST, fn_xml_of_type_pos_last },
|
|
1085
|
+
#endif
|
|
1046
1086
|
{ NULL, NULL },
|
|
1047
1087
|
};
|
|
1048
1088
|
|
|
1049
|
-
|
|
1089
|
+
/* File-static: the function table is engine-internal. eval_fncall (later in the
|
|
1090
|
+
* merged engine TU) resolves through it; nothing outside the engine does. */
|
|
1091
|
+
static mkr_func_impl_t
|
|
1050
1092
|
mkr_lookup_function(const char *ns_uri, const char *local_name)
|
|
1051
1093
|
{
|
|
1052
1094
|
if (local_name == NULL) return NULL;
|
|
1053
1095
|
if (ns_uri != NULL) {
|
|
1054
1096
|
/* Nokogiri-compatible builtins live in MKR_NS_NOKOGIRI_BUILTIN_URI.
|
|
1055
|
-
* Other registered namespaces resolve to user-defined functions
|
|
1097
|
+
* Other registered namespaces resolve to user-defined functions -
|
|
1056
1098
|
* not yet implemented (Phase 3 TODO: custom function registry). */
|
|
1057
1099
|
if (strcmp(ns_uri, MKR_NS_NOKOGIRI_BUILTIN_URI) == 0) {
|
|
1058
1100
|
for (size_t i = 0; fn_table_nokogiri_builtin[i].name; ++i) {
|
|
@@ -1063,7 +1105,7 @@ mkr_lookup_function(const char *ns_uri, const char *local_name)
|
|
|
1063
1105
|
}
|
|
1064
1106
|
return NULL;
|
|
1065
1107
|
}
|
|
1066
|
-
/* Default namespace
|
|
1108
|
+
/* Default namespace - XPath 1.0 standard library. */
|
|
1067
1109
|
for (size_t i = 0; fn_table[i].name; ++i) {
|
|
1068
1110
|
if (strcmp(fn_table[i].name, local_name) == 0) return fn_table[i].fn;
|
|
1069
1111
|
}
|