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
|
@@ -2,46 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
#include <ctype.h>
|
|
4
4
|
#include <stdint.h>
|
|
5
|
-
#include <stdlib.h>
|
|
6
5
|
#include <string.h>
|
|
7
6
|
|
|
8
7
|
/*
|
|
9
8
|
* XPath 1.0 lexer. The parser drives token consumption and handles
|
|
10
9
|
* context-sensitive keywords (and, or, div, mod, *, node()...) via
|
|
11
|
-
* lookahead
|
|
10
|
+
* lookahead - the lexer is intentionally context-free.
|
|
11
|
+
*
|
|
12
|
+
* All input reads go through the bounded reader (core mkr_span_t): the input
|
|
13
|
+
* is a verified text (NUL-free, NUL-terminated, valid UTF-8) of known length,
|
|
14
|
+
* and the span makes an out-of-bounds read structurally impossible rather
|
|
15
|
+
* than a per-site convention. One-codepoint decoding is the strict core
|
|
16
|
+
* mkr_utf8_decode1 (the lexer formerly carried its own equivalent copy).
|
|
12
17
|
*/
|
|
13
18
|
|
|
14
|
-
/* Decode one UTF-8 code point at NUL-terminated +p+. Returns its byte length
|
|
15
|
-
* (1-4) and writes the code point to *cp; returns 0 at the terminator or on a
|
|
16
|
-
* malformed/truncated/overlong/surrogate/out-of-range sequence (fail closed).
|
|
17
|
-
* Never reads past the NUL: a NUL where a continuation byte is expected fails
|
|
18
|
-
* the (b & 0xC0) == 0x80 check. */
|
|
19
|
-
static int
|
|
20
|
-
utf8_decode(const char *p, uint32_t *cp)
|
|
21
|
-
{
|
|
22
|
-
const unsigned char *u = (const unsigned char *)p;
|
|
23
|
-
unsigned char b0 = u[0];
|
|
24
|
-
if (b0 < 0x80) { *cp = b0; return b0 == 0 ? 0 : 1; }
|
|
25
|
-
|
|
26
|
-
int n;
|
|
27
|
-
uint32_t c, min;
|
|
28
|
-
if ((b0 & 0xE0) == 0xC0) { n = 1; c = b0 & 0x1Fu; min = 0x80; }
|
|
29
|
-
else if ((b0 & 0xF0) == 0xE0) { n = 2; c = b0 & 0x0Fu; min = 0x800; }
|
|
30
|
-
else if ((b0 & 0xF8) == 0xF0) { n = 3; c = b0 & 0x07u; min = 0x10000; }
|
|
31
|
-
else return 0; /* continuation byte or 0xF8+ as a lead: invalid */
|
|
32
|
-
|
|
33
|
-
for (int i = 1; i <= n; i++) {
|
|
34
|
-
unsigned char b = u[i];
|
|
35
|
-
if ((b & 0xC0) != 0x80) return 0; /* truncated (incl. NUL) or bad continuation */
|
|
36
|
-
c = (c << 6) | (b & 0x3Fu);
|
|
37
|
-
}
|
|
38
|
-
if (c < min) return 0; /* overlong */
|
|
39
|
-
if (c >= 0xD800 && c <= 0xDFFF) return 0; /* surrogate */
|
|
40
|
-
if (c > 0x10FFFF) return 0; /* out of Unicode range */
|
|
41
|
-
*cp = c;
|
|
42
|
-
return n + 1;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
19
|
/* NCName code-point classes per XPath 1.0 §3.7 -> Namespaces in XML -> the
|
|
46
20
|
* XML 1.0 (5th ed.) Name production, minus ':' (NCName). NameStartChar and
|
|
47
21
|
* the extra NameChar ranges; this is the definition browsers / libxml2 track. */
|
|
@@ -67,109 +41,92 @@ is_ncname_cont_cp(uint32_t c)
|
|
|
67
41
|
|| (c >= 0x0300 && c <= 0x036F) || (c >= 0x203F && c <= 0x2040);
|
|
68
42
|
}
|
|
69
43
|
|
|
70
|
-
/* If +
|
|
71
|
-
*
|
|
44
|
+
/* If the span's cursor (+off bytes) begins an NCName character, return its
|
|
45
|
+
* byte length (1-4); else 0 (including at end-of-input - the strict decode
|
|
46
|
+
* fails closed there). +start+ selects NameStartChar (1) vs NameChar (0). */
|
|
72
47
|
static int
|
|
73
|
-
ncname_char(const
|
|
48
|
+
ncname_char(const mkr_span_t *in, size_t off, int start)
|
|
74
49
|
{
|
|
50
|
+
mkr_span_t s = mkr_span_tail(in, off);
|
|
75
51
|
uint32_t cp;
|
|
76
|
-
int len =
|
|
52
|
+
int len = mkr_utf8_decode1_span(&s, &cp);
|
|
77
53
|
if (len == 0) return 0;
|
|
78
54
|
return (start ? is_ncname_start_cp(cp) : is_ncname_cont_cp(cp)) ? len : 0;
|
|
79
55
|
}
|
|
80
56
|
|
|
81
|
-
static void
|
|
82
|
-
skip_ws(mkr_lexer_t *L)
|
|
83
|
-
{
|
|
84
|
-
/* XPath 1.0 §3.7 ExprWhitespace = XML S = (#x20 | #x9 | #xD | #xA)+ only.
|
|
85
|
-
* Not C isspace(), which would also skip #xB (\v) and #xC (\f) — those are
|
|
86
|
-
* not XPath whitespace and must surface as a syntax error. */
|
|
87
|
-
char c;
|
|
88
|
-
while ((c = *L->cur) == ' ' || c == '\t' || c == '\r' || c == '\n') L->cur++;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
57
|
static int
|
|
92
58
|
lex_number(mkr_lexer_t *L, mkr_token_t *t, mkr_xpath_error_t *err)
|
|
93
59
|
{
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
60
|
+
/* Grammar-exact Number lex: the audited shared helpers scan and convert ONLY
|
|
61
|
+
* the bytes matching XPath 1.0 `Digits ('.' Digits?)? | '.' Digits`. We hand
|
|
62
|
+
* the helper a mark+length slice of the bounded reader (the sanctioned
|
|
63
|
+
* parser-TU pattern); it stays C-locale, so "0x1A" lexes as NUMBER 0 then NAME
|
|
64
|
+
* "x1A" and "1e3" as NUMBER 1 then NAME "e3", matching libxml2 / browsers. No
|
|
65
|
+
* raw scan (no strtod) remains in this TU. */
|
|
66
|
+
const char *mark = mkr_span_mark(&L->in);
|
|
67
|
+
size_t extent = mkr_xpath_number_extent(mark, mkr_span_left(&L->in));
|
|
68
|
+
if (extent == 0) {
|
|
69
|
+
mkr_err_setf(err, MKR_XPATH_ERR_SYNTAX, "expected number at '%.10s'", mark);
|
|
99
70
|
return -1;
|
|
100
71
|
}
|
|
101
|
-
t->kind
|
|
102
|
-
t->text.ptr =
|
|
103
|
-
t->text.len
|
|
104
|
-
t->num
|
|
105
|
-
L->
|
|
72
|
+
t->kind = MKR_TK_NUMBER;
|
|
73
|
+
t->text.ptr = mark;
|
|
74
|
+
t->text.len = extent;
|
|
75
|
+
t->num = mkr_xpath_number_from_extent(mark, extent);
|
|
76
|
+
mkr_span_skip(&L->in, extent);
|
|
106
77
|
return 0;
|
|
107
78
|
}
|
|
108
79
|
|
|
109
|
-
/* Is [s, s+len) well-formed UTF-8? Strict (same rules as utf8_decode: rejects
|
|
110
|
-
* malformed/overlong/surrogate/out-of-range). */
|
|
111
|
-
static int
|
|
112
|
-
utf8_valid_range(const char *s, size_t len)
|
|
113
|
-
{
|
|
114
|
-
const char *p = s, *e = s + len;
|
|
115
|
-
while (p < e) {
|
|
116
|
-
uint32_t cp;
|
|
117
|
-
int n = utf8_decode(p, &cp);
|
|
118
|
-
if (n <= 0 || (size_t)n > (size_t)(e - p)) return 0;
|
|
119
|
-
p += n;
|
|
120
|
-
}
|
|
121
|
-
return 1;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
80
|
static int
|
|
125
81
|
lex_string(mkr_lexer_t *L, mkr_token_t *t, mkr_xpath_error_t *err)
|
|
126
82
|
{
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const char *
|
|
130
|
-
|
|
131
|
-
if (
|
|
83
|
+
int quote = mkr_span_peek(&L->in);
|
|
84
|
+
mkr_span_skip(&L->in, 1);
|
|
85
|
+
const char *start = mkr_span_mark(&L->in);
|
|
86
|
+
size_t len;
|
|
87
|
+
if (!mkr_span_find(&L->in, (char)quote, &len)) {
|
|
132
88
|
mkr_err_set(err, MKR_XPATH_ERR_SYNTAX, "unterminated string literal");
|
|
133
89
|
return -1;
|
|
134
90
|
}
|
|
135
91
|
/* Validate UTF-8 here, once, so every character-wise string function
|
|
136
92
|
* (translate, substring, string-length, ...) can assume well-formed input
|
|
137
93
|
* and so an invalid literal fails closed (SyntaxError) rather than producing
|
|
138
|
-
* a silently wrong/truncated result.
|
|
139
|
-
|
|
140
|
-
|
|
94
|
+
* a silently wrong/truncated result. The strict core validator applies the
|
|
95
|
+
* same rules (overlong/surrogate/out-of-range rejected) as the strict
|
|
96
|
+
* decoder the rest of the lexer uses. */
|
|
97
|
+
if (!mkr_utf8_valid((const unsigned char *)start, len)) {
|
|
141
98
|
mkr_err_set(err, MKR_XPATH_ERR_SYNTAX, "invalid UTF-8 in string literal");
|
|
142
99
|
return -1;
|
|
143
100
|
}
|
|
144
101
|
t->kind = MKR_TK_LITERAL;
|
|
145
102
|
t->text.ptr = start;
|
|
146
103
|
t->text.len = len;
|
|
147
|
-
L->
|
|
104
|
+
mkr_span_skip(&L->in, len + 1); /* content + closing quote */
|
|
148
105
|
return 0;
|
|
149
106
|
}
|
|
150
107
|
|
|
151
108
|
static int
|
|
152
109
|
lex_name(mkr_lexer_t *L, mkr_token_t *t)
|
|
153
110
|
{
|
|
154
|
-
const char *s = L->
|
|
111
|
+
const char *s = mkr_span_mark(&L->in);
|
|
155
112
|
int n;
|
|
156
|
-
while ((n = ncname_char(L->
|
|
113
|
+
while ((n = ncname_char(&L->in, 0, 0)) > 0) mkr_span_skip(&L->in, (size_t)n);
|
|
157
114
|
/* QName NameTest is `prefix:local` OR `prefix:*` (XPath 1.0 §2.3); the ':'
|
|
158
115
|
* must not be the '::' axis separator. */
|
|
159
|
-
if (
|
|
160
|
-
&& (L->
|
|
161
|
-
L->
|
|
162
|
-
if (
|
|
163
|
-
L->
|
|
116
|
+
if (mkr_span_peek(&L->in) == ':' && mkr_span_at(&L->in, 1) != ':'
|
|
117
|
+
&& (mkr_span_at(&L->in, 1) == '*' || ncname_char(&L->in, 1, 1) > 0)) {
|
|
118
|
+
mkr_span_skip(&L->in, 1); /* eat ':' */
|
|
119
|
+
if (mkr_span_peek(&L->in) == '*') {
|
|
120
|
+
mkr_span_skip(&L->in, 1); /* prefix:* */
|
|
164
121
|
} else {
|
|
165
|
-
while ((n = ncname_char(L->
|
|
122
|
+
while ((n = ncname_char(&L->in, 0, 0)) > 0) mkr_span_skip(&L->in, (size_t)n);
|
|
166
123
|
}
|
|
167
124
|
t->kind = MKR_TK_QNAME;
|
|
168
125
|
} else {
|
|
169
126
|
t->kind = MKR_TK_NAME;
|
|
170
127
|
}
|
|
171
128
|
t->text.ptr = s;
|
|
172
|
-
t->text.len = (
|
|
129
|
+
t->text.len = mkr_span_since(&L->in, s);
|
|
173
130
|
return 0;
|
|
174
131
|
}
|
|
175
132
|
|
|
@@ -177,45 +134,46 @@ static int
|
|
|
177
134
|
next_token(mkr_lexer_t *L, mkr_token_t *t, mkr_xpath_error_t *err)
|
|
178
135
|
{
|
|
179
136
|
memset(t, 0, sizeof(*t));
|
|
180
|
-
|
|
181
|
-
const char *s = L->
|
|
182
|
-
|
|
137
|
+
mkr_span_skip_xpath_ws(&L->in); /* XPath S only - see mkr_xpath_is_ws */
|
|
138
|
+
const char *s = mkr_span_mark(&L->in);
|
|
139
|
+
int c = mkr_span_peek(&L->in);
|
|
183
140
|
|
|
184
|
-
if (c
|
|
141
|
+
if (c < 0) {
|
|
185
142
|
t->kind = MKR_TK_EOF;
|
|
186
143
|
t->text.ptr = s;
|
|
187
144
|
t->text.len = 0;
|
|
188
145
|
return 0;
|
|
189
146
|
}
|
|
190
147
|
|
|
191
|
-
|
|
192
|
-
if (c == '
|
|
193
|
-
if (c == '
|
|
194
|
-
if (c == '
|
|
195
|
-
if (c == '
|
|
196
|
-
if (c == '
|
|
148
|
+
int c1 = mkr_span_at(&L->in, 1);
|
|
149
|
+
if (c == '/' && c1 == '/') { t->kind = MKR_TK_DSLASH; t->text.ptr = s; t->text.len = 2; mkr_span_skip(&L->in, 2); return 0; }
|
|
150
|
+
if (c == '.' && c1 == '.') { t->kind = MKR_TK_DOTDOT; t->text.ptr = s; t->text.len = 2; mkr_span_skip(&L->in, 2); return 0; }
|
|
151
|
+
if (c == ':' && c1 == ':') { t->kind = MKR_TK_COLONCOLON; t->text.ptr = s; t->text.len = 2; mkr_span_skip(&L->in, 2); return 0; }
|
|
152
|
+
if (c == '!' && c1 == '=') { t->kind = MKR_TK_NE; t->text.ptr = s; t->text.len = 2; mkr_span_skip(&L->in, 2); return 0; }
|
|
153
|
+
if (c == '<' && c1 == '=') { t->kind = MKR_TK_LE; t->text.ptr = s; t->text.len = 2; mkr_span_skip(&L->in, 2); return 0; }
|
|
154
|
+
if (c == '>' && c1 == '=') { t->kind = MKR_TK_GE; t->text.ptr = s; t->text.len = 2; mkr_span_skip(&L->in, 2); return 0; }
|
|
197
155
|
|
|
198
156
|
switch (c) {
|
|
199
|
-
case '(': t->kind = MKR_TK_LPAREN; t->text.ptr = s; t->text.len = 1; L->
|
|
200
|
-
case ')': t->kind = MKR_TK_RPAREN; t->text.ptr = s; t->text.len = 1; L->
|
|
201
|
-
case '[': t->kind = MKR_TK_LBRACKET; t->text.ptr = s; t->text.len = 1; L->
|
|
202
|
-
case ']': t->kind = MKR_TK_RBRACKET; t->text.ptr = s; t->text.len = 1; L->
|
|
157
|
+
case '(': t->kind = MKR_TK_LPAREN; t->text.ptr = s; t->text.len = 1; mkr_span_skip(&L->in, 1); return 0;
|
|
158
|
+
case ')': t->kind = MKR_TK_RPAREN; t->text.ptr = s; t->text.len = 1; mkr_span_skip(&L->in, 1); return 0;
|
|
159
|
+
case '[': t->kind = MKR_TK_LBRACKET; t->text.ptr = s; t->text.len = 1; mkr_span_skip(&L->in, 1); return 0;
|
|
160
|
+
case ']': t->kind = MKR_TK_RBRACKET; t->text.ptr = s; t->text.len = 1; mkr_span_skip(&L->in, 1); return 0;
|
|
203
161
|
case '.':
|
|
204
|
-
if (isdigit(
|
|
162
|
+
if (c1 >= 0 && isdigit(c1)) {
|
|
205
163
|
return lex_number(L, t, err);
|
|
206
164
|
}
|
|
207
|
-
t->kind = MKR_TK_DOT; t->text.ptr = s; t->text.len = 1; L->
|
|
208
|
-
case '@': t->kind = MKR_TK_AT; t->text.ptr = s; t->text.len = 1; L->
|
|
209
|
-
case ',': t->kind = MKR_TK_COMMA; t->text.ptr = s; t->text.len = 1; L->
|
|
210
|
-
case '|': t->kind = MKR_TK_PIPE; t->text.ptr = s; t->text.len = 1; L->
|
|
211
|
-
case '/': t->kind = MKR_TK_SLASH; t->text.ptr = s; t->text.len = 1; L->
|
|
212
|
-
case '+': t->kind = MKR_TK_PLUS; t->text.ptr = s; t->text.len = 1; L->
|
|
213
|
-
case '-': t->kind = MKR_TK_MINUS; t->text.ptr = s; t->text.len = 1; L->
|
|
214
|
-
case '*': t->kind = MKR_TK_STAR; t->text.ptr = s; t->text.len = 1; L->
|
|
215
|
-
case '=': t->kind = MKR_TK_EQ; t->text.ptr = s; t->text.len = 1; L->
|
|
216
|
-
case '<': t->kind = MKR_TK_LT; t->text.ptr = s; t->text.len = 1; L->
|
|
217
|
-
case '>': t->kind = MKR_TK_GT; t->text.ptr = s; t->text.len = 1; L->
|
|
218
|
-
case '$': t->kind = MKR_TK_DOLLAR; t->text.ptr = s; t->text.len = 1; L->
|
|
165
|
+
t->kind = MKR_TK_DOT; t->text.ptr = s; t->text.len = 1; mkr_span_skip(&L->in, 1); return 0;
|
|
166
|
+
case '@': t->kind = MKR_TK_AT; t->text.ptr = s; t->text.len = 1; mkr_span_skip(&L->in, 1); return 0;
|
|
167
|
+
case ',': t->kind = MKR_TK_COMMA; t->text.ptr = s; t->text.len = 1; mkr_span_skip(&L->in, 1); return 0;
|
|
168
|
+
case '|': t->kind = MKR_TK_PIPE; t->text.ptr = s; t->text.len = 1; mkr_span_skip(&L->in, 1); return 0;
|
|
169
|
+
case '/': t->kind = MKR_TK_SLASH; t->text.ptr = s; t->text.len = 1; mkr_span_skip(&L->in, 1); return 0;
|
|
170
|
+
case '+': t->kind = MKR_TK_PLUS; t->text.ptr = s; t->text.len = 1; mkr_span_skip(&L->in, 1); return 0;
|
|
171
|
+
case '-': t->kind = MKR_TK_MINUS; t->text.ptr = s; t->text.len = 1; mkr_span_skip(&L->in, 1); return 0;
|
|
172
|
+
case '*': t->kind = MKR_TK_STAR; t->text.ptr = s; t->text.len = 1; mkr_span_skip(&L->in, 1); return 0;
|
|
173
|
+
case '=': t->kind = MKR_TK_EQ; t->text.ptr = s; t->text.len = 1; mkr_span_skip(&L->in, 1); return 0;
|
|
174
|
+
case '<': t->kind = MKR_TK_LT; t->text.ptr = s; t->text.len = 1; mkr_span_skip(&L->in, 1); return 0;
|
|
175
|
+
case '>': t->kind = MKR_TK_GT; t->text.ptr = s; t->text.len = 1; mkr_span_skip(&L->in, 1); return 0;
|
|
176
|
+
case '$': t->kind = MKR_TK_DOLLAR; t->text.ptr = s; t->text.len = 1; mkr_span_skip(&L->in, 1); return 0;
|
|
219
177
|
case '\'':
|
|
220
178
|
case '"':
|
|
221
179
|
return lex_string(L, t, err);
|
|
@@ -223,27 +181,25 @@ next_token(mkr_lexer_t *L, mkr_token_t *t, mkr_xpath_error_t *err)
|
|
|
223
181
|
break;
|
|
224
182
|
}
|
|
225
183
|
|
|
226
|
-
if (isdigit(
|
|
184
|
+
if (isdigit(c)) {
|
|
227
185
|
return lex_number(L, t, err);
|
|
228
186
|
}
|
|
229
|
-
if (ncname_char(
|
|
187
|
+
if (ncname_char(&L->in, 0, 1) > 0) {
|
|
230
188
|
return lex_name(L, t);
|
|
231
189
|
}
|
|
232
190
|
|
|
233
|
-
|
|
234
|
-
if (uc >= 0x20 && uc < 0x7F) {
|
|
191
|
+
if (c >= 0x20 && c < 0x7F) {
|
|
235
192
|
mkr_err_setf(err, MKR_XPATH_ERR_SYNTAX, "unexpected character '%c'", c);
|
|
236
193
|
} else {
|
|
237
|
-
mkr_err_setf(err, MKR_XPATH_ERR_SYNTAX, "unexpected byte 0x%02x",
|
|
194
|
+
mkr_err_setf(err, MKR_XPATH_ERR_SYNTAX, "unexpected byte 0x%02x", (unsigned)c);
|
|
238
195
|
}
|
|
239
196
|
return -1;
|
|
240
197
|
}
|
|
241
198
|
|
|
242
199
|
void
|
|
243
|
-
mkr_lexer_init(mkr_lexer_t *L, const char *src, mkr_xpath_error_t *err)
|
|
200
|
+
mkr_lexer_init(mkr_lexer_t *L, const char *src, size_t len, mkr_xpath_error_t *err)
|
|
244
201
|
{
|
|
245
|
-
L->
|
|
246
|
-
L->cur = src;
|
|
202
|
+
L->in = mkr_span(src, len);
|
|
247
203
|
memset(&L->tok, 0, sizeof(L->tok));
|
|
248
204
|
L->good = (next_token(L, &L->tok, err) == 0);
|
|
249
205
|
}
|
|
@@ -255,6 +211,20 @@ mkr_lexer_advance(mkr_lexer_t *L, mkr_xpath_error_t *err)
|
|
|
255
211
|
return L->good ? 0 : -1;
|
|
256
212
|
}
|
|
257
213
|
|
|
214
|
+
/* The next non-whitespace byte after the current token (or -1 at the end),
|
|
215
|
+
* without consuming anything - the parser's function-call lookahead ("is this
|
|
216
|
+
* NAME followed by '('?"), kept here so the raw cursor never leaves the lexer. */
|
|
217
|
+
int
|
|
218
|
+
mkr_lexer_peek_nonws(const mkr_lexer_t *L)
|
|
219
|
+
{
|
|
220
|
+
mkr_span_t s = L->in;
|
|
221
|
+
for (;;) {
|
|
222
|
+
int c = mkr_span_peek(&s);
|
|
223
|
+
if (mkr_xpath_is_ws(c)) { mkr_span_skip(&s, 1); continue; }
|
|
224
|
+
return c;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
258
228
|
int
|
|
259
229
|
mkr_tok_is_word_len(const mkr_token_t *t, const char *word, size_t word_len)
|
|
260
230
|
{
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/* mkr_xpath_node_access_html.h - node-access contract for the XPath engine.
|
|
2
|
+
*
|
|
3
|
+
* The engine's per-node work (navigation, name tests, namespace tests) is
|
|
4
|
+
* expressed through these MKR_NODE_* / MKR_ELEM_* macros instead of touching a
|
|
5
|
+
* concrete node struct directly. This lets the same engine logic be compiled
|
|
6
|
+
* for two node representations with ZERO runtime dispatch (compile-time
|
|
7
|
+
* monomorphization - measured ~0% vs direct field reads, whereas a runtime
|
|
8
|
+
* kind-branch costs ~+150%; see tmp/xml_spike/accessor_perf.c and
|
|
9
|
+
* docs/xml_parser_plan.ja.md §2.5):
|
|
10
|
+
*
|
|
11
|
+
* - HTML: this header, accessors → lxb_dom_node_t fields / Lexbor lookups.
|
|
12
|
+
* - XML (planned, §8.5): a sibling header binding the same macros to the
|
|
13
|
+
* custom mkr_xml_node_t, then `#include`-ing the shared engine body.
|
|
14
|
+
*
|
|
15
|
+
* For the HTML instance every macro expands to exactly the field read / call
|
|
16
|
+
* the engine used before, so the compiled object code (and `rake bench`) is
|
|
17
|
+
* unchanged by construction. Node-type numeric values are shared between the
|
|
18
|
+
* representations (the custom XML node reuses LXB_DOM_NODE_TYPE_* values), so
|
|
19
|
+
* the type switches in the engine stay representation-neutral.
|
|
20
|
+
*/
|
|
21
|
+
#ifndef MKR_XPATH_NODE_ACCESS_HTML_H
|
|
22
|
+
#define MKR_XPATH_NODE_ACCESS_HTML_H
|
|
23
|
+
|
|
24
|
+
#include <lexbor/dom/dom.h>
|
|
25
|
+
#include <lexbor/ns/ns.h>
|
|
26
|
+
|
|
27
|
+
/* (The DOM node handle for this instance is lxb_dom_node_t; the XML instance
|
|
28
|
+
* will bind these macros to mkr_xml_node_t. No engine-wide typedef here - the
|
|
29
|
+
* engine already uses mkr_node_t for its XPath AST node.) */
|
|
30
|
+
|
|
31
|
+
/* --- neutral node-type constants ---
|
|
32
|
+
* The shared engine body compares MKR_NODE_TYPE(n) against these instead of a
|
|
33
|
+
* representation-specific name, so the body is representation-neutral. This is
|
|
34
|
+
* the per-instance binding: the HTML instance maps the neutral names to Lexbor's
|
|
35
|
+
* LXB_DOM_NODE_TYPE_*; the XML instance (mkr_xpath_node_access_xml.h) maps the
|
|
36
|
+
* same names to MKR_XML_NODE_TYPE_*. The two encodings are asserted equal there,
|
|
37
|
+
* so the integer in a node's `type` field means the same thing to either
|
|
38
|
+
* instance (byte-identical codegen for HTML).
|
|
39
|
+
*
|
|
40
|
+
* NB: named MKR_NTYPE_* (node *type*), distinct from the engine's existing
|
|
41
|
+
* mkr_nt_kind_t enum MKR_NT_* (node *test* kinds: text()/comment()/pi()/...).
|
|
42
|
+
* An earlier MKR_NT_* name collided with that enum and broke //comment() etc. */
|
|
43
|
+
#define MKR_NTYPE_ELEMENT LXB_DOM_NODE_TYPE_ELEMENT
|
|
44
|
+
#define MKR_NTYPE_ATTRIBUTE LXB_DOM_NODE_TYPE_ATTRIBUTE
|
|
45
|
+
#define MKR_NTYPE_TEXT LXB_DOM_NODE_TYPE_TEXT
|
|
46
|
+
#define MKR_NTYPE_CDATA_SECTION LXB_DOM_NODE_TYPE_CDATA_SECTION
|
|
47
|
+
#define MKR_NTYPE_COMMENT LXB_DOM_NODE_TYPE_COMMENT
|
|
48
|
+
#define MKR_NTYPE_PI LXB_DOM_NODE_TYPE_PROCESSING_INSTRUCTION
|
|
49
|
+
#define MKR_NTYPE_DOCUMENT_TYPE LXB_DOM_NODE_TYPE_DOCUMENT_TYPE
|
|
50
|
+
#define MKR_NTYPE_ENTITY LXB_DOM_NODE_TYPE_ENTITY
|
|
51
|
+
#define MKR_NTYPE_ENTITY_REFERENCE LXB_DOM_NODE_TYPE_ENTITY_REFERENCE
|
|
52
|
+
#define MKR_NTYPE_NOTATION LXB_DOM_NODE_TYPE_NOTATION
|
|
53
|
+
|
|
54
|
+
/* --- type / namespace-id (numeric, shared values) --- */
|
|
55
|
+
#define MKR_NODE_TYPE(n) ((n)->type)
|
|
56
|
+
#define MKR_NODE_NS_ID(n) ((n)->ns)
|
|
57
|
+
|
|
58
|
+
/* --- navigation (return the node handle for this instance) --- */
|
|
59
|
+
#define MKR_NODE_FIRST_CHILD(n) ((n)->first_child)
|
|
60
|
+
#define MKR_NODE_LAST_CHILD(n) ((n)->last_child)
|
|
61
|
+
#define MKR_NODE_NEXT(n) ((n)->next)
|
|
62
|
+
#define MKR_NODE_PREV(n) ((n)->prev)
|
|
63
|
+
#define MKR_NODE_PARENT(n) ((n)->parent)
|
|
64
|
+
|
|
65
|
+
/* --- element / attribute handles & iteration --- */
|
|
66
|
+
#define MKR_NODE_AS_ELEMENT(n) lxb_dom_interface_element(n) /* node -> element handle */
|
|
67
|
+
#define MKR_ELEM_FIRST_ATTR(el) ((el)->first_attr)
|
|
68
|
+
#define MKR_ATTR_NEXT(a) ((a)->next)
|
|
69
|
+
#define MKR_ATTR_VALUE(a, lenp) lxb_dom_attr_value((a), (lenp)) /* borrowed value bytes */
|
|
70
|
+
|
|
71
|
+
/* Strict element name tests resolve in the HTML namespace, so a foreign
|
|
72
|
+
* (non-HTML, non-null) element namespace is a non-match. The XML instance will
|
|
73
|
+
* redefine this against its own namespace representation. */
|
|
74
|
+
#define MKR_NODE_IS_FOREIGN_NS(n) \
|
|
75
|
+
((n)->ns != LXB_NS_HTML && (n)->ns != LXB_NS__UNDEF)
|
|
76
|
+
|
|
77
|
+
/* --- name lookups (return borrowed lxb_char_t* bytes, set *(lenp)) --- */
|
|
78
|
+
#define MKR_ELEM_LOCAL_NAME(n, lenp) \
|
|
79
|
+
lxb_dom_element_local_name((lxb_dom_element_t *)(n), (lenp))
|
|
80
|
+
#define MKR_ATTR_LOCAL_NAME(n, lenp) \
|
|
81
|
+
lxb_dom_attr_local_name((lxb_dom_attr_t *)(n), (lenp))
|
|
82
|
+
/* Borrowed qualified name of any node: HTML elements report their lowercase
|
|
83
|
+
* local name (the lowercase-tag HTML data model the engine assumes, matching
|
|
84
|
+
* Makiri::Node#name); every other kind defers to lxb_dom_node_name. The XML
|
|
85
|
+
* binding returns the node's contiguous qname instead. */
|
|
86
|
+
static inline const lxb_char_t *
|
|
87
|
+
mkr_html_node_name_qualified(lxb_dom_node_t *node, size_t *len)
|
|
88
|
+
{
|
|
89
|
+
if (node == NULL) { if (len) *len = 0; return NULL; }
|
|
90
|
+
if (node->type == LXB_DOM_NODE_TYPE_ELEMENT) {
|
|
91
|
+
return lxb_dom_element_qualified_name(lxb_dom_interface_element(node), len);
|
|
92
|
+
}
|
|
93
|
+
return lxb_dom_node_name(node, len);
|
|
94
|
+
}
|
|
95
|
+
#define MKR_ELEM_QUALIFIED_NAME(n, lenp) \
|
|
96
|
+
mkr_html_node_name_qualified((n), (lenp))
|
|
97
|
+
#define MKR_ATTR_QUALIFIED_NAME(n, lenp) \
|
|
98
|
+
lxb_dom_attr_qualified_name((lxb_dom_attr_t *)(n), (lenp))
|
|
99
|
+
#define MKR_NODE_PI_NAME(n, lenp) \
|
|
100
|
+
lxb_dom_node_name((n), (lenp))
|
|
101
|
+
|
|
102
|
+
/* element attribute value by (raw) name; returns borrowed bytes or NULL. */
|
|
103
|
+
#define MKR_ELEM_GET_ATTRIBUTE(el, name, nlen, vlenp) \
|
|
104
|
+
lxb_dom_element_get_attribute((el), (const lxb_char_t *)(name), (nlen), (vlenp))
|
|
105
|
+
|
|
106
|
+
/* --- per-instance services (resolved against the concrete representation) ---
|
|
107
|
+
* The engine body uses these instead of calling Lexbor directly, so the XML
|
|
108
|
+
* instance can bind them to the custom node (which carries ns_uri / value
|
|
109
|
+
* directly and has no Lexbor ns hash / tag table). */
|
|
110
|
+
|
|
111
|
+
/* Borrowed namespace-URI bytes for a node (or NULL, *lenp=0, if it has none).
|
|
112
|
+
* HTML resolves the node's ns-id against the document's ns hash. */
|
|
113
|
+
static inline const char *
|
|
114
|
+
mkr_html_node_ns_uri(lxb_dom_node_t *node, lxb_dom_document_t *doc, size_t *lenp)
|
|
115
|
+
{
|
|
116
|
+
*lenp = 0;
|
|
117
|
+
if (node->ns == LXB_NS__UNDEF || doc == NULL || doc->ns == NULL) return NULL;
|
|
118
|
+
return (const char *)lxb_ns_by_id(doc->ns, node->ns, lenp);
|
|
119
|
+
}
|
|
120
|
+
#define MKR_NODE_NS_URI(node, doc, lenp) mkr_html_node_ns_uri((node), (doc), (lenp))
|
|
121
|
+
|
|
122
|
+
/* Append a node's own text content to a mkr_buf_t, setting `st` (mkr_status_t).
|
|
123
|
+
* HTML uses lxb_dom_node_text_content (allocates; freed after the append). */
|
|
124
|
+
#define MKR_NODE_APPEND_OWN_TEXT(node, buf, st) \
|
|
125
|
+
do { \
|
|
126
|
+
size_t mkr__tlen = 0; \
|
|
127
|
+
lxb_char_t *mkr__t = lxb_dom_node_text_content((node), &mkr__tlen); \
|
|
128
|
+
(st) = (mkr__t == NULL) ? MKR_OK : mkr_buf_append((buf), mkr__t, mkr__tlen); \
|
|
129
|
+
if (mkr__t) lxb_dom_document_destroy_text((node)->owner_document, mkr__t); \
|
|
130
|
+
} while (0)
|
|
131
|
+
|
|
132
|
+
/* Resolve a tag-name to a Lexbor tag id for the //tag element-index fast path.
|
|
133
|
+
* Only ever reached when the element index is present (HTML); for XML the index
|
|
134
|
+
* is NULL so the fast path bails before this, but it must still compile. */
|
|
135
|
+
#define MKR_DOC_TAG_ID_BY_NAME(doc, ptr, len) \
|
|
136
|
+
lxb_tag_id_by_name((doc)->tags, (const lxb_char_t *)(ptr), (len))
|
|
137
|
+
|
|
138
|
+
#endif /* MKR_XPATH_NODE_ACCESS_HTML_H */
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/* mkr_xpath_node_access_xml.h - node-access contract bound to the custom XML node.
|
|
2
|
+
*
|
|
3
|
+
* The XML counterpart of mkr_xpath_node_access_html.h: it binds the MKR_NODE_* / MKR_ELEM_*
|
|
4
|
+
* field-access macros + per-instance services to mkr_xml_node_t, so the shared
|
|
5
|
+
* XPath engine body (mkr_xpath_*_body.h) compiles for XML with zero runtime
|
|
6
|
+
* dispatch (§2.5). MKR_HOST_XML selects the engine's XML host-policy branches
|
|
7
|
+
* (name-test matching, §8.6). The custom node carries its namespace URI and a
|
|
8
|
+
* contiguous "prefix:local" qname directly, so there is no ns hash / tag table.
|
|
9
|
+
*/
|
|
10
|
+
#ifndef MKR_XPATH_NODE_ACCESS_XML_H
|
|
11
|
+
#define MKR_XPATH_NODE_ACCESS_XML_H
|
|
12
|
+
|
|
13
|
+
#include <lexbor/dom/dom.h> /* for the MKR_XML_NODE_TYPE_* == LXB asserts + lxb_char_t */
|
|
14
|
+
#include "../xml/mkr_xml_node.h"
|
|
15
|
+
#include "../core/mkr_span.h" /* mkr_bytes_eq / mkr_span_starts (this header is
|
|
16
|
+
* included before mkr_core.h in the XML engine TU) */
|
|
17
|
+
#include <string.h>
|
|
18
|
+
|
|
19
|
+
#define MKR_HOST_XML 1
|
|
20
|
+
|
|
21
|
+
/* Bind the engine's neutral node-type names to the XML representation's own
|
|
22
|
+
* constants (the html binding maps the same names to LXB_DOM_NODE_TYPE_*). */
|
|
23
|
+
#define MKR_NTYPE_ELEMENT MKR_XML_NODE_TYPE_ELEMENT
|
|
24
|
+
#define MKR_NTYPE_ATTRIBUTE MKR_XML_NODE_TYPE_ATTRIBUTE
|
|
25
|
+
#define MKR_NTYPE_TEXT MKR_XML_NODE_TYPE_TEXT
|
|
26
|
+
#define MKR_NTYPE_CDATA_SECTION MKR_XML_NODE_TYPE_CDATA_SECTION
|
|
27
|
+
#define MKR_NTYPE_COMMENT MKR_XML_NODE_TYPE_COMMENT
|
|
28
|
+
#define MKR_NTYPE_PI MKR_XML_NODE_TYPE_PI
|
|
29
|
+
#define MKR_NTYPE_DOCUMENT_TYPE MKR_XML_NODE_TYPE_DOCUMENT_TYPE
|
|
30
|
+
#define MKR_NTYPE_ENTITY MKR_XML_NODE_TYPE_ENTITY
|
|
31
|
+
#define MKR_NTYPE_ENTITY_REFERENCE MKR_XML_NODE_TYPE_ENTITY_REFERENCE
|
|
32
|
+
#define MKR_NTYPE_NOTATION MKR_XML_NODE_TYPE_NOTATION
|
|
33
|
+
|
|
34
|
+
/* The whole monomorphization rests on the two representations agreeing on the
|
|
35
|
+
* node-type encoding (so a node's `type` integer means the same thing whichever
|
|
36
|
+
* instance walks it). Enforce that agreement at compile time rather than by
|
|
37
|
+
* comment - the XML constants must equal the Lexbor ones. */
|
|
38
|
+
_Static_assert((int)MKR_XML_NODE_TYPE_ELEMENT == (int)LXB_DOM_NODE_TYPE_ELEMENT, "node-type encoding drift: ELEMENT");
|
|
39
|
+
_Static_assert((int)MKR_XML_NODE_TYPE_ATTRIBUTE == (int)LXB_DOM_NODE_TYPE_ATTRIBUTE, "node-type encoding drift: ATTRIBUTE");
|
|
40
|
+
_Static_assert((int)MKR_XML_NODE_TYPE_TEXT == (int)LXB_DOM_NODE_TYPE_TEXT, "node-type encoding drift: TEXT");
|
|
41
|
+
_Static_assert((int)MKR_XML_NODE_TYPE_CDATA_SECTION == (int)LXB_DOM_NODE_TYPE_CDATA_SECTION, "node-type encoding drift: CDATA_SECTION");
|
|
42
|
+
_Static_assert((int)MKR_XML_NODE_TYPE_ENTITY_REFERENCE == (int)LXB_DOM_NODE_TYPE_ENTITY_REFERENCE, "node-type encoding drift: ENTITY_REFERENCE");
|
|
43
|
+
_Static_assert((int)MKR_XML_NODE_TYPE_ENTITY == (int)LXB_DOM_NODE_TYPE_ENTITY, "node-type encoding drift: ENTITY");
|
|
44
|
+
_Static_assert((int)MKR_XML_NODE_TYPE_PI == (int)LXB_DOM_NODE_TYPE_PROCESSING_INSTRUCTION, "node-type encoding drift: PI");
|
|
45
|
+
_Static_assert((int)MKR_XML_NODE_TYPE_COMMENT == (int)LXB_DOM_NODE_TYPE_COMMENT, "node-type encoding drift: COMMENT");
|
|
46
|
+
_Static_assert((int)MKR_XML_NODE_TYPE_DOCUMENT == (int)LXB_DOM_NODE_TYPE_DOCUMENT, "node-type encoding drift: DOCUMENT");
|
|
47
|
+
_Static_assert((int)MKR_XML_NODE_TYPE_DOCUMENT_TYPE == (int)LXB_DOM_NODE_TYPE_DOCUMENT_TYPE, "node-type encoding drift: DOCUMENT_TYPE");
|
|
48
|
+
_Static_assert((int)MKR_XML_NODE_TYPE_NOTATION == (int)LXB_DOM_NODE_TYPE_NOTATION, "node-type encoding drift: NOTATION");
|
|
49
|
+
|
|
50
|
+
/* type / synthetic namespace-id (1 = has a namespace, 0 = none). */
|
|
51
|
+
#define MKR_NODE_TYPE(n) ((n)->type)
|
|
52
|
+
#define MKR_NODE_NS_ID(n) ((n)->ns_uri_len ? 1u : 0u)
|
|
53
|
+
|
|
54
|
+
/* navigation */
|
|
55
|
+
#define MKR_NODE_FIRST_CHILD(n) ((n)->first_child)
|
|
56
|
+
#define MKR_NODE_LAST_CHILD(n) ((n)->last_child)
|
|
57
|
+
#define MKR_NODE_NEXT(n) ((n)->next)
|
|
58
|
+
#define MKR_NODE_PREV(n) ((n)->prev)
|
|
59
|
+
#define MKR_NODE_PARENT(n) ((n)->parent)
|
|
60
|
+
|
|
61
|
+
/* element / attribute handles & iteration - the node IS its own element handle;
|
|
62
|
+
* attributes are a sibling-linked list off the element's `attrs`.
|
|
63
|
+
*
|
|
64
|
+
* Host policy (§8.6): a namespace declaration (xmlns / xmlns:*) is a NAMESPACE
|
|
65
|
+
* node in XPath 1.0, NOT an attribute, so it must not appear on the attribute
|
|
66
|
+
* axis (an unprefixed-wildcard attribute test, count of attributes, etc. ignore
|
|
67
|
+
* it). The reader still keeps it as a DOM attribute node (Node#attribute_nodes /
|
|
68
|
+
* Node#[] read el->attrs directly, in glue), matching DOM Level 2; only the
|
|
69
|
+
* XPath attribute iteration skips it. So the engine's MKR_ELEM_FIRST_ATTR /
|
|
70
|
+
* MKR_ATTR_NEXT advance past ns declarations, and every attribute-axis consumer
|
|
71
|
+
* (walk, predicates, doc-order) inherits that. The iterators take const and
|
|
72
|
+
* yield mutable (like strchr), so const callers (the doc-order comparator) and
|
|
73
|
+
* mutable ones share one implementation. */
|
|
74
|
+
#define MKR_NODE_AS_ELEMENT(n) (n)
|
|
75
|
+
|
|
76
|
+
static inline int
|
|
77
|
+
mkr_xml_attr_is_ns_decl(const mkr_xml_node_t *a)
|
|
78
|
+
{
|
|
79
|
+
mkr_span_t q = mkr_span(a->qname, a->qname_len);
|
|
80
|
+
return mkr_bytes_eq(a->qname, a->qname_len, "xmlns", 5) /* exact "xmlns" */
|
|
81
|
+
|| mkr_span_starts(&q, "xmlns:", 6); /* "xmlns:*" prefix */
|
|
82
|
+
}
|
|
83
|
+
static inline mkr_xml_node_t *
|
|
84
|
+
mkr_xml_first_xpath_attr(const mkr_xml_node_t *el)
|
|
85
|
+
{
|
|
86
|
+
const mkr_xml_node_t *a = el->attrs;
|
|
87
|
+
while (a != NULL && mkr_xml_attr_is_ns_decl(a)) a = a->next;
|
|
88
|
+
return (mkr_xml_node_t *)a;
|
|
89
|
+
}
|
|
90
|
+
static inline mkr_xml_node_t *
|
|
91
|
+
mkr_xml_next_xpath_attr(const mkr_xml_node_t *a)
|
|
92
|
+
{
|
|
93
|
+
for (a = a->next; a != NULL && mkr_xml_attr_is_ns_decl(a); a = a->next) { }
|
|
94
|
+
return (mkr_xml_node_t *)a;
|
|
95
|
+
}
|
|
96
|
+
#define MKR_ELEM_FIRST_ATTR(el) mkr_xml_first_xpath_attr(el)
|
|
97
|
+
#define MKR_ATTR_NEXT(a) mkr_xml_next_xpath_attr(a)
|
|
98
|
+
#define MKR_ATTR_VALUE(a, lenp) (*(lenp) = (a)->value_len, (const lxb_char_t *)(a)->value)
|
|
99
|
+
|
|
100
|
+
/* Strict unprefixed element name tests must match a no-namespace node only, so a
|
|
101
|
+
* node with any namespace URI is "foreign" to an unprefixed test (§8.6). */
|
|
102
|
+
#define MKR_NODE_IS_FOREIGN_NS(n) ((n)->ns_uri_len != 0)
|
|
103
|
+
|
|
104
|
+
/* name lookups (borrowed bytes; the qname is the contiguous "prefix:local"). */
|
|
105
|
+
#define MKR_ELEM_LOCAL_NAME(n, lenp) (*(lenp) = (n)->local_len, (const lxb_char_t *)(n)->local)
|
|
106
|
+
#define MKR_ATTR_LOCAL_NAME(n, lenp) (*(lenp) = (n)->local_len, (const lxb_char_t *)(n)->local)
|
|
107
|
+
#define MKR_ELEM_QUALIFIED_NAME(n, lenp) (*(lenp) = (n)->qname_len, (const lxb_char_t *)(n)->qname)
|
|
108
|
+
#define MKR_ATTR_QUALIFIED_NAME(n, lenp) (*(lenp) = (n)->qname_len, (const lxb_char_t *)(n)->qname)
|
|
109
|
+
#define MKR_NODE_PI_NAME(n, lenp) (*(lenp) = (n)->local_len, (const lxb_char_t *)(n)->local)
|
|
110
|
+
|
|
111
|
+
/* element attribute value by (raw, qualified) name; borrowed bytes or NULL. */
|
|
112
|
+
static inline const lxb_char_t *
|
|
113
|
+
mkr_xml_node_get_attribute(mkr_xml_node_t *el, const char *name, size_t nlen, size_t *vlenp)
|
|
114
|
+
{
|
|
115
|
+
for (mkr_xml_node_t *a = el->attrs; a != NULL; a = a->next) {
|
|
116
|
+
if (mkr_xml_attr_is_ns_decl(a)) continue; /* xmlns is a namespace node, not @attr */
|
|
117
|
+
if (mkr_bytes_eq(a->qname, a->qname_len, name, nlen)) {
|
|
118
|
+
*vlenp = a->value_len;
|
|
119
|
+
return (const lxb_char_t *)a->value;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
*vlenp = 0;
|
|
123
|
+
return NULL;
|
|
124
|
+
}
|
|
125
|
+
#define MKR_ELEM_GET_ATTRIBUTE(el, name, nlen, vlenp) \
|
|
126
|
+
mkr_xml_node_get_attribute((el), (const char *)(name), (nlen), (vlenp))
|
|
127
|
+
|
|
128
|
+
/* --- per-instance services (the custom node carries these directly) --- */
|
|
129
|
+
|
|
130
|
+
/* Namespace URI bytes (borrowed) or NULL (len 0) if the node is in no namespace.
|
|
131
|
+
* +doc+ is unused: the node holds the resolved URI. */
|
|
132
|
+
#define MKR_NODE_NS_URI(node, doc, lenp) \
|
|
133
|
+
(*(lenp) = (node)->ns_uri_len, (node)->ns_uri_len ? (node)->ns_uri : NULL)
|
|
134
|
+
|
|
135
|
+
/* Append a node's own text content (its value slice) to a mkr_buf_t. */
|
|
136
|
+
#define MKR_NODE_APPEND_OWN_TEXT(node, buf, st) \
|
|
137
|
+
(st) = ((node)->value_len \
|
|
138
|
+
? mkr_buf_append((buf), (const lxb_char_t *)(node)->value, (node)->value_len) \
|
|
139
|
+
: MKR_OK)
|
|
140
|
+
|
|
141
|
+
/* No tag-id table on the XML document; the //tag element-index fast path is
|
|
142
|
+
* never enabled for XML (the element index is NULL), so this is unreachable. */
|
|
143
|
+
#define MKR_DOC_TAG_ID_BY_NAME(doc, ptr, len) LXB_TAG__UNDEF
|
|
144
|
+
|
|
145
|
+
#endif /* MKR_XPATH_NODE_ACCESS_XML_H */
|