makiri 0.2.0 → 0.3.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/release.yml +12 -7
- data/CHANGELOG.md +93 -14
- data/README.md +173 -7
- data/Rakefile +103 -7
- data/ext/makiri/bridge/bridge.h +28 -0
- data/ext/makiri/bridge/ruby_string.c +217 -0
- data/ext/makiri/core/mkr_alloc.h +1 -1
- data/ext/makiri/core/mkr_buf.c +35 -1
- data/ext/makiri/core/mkr_buf.h +37 -3
- data/ext/makiri/core/mkr_core.h +1 -1
- data/ext/makiri/core/mkr_hash.h +1 -1
- data/ext/makiri/core/mkr_text.h +8 -8
- data/ext/makiri/extconf.rb +20 -2
- data/ext/makiri/glue/glue.h +47 -11
- data/ext/makiri/glue/ruby_doc.c +117 -43
- data/ext/makiri/glue/ruby_html_css.c +246 -0
- data/ext/makiri/glue/{ruby_mutate.c → ruby_html_mutate.c} +242 -51
- data/ext/makiri/glue/ruby_html_node.c +888 -0
- data/ext/makiri/glue/ruby_html_serialize.c +154 -0
- data/ext/makiri/glue/ruby_node.c +54 -748
- data/ext/makiri/glue/ruby_node_set.c +167 -32
- data/ext/makiri/glue/ruby_xml.c +420 -0
- data/ext/makiri/glue/ruby_xml_node.c +1386 -0
- data/ext/makiri/glue/ruby_xpath.c +59 -26
- 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 +13 -9
- data/ext/makiri/lexbor_compat/text_index.c +14 -8
- data/ext/makiri/lexbor_compat/utf8_input.c +85 -26
- data/ext/makiri/makiri.c +139 -6
- data/ext/makiri/makiri.h +43 -2
- data/ext/makiri/xml/mkr_xml.h +126 -0
- data/ext/makiri/xml/mkr_xml_chars.c +225 -0
- data/ext/makiri/xml/mkr_xml_mutate.c +875 -0
- data/ext/makiri/xml/mkr_xml_mutate.h +139 -0
- data/ext/makiri/xml/mkr_xml_node.c +267 -0
- data/ext/makiri/xml/mkr_xml_node.h +119 -0
- data/ext/makiri/xml/mkr_xml_tree.c +1479 -0
- data/ext/makiri/xpath/mkr_xpath.c +59 -32
- data/ext/makiri/xpath/mkr_xpath.h +96 -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} +202 -175
- data/ext/makiri/xpath/{mkr_xpath_funcs.c → mkr_xpath_funcs_body.h} +110 -86
- data/ext/makiri/xpath/mkr_xpath_internal.h +91 -200
- data/ext/makiri/xpath/mkr_xpath_lex.c +2 -2
- data/ext/makiri/xpath/mkr_xpath_node_access_html.h +138 -0
- data/ext/makiri/xpath/mkr_xpath_node_access_xml.h +142 -0
- data/ext/makiri/xpath/mkr_xpath_parse.c +5 -5
- 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 +593 -0
- data/ext/makiri/xpath/{mkr_xpath_value.c → mkr_xpath_value_body.h} +145 -656
- 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 +21 -0
- data/lib/makiri/comment.rb +12 -0
- data/lib/makiri/compat_aliases.rb +30 -0
- data/lib/makiri/document.rb +4 -76
- data/lib/makiri/document_fragment.rb +14 -9
- data/lib/makiri/element.rb +5 -3
- 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 +12 -0
- data/lib/makiri/text.rb +2 -0
- data/lib/makiri/version.rb +1 -1
- data/lib/makiri/xml/document.rb +24 -0
- data/lib/makiri/xml/node_methods.rb +37 -0
- data/lib/makiri/xml.rb +10 -0
- data/lib/makiri/xpath_context.rb +1 -1
- data/lib/makiri.rb +23 -5
- data/script/build_native_gem.rb +2 -2
- data/script/check_c_safety.rb +32 -0
- data/script/check_c_safety_allowlist.yml +83 -0
- metadata +35 -9
- data/ext/makiri/glue/ruby_css.c +0 -185
- data/ext/makiri/glue/ruby_serialize.c +0 -92
- data/lib/makiri/cdata.rb +0 -6
|
@@ -4,20 +4,41 @@
|
|
|
4
4
|
#include <stdint.h>
|
|
5
5
|
|
|
6
6
|
/* MKR_NODE_SET_MAX (the per-set node cap, shared with the CSS/XPath glue) is
|
|
7
|
-
* defined in glue.h. Every node-collecting path
|
|
8
|
-
* (children / element_children / attribute_nodes), XPath, and CSS
|
|
7
|
+
* defined in glue.h. Every node-collecting path - tree walks
|
|
8
|
+
* (children / element_children / attribute_nodes), XPath, and CSS - fails
|
|
9
9
|
* closed at that bound instead of growing without limit. */
|
|
10
10
|
|
|
11
11
|
/* A NodeSet is a plain dynamic array of Lexbor node pointers plus a keepalive
|
|
12
12
|
* reference to the owning Document. Nodes are owned by the document arena, so
|
|
13
13
|
* marking the document keeps them all alive. */
|
|
14
|
+
/* The stored nodes are mkr_raw_node_t* (representation-opaque; see glue.h): the
|
|
15
|
+
* set never dereferences them, it only compares them for identity and, when
|
|
16
|
+
* vending a node, casts to the representation named by doc_is_xml. This keeps an
|
|
17
|
+
* XML set from ever reading its mkr_xml_node_t* pointers as lxb_dom_node_t. */
|
|
14
18
|
typedef struct {
|
|
15
|
-
|
|
19
|
+
mkr_raw_node_t **nodes;
|
|
16
20
|
size_t count;
|
|
17
21
|
size_t cap;
|
|
18
22
|
VALUE document;
|
|
23
|
+
int doc_is_xml; /* cached once: the stored pointers are
|
|
24
|
+
* mkr_xml_node_t* (wrap as Makiri::XML::*) */
|
|
19
25
|
} mkr_node_set_data_t;
|
|
20
26
|
|
|
27
|
+
/* Wrap a stored node into a Ruby Node, choosing the representation by the set's
|
|
28
|
+
* (fixed) document kind - an XML document's nodes are custom mkr_xml_node_t. This
|
|
29
|
+
* is the ONLY place a stored raw node is cast back to a typed pointer, and the
|
|
30
|
+
* cast is justified by doc_is_xml. The kind is cached at construction so this
|
|
31
|
+
* stays a single branch per node (a per-node is_kind_of/parsed-kind probe would
|
|
32
|
+
* regress the hot traversal path). */
|
|
33
|
+
static VALUE
|
|
34
|
+
mkr_node_set_wrap(const mkr_node_set_data_t *s, mkr_raw_node_t *node)
|
|
35
|
+
{
|
|
36
|
+
if (s->doc_is_xml) {
|
|
37
|
+
return mkr_wrap_xml_node((struct mkr_xml_node *)node, s->document);
|
|
38
|
+
}
|
|
39
|
+
return mkr_wrap_html_node((lxb_dom_node_t *)node, s->document);
|
|
40
|
+
}
|
|
41
|
+
|
|
21
42
|
static void
|
|
22
43
|
mkr_node_set_gc_mark(void *ptr)
|
|
23
44
|
{
|
|
@@ -41,7 +62,7 @@ mkr_node_set_memsize(const void *ptr)
|
|
|
41
62
|
const mkr_node_set_data_t *s = (const mkr_node_set_data_t *)ptr;
|
|
42
63
|
size_t nodes_bytes;
|
|
43
64
|
size_t total;
|
|
44
|
-
if (!mkr_size_mul(s->cap, sizeof(
|
|
65
|
+
if (!mkr_size_mul(s->cap, sizeof(mkr_raw_node_t *), &nodes_bytes)) {
|
|
45
66
|
return sizeof(*s);
|
|
46
67
|
}
|
|
47
68
|
if (!mkr_size_add(sizeof(*s), nodes_bytes, &total)) {
|
|
@@ -62,15 +83,16 @@ mkr_node_set_new(VALUE document)
|
|
|
62
83
|
mkr_node_set_data_t *s;
|
|
63
84
|
VALUE obj = TypedData_Make_Struct(mkr_cNodeSet, mkr_node_set_data_t,
|
|
64
85
|
&mkr_node_set_type, s);
|
|
65
|
-
s->nodes
|
|
66
|
-
s->count
|
|
67
|
-
s->cap
|
|
68
|
-
s->document
|
|
86
|
+
s->nodes = NULL;
|
|
87
|
+
s->count = 0;
|
|
88
|
+
s->cap = 0;
|
|
89
|
+
s->document = document;
|
|
90
|
+
s->doc_is_xml = rb_obj_is_kind_of(document, mkr_cXmlDocument) ? 1 : 0;
|
|
69
91
|
return obj;
|
|
70
92
|
}
|
|
71
93
|
|
|
72
94
|
void
|
|
73
|
-
mkr_node_set_push(VALUE rb_set,
|
|
95
|
+
mkr_node_set_push(VALUE rb_set, mkr_raw_node_t *node)
|
|
74
96
|
{
|
|
75
97
|
mkr_node_set_data_t *s;
|
|
76
98
|
TypedData_Get_Struct(rb_set, mkr_node_set_data_t, &mkr_node_set_type, s);
|
|
@@ -85,10 +107,10 @@ mkr_node_set_push(VALUE rb_set, lxb_dom_node_t *node)
|
|
|
85
107
|
* overflow-checked (count * size) allocation while keeping the buffer
|
|
86
108
|
* GC-accounted and paired with xfree in gc_free. */
|
|
87
109
|
size_t new_cap;
|
|
88
|
-
if (!mkr_grow_capacity(s->cap, s->count + 1, sizeof(
|
|
110
|
+
if (!mkr_grow_capacity(s->cap, s->count + 1, sizeof(mkr_raw_node_t *), &new_cap)) {
|
|
89
111
|
rb_raise(mkr_eError, "node set capacity overflow");
|
|
90
112
|
}
|
|
91
|
-
s->nodes = xrealloc2(s->nodes, new_cap, sizeof(
|
|
113
|
+
s->nodes = xrealloc2(s->nodes, new_cap, sizeof(mkr_raw_node_t *));
|
|
92
114
|
s->cap = new_cap;
|
|
93
115
|
}
|
|
94
116
|
s->nodes[s->count++] = node;
|
|
@@ -102,21 +124,52 @@ mkr_node_set_length(VALUE self)
|
|
|
102
124
|
return ULONG2NUM(s->count);
|
|
103
125
|
}
|
|
104
126
|
|
|
105
|
-
/*
|
|
127
|
+
/* A new NodeSet from the [beg, beg+len) run of `s` (caller has clamped them to
|
|
128
|
+
* [0, count]). */
|
|
129
|
+
static VALUE
|
|
130
|
+
mkr_node_set_slice(mkr_node_set_data_t *s, long beg, long len)
|
|
131
|
+
{
|
|
132
|
+
VALUE result = mkr_node_set_new(s->document);
|
|
133
|
+
for (long i = 0; i < len; i++) {
|
|
134
|
+
mkr_node_set_push(result, s->nodes[beg + i]);
|
|
135
|
+
}
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* set[i] -> Node or nil (negative indices count from the end).
|
|
140
|
+
* set[start, length] -> a new NodeSet (nil if start is out of range).
|
|
141
|
+
* set[range] -> a new NodeSet (nil if the range start is out of range).
|
|
142
|
+
* Mirrors Array#[]. */
|
|
106
143
|
static VALUE
|
|
107
|
-
mkr_node_set_aref(VALUE
|
|
144
|
+
mkr_node_set_aref(int argc, VALUE *argv, VALUE self)
|
|
108
145
|
{
|
|
109
146
|
mkr_node_set_data_t *s;
|
|
110
147
|
TypedData_Get_Struct(self, mkr_node_set_data_t, &mkr_node_set_type, s);
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (
|
|
114
|
-
|
|
148
|
+
long count = (long)s->count;
|
|
149
|
+
|
|
150
|
+
if (argc == 2) { /* set[start, length] */
|
|
151
|
+
long beg = NUM2LONG(argv[0]);
|
|
152
|
+
long len = NUM2LONG(argv[1]);
|
|
153
|
+
if (beg < 0) beg += count;
|
|
154
|
+
if (beg < 0 || beg > count || len < 0) return Qnil;
|
|
155
|
+
if (len > count - beg) len = count - beg;
|
|
156
|
+
return mkr_node_set_slice(s, beg, len);
|
|
115
157
|
}
|
|
116
|
-
|
|
117
|
-
|
|
158
|
+
|
|
159
|
+
rb_check_arity(argc, 1, 2);
|
|
160
|
+
|
|
161
|
+
if (rb_obj_is_kind_of(argv[0], rb_cRange)) {
|
|
162
|
+
long beg, len;
|
|
163
|
+
if (rb_range_beg_len(argv[0], &beg, &len, count, 0) != Qtrue) {
|
|
164
|
+
return Qnil; /* start out of range */
|
|
165
|
+
}
|
|
166
|
+
return mkr_node_set_slice(s, beg, len);
|
|
118
167
|
}
|
|
119
|
-
|
|
168
|
+
|
|
169
|
+
long i = NUM2LONG(argv[0]);
|
|
170
|
+
if (i < 0) i += count;
|
|
171
|
+
if (i < 0 || i >= count) return Qnil;
|
|
172
|
+
return mkr_node_set_wrap(s, s->nodes[i]);
|
|
120
173
|
}
|
|
121
174
|
|
|
122
175
|
static VALUE
|
|
@@ -128,7 +181,7 @@ mkr_node_set_each(VALUE self)
|
|
|
128
181
|
RETURN_ENUMERATOR(self, 0, 0);
|
|
129
182
|
|
|
130
183
|
for (size_t i = 0; i < s->count; i++) {
|
|
131
|
-
rb_yield(
|
|
184
|
+
rb_yield(mkr_node_set_wrap(s, s->nodes[i]));
|
|
132
185
|
}
|
|
133
186
|
return self;
|
|
134
187
|
}
|
|
@@ -144,8 +197,24 @@ mkr_node_set_get(VALUE v)
|
|
|
144
197
|
return s;
|
|
145
198
|
}
|
|
146
199
|
|
|
200
|
+
/* The "other" operand of a set operation, requiring it to share +s+'s document.
|
|
201
|
+
* A result NodeSet borrows exactly one document VALUE (GC keepalive) and one
|
|
202
|
+
* representation flag (HTML vs XML) to wrap its nodes; mixing two documents -
|
|
203
|
+
* which also means possibly mixing HTML and XML - would wrap a node under the
|
|
204
|
+
* wrong representation and fail to keep its document alive. Fail closed instead
|
|
205
|
+
* of silently producing a corrupt set. */
|
|
206
|
+
static mkr_node_set_data_t *
|
|
207
|
+
mkr_node_set_other(const mkr_node_set_data_t *s, VALUE other)
|
|
208
|
+
{
|
|
209
|
+
mkr_node_set_data_t *o = mkr_node_set_get(other);
|
|
210
|
+
if (o->document != s->document) {
|
|
211
|
+
rb_raise(mkr_eError, "cannot combine node sets from different documents");
|
|
212
|
+
}
|
|
213
|
+
return o;
|
|
214
|
+
}
|
|
215
|
+
|
|
147
216
|
static int
|
|
148
|
-
mkr_node_set_member(const mkr_node_set_data_t *s, const
|
|
217
|
+
mkr_node_set_member(const mkr_node_set_data_t *s, const mkr_raw_node_t *n)
|
|
149
218
|
{
|
|
150
219
|
for (size_t i = 0; i < s->count; i++) {
|
|
151
220
|
if (s->nodes[i] == n) {
|
|
@@ -158,10 +227,10 @@ mkr_node_set_member(const mkr_node_set_data_t *s, const lxb_dom_node_t *n)
|
|
|
158
227
|
/* Open-addressing pointer-hash set, used to keep the set operators below O(n²)
|
|
159
228
|
* (a CPU-DoS vector at large operand sizes). NULL is the empty sentinel; DOM
|
|
160
229
|
* node pointers are never NULL. Sized once for the expected element count (load
|
|
161
|
-
* factor < 0.5), so it never rehashes. cap == 0 means "not built"
|
|
230
|
+
* factor < 0.5), so it never rehashes. cap == 0 means "not built" - the caller
|
|
162
231
|
* then falls back to a linear scan (small operands, or allocation failure). */
|
|
163
232
|
typedef struct {
|
|
164
|
-
const
|
|
233
|
+
const mkr_raw_node_t **slots;
|
|
165
234
|
size_t cap;
|
|
166
235
|
} mkr_ptrset_t;
|
|
167
236
|
|
|
@@ -195,7 +264,7 @@ mkr_ptrset_free(mkr_ptrset_t *set)
|
|
|
195
264
|
|
|
196
265
|
/* Add p; returns 1 if newly added, 0 if already present. */
|
|
197
266
|
static int
|
|
198
|
-
mkr_ptrset_add(mkr_ptrset_t *set, const
|
|
267
|
+
mkr_ptrset_add(mkr_ptrset_t *set, const mkr_raw_node_t *p)
|
|
199
268
|
{
|
|
200
269
|
size_t mask = set->cap - 1;
|
|
201
270
|
size_t j = mkr_ptr_hash(p) & mask;
|
|
@@ -208,7 +277,7 @@ mkr_ptrset_add(mkr_ptrset_t *set, const lxb_dom_node_t *p)
|
|
|
208
277
|
}
|
|
209
278
|
|
|
210
279
|
static int
|
|
211
|
-
mkr_ptrset_has(const mkr_ptrset_t *set, const
|
|
280
|
+
mkr_ptrset_has(const mkr_ptrset_t *set, const mkr_raw_node_t *p)
|
|
212
281
|
{
|
|
213
282
|
size_t mask = set->cap - 1;
|
|
214
283
|
size_t j = mkr_ptr_hash(p) & mask;
|
|
@@ -230,7 +299,7 @@ static VALUE
|
|
|
230
299
|
mkr_node_set_op_or(VALUE self, VALUE other)
|
|
231
300
|
{
|
|
232
301
|
mkr_node_set_data_t *s = mkr_node_set_get(self);
|
|
233
|
-
mkr_node_set_data_t *o =
|
|
302
|
+
mkr_node_set_data_t *o = mkr_node_set_other(s, other);
|
|
234
303
|
VALUE result = mkr_node_set_new(s->document);
|
|
235
304
|
mkr_node_set_data_t *r = mkr_node_set_get(result);
|
|
236
305
|
|
|
@@ -241,7 +310,7 @@ mkr_node_set_op_or(VALUE self, VALUE other)
|
|
|
241
310
|
mkr_node_set_data_t *srcs[2] = { s, o };
|
|
242
311
|
for (int k = 0; k < 2; k++) {
|
|
243
312
|
for (size_t i = 0; i < srcs[k]->count; i++) {
|
|
244
|
-
|
|
313
|
+
mkr_raw_node_t *n = srcs[k]->nodes[i];
|
|
245
314
|
int fresh = seen.cap ? mkr_ptrset_add(&seen, n)
|
|
246
315
|
: !mkr_node_set_member(r, n);
|
|
247
316
|
if (fresh) mkr_node_set_push(result, n);
|
|
@@ -256,7 +325,7 @@ static VALUE
|
|
|
256
325
|
mkr_node_set_op_plus(VALUE self, VALUE other)
|
|
257
326
|
{
|
|
258
327
|
mkr_node_set_data_t *s = mkr_node_set_get(self);
|
|
259
|
-
mkr_node_set_data_t *o =
|
|
328
|
+
mkr_node_set_data_t *o = mkr_node_set_other(s, other);
|
|
260
329
|
VALUE result = mkr_node_set_new(s->document);
|
|
261
330
|
for (size_t i = 0; i < s->count; i++) mkr_node_set_push(result, s->nodes[i]);
|
|
262
331
|
for (size_t i = 0; i < o->count; i++) mkr_node_set_push(result, o->nodes[i]);
|
|
@@ -269,7 +338,7 @@ static VALUE
|
|
|
269
338
|
mkr_node_set_op_filter(VALUE self, VALUE other, int keep_if_in_other)
|
|
270
339
|
{
|
|
271
340
|
mkr_node_set_data_t *s = mkr_node_set_get(self);
|
|
272
|
-
mkr_node_set_data_t *o =
|
|
341
|
+
mkr_node_set_data_t *o = mkr_node_set_other(s, other);
|
|
273
342
|
VALUE result = mkr_node_set_new(s->document);
|
|
274
343
|
mkr_node_set_data_t *r = mkr_node_set_get(result);
|
|
275
344
|
|
|
@@ -286,7 +355,7 @@ mkr_node_set_op_filter(VALUE self, VALUE other, int keep_if_in_other)
|
|
|
286
355
|
}
|
|
287
356
|
|
|
288
357
|
for (size_t i = 0; i < s->count; i++) {
|
|
289
|
-
|
|
358
|
+
mkr_raw_node_t *n = s->nodes[i];
|
|
290
359
|
int in_o = oset.cap ? mkr_ptrset_has(&oset, n) : mkr_node_set_member(o, n);
|
|
291
360
|
if (in_o != keep_if_in_other) continue;
|
|
292
361
|
int fresh = seen.cap ? mkr_ptrset_add(&seen, n) : !mkr_node_set_member(r, n);
|
|
@@ -311,15 +380,81 @@ mkr_node_set_op_minus(VALUE self, VALUE other)
|
|
|
311
380
|
return mkr_node_set_op_filter(self, other, 0);
|
|
312
381
|
}
|
|
313
382
|
|
|
383
|
+
/* #dup / #clone: a new NodeSet over the same nodes (the nodes are shared - they
|
|
384
|
+
* are owned by the document arena - but the set itself is independent), like
|
|
385
|
+
* Nokogiri. Defined here because the allocator is undef'd, so Ruby's default
|
|
386
|
+
* allocate-then-copy raises; any level/freeze argument is ignored. */
|
|
387
|
+
static VALUE
|
|
388
|
+
mkr_node_set_dup(int argc, VALUE *argv, VALUE self)
|
|
389
|
+
{
|
|
390
|
+
(void)argc;
|
|
391
|
+
(void)argv;
|
|
392
|
+
mkr_node_set_data_t *s = mkr_node_set_get(self);
|
|
393
|
+
VALUE copy = mkr_node_set_new(s->document);
|
|
394
|
+
/* Reuse the overflow-checked growth + cap enforcement of mkr_node_set_push;
|
|
395
|
+
* the source already has no duplicates, so this is a faithful copy. */
|
|
396
|
+
for (size_t i = 0; i < s->count; i++) {
|
|
397
|
+
mkr_node_set_push(copy, s->nodes[i]);
|
|
398
|
+
}
|
|
399
|
+
return copy;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/* NodeSet.new(document_or_node, list = []) -> NodeSet.
|
|
403
|
+
* Mirrors Nokogiri: the first argument is the owning Document (or any node, whose
|
|
404
|
+
* document is taken) that the set pins as a GC keepalive; the optional list seeds
|
|
405
|
+
* it. Every listed node MUST belong to that document - one from another document
|
|
406
|
+
* or representation would be re-wrapped under the wrong document/kind, so it is
|
|
407
|
+
* rejected (fail-closed, preventing the HTML/XML type confusion the set's
|
|
408
|
+
* representation-opaque storage otherwise relies on document kind to avoid). */
|
|
409
|
+
static VALUE
|
|
410
|
+
mkr_node_set_s_new(int argc, VALUE *argv, VALUE klass)
|
|
411
|
+
{
|
|
412
|
+
(void)klass;
|
|
413
|
+
VALUE rb_ctx, rb_list;
|
|
414
|
+
rb_scan_args(argc, argv, "11", &rb_ctx, &rb_list);
|
|
415
|
+
|
|
416
|
+
VALUE rb_doc;
|
|
417
|
+
if (rb_obj_is_kind_of(rb_ctx, mkr_cDocument)) {
|
|
418
|
+
rb_doc = rb_ctx;
|
|
419
|
+
} else if (rb_obj_is_kind_of(rb_ctx, mkr_cNode)) {
|
|
420
|
+
rb_doc = mkr_node_document(rb_ctx);
|
|
421
|
+
} else {
|
|
422
|
+
rb_raise(rb_eTypeError, "expected a Makiri::Document or Node as the first argument");
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
VALUE set = mkr_node_set_new(rb_doc);
|
|
426
|
+
if (NIL_P(rb_list)) {
|
|
427
|
+
return set;
|
|
428
|
+
}
|
|
429
|
+
VALUE arr = rb_check_array_type(rb_list);
|
|
430
|
+
if (NIL_P(arr)) {
|
|
431
|
+
rb_raise(rb_eTypeError, "expected an Array of nodes as the second argument");
|
|
432
|
+
}
|
|
433
|
+
for (long i = 0; i < RARRAY_LEN(arr); i++) {
|
|
434
|
+
VALUE node = RARRAY_AREF(arr, i);
|
|
435
|
+
if (!rb_obj_is_kind_of(node, mkr_cNode) || mkr_node_document(node) != rb_doc) {
|
|
436
|
+
rb_raise(rb_eArgError,
|
|
437
|
+
"every node must be a Makiri node belonging to the given document");
|
|
438
|
+
}
|
|
439
|
+
mkr_node_set_push(set, (mkr_raw_node_t *)mkr_node_raw(node));
|
|
440
|
+
}
|
|
441
|
+
return set;
|
|
442
|
+
}
|
|
443
|
+
|
|
314
444
|
void
|
|
315
445
|
mkr_init_node_set(void)
|
|
316
446
|
{
|
|
447
|
+
rb_undef_alloc_func(mkr_cNodeSet); /* nodes come only from C; .new seeds via the factory */
|
|
448
|
+
rb_define_singleton_method(mkr_cNodeSet, "new", mkr_node_set_s_new, -1);
|
|
449
|
+
|
|
317
450
|
rb_define_method(mkr_cNodeSet, "|", mkr_node_set_op_or, 1);
|
|
318
451
|
rb_define_method(mkr_cNodeSet, "+", mkr_node_set_op_plus, 1);
|
|
319
452
|
rb_define_method(mkr_cNodeSet, "&", mkr_node_set_op_and, 1);
|
|
320
453
|
rb_define_method(mkr_cNodeSet, "-", mkr_node_set_op_minus, 1);
|
|
321
454
|
|
|
322
455
|
rb_define_method(mkr_cNodeSet, "length", mkr_node_set_length, 0);
|
|
323
|
-
rb_define_method(mkr_cNodeSet, "[]", mkr_node_set_aref,
|
|
456
|
+
rb_define_method(mkr_cNodeSet, "[]", mkr_node_set_aref, -1);
|
|
324
457
|
rb_define_method(mkr_cNodeSet, "each", mkr_node_set_each, 0);
|
|
458
|
+
rb_define_method(mkr_cNodeSet, "dup", mkr_node_set_dup, -1);
|
|
459
|
+
/* #clone is defined in Ruby (node_set.rb) so it can honour `freeze:`. */
|
|
325
460
|
}
|