makiri 0.3.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/security.yml +88 -3
- data/.github/workflows/valgrind.yml +135 -0
- data/CHANGELOG.md +60 -2
- data/README.md +81 -77
- data/Rakefile +194 -3
- data/ext/makiri/bridge/ruby_string.c +119 -66
- data/ext/makiri/core/mkr_alloc.c +40 -3
- data/ext/makiri/core/mkr_alloc.h +27 -4
- data/ext/makiri/core/mkr_buf.c +13 -3
- data/ext/makiri/core/mkr_buf.h +80 -5
- data/ext/makiri/core/mkr_core.c +143 -0
- data/ext/makiri/core/mkr_core.h +10 -1
- data/ext/makiri/core/mkr_span.h +186 -0
- data/ext/makiri/core/mkr_utf8.c +101 -0
- data/ext/makiri/core/mkr_utf8.h +88 -0
- data/ext/makiri/extconf.rb +104 -9
- 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 +8 -0
- data/ext/makiri/glue/ruby_doc.c +20 -24
- data/ext/makiri/glue/ruby_html_css.c +58 -12
- data/ext/makiri/glue/ruby_html_mutate.c +11 -6
- data/ext/makiri/glue/ruby_html_node.c +3 -32
- data/ext/makiri/glue/ruby_node.c +39 -0
- data/ext/makiri/glue/ruby_xml.c +198 -16
- data/ext/makiri/glue/ruby_xml_node.c +46 -59
- data/ext/makiri/glue/ruby_xpath.c +4 -4
- data/ext/makiri/lexbor_compat/source_loc.c +14 -16
- data/ext/makiri/lexbor_compat/utf8_input.c +5 -78
- data/ext/makiri/makiri.c +45 -0
- data/ext/makiri/xml/mkr_xml.h +2 -3
- data/ext/makiri/xml/mkr_xml_chars.c +67 -97
- 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 +63 -121
- data/ext/makiri/xml/mkr_xml_node.c +147 -15
- data/ext/makiri/xml/mkr_xml_node.h +71 -6
- data/ext/makiri/xml/mkr_xml_tree.c +185 -149
- 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 +37 -0
- data/ext/makiri/xpath/mkr_xpath.h +13 -0
- data/ext/makiri/xpath/mkr_xpath_eval_body.h +373 -90
- data/ext/makiri/xpath/mkr_xpath_funcs_body.h +249 -231
- data/ext/makiri/xpath/mkr_xpath_internal.h +89 -9
- data/ext/makiri/xpath/mkr_xpath_lex.c +94 -124
- data/ext/makiri/xpath/mkr_xpath_node_access_xml.h +6 -3
- data/ext/makiri/xpath/mkr_xpath_number.c +109 -0
- data/ext/makiri/xpath/mkr_xpath_parse.c +79 -90
- data/ext/makiri/xpath/mkr_xpath_shared.c +40 -24
- data/ext/makiri/xpath/mkr_xpath_value_body.h +50 -24
- data/lib/makiri/cdata_section.rb +1 -3
- data/lib/makiri/comment.rb +1 -3
- data/lib/makiri/document.rb +8 -0
- data/lib/makiri/element.rb +1 -3
- data/lib/makiri/processing_instruction.rb +1 -3
- data/lib/makiri/text.rb +1 -3
- data/lib/makiri/version.rb +1 -1
- data/lib/makiri/xml/builder.rb +263 -0
- data/lib/makiri/xml/node_methods.rb +47 -0
- data/lib/makiri.rb +1 -0
- data/script/check_alloc_failures.rb +266 -0
- data/script/check_c_safety.rb +45 -2
- data/script/check_c_safety_allowlist.yml +19 -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 +19 -1
|
@@ -729,19 +729,9 @@ mkr_node_line(VALUE self)
|
|
|
729
729
|
/* ------------------------------------------------------------------ */
|
|
730
730
|
/* identity */
|
|
731
731
|
/* ------------------------------------------------------------------ */
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
mkr_node_equals(VALUE self, VALUE other)
|
|
736
|
-
{
|
|
737
|
-
if (!rb_obj_is_kind_of(other, mkr_cNode)) {
|
|
738
|
-
return Qfalse;
|
|
739
|
-
}
|
|
740
|
-
/* Identity by pointer, kind-agnostic (an HTML node is simply never equal to an
|
|
741
|
-
* XML node) - mkr_node_id never dereferences, so comparing across
|
|
742
|
-
* representations is safe. */
|
|
743
|
-
return mkr_node_id(self) == mkr_node_id(other) ? Qtrue : Qfalse;
|
|
744
|
-
}
|
|
732
|
+
/* ==/eql?/hash/pointer_id are representation-neutral (mkr_node_id only) and live
|
|
733
|
+
* in ruby_node.c (the shared node core); this file just binds them below. The
|
|
734
|
+
* HTML-only <=> (document-order comparison) stays here. */
|
|
745
735
|
|
|
746
736
|
/* Distance from `n` to the root (a node with no parent). */
|
|
747
737
|
static size_t
|
|
@@ -812,25 +802,6 @@ mkr_node_spaceship(VALUE self, VALUE other)
|
|
|
812
802
|
return Qnil; /* unreachable for a well-formed tree */
|
|
813
803
|
}
|
|
814
804
|
|
|
815
|
-
/* Nokogiri-compatible identity: the underlying lxb_dom_node_t pointer as an
|
|
816
|
-
* Integer. Stable for the node's lifetime and unique among currently-live
|
|
817
|
-
* nodes; a freed-then-reallocated node may reuse an address (same caveat as
|
|
818
|
-
* Nokogiri::XML::Node#pointer_id). a.pointer_id == b.pointer_id iff a.eql?(b). */
|
|
819
|
-
static VALUE
|
|
820
|
-
mkr_node_pointer_id(VALUE self)
|
|
821
|
-
{
|
|
822
|
-
return ULL2NUM((unsigned long long)mkr_node_id(self));
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
/* Stable hash derived from the node pointer, so a == b implies a.hash ==
|
|
826
|
-
* b.hash even across separately-created wrappers. Shares the pointer value
|
|
827
|
-
* with #pointer_id. */
|
|
828
|
-
static VALUE
|
|
829
|
-
mkr_node_hash(VALUE self)
|
|
830
|
-
{
|
|
831
|
-
return mkr_node_pointer_id(self);
|
|
832
|
-
}
|
|
833
|
-
|
|
834
805
|
void
|
|
835
806
|
mkr_init_node(void)
|
|
836
807
|
{
|
data/ext/makiri/glue/ruby_node.c
CHANGED
|
@@ -104,3 +104,42 @@ mkr_node_document(VALUE rb_node)
|
|
|
104
104
|
TypedData_Get_Struct(rb_node, mkr_node_data_t, &mkr_node_type, nd);
|
|
105
105
|
return nd->document;
|
|
106
106
|
}
|
|
107
|
+
|
|
108
|
+
/* ------------------------------------------------------------------ */
|
|
109
|
+
/* identity (representation-neutral) */
|
|
110
|
+
/* ------------------------------------------------------------------ */
|
|
111
|
+
/* These depend only on the kind-agnostic mkr_node_id (which never dereferences a
|
|
112
|
+
* node), so they are identical for HTML and XML and live here, in the shared
|
|
113
|
+
* node core, rather than duplicated in each representation's reader file. The
|
|
114
|
+
* HTML and XML NodeMethods modules both bind their ==/eql?/hash/pointer_id to
|
|
115
|
+
* these. */
|
|
116
|
+
|
|
117
|
+
/* Pointer identity: equal iff both wrappers resolve to the same node pointer (an
|
|
118
|
+
* HTML node is thus never equal to an XML node). */
|
|
119
|
+
VALUE
|
|
120
|
+
mkr_node_equals(VALUE self, VALUE other)
|
|
121
|
+
{
|
|
122
|
+
if (!rb_obj_is_kind_of(other, mkr_cNode)) {
|
|
123
|
+
return Qfalse;
|
|
124
|
+
}
|
|
125
|
+
return mkr_node_id(self) == mkr_node_id(other) ? Qtrue : Qfalse;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* Nokogiri-compatible identity: the underlying node pointer as an Integer. Stable
|
|
129
|
+
* for the node's lifetime and unique among currently-live nodes; a
|
|
130
|
+
* freed-then-reallocated node may reuse an address (same caveat as
|
|
131
|
+
* Nokogiri::XML::Node#pointer_id). a.pointer_id == b.pointer_id iff a.eql?(b). */
|
|
132
|
+
VALUE
|
|
133
|
+
mkr_node_pointer_id(VALUE self)
|
|
134
|
+
{
|
|
135
|
+
return ULL2NUM((unsigned long long)mkr_node_id(self));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* Stable hash derived from the node pointer, so a == b implies a.hash == b.hash
|
|
139
|
+
* even across separately-created wrappers. Shares the pointer value with
|
|
140
|
+
* #pointer_id. */
|
|
141
|
+
VALUE
|
|
142
|
+
mkr_node_hash(VALUE self)
|
|
143
|
+
{
|
|
144
|
+
return mkr_node_pointer_id(self);
|
|
145
|
+
}
|
data/ext/makiri/glue/ruby_xml.c
CHANGED
|
@@ -14,9 +14,38 @@
|
|
|
14
14
|
#include "glue.h" /* mkr_wrap_document, mkr_parsed_* (via compat.h) */
|
|
15
15
|
#include "ruby_xpath.h" /* mkr_xpath_value_to_ruby / mkr_xpath_raise (shared) */
|
|
16
16
|
#include "../xpath/mkr_xpath.h"
|
|
17
|
+
#include "../xpath/mkr_css.h" /* mkr_css_compile - CSS selectors over XML via the XPath engine */
|
|
18
|
+
#include "../xml/mkr_xml_index.h" /* element-name index for the //name fast path */
|
|
17
19
|
|
|
18
20
|
#include <ruby/thread.h>
|
|
19
21
|
|
|
22
|
+
/* void-typed adapters so the engine's representation-neutral name-index hooks
|
|
23
|
+
* can reach the XML element-name index (mkr_xml_index.c) without the engine
|
|
24
|
+
* knowing its concrete types. */
|
|
25
|
+
static void *
|
|
26
|
+
mkr_xml_name_index_get_v(void *owner)
|
|
27
|
+
{
|
|
28
|
+
return mkr_xml_name_index_get((mkr_xml_doc_t *)owner);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static void *const *
|
|
32
|
+
mkr_xml_name_index_lookup_v(const void *idx, const char *local, size_t local_len,
|
|
33
|
+
const char *ns_uri, size_t ns_uri_len, size_t *count)
|
|
34
|
+
{
|
|
35
|
+
return (void *const *)mkr_xml_name_index_lookup(
|
|
36
|
+
(const mkr_xml_name_index_t *)idx, local, local_len, ns_uri, ns_uri_len, count);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* Install the document's element-name index hooks on +ctx+ (the engine lazily
|
|
40
|
+
* builds it only when a document-rooted descendant name test runs). */
|
|
41
|
+
static void
|
|
42
|
+
mkr_xml_install_name_index(mkr_xpath_context_t *ctx, mkr_xml_doc_t *xdoc)
|
|
43
|
+
{
|
|
44
|
+
mkr_xpath_context_set_name_index(ctx, (void *)xdoc,
|
|
45
|
+
mkr_xml_name_index_get_v,
|
|
46
|
+
mkr_xml_name_index_lookup_v);
|
|
47
|
+
}
|
|
48
|
+
|
|
20
49
|
/* ---- GVL-released parse ---- */
|
|
21
50
|
typedef struct {
|
|
22
51
|
const char *src;
|
|
@@ -93,23 +122,26 @@ mkr_xml_s_parse(int argc, VALUE *argv, VALUE self)
|
|
|
93
122
|
* document is not materialised twice for a doomed parse. */
|
|
94
123
|
VALUE decoded = mkr_xml_decode_input(rb_String(rb_source), budget);
|
|
95
124
|
|
|
96
|
-
/*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
125
|
+
/* Copy the decoded bytes into a private C buffer up front - BEFORE allocating
|
|
126
|
+
* any Ruby object (the wrap below) - so there is NO GC point between obtaining
|
|
127
|
+
* +decoded+ and copying it, and the parse can then run with the GVL released
|
|
128
|
+
* without racing GC/compaction on the String's backing store. */
|
|
129
|
+
mkr_owned_bytes_t source = {0};
|
|
130
|
+
if (mkr_ruby_copy_bytes(decoded, &source) != 0) {
|
|
131
|
+
rb_raise(mkr_eError, "out of memory copying XML source");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* Build an empty XML handle and wrap it (doc == NULL) so a failure mid-parse
|
|
135
|
+
* frees cleanly via GC (mkr_parsed_destroy -> the XML branch ->
|
|
136
|
+
* mkr_xml_doc_destroy(NULL), a no-op). The source is already copied, so this
|
|
137
|
+
* Ruby allocation cannot disturb it. */
|
|
99
138
|
mkr_parsed_t *parsed = mkr_parsed_new_xml(NULL);
|
|
100
139
|
if (parsed == NULL) {
|
|
140
|
+
mkr_owned_bytes_clear(&source);
|
|
101
141
|
rb_raise(mkr_eError, "out of memory allocating XML document");
|
|
102
142
|
}
|
|
103
143
|
VALUE obj = mkr_wrap_document(parsed); /* GC owns +parsed+ from here */
|
|
104
144
|
|
|
105
|
-
/* Copy the decoded bytes so the parse can run with the GVL released without
|
|
106
|
-
* racing GC/compaction on the String's backing store. */
|
|
107
|
-
mkr_owned_bytes_t source = {0};
|
|
108
|
-
if (mkr_ruby_copy_bytes(decoded, &source) != 0) {
|
|
109
|
-
rb_raise(mkr_eError, "out of memory copying XML source");
|
|
110
|
-
}
|
|
111
|
-
RB_GC_GUARD(decoded);
|
|
112
|
-
|
|
113
145
|
mkr_xml_parse_nogvl_t args = { source.ptr, source.len, limits, NULL, MKR_XML_OK };
|
|
114
146
|
rb_thread_call_without_gvl(mkr_xml_parse_nogvl, &args, NULL, NULL);
|
|
115
147
|
mkr_owned_bytes_clear(&source);
|
|
@@ -124,7 +156,6 @@ mkr_xml_s_parse(int argc, VALUE *argv, VALUE self)
|
|
|
124
156
|
}
|
|
125
157
|
}
|
|
126
158
|
mkr_parsed_set_xml_doc(parsed, args.result);
|
|
127
|
-
RB_GC_GUARD(obj);
|
|
128
159
|
return obj;
|
|
129
160
|
}
|
|
130
161
|
|
|
@@ -182,7 +213,6 @@ mkr_xml_register_query_namespaces(mkr_xpath_context_t *ctx, VALUE rb_ns)
|
|
|
182
213
|
rb_raise(mkr_eError, "failed to register namespace");
|
|
183
214
|
}
|
|
184
215
|
}
|
|
185
|
-
RB_GC_GUARD(keys);
|
|
186
216
|
}
|
|
187
217
|
|
|
188
218
|
/* Makiri::XML::{Document,*}#xpath(expr, namespaces = nil) / #at_xpath(...):
|
|
@@ -211,6 +241,7 @@ mkr_xml_doc_xpath_run(VALUE self, VALUE rb_expr, VALUE rb_ns, int first_only)
|
|
|
211
241
|
rb_raise(mkr_eError, "failed to allocate XPath context");
|
|
212
242
|
}
|
|
213
243
|
mkr_xpath_set_engine_kind(ctx, 1);
|
|
244
|
+
mkr_xml_install_name_index(ctx, xdoc);
|
|
214
245
|
mkr_xml_register_query_namespaces(ctx, rb_ns); /* frees ctx + raises on error */
|
|
215
246
|
|
|
216
247
|
/* Mint the borrowed expression view AFTER namespace registration: that step
|
|
@@ -260,6 +291,153 @@ mkr_xml_doc_at_xpath(int argc, VALUE *argv, VALUE self)
|
|
|
260
291
|
return mkr_xml_doc_xpath_run(self, expr, ns, 1);
|
|
261
292
|
}
|
|
262
293
|
|
|
294
|
+
/* ---- CSS selectors over XML (lowered to the native XPath engine) ----
|
|
295
|
+
*
|
|
296
|
+
* Makiri::XML CSS support compiles a selector to the engine's AST (mkr_css.c)
|
|
297
|
+
* and runs it through the SAME evaluator as #xpath, so case-sensitivity,
|
|
298
|
+
* namespaces, budgets and document order are identical. The Ruby wrappers
|
|
299
|
+
* (lib/makiri/xml/node_methods.rb) collect the document's namespaces and pass a
|
|
300
|
+
* normalised {prefix => uri} hash; the default namespace, if any, arrives under
|
|
301
|
+
* the synthetic prefix "xmlns" (Nokogiri's convention), and a bare type selector
|
|
302
|
+
* binds to it. These are the private `_css` / `_at_css` / `_css_matches?`
|
|
303
|
+
* primitives those wrappers call. */
|
|
304
|
+
|
|
305
|
+
/* The synthetic default-namespace prefix, present iff the (already
|
|
306
|
+
* prefix-normalised) namespace hash carries an "xmlns" key. */
|
|
307
|
+
static const char *
|
|
308
|
+
mkr_css_default_prefix(VALUE rb_ns)
|
|
309
|
+
{
|
|
310
|
+
if (RB_TYPE_P(rb_ns, T_HASH)
|
|
311
|
+
&& !NIL_P(rb_hash_aref(rb_ns, rb_str_new_cstr(MKR_CSS_DEFAULT_NS_PREFIX)))) {
|
|
312
|
+
return MKR_CSS_DEFAULT_NS_PREFIX;
|
|
313
|
+
}
|
|
314
|
+
return NULL;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/* Compile +rb_selector+ to an AST under +ctx+ (its namespaces already
|
|
318
|
+
* registered). On a CSS syntax error raises Makiri::CSS::SyntaxError; on any
|
|
319
|
+
* other engine error frees ctx and raises via the shared raiser. Returns the AST
|
|
320
|
+
* (caller frees with mkr_node_free). */
|
|
321
|
+
static mkr_node_t *
|
|
322
|
+
mkr_css_compile_or_raise(mkr_xpath_context_t *ctx, VALUE rb_selector, VALUE rb_ns)
|
|
323
|
+
{
|
|
324
|
+
mkr_css_ns_t cns = { mkr_css_default_prefix(rb_ns) };
|
|
325
|
+
mkr_ruby_borrowed_text_t sv = mkr_ruby_verified_text(rb_selector, "CSS selector");
|
|
326
|
+
mkr_xpath_error_t error = {0};
|
|
327
|
+
mkr_xpath_limits_t *limits = mkr_ctx_limits(ctx);
|
|
328
|
+
limits->ast_nodes = 0;
|
|
329
|
+
mkr_node_t *ast = mkr_css_compile(mkr_verified_text_from_view(sv), &cns, limits, &error);
|
|
330
|
+
RB_GC_GUARD(sv.value);
|
|
331
|
+
if (ast == NULL) {
|
|
332
|
+
int syntax = (error.status == MKR_XPATH_ERR_SYNTAX);
|
|
333
|
+
mkr_xpath_context_free(ctx);
|
|
334
|
+
if (syntax) {
|
|
335
|
+
VALUE msg = error.message ? rb_utf8_str_new_cstr(error.message)
|
|
336
|
+
: rb_str_new_cstr("invalid CSS selector");
|
|
337
|
+
mkr_xpath_error_clear(&error);
|
|
338
|
+
rb_raise(mkr_eCSSSyntaxError, "%" PRIsVALUE, msg);
|
|
339
|
+
}
|
|
340
|
+
mkr_xpath_raise(&error); /* frees error.message, never returns */
|
|
341
|
+
}
|
|
342
|
+
return ast;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
static VALUE
|
|
346
|
+
mkr_xml_doc_css_run(VALUE self, VALUE rb_selector, VALUE rb_ns, int first_only)
|
|
347
|
+
{
|
|
348
|
+
VALUE document = Qnil;
|
|
349
|
+
mkr_xml_node_t *context = mkr_xml_query_context(self, &document);
|
|
350
|
+
if (context == NULL) {
|
|
351
|
+
return first_only ? Qnil : mkr_node_set_new(document);
|
|
352
|
+
}
|
|
353
|
+
mkr_xml_doc_t *xdoc = mkr_parsed_xml_doc(mkr_doc_parsed(document));
|
|
354
|
+
|
|
355
|
+
mkr_xpath_context_t *ctx =
|
|
356
|
+
mkr_xpath_context_new((void *)xdoc->doc_node, (void *)context);
|
|
357
|
+
if (ctx == NULL) {
|
|
358
|
+
rb_raise(mkr_eError, "failed to allocate XPath context");
|
|
359
|
+
}
|
|
360
|
+
mkr_xpath_set_engine_kind(ctx, 1);
|
|
361
|
+
mkr_xml_install_name_index(ctx, xdoc);
|
|
362
|
+
mkr_xml_register_query_namespaces(ctx, rb_ns); /* frees ctx + raises on error */
|
|
363
|
+
|
|
364
|
+
mkr_node_t *ast = mkr_css_compile_or_raise(ctx, rb_selector, rb_ns); /* frees ctx + raises on error */
|
|
365
|
+
|
|
366
|
+
mkr_xpath_value_t value = {0};
|
|
367
|
+
mkr_xpath_error_t error = {0};
|
|
368
|
+
int rc = first_only ? mkr_xpath_eval_compiled_first(ctx, ast, &value, &error)
|
|
369
|
+
: mkr_xpath_eval_compiled(ctx, ast, &value, &error);
|
|
370
|
+
mkr_node_free(ast);
|
|
371
|
+
if (rc != 0) {
|
|
372
|
+
mkr_xpath_context_free(ctx);
|
|
373
|
+
mkr_xpath_raise(&error);
|
|
374
|
+
}
|
|
375
|
+
VALUE result = mkr_xpath_value_to_ruby(&value, document); /* converts AND clears value */
|
|
376
|
+
mkr_xpath_context_free(ctx);
|
|
377
|
+
|
|
378
|
+
if (first_only && rb_obj_is_kind_of(result, mkr_cNodeSet)) {
|
|
379
|
+
return rb_funcall(result, rb_intern("first"), 0);
|
|
380
|
+
}
|
|
381
|
+
return result;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
static VALUE
|
|
385
|
+
mkr_xml_node_css(VALUE self, VALUE selector, VALUE ns)
|
|
386
|
+
{
|
|
387
|
+
return mkr_xml_doc_css_run(self, selector, ns, 0);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
static VALUE
|
|
391
|
+
mkr_xml_node_at_css(VALUE self, VALUE selector, VALUE ns)
|
|
392
|
+
{
|
|
393
|
+
return mkr_xml_doc_css_run(self, selector, ns, 1);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/* #matches?(selector): does THIS node match the selector? Evaluated by selecting
|
|
397
|
+
* every matching node in the whole document (context = the document node, so the
|
|
398
|
+
* descendant-rooted selector scans the entire tree) and testing membership by
|
|
399
|
+
* node identity - the full-combinator-correct semantics. */
|
|
400
|
+
static VALUE
|
|
401
|
+
mkr_xml_node_css_matches(VALUE self, VALUE selector, VALUE ns)
|
|
402
|
+
{
|
|
403
|
+
VALUE document = Qnil;
|
|
404
|
+
mkr_xml_node_t *node = mkr_xml_query_context(self, &document);
|
|
405
|
+
if (node == NULL) {
|
|
406
|
+
return Qfalse;
|
|
407
|
+
}
|
|
408
|
+
mkr_xml_doc_t *xdoc = mkr_parsed_xml_doc(mkr_doc_parsed(document));
|
|
409
|
+
|
|
410
|
+
mkr_xpath_context_t *ctx =
|
|
411
|
+
mkr_xpath_context_new((void *)xdoc->doc_node, (void *)xdoc->doc_node);
|
|
412
|
+
if (ctx == NULL) {
|
|
413
|
+
rb_raise(mkr_eError, "failed to allocate XPath context");
|
|
414
|
+
}
|
|
415
|
+
mkr_xpath_set_engine_kind(ctx, 1);
|
|
416
|
+
mkr_xml_install_name_index(ctx, xdoc);
|
|
417
|
+
mkr_xml_register_query_namespaces(ctx, ns); /* frees ctx + raises on error */
|
|
418
|
+
|
|
419
|
+
mkr_node_t *ast = mkr_css_compile_or_raise(ctx, selector, ns); /* frees ctx + raises on error */
|
|
420
|
+
|
|
421
|
+
mkr_xpath_value_t value = {0};
|
|
422
|
+
mkr_xpath_error_t error = {0};
|
|
423
|
+
int rc = mkr_xpath_eval_compiled(ctx, ast, &value, &error);
|
|
424
|
+
mkr_node_free(ast);
|
|
425
|
+
if (rc != 0) {
|
|
426
|
+
mkr_xpath_context_free(ctx);
|
|
427
|
+
mkr_xpath_raise(&error);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
int found = 0;
|
|
431
|
+
if (value.type == MKR_XPATH_TYPE_NODESET) {
|
|
432
|
+
for (size_t i = 0; i < value.u.nodeset.count; i++) {
|
|
433
|
+
if (value.u.nodeset.nodes[i] == (void *)node) { found = 1; break; }
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
mkr_xpath_value_clear(&value);
|
|
437
|
+
mkr_xpath_context_free(ctx);
|
|
438
|
+
return found ? Qtrue : Qfalse;
|
|
439
|
+
}
|
|
440
|
+
|
|
263
441
|
/* The document's root element. */
|
|
264
442
|
static VALUE
|
|
265
443
|
mkr_xml_doc_root(VALUE self)
|
|
@@ -311,7 +489,6 @@ mkr_xml_fragment_into(mkr_xml_doc_t *xdoc, VALUE rb_source, int inherit_doc_ns)
|
|
|
311
489
|
if (mkr_ruby_copy_bytes(decoded, &src) != 0) {
|
|
312
490
|
rb_raise(mkr_eError, "out of memory copying XML fragment source");
|
|
313
491
|
}
|
|
314
|
-
RB_GC_GUARD(decoded);
|
|
315
492
|
|
|
316
493
|
mkr_xml_status_t st = MKR_XML_OK;
|
|
317
494
|
mkr_xml_node_t *frag = mkr_xml_parse_fragment(xdoc, src.ptr, src.len, inherit_doc_ns, &st);
|
|
@@ -369,7 +546,6 @@ mkr_xml_fragment_s_parse(VALUE klass, VALUE rb_source)
|
|
|
369
546
|
mkr_xml_doc_t *xdoc = mkr_parsed_xml_doc(mkr_doc_parsed(doc_obj));
|
|
370
547
|
mkr_xml_node_t *frag = mkr_xml_fragment_into(xdoc, rb_source, 0);
|
|
371
548
|
VALUE result = mkr_wrap_xml_node(frag, doc_obj);
|
|
372
|
-
RB_GC_GUARD(doc_obj);
|
|
373
549
|
return result;
|
|
374
550
|
}
|
|
375
551
|
|
|
@@ -386,7 +562,6 @@ mkr_xml_doc_fragment(VALUE self, VALUE rb_source)
|
|
|
386
562
|
}
|
|
387
563
|
mkr_xml_node_t *frag = mkr_xml_fragment_into(xdoc, rb_source, 1);
|
|
388
564
|
VALUE result = mkr_wrap_xml_node(frag, self);
|
|
389
|
-
RB_GC_GUARD(self);
|
|
390
565
|
return result;
|
|
391
566
|
}
|
|
392
567
|
|
|
@@ -413,6 +588,13 @@ mkr_init_xml(void)
|
|
|
413
588
|
rb_define_method(mkr_mXmlNodeMethods, "xpath", mkr_xml_doc_xpath, -1);
|
|
414
589
|
rb_define_method(mkr_mXmlNodeMethods, "at_xpath", mkr_xml_doc_at_xpath, -1);
|
|
415
590
|
|
|
591
|
+
/* CSS selectors over XML: private primitives called by the Ruby #css /
|
|
592
|
+
* #at_css / #matches? wrappers (which collect the document namespaces). The
|
|
593
|
+
* selector is lowered to the native XPath engine (mkr_css.c). */
|
|
594
|
+
rb_define_private_method(mkr_mXmlNodeMethods, "_css", mkr_xml_node_css, 2);
|
|
595
|
+
rb_define_private_method(mkr_mXmlNodeMethods, "_at_css", mkr_xml_node_at_css, 2);
|
|
596
|
+
rb_define_private_method(mkr_mXmlNodeMethods, "_css_matches", mkr_xml_node_css_matches, 2);
|
|
597
|
+
|
|
416
598
|
/* The native XML parser, exposed as XML::Document.parse, mirroring HTML
|
|
417
599
|
* (HTML::Document.parse). The Makiri::XML(source) convenience delegates to it
|
|
418
600
|
* in Ruby (lib/makiri.rb), as Makiri.HTML does for HTML::Document.parse. */
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
#include "glue.h"
|
|
14
14
|
#include "../xml/mkr_xml_node.h"
|
|
15
15
|
#include "../xml/mkr_xml_mutate.h"
|
|
16
|
+
#include "../xml/mkr_xml_index.h" /* element-name index invalidation on mutation */
|
|
16
17
|
#include "../core/mkr_core.h" /* mkr_buf */
|
|
17
18
|
|
|
18
19
|
#include <ruby/encoding.h> /* rb_to_encoding / rb_str_encode (output encoding) */
|
|
@@ -339,7 +340,7 @@ mkr_xml_node_aref(VALUE self, VALUE rb_name)
|
|
|
339
340
|
mkr_ruby_borrowed_text_t nv = mkr_ruby_verified_text(rb_name, "attribute name");
|
|
340
341
|
VALUE out = Qnil;
|
|
341
342
|
for (mkr_xml_node_t *a = n->attrs; a != NULL; a = a->next) {
|
|
342
|
-
if (a->
|
|
343
|
+
if (mkr_bytes_eq(a->qname, a->qname_len, nv.ptr, nv.len)) {
|
|
343
344
|
out = rb_utf8_str_new(a->value ? a->value : "", (long)a->value_len);
|
|
344
345
|
break;
|
|
345
346
|
}
|
|
@@ -565,15 +566,25 @@ mkr_xml_node_to_xml(int argc, VALUE *argv, VALUE self)
|
|
|
565
566
|
int rc = 0;
|
|
566
567
|
|
|
567
568
|
if (rb_obj_is_kind_of(self, mkr_cXmlDocument)) {
|
|
568
|
-
static const char decl_a[] = "<?xml version=\"1.0\" encoding=\"";
|
|
569
|
-
static const char decl_b[] = "\"?>\n";
|
|
570
|
-
VALUE name = NIL_P(enc_name) ? rb_utf8_str_new_cstr("UTF-8") : enc_name;
|
|
571
|
-
mkr_ruby_borrowed_bytes_t nv = mkr_ruby_bytes_view(name);
|
|
572
569
|
mkr_xml_doc_t *xdoc = mkr_parsed_xml_doc(mkr_doc_parsed(self));
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
570
|
+
/* Emit the encoding pseudo-attribute only when an explicit encoding: was
|
|
571
|
+
* requested or the parsed source declared one; otherwise a built or
|
|
572
|
+
* declaration-less document round-trips to a bare `<?xml version="1.0"?>`,
|
|
573
|
+
* matching Nokogiri (the output is UTF-8 either way). */
|
|
574
|
+
int emit_enc = !NIL_P(enc_name) || (xdoc != NULL && xdoc->has_encoding_decl);
|
|
575
|
+
if (emit_enc) {
|
|
576
|
+
static const char decl_a[] = "<?xml version=\"1.0\" encoding=\"";
|
|
577
|
+
static const char decl_b[] = "\"?>\n";
|
|
578
|
+
VALUE name = NIL_P(enc_name) ? rb_utf8_str_new_cstr("UTF-8") : enc_name;
|
|
579
|
+
mkr_ruby_borrowed_bytes_t nv = mkr_ruby_bytes_view(name);
|
|
580
|
+
rc = (mkr_buf_append(&buf, decl_a, sizeof(decl_a) - 1) == MKR_OK) ? 0 : -1;
|
|
581
|
+
if (rc == 0) rc = (mkr_buf_append(&buf, nv.ptr, nv.len) == MKR_OK) ? 0 : -1;
|
|
582
|
+
if (rc == 0) rc = (mkr_buf_append(&buf, decl_b, sizeof(decl_b) - 1) == MKR_OK) ? 0 : -1;
|
|
583
|
+
RB_GC_GUARD(nv.value);
|
|
584
|
+
} else {
|
|
585
|
+
static const char decl[] = "<?xml version=\"1.0\"?>\n";
|
|
586
|
+
rc = (mkr_buf_append(&buf, decl, sizeof(decl) - 1) == MKR_OK) ? 0 : -1;
|
|
587
|
+
}
|
|
577
588
|
if (rc == 0 && xdoc != NULL && xdoc->doctype != NULL) {
|
|
578
589
|
rc = mkr_xser_doctype(&buf, xdoc->doctype);
|
|
579
590
|
if (rc == 0) rc = (mkr_buf_append(&buf, "\n", 1) == MKR_OK) ? 0 : -1;
|
|
@@ -678,8 +689,8 @@ mkr_c14n_nearest(const mkr_xml_node_t *node, const char *prefix, uint32_t plen,
|
|
|
678
689
|
if (e->type != MKR_XML_NODE_TYPE_ELEMENT) continue;
|
|
679
690
|
for (mkr_xml_node_t *a = e->attrs; a != NULL; a = a->next) {
|
|
680
691
|
const char *p, *u; uint32_t pl, ul;
|
|
681
|
-
if (mkr_xml_node_xmlns_decl(a, &p, &pl, &u, &ul)
|
|
682
|
-
&& (
|
|
692
|
+
if (mkr_xml_node_xmlns_decl(a, &p, &pl, &u, &ul)
|
|
693
|
+
&& mkr_bytes_eq(p, pl, prefix, plen)) {
|
|
683
694
|
*uri = u; *ulen = ul; return 1;
|
|
684
695
|
}
|
|
685
696
|
}
|
|
@@ -691,7 +702,7 @@ static int
|
|
|
691
702
|
mkr_c14n_has_prefix(const mkr_c14n_ns_t *arr, size_t n, const char *p, uint32_t pl)
|
|
692
703
|
{
|
|
693
704
|
for (size_t i = 0; i < n; i++)
|
|
694
|
-
if (arr[i].
|
|
705
|
+
if (mkr_bytes_eq(arr[i].prefix, arr[i].plen, p, pl)) return 1;
|
|
695
706
|
return 0;
|
|
696
707
|
}
|
|
697
708
|
|
|
@@ -724,7 +735,7 @@ mkr_c14n_namespaces(const mkr_xml_node_t *n, int is_apex, mkr_c14n_ns_t **out)
|
|
|
724
735
|
for (mkr_xml_node_t *a = e->attrs; a != NULL; a = a->next) {
|
|
725
736
|
const char *p, *u; uint32_t pl, ul;
|
|
726
737
|
if (!mkr_xml_node_xmlns_decl(a, &p, &pl, &u, &ul)) continue;
|
|
727
|
-
if (
|
|
738
|
+
if (mkr_bytes_eq(p, pl, "xml", 3)) continue; /* implicit xml: */
|
|
728
739
|
if (is_apex) {
|
|
729
740
|
if (pl == 0) { /* default: nearest decl wins */
|
|
730
741
|
if (default_seen) continue;
|
|
@@ -736,7 +747,7 @@ mkr_c14n_namespaces(const mkr_xml_node_t *n, int is_apex, mkr_c14n_ns_t **out)
|
|
|
736
747
|
} else { /* descendant: only changes to the binding */
|
|
737
748
|
const char *au; uint32_t aul;
|
|
738
749
|
int above = mkr_c14n_nearest(e->parent, p, pl, &au, &aul);
|
|
739
|
-
if (above &&
|
|
750
|
+
if (above && mkr_bytes_eq(au, aul, u, ul)) continue; /* superfluous */
|
|
740
751
|
if (pl == 0 && ul == 0 && !(above && aul > 0)) continue; /* xmlns="" only undeclares */
|
|
741
752
|
}
|
|
742
753
|
arr[cnt].prefix = p; arr[cnt].plen = pl;
|
|
@@ -884,27 +895,13 @@ mkr_xml_node_canonicalize(int argc, VALUE *argv, VALUE self)
|
|
|
884
895
|
return str;
|
|
885
896
|
}
|
|
886
897
|
|
|
887
|
-
/* ---- fail-closed
|
|
898
|
+
/* ---- fail-closed guard for the unsupported serialization surface ----
|
|
888
899
|
*
|
|
889
|
-
*
|
|
890
|
-
*
|
|
891
|
-
* NotImplementedError
|
|
892
|
-
*
|
|
893
|
-
*
|
|
894
|
-
* - HTML serialization (to_html/inner_html/outer_html): emitting
|
|
895
|
-
* HTML-serialized markup for an XML document would be wrong (escaping /
|
|
896
|
-
* CDATA / void elements differ). Use #to_xml. */
|
|
897
|
-
static VALUE
|
|
898
|
-
mkr_xml_node_no_css(int argc, VALUE *argv, VALUE self)
|
|
899
|
-
{
|
|
900
|
-
(void)argc; (void)argv; (void)self;
|
|
901
|
-
rb_raise(rb_eNotImpError,
|
|
902
|
-
"Makiri::XML does not support CSS selectors; use #xpath. "
|
|
903
|
-
"(CSS cannot preserve XML element-name case or namespaces.) "
|
|
904
|
-
"Default-namespace documents (RSS/Atom) need a registered prefix, "
|
|
905
|
-
"e.g. doc.xpath(\"//a:entry\", \"a\" => \"http://www.w3.org/2005/Atom\").");
|
|
906
|
-
}
|
|
907
|
-
|
|
900
|
+
* HTML serialization (to_html/inner_html/outer_html) would silently misbehave on
|
|
901
|
+
* XML (escaping / CDATA / void elements differ), so it is an explicit
|
|
902
|
+
* NotImplementedError rather than a wrong result. Use #to_xml. (CSS selectors,
|
|
903
|
+
* once unsupported here, are now lowered to the native XPath engine - see
|
|
904
|
+
* ruby_xml.c.) */
|
|
908
905
|
static VALUE
|
|
909
906
|
mkr_xml_node_no_serialize(int argc, VALUE *argv, VALUE self)
|
|
910
907
|
{
|
|
@@ -917,23 +914,10 @@ mkr_xml_node_no_serialize(int argc, VALUE *argv, VALUE self)
|
|
|
917
914
|
/* ---- node identity (== / eql? / hash / pointer_id) ----
|
|
918
915
|
*
|
|
919
916
|
* XML nodes share the mkr_node_data_t typed-data with HTML nodes, so the
|
|
920
|
-
* underlying node pointer (mkr_node_id, representation-agnostic) IS the
|
|
921
|
-
*
|
|
922
|
-
* (
|
|
923
|
-
*
|
|
924
|
-
* iff a.eql?(b). */
|
|
925
|
-
static VALUE
|
|
926
|
-
mkr_xml_node_pointer_id(VALUE self)
|
|
927
|
-
{
|
|
928
|
-
return ULL2NUM((unsigned long long)mkr_node_id(self));
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
static VALUE
|
|
932
|
-
mkr_xml_node_equals(VALUE self, VALUE other)
|
|
933
|
-
{
|
|
934
|
-
if (!rb_obj_is_kind_of(other, mkr_cNode)) return Qfalse;
|
|
935
|
-
return mkr_node_id(self) == mkr_node_id(other) ? Qtrue : Qfalse;
|
|
936
|
-
}
|
|
917
|
+
* underlying node pointer (mkr_node_id, representation-agnostic) IS the identity.
|
|
918
|
+
* The implementations are therefore representation-neutral and live once in
|
|
919
|
+
* ruby_node.c (mkr_node_equals / mkr_node_hash / mkr_node_pointer_id); the XML
|
|
920
|
+
* NodeMethods module just binds to them below, exactly as the HTML module does. */
|
|
937
921
|
|
|
938
922
|
/* ---- mutation (Phase 1: in-place edits) ----------------------------------
|
|
939
923
|
*
|
|
@@ -991,6 +975,10 @@ static mkr_xml_node_t *
|
|
|
991
975
|
mkr_xml_node_unwrap_mutable(VALUE self)
|
|
992
976
|
{
|
|
993
977
|
rb_check_frozen(self);
|
|
978
|
+
/* Single mutation choke point (every mutator calls this): drop the cached
|
|
979
|
+
* element-name index so the next query rebuilds it. Same discipline as the
|
|
980
|
+
* HTML attr/text indices, in one place that cannot be forgotten. */
|
|
981
|
+
mkr_xml_name_index_invalidate(mkr_xml_node_xdoc(self));
|
|
994
982
|
return mkr_xml_node_unwrap(self);
|
|
995
983
|
}
|
|
996
984
|
|
|
@@ -1359,10 +1347,10 @@ mkr_init_xml_node(void)
|
|
|
1359
1347
|
|
|
1360
1348
|
/* Node identity by underlying pointer, so #path / NodeSet dedup / Set / Hash
|
|
1361
1349
|
* work (the same contract HTML nodes have). */
|
|
1362
|
-
rb_define_method(mkr_mXmlNodeMethods, "==",
|
|
1363
|
-
rb_define_method(mkr_mXmlNodeMethods, "eql?",
|
|
1364
|
-
rb_define_method(mkr_mXmlNodeMethods, "hash",
|
|
1365
|
-
rb_define_method(mkr_mXmlNodeMethods, "pointer_id",
|
|
1350
|
+
rb_define_method(mkr_mXmlNodeMethods, "==", mkr_node_equals, 1);
|
|
1351
|
+
rb_define_method(mkr_mXmlNodeMethods, "eql?", mkr_node_equals, 1);
|
|
1352
|
+
rb_define_method(mkr_mXmlNodeMethods, "hash", mkr_node_hash, 0);
|
|
1353
|
+
rb_define_method(mkr_mXmlNodeMethods, "pointer_id", mkr_node_pointer_id, 0);
|
|
1366
1354
|
|
|
1367
1355
|
/* XML serialization: #to_xml / #to_s re-emit the subtree (a Document also
|
|
1368
1356
|
* emits the declaration + DOCTYPE). Also defined on XML::Document below. */
|
|
@@ -1370,10 +1358,9 @@ mkr_init_xml_node(void)
|
|
|
1370
1358
|
rb_define_method(mkr_mXmlNodeMethods, "to_s", mkr_xml_node_to_xml, -1);
|
|
1371
1359
|
rb_define_method(mkr_mXmlNodeMethods, "canonicalize", mkr_xml_node_canonicalize, -1);
|
|
1372
1360
|
|
|
1373
|
-
/*
|
|
1374
|
-
*
|
|
1375
|
-
|
|
1376
|
-
rb_define_method(mkr_mXmlNodeMethods, "at_css", mkr_xml_node_no_css, -1);
|
|
1361
|
+
/* CSS selectors (#css / #at_css / #matches?) are supported on XML via the
|
|
1362
|
+
* native XPath engine - registered in ruby_xml.c. Fail-closed: HTML
|
|
1363
|
+
* serialization is unsupported on XML; raise rather than emit wrong markup. */
|
|
1377
1364
|
rb_define_method(mkr_mXmlNodeMethods, "to_html", mkr_xml_node_no_serialize, -1);
|
|
1378
1365
|
rb_define_method(mkr_mXmlNodeMethods, "inner_html", mkr_xml_node_no_serialize, -1);
|
|
1379
1366
|
rb_define_method(mkr_mXmlNodeMethods, "outer_html", mkr_xml_node_no_serialize, -1);
|
|
@@ -396,7 +396,6 @@ mkr_ruby_to_out(mkr_xpath_context_t *ctx, VALUE document, VALUE r, mkr_val_t *ou
|
|
|
396
396
|
if (mkr_val_set_borrowed_text_copy(out, mkr_borrowed_text(vv.ptr, vv.len),
|
|
397
397
|
NULL, NULL) != 0) {
|
|
398
398
|
snprintf(errbuf, errlen, "out of memory converting handler result");
|
|
399
|
-
RB_GC_GUARD(vv.value);
|
|
400
399
|
return -1;
|
|
401
400
|
}
|
|
402
401
|
RB_GC_GUARD(vv.value);
|
|
@@ -642,10 +641,11 @@ static VALUE
|
|
|
642
641
|
mkr_xpath_ctx_register_variable(VALUE self, VALUE rb_name, VALUE rb_value)
|
|
643
642
|
{
|
|
644
643
|
mkr_xpath_ctx_data_t *d = mkr_xpath_ctx_unwrap(self);
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
* of no-NUL / valid-UTF-8). */
|
|
644
|
+
/* Coerce the value FIRST (rb_obj_as_string allocates = a GC point), so no
|
|
645
|
+
* borrowed name bytes are held across it. The value gets the stricter
|
|
646
|
+
* engine-string check (adds the byte cap on top of no-NUL / valid-UTF-8). */
|
|
648
647
|
VALUE value = rb_obj_as_string(rb_value);
|
|
648
|
+
mkr_ruby_borrowed_text_t nv = mkr_ruby_verified_text(rb_name, "variable name");
|
|
649
649
|
mkr_ruby_borrowed_text_t vv;
|
|
650
650
|
const char *bad =
|
|
651
651
|
mkr_ruby_try_verified_text(value, mkr_ctx_limits(d->ctx)->max_string_bytes, &vv);
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
|
|
9
9
|
#include <stdint.h>
|
|
10
10
|
#include <stdlib.h>
|
|
11
|
-
#include <string.h> /* memchr */
|
|
12
11
|
|
|
13
12
|
/*
|
|
14
13
|
* Source location tracking. Lexbor does not record where in the input a node
|
|
@@ -39,18 +38,15 @@ typedef struct {
|
|
|
39
38
|
void *
|
|
40
39
|
mkr_lines_build(const lxb_char_t *src, size_t len)
|
|
41
40
|
{
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const lxb_char_t *const end = src + len;
|
|
41
|
+
/* mkr_span normalizes src == NULL to the empty span, so the absent-input
|
|
42
|
+
* case (no newlines, one line) flows through with no special guard. */
|
|
47
43
|
|
|
48
|
-
/* One pass to count newlines (
|
|
49
|
-
* once. */
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
44
|
+
/* One pass to count newlines (mkr_span_find is still one memchr per line,
|
|
45
|
+
* inside the audited core), so the array is sized once. */
|
|
46
|
+
mkr_span_t s = mkr_span((const char *)src, len);
|
|
47
|
+
size_t nl = 0, at;
|
|
48
|
+
while (mkr_span_find(&s, '\n', &at)) {
|
|
49
|
+
mkr_span_skip(&s, at + 1); /* past the '\n' (find proved it exists) */
|
|
54
50
|
nl++;
|
|
55
51
|
}
|
|
56
52
|
|
|
@@ -65,12 +61,14 @@ mkr_lines_build(const lxb_char_t *src, size_t len)
|
|
|
65
61
|
return NULL;
|
|
66
62
|
}
|
|
67
63
|
|
|
64
|
+
mkr_span_t f = mkr_span((const char *)src, len);
|
|
65
|
+
const char *start = mkr_span_mark(&f);
|
|
68
66
|
size_t line = 0;
|
|
69
67
|
t->starts[line++] = 0;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
t->starts[line++] = (
|
|
68
|
+
while (mkr_span_find(&f, '\n', &at)) {
|
|
69
|
+
mkr_span_skip(&f, at + 1);
|
|
70
|
+
/* offset just past the '\n' = next line's start */
|
|
71
|
+
t->starts[line++] = mkr_span_since(&f, start);
|
|
74
72
|
}
|
|
75
73
|
return t;
|
|
76
74
|
}
|