nokogiri 1.11.7 → 1.12.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of nokogiri might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/LICENSE-DEPENDENCIES.md +243 -22
- data/LICENSE.md +1 -1
- data/README.md +6 -5
- data/ext/nokogiri/depend +35 -34
- data/ext/nokogiri/extconf.rb +181 -103
- data/ext/nokogiri/gumbo.c +611 -0
- data/ext/nokogiri/{html_document.c → html4_document.c} +8 -8
- data/ext/nokogiri/{html_element_description.c → html4_element_description.c} +20 -18
- data/ext/nokogiri/{html_entity_lookup.c → html4_entity_lookup.c} +7 -7
- data/ext/nokogiri/{html_sax_parser_context.c → html4_sax_parser_context.c} +5 -5
- data/ext/nokogiri/{html_sax_push_parser.c → html4_sax_push_parser.c} +4 -4
- data/ext/nokogiri/libxml2_backwards_compat.c +30 -30
- data/ext/nokogiri/nokogiri.c +51 -38
- data/ext/nokogiri/nokogiri.h +16 -9
- data/ext/nokogiri/xml_document.c +13 -13
- data/ext/nokogiri/xml_element_content.c +2 -0
- data/ext/nokogiri/xml_encoding_handler.c +11 -6
- data/ext/nokogiri/xml_namespace.c +2 -0
- data/ext/nokogiri/xml_node.c +102 -102
- data/ext/nokogiri/xml_node_set.c +20 -20
- data/ext/nokogiri/xml_reader.c +2 -0
- data/ext/nokogiri/xml_sax_parser.c +6 -6
- data/ext/nokogiri/xml_sax_parser_context.c +2 -0
- data/ext/nokogiri/xml_schema.c +2 -0
- data/ext/nokogiri/xml_xpath_context.c +67 -65
- data/ext/nokogiri/xslt_stylesheet.c +2 -1
- data/gumbo-parser/CHANGES.md +63 -0
- data/gumbo-parser/Makefile +101 -0
- data/gumbo-parser/THANKS +27 -0
- data/gumbo-parser/src/Makefile +17 -0
- data/gumbo-parser/src/README.md +41 -0
- data/gumbo-parser/src/ascii.c +75 -0
- data/gumbo-parser/src/ascii.h +115 -0
- data/gumbo-parser/src/attribute.c +42 -0
- data/gumbo-parser/src/attribute.h +17 -0
- data/gumbo-parser/src/char_ref.c +22225 -0
- data/gumbo-parser/src/char_ref.h +29 -0
- data/gumbo-parser/src/char_ref.rl +2154 -0
- data/gumbo-parser/src/error.c +626 -0
- data/gumbo-parser/src/error.h +148 -0
- data/gumbo-parser/src/foreign_attrs.c +104 -0
- data/gumbo-parser/src/foreign_attrs.gperf +27 -0
- data/gumbo-parser/src/gumbo.h +943 -0
- data/gumbo-parser/src/insertion_mode.h +33 -0
- data/gumbo-parser/src/macros.h +91 -0
- data/gumbo-parser/src/parser.c +4886 -0
- data/gumbo-parser/src/parser.h +41 -0
- data/gumbo-parser/src/replacement.h +33 -0
- data/gumbo-parser/src/string_buffer.c +103 -0
- data/gumbo-parser/src/string_buffer.h +68 -0
- data/gumbo-parser/src/string_piece.c +48 -0
- data/gumbo-parser/src/svg_attrs.c +174 -0
- data/gumbo-parser/src/svg_attrs.gperf +77 -0
- data/gumbo-parser/src/svg_tags.c +137 -0
- data/gumbo-parser/src/svg_tags.gperf +55 -0
- data/gumbo-parser/src/tag.c +222 -0
- data/gumbo-parser/src/tag_lookup.c +382 -0
- data/gumbo-parser/src/tag_lookup.gperf +169 -0
- data/gumbo-parser/src/tag_lookup.h +13 -0
- data/gumbo-parser/src/token_buffer.c +79 -0
- data/gumbo-parser/src/token_buffer.h +71 -0
- data/gumbo-parser/src/token_type.h +17 -0
- data/gumbo-parser/src/tokenizer.c +3463 -0
- data/gumbo-parser/src/tokenizer.h +112 -0
- data/gumbo-parser/src/tokenizer_states.h +339 -0
- data/gumbo-parser/src/utf8.c +245 -0
- data/gumbo-parser/src/utf8.h +164 -0
- data/gumbo-parser/src/util.c +68 -0
- data/gumbo-parser/src/util.h +30 -0
- data/gumbo-parser/src/vector.c +111 -0
- data/gumbo-parser/src/vector.h +45 -0
- data/lib/nokogiri.rb +31 -29
- data/lib/nokogiri/css.rb +14 -14
- data/lib/nokogiri/css/parser.rb +1 -1
- data/lib/nokogiri/css/parser.y +1 -1
- data/lib/nokogiri/css/syntax_error.rb +1 -1
- data/lib/nokogiri/extension.rb +2 -2
- data/lib/nokogiri/gumbo.rb +14 -0
- data/lib/nokogiri/html.rb +31 -27
- data/lib/nokogiri/html4.rb +40 -0
- data/lib/nokogiri/{html → html4}/builder.rb +2 -2
- data/lib/nokogiri/{html → html4}/document.rb +4 -4
- data/lib/nokogiri/{html → html4}/document_fragment.rb +3 -3
- data/lib/nokogiri/{html → html4}/element_description.rb +1 -1
- data/lib/nokogiri/{html → html4}/element_description_defaults.rb +1 -1
- data/lib/nokogiri/{html → html4}/entity_lookup.rb +1 -1
- data/lib/nokogiri/{html → html4}/sax/parser.rb +11 -14
- data/lib/nokogiri/html4/sax/parser_context.rb +19 -0
- data/lib/nokogiri/{html → html4}/sax/push_parser.rb +5 -5
- data/lib/nokogiri/html5.rb +473 -0
- data/lib/nokogiri/html5/document.rb +74 -0
- data/lib/nokogiri/html5/document_fragment.rb +80 -0
- data/lib/nokogiri/html5/node.rb +93 -0
- data/lib/nokogiri/version/constant.rb +1 -1
- data/lib/nokogiri/version/info.rb +11 -2
- data/lib/nokogiri/xml.rb +35 -36
- data/lib/nokogiri/xml/node.rb +6 -5
- data/lib/nokogiri/xml/parse_options.rb +2 -0
- data/lib/nokogiri/xml/pp.rb +2 -2
- data/lib/nokogiri/xml/sax.rb +4 -4
- data/lib/nokogiri/xml/sax/document.rb +24 -30
- data/lib/nokogiri/xml/xpath.rb +2 -2
- data/lib/nokogiri/xslt.rb +16 -16
- data/lib/nokogiri/xslt/stylesheet.rb +1 -1
- metadata +102 -60
- data/lib/nokogiri/html/sax/parser_context.rb +0 -17
@@ -0,0 +1,611 @@
|
|
1
|
+
//
|
2
|
+
// Copyright 2013-2021 Sam Ruby, Stephen Checkoway
|
3
|
+
//
|
4
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
// you may not use this file except in compliance with the License.
|
6
|
+
// You may obtain a copy of the License at
|
7
|
+
//
|
8
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
//
|
10
|
+
// Unless required by applicable law or agreed to in writing, software
|
11
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
// See the License for the specific language governing permissions and
|
14
|
+
// limitations under the License.
|
15
|
+
//
|
16
|
+
|
17
|
+
//
|
18
|
+
// nokogumbo.c defines the following:
|
19
|
+
//
|
20
|
+
// class Nokogumbo
|
21
|
+
// def parse(utf8_string) # returns Nokogiri::HTML5::Document
|
22
|
+
// end
|
23
|
+
//
|
24
|
+
// Processing starts by calling gumbo_parse_with_options. The resulting document tree
|
25
|
+
// is then walked, a parallel libxml2 tree is constructed, and the final document is
|
26
|
+
// then wrapped using Nokogiri_wrap_xml_document. This approach reduces memory and CPU
|
27
|
+
// requirements as Ruby objects are only built when necessary.
|
28
|
+
//
|
29
|
+
|
30
|
+
#include <nokogiri.h>
|
31
|
+
|
32
|
+
#include "gumbo.h"
|
33
|
+
|
34
|
+
VALUE cNokogiriHtml5Document;
|
35
|
+
|
36
|
+
// Interned symbols
|
37
|
+
static ID internal_subset;
|
38
|
+
static ID parent;
|
39
|
+
|
40
|
+
/* Backwards compatibility to Ruby 2.1.0 */
|
41
|
+
#if RUBY_API_VERSION_CODE < 20200
|
42
|
+
#define ONIG_ESCAPE_UCHAR_COLLISION 1
|
43
|
+
#include <ruby/encoding.h>
|
44
|
+
|
45
|
+
static VALUE
|
46
|
+
rb_utf8_str_new(const char *str, long length)
|
47
|
+
{
|
48
|
+
return rb_enc_str_new(str, length, rb_utf8_encoding());
|
49
|
+
}
|
50
|
+
|
51
|
+
static VALUE
|
52
|
+
rb_utf8_str_new_cstr(const char *str)
|
53
|
+
{
|
54
|
+
return rb_enc_str_new_cstr(str, rb_utf8_encoding());
|
55
|
+
}
|
56
|
+
|
57
|
+
static VALUE
|
58
|
+
rb_utf8_str_new_static(const char *str, long length)
|
59
|
+
{
|
60
|
+
return rb_enc_str_new(str, length, rb_utf8_encoding());
|
61
|
+
}
|
62
|
+
#endif
|
63
|
+
|
64
|
+
#include <nokogiri.h>
|
65
|
+
#include <libxml/tree.h>
|
66
|
+
#include <libxml/HTMLtree.h>
|
67
|
+
|
68
|
+
// URI = system id
|
69
|
+
// external id = public id
|
70
|
+
static xmlDocPtr
|
71
|
+
new_html_doc(const char *dtd_name, const char *system, const char *public)
|
72
|
+
{
|
73
|
+
// These two libxml2 functions take the public and system ids in
|
74
|
+
// opposite orders.
|
75
|
+
htmlDocPtr doc = htmlNewDocNoDtD(/* URI */ NULL, /* ExternalID */NULL);
|
76
|
+
assert(doc);
|
77
|
+
if (dtd_name) {
|
78
|
+
xmlCreateIntSubset(doc, BAD_CAST dtd_name, BAD_CAST public, BAD_CAST system);
|
79
|
+
}
|
80
|
+
return doc;
|
81
|
+
}
|
82
|
+
|
83
|
+
static xmlNodePtr
|
84
|
+
get_parent(xmlNodePtr node)
|
85
|
+
{
|
86
|
+
return node->parent;
|
87
|
+
}
|
88
|
+
|
89
|
+
static GumboOutput *
|
90
|
+
perform_parse(const GumboOptions *options, VALUE input)
|
91
|
+
{
|
92
|
+
assert(RTEST(input));
|
93
|
+
Check_Type(input, T_STRING);
|
94
|
+
GumboOutput *output = gumbo_parse_with_options(
|
95
|
+
options,
|
96
|
+
RSTRING_PTR(input),
|
97
|
+
RSTRING_LEN(input)
|
98
|
+
);
|
99
|
+
|
100
|
+
const char *status_string = gumbo_status_to_string(output->status);
|
101
|
+
switch (output->status) {
|
102
|
+
case GUMBO_STATUS_OK:
|
103
|
+
break;
|
104
|
+
case GUMBO_STATUS_TOO_MANY_ATTRIBUTES:
|
105
|
+
case GUMBO_STATUS_TREE_TOO_DEEP:
|
106
|
+
gumbo_destroy_output(output);
|
107
|
+
rb_raise(rb_eArgError, "%s", status_string);
|
108
|
+
case GUMBO_STATUS_OUT_OF_MEMORY:
|
109
|
+
gumbo_destroy_output(output);
|
110
|
+
rb_raise(rb_eNoMemError, "%s", status_string);
|
111
|
+
}
|
112
|
+
return output;
|
113
|
+
}
|
114
|
+
|
115
|
+
static xmlNsPtr
|
116
|
+
lookup_or_add_ns(
|
117
|
+
xmlDocPtr doc,
|
118
|
+
xmlNodePtr root,
|
119
|
+
const char *href,
|
120
|
+
const char *prefix
|
121
|
+
)
|
122
|
+
{
|
123
|
+
xmlNsPtr ns = xmlSearchNs(doc, root, BAD_CAST prefix);
|
124
|
+
if (ns) {
|
125
|
+
return ns;
|
126
|
+
}
|
127
|
+
return xmlNewNs(root, BAD_CAST href, BAD_CAST prefix);
|
128
|
+
}
|
129
|
+
|
130
|
+
static void
|
131
|
+
set_line(xmlNodePtr node, size_t line)
|
132
|
+
{
|
133
|
+
// libxml2 uses 65535 to mean look elsewhere for the line number on some
|
134
|
+
// nodes.
|
135
|
+
if (line < 65535) {
|
136
|
+
node->line = (unsigned short)line;
|
137
|
+
}
|
138
|
+
}
|
139
|
+
|
140
|
+
// Construct an XML tree rooted at xml_output_node from the Gumbo tree rooted
|
141
|
+
// at gumbo_node.
|
142
|
+
static void
|
143
|
+
build_tree(
|
144
|
+
xmlDocPtr doc,
|
145
|
+
xmlNodePtr xml_output_node,
|
146
|
+
const GumboNode *gumbo_node
|
147
|
+
)
|
148
|
+
{
|
149
|
+
xmlNodePtr xml_root = NULL;
|
150
|
+
xmlNodePtr xml_node = xml_output_node;
|
151
|
+
size_t child_index = 0;
|
152
|
+
|
153
|
+
while (true) {
|
154
|
+
assert(gumbo_node != NULL);
|
155
|
+
const GumboVector *children = gumbo_node->type == GUMBO_NODE_DOCUMENT ?
|
156
|
+
&gumbo_node->v.document.children : &gumbo_node->v.element.children;
|
157
|
+
if (child_index >= children->length) {
|
158
|
+
// Move up the tree and to the next child.
|
159
|
+
if (xml_node == xml_output_node) {
|
160
|
+
// We've built as much of the tree as we can.
|
161
|
+
return;
|
162
|
+
}
|
163
|
+
child_index = gumbo_node->index_within_parent + 1;
|
164
|
+
gumbo_node = gumbo_node->parent;
|
165
|
+
xml_node = get_parent(xml_node);
|
166
|
+
// Children of fragments don't share the same root, so reset it and
|
167
|
+
// it'll be set below. In the non-fragment case, this will only happen
|
168
|
+
// after the html element has been finished at which point there are no
|
169
|
+
// further elements.
|
170
|
+
if (xml_node == xml_output_node) {
|
171
|
+
xml_root = NULL;
|
172
|
+
}
|
173
|
+
continue;
|
174
|
+
}
|
175
|
+
const GumboNode *gumbo_child = children->data[child_index++];
|
176
|
+
xmlNodePtr xml_child;
|
177
|
+
|
178
|
+
switch (gumbo_child->type) {
|
179
|
+
case GUMBO_NODE_DOCUMENT:
|
180
|
+
abort(); // Bug in Gumbo.
|
181
|
+
|
182
|
+
case GUMBO_NODE_TEXT:
|
183
|
+
case GUMBO_NODE_WHITESPACE:
|
184
|
+
xml_child = xmlNewDocText(doc, BAD_CAST gumbo_child->v.text.text);
|
185
|
+
set_line(xml_child, gumbo_child->v.text.start_pos.line);
|
186
|
+
xmlAddChild(xml_node, xml_child);
|
187
|
+
break;
|
188
|
+
|
189
|
+
case GUMBO_NODE_CDATA:
|
190
|
+
xml_child = xmlNewCDataBlock(doc, BAD_CAST gumbo_child->v.text.text,
|
191
|
+
(int) strlen(gumbo_child->v.text.text));
|
192
|
+
set_line(xml_child, gumbo_child->v.text.start_pos.line);
|
193
|
+
xmlAddChild(xml_node, xml_child);
|
194
|
+
break;
|
195
|
+
|
196
|
+
case GUMBO_NODE_COMMENT:
|
197
|
+
xml_child = xmlNewDocComment(doc, BAD_CAST gumbo_child->v.text.text);
|
198
|
+
set_line(xml_child, gumbo_child->v.text.start_pos.line);
|
199
|
+
xmlAddChild(xml_node, xml_child);
|
200
|
+
break;
|
201
|
+
|
202
|
+
case GUMBO_NODE_TEMPLATE:
|
203
|
+
// XXX: Should create a template element and a new DocumentFragment
|
204
|
+
case GUMBO_NODE_ELEMENT: {
|
205
|
+
xml_child = xmlNewDocNode(doc, NULL, BAD_CAST gumbo_child->v.element.name, NULL);
|
206
|
+
set_line(xml_child, gumbo_child->v.element.start_pos.line);
|
207
|
+
if (xml_root == NULL) {
|
208
|
+
xml_root = xml_child;
|
209
|
+
}
|
210
|
+
xmlNsPtr ns = NULL;
|
211
|
+
switch (gumbo_child->v.element.tag_namespace) {
|
212
|
+
case GUMBO_NAMESPACE_HTML:
|
213
|
+
break;
|
214
|
+
case GUMBO_NAMESPACE_SVG:
|
215
|
+
ns = lookup_or_add_ns(doc, xml_root, "http://www.w3.org/2000/svg", "svg");
|
216
|
+
break;
|
217
|
+
case GUMBO_NAMESPACE_MATHML:
|
218
|
+
ns = lookup_or_add_ns(doc, xml_root, "http://www.w3.org/1998/Math/MathML", "math");
|
219
|
+
break;
|
220
|
+
}
|
221
|
+
if (ns != NULL) {
|
222
|
+
xmlSetNs(xml_child, ns);
|
223
|
+
}
|
224
|
+
xmlAddChild(xml_node, xml_child);
|
225
|
+
|
226
|
+
// Add the attributes.
|
227
|
+
const GumboVector *attrs = &gumbo_child->v.element.attributes;
|
228
|
+
for (size_t i = 0; i < attrs->length; i++) {
|
229
|
+
const GumboAttribute *attr = attrs->data[i];
|
230
|
+
|
231
|
+
switch (attr->attr_namespace) {
|
232
|
+
case GUMBO_ATTR_NAMESPACE_XLINK:
|
233
|
+
ns = lookup_or_add_ns(doc, xml_root, "http://www.w3.org/1999/xlink", "xlink");
|
234
|
+
break;
|
235
|
+
|
236
|
+
case GUMBO_ATTR_NAMESPACE_XML:
|
237
|
+
ns = lookup_or_add_ns(doc, xml_root, "http://www.w3.org/XML/1998/namespace", "xml");
|
238
|
+
break;
|
239
|
+
|
240
|
+
case GUMBO_ATTR_NAMESPACE_XMLNS:
|
241
|
+
ns = lookup_or_add_ns(doc, xml_root, "http://www.w3.org/2000/xmlns/", "xmlns");
|
242
|
+
break;
|
243
|
+
|
244
|
+
default:
|
245
|
+
ns = NULL;
|
246
|
+
}
|
247
|
+
xmlNewNsProp(xml_child, ns, BAD_CAST attr->name, BAD_CAST attr->value);
|
248
|
+
}
|
249
|
+
|
250
|
+
// Add children for this element.
|
251
|
+
child_index = 0;
|
252
|
+
gumbo_node = gumbo_child;
|
253
|
+
xml_node = xml_child;
|
254
|
+
}
|
255
|
+
}
|
256
|
+
}
|
257
|
+
}
|
258
|
+
|
259
|
+
static void
|
260
|
+
add_errors(const GumboOutput *output, VALUE rdoc, VALUE input, VALUE url)
|
261
|
+
{
|
262
|
+
const char *input_str = RSTRING_PTR(input);
|
263
|
+
size_t input_len = RSTRING_LEN(input);
|
264
|
+
|
265
|
+
// Add parse errors to rdoc.
|
266
|
+
if (output->errors.length) {
|
267
|
+
const GumboVector *errors = &output->errors;
|
268
|
+
VALUE rerrors = rb_ary_new2(errors->length);
|
269
|
+
|
270
|
+
for (size_t i = 0; i < errors->length; i++) {
|
271
|
+
GumboError *err = errors->data[i];
|
272
|
+
GumboSourcePosition position = gumbo_error_position(err);
|
273
|
+
char *msg;
|
274
|
+
size_t size = gumbo_caret_diagnostic_to_string(err, input_str, input_len, &msg);
|
275
|
+
VALUE err_str = rb_utf8_str_new(msg, size);
|
276
|
+
free(msg);
|
277
|
+
VALUE syntax_error = rb_class_new_instance(1, &err_str, cNokogiriXmlSyntaxError);
|
278
|
+
const char *error_code = gumbo_error_code(err);
|
279
|
+
VALUE str1 = error_code ? rb_utf8_str_new_static(error_code, strlen(error_code)) : Qnil;
|
280
|
+
rb_iv_set(syntax_error, "@domain", INT2NUM(1)); // XML_FROM_PARSER
|
281
|
+
rb_iv_set(syntax_error, "@code", INT2NUM(1)); // XML_ERR_INTERNAL_ERROR
|
282
|
+
rb_iv_set(syntax_error, "@level", INT2NUM(2)); // XML_ERR_ERROR
|
283
|
+
rb_iv_set(syntax_error, "@file", url);
|
284
|
+
rb_iv_set(syntax_error, "@line", INT2NUM(position.line));
|
285
|
+
rb_iv_set(syntax_error, "@str1", str1);
|
286
|
+
rb_iv_set(syntax_error, "@str2", Qnil);
|
287
|
+
rb_iv_set(syntax_error, "@str3", Qnil);
|
288
|
+
rb_iv_set(syntax_error, "@int1", INT2NUM(0));
|
289
|
+
rb_iv_set(syntax_error, "@column", INT2NUM(position.column));
|
290
|
+
rb_ary_push(rerrors, syntax_error);
|
291
|
+
}
|
292
|
+
rb_iv_set(rdoc, "@errors", rerrors);
|
293
|
+
}
|
294
|
+
}
|
295
|
+
|
296
|
+
typedef struct {
|
297
|
+
GumboOutput *output;
|
298
|
+
VALUE input;
|
299
|
+
VALUE url_or_frag;
|
300
|
+
xmlDocPtr doc;
|
301
|
+
} ParseArgs;
|
302
|
+
|
303
|
+
static void
|
304
|
+
parse_args_mark(void *parse_args)
|
305
|
+
{
|
306
|
+
ParseArgs *args = parse_args;
|
307
|
+
rb_gc_mark_maybe(args->input);
|
308
|
+
rb_gc_mark_maybe(args->url_or_frag);
|
309
|
+
}
|
310
|
+
|
311
|
+
// Wrap a ParseArgs pointer. The underlying ParseArgs must outlive the
|
312
|
+
// wrapper.
|
313
|
+
static VALUE
|
314
|
+
wrap_parse_args(ParseArgs *args)
|
315
|
+
{
|
316
|
+
return Data_Wrap_Struct(rb_cObject, parse_args_mark, RUBY_NEVER_FREE, args);
|
317
|
+
}
|
318
|
+
|
319
|
+
// Returnsd the underlying ParseArgs wrapped by wrap_parse_args.
|
320
|
+
static ParseArgs *
|
321
|
+
unwrap_parse_args(VALUE obj)
|
322
|
+
{
|
323
|
+
ParseArgs *args;
|
324
|
+
Data_Get_Struct(obj, ParseArgs, args);
|
325
|
+
return args;
|
326
|
+
}
|
327
|
+
|
328
|
+
static VALUE
|
329
|
+
parse_cleanup(VALUE parse_args)
|
330
|
+
{
|
331
|
+
ParseArgs *args = unwrap_parse_args(parse_args);
|
332
|
+
gumbo_destroy_output(args->output);
|
333
|
+
// Make sure garbage collection doesn't mark the objects as being live based
|
334
|
+
// on references from the ParseArgs. This may be unnecessary.
|
335
|
+
args->input = Qnil;
|
336
|
+
args->url_or_frag = Qnil;
|
337
|
+
if (args->doc != NULL) {
|
338
|
+
xmlFreeDoc(args->doc);
|
339
|
+
}
|
340
|
+
return Qnil;
|
341
|
+
}
|
342
|
+
|
343
|
+
static VALUE parse_continue(VALUE parse_args);
|
344
|
+
|
345
|
+
/*
|
346
|
+
* @!visibility protected
|
347
|
+
*/
|
348
|
+
static VALUE
|
349
|
+
parse(VALUE self, VALUE input, VALUE url, VALUE max_attributes, VALUE max_errors, VALUE max_depth)
|
350
|
+
{
|
351
|
+
GumboOptions options = kGumboDefaultOptions;
|
352
|
+
options.max_attributes = NUM2INT(max_attributes);
|
353
|
+
options.max_errors = NUM2INT(max_errors);
|
354
|
+
options.max_tree_depth = NUM2INT(max_depth);
|
355
|
+
|
356
|
+
GumboOutput *output = perform_parse(&options, input);
|
357
|
+
ParseArgs args = {
|
358
|
+
.output = output,
|
359
|
+
.input = input,
|
360
|
+
.url_or_frag = url,
|
361
|
+
.doc = NULL,
|
362
|
+
};
|
363
|
+
VALUE parse_args = wrap_parse_args(&args);
|
364
|
+
|
365
|
+
return rb_ensure(parse_continue, parse_args, parse_cleanup, parse_args);
|
366
|
+
}
|
367
|
+
|
368
|
+
static VALUE
|
369
|
+
parse_continue(VALUE parse_args)
|
370
|
+
{
|
371
|
+
ParseArgs *args = unwrap_parse_args(parse_args);
|
372
|
+
GumboOutput *output = args->output;
|
373
|
+
xmlDocPtr doc;
|
374
|
+
if (output->document->v.document.has_doctype) {
|
375
|
+
const char *name = output->document->v.document.name;
|
376
|
+
const char *public = output->document->v.document.public_identifier;
|
377
|
+
const char *system = output->document->v.document.system_identifier;
|
378
|
+
public = public[0] ? public : NULL;
|
379
|
+
system = system[0] ? system : NULL;
|
380
|
+
doc = new_html_doc(name, system, public);
|
381
|
+
} else {
|
382
|
+
doc = new_html_doc(NULL, NULL, NULL);
|
383
|
+
}
|
384
|
+
args->doc = doc; // Make sure doc gets cleaned up if an error is thrown.
|
385
|
+
build_tree(doc, (xmlNodePtr)doc, output->document);
|
386
|
+
VALUE rdoc = Nokogiri_wrap_xml_document(cNokogiriHtml5Document, doc);
|
387
|
+
args->doc = NULL; // The Ruby runtime now owns doc so don't delete it.
|
388
|
+
add_errors(output, rdoc, args->input, args->url_or_frag);
|
389
|
+
return rdoc;
|
390
|
+
}
|
391
|
+
|
392
|
+
static int
|
393
|
+
lookup_namespace(VALUE node, bool require_known_ns)
|
394
|
+
{
|
395
|
+
ID namespace, href;
|
396
|
+
CONST_ID(namespace, "namespace");
|
397
|
+
CONST_ID(href, "href");
|
398
|
+
VALUE ns = rb_funcall(node, namespace, 0);
|
399
|
+
|
400
|
+
if (NIL_P(ns)) {
|
401
|
+
return GUMBO_NAMESPACE_HTML;
|
402
|
+
}
|
403
|
+
ns = rb_funcall(ns, href, 0);
|
404
|
+
assert(RTEST(ns));
|
405
|
+
Check_Type(ns, T_STRING);
|
406
|
+
|
407
|
+
const char *href_ptr = RSTRING_PTR(ns);
|
408
|
+
size_t href_len = RSTRING_LEN(ns);
|
409
|
+
#define NAMESPACE_P(uri) (href_len == sizeof uri - 1 && !memcmp(href_ptr, uri, href_len))
|
410
|
+
if (NAMESPACE_P("http://www.w3.org/1999/xhtml")) {
|
411
|
+
return GUMBO_NAMESPACE_HTML;
|
412
|
+
}
|
413
|
+
if (NAMESPACE_P("http://www.w3.org/1998/Math/MathML")) {
|
414
|
+
return GUMBO_NAMESPACE_MATHML;
|
415
|
+
}
|
416
|
+
if (NAMESPACE_P("http://www.w3.org/2000/svg")) {
|
417
|
+
return GUMBO_NAMESPACE_SVG;
|
418
|
+
}
|
419
|
+
#undef NAMESPACE_P
|
420
|
+
if (require_known_ns) {
|
421
|
+
rb_raise(rb_eArgError, "Unexpected namespace URI \"%*s\"", (int)href_len, href_ptr);
|
422
|
+
}
|
423
|
+
return -1;
|
424
|
+
}
|
425
|
+
|
426
|
+
static xmlNodePtr
|
427
|
+
extract_xml_node(VALUE node)
|
428
|
+
{
|
429
|
+
xmlNodePtr xml_node;
|
430
|
+
Data_Get_Struct(node, xmlNode, xml_node);
|
431
|
+
return xml_node;
|
432
|
+
}
|
433
|
+
|
434
|
+
static VALUE fragment_continue(VALUE parse_args);
|
435
|
+
|
436
|
+
/*
|
437
|
+
* @!visibility protected
|
438
|
+
*/
|
439
|
+
static VALUE
|
440
|
+
fragment(
|
441
|
+
VALUE self,
|
442
|
+
VALUE doc_fragment,
|
443
|
+
VALUE tags,
|
444
|
+
VALUE ctx,
|
445
|
+
VALUE max_attributes,
|
446
|
+
VALUE max_errors,
|
447
|
+
VALUE max_depth
|
448
|
+
)
|
449
|
+
{
|
450
|
+
ID name = rb_intern_const("name");
|
451
|
+
const char *ctx_tag;
|
452
|
+
GumboNamespaceEnum ctx_ns;
|
453
|
+
GumboQuirksModeEnum quirks_mode;
|
454
|
+
bool form = false;
|
455
|
+
const char *encoding = NULL;
|
456
|
+
|
457
|
+
if (NIL_P(ctx)) {
|
458
|
+
ctx_tag = "body";
|
459
|
+
ctx_ns = GUMBO_NAMESPACE_HTML;
|
460
|
+
} else if (TYPE(ctx) == T_STRING) {
|
461
|
+
ctx_tag = StringValueCStr(ctx);
|
462
|
+
ctx_ns = GUMBO_NAMESPACE_HTML;
|
463
|
+
size_t len = RSTRING_LEN(ctx);
|
464
|
+
const char *colon = memchr(ctx_tag, ':', len);
|
465
|
+
if (colon) {
|
466
|
+
switch (colon - ctx_tag) {
|
467
|
+
case 3:
|
468
|
+
if (st_strncasecmp(ctx_tag, "svg", 3) != 0) {
|
469
|
+
goto error;
|
470
|
+
}
|
471
|
+
ctx_ns = GUMBO_NAMESPACE_SVG;
|
472
|
+
break;
|
473
|
+
case 4:
|
474
|
+
if (st_strncasecmp(ctx_tag, "html", 4) == 0) {
|
475
|
+
ctx_ns = GUMBO_NAMESPACE_HTML;
|
476
|
+
} else if (st_strncasecmp(ctx_tag, "math", 4) == 0) {
|
477
|
+
ctx_ns = GUMBO_NAMESPACE_MATHML;
|
478
|
+
} else {
|
479
|
+
goto error;
|
480
|
+
}
|
481
|
+
break;
|
482
|
+
default:
|
483
|
+
error:
|
484
|
+
rb_raise(rb_eArgError, "Invalid context namespace '%*s'", (int)(colon - ctx_tag), ctx_tag);
|
485
|
+
}
|
486
|
+
ctx_tag = colon + 1;
|
487
|
+
} else {
|
488
|
+
// For convenience, put 'svg' and 'math' in their namespaces.
|
489
|
+
if (len == 3 && st_strncasecmp(ctx_tag, "svg", 3) == 0) {
|
490
|
+
ctx_ns = GUMBO_NAMESPACE_SVG;
|
491
|
+
} else if (len == 4 && st_strncasecmp(ctx_tag, "math", 4) == 0) {
|
492
|
+
ctx_ns = GUMBO_NAMESPACE_MATHML;
|
493
|
+
}
|
494
|
+
}
|
495
|
+
|
496
|
+
// Check if it's a form.
|
497
|
+
form = ctx_ns == GUMBO_NAMESPACE_HTML && st_strcasecmp(ctx_tag, "form") == 0;
|
498
|
+
} else {
|
499
|
+
ID element_ = rb_intern_const("element?");
|
500
|
+
|
501
|
+
// Context fragment name.
|
502
|
+
VALUE tag_name = rb_funcall(ctx, name, 0);
|
503
|
+
assert(RTEST(tag_name));
|
504
|
+
Check_Type(tag_name, T_STRING);
|
505
|
+
ctx_tag = StringValueCStr(tag_name);
|
506
|
+
|
507
|
+
// Context fragment namespace.
|
508
|
+
ctx_ns = lookup_namespace(ctx, true);
|
509
|
+
|
510
|
+
// Check for a form ancestor, including self.
|
511
|
+
for (VALUE node = ctx;
|
512
|
+
!NIL_P(node);
|
513
|
+
node = rb_respond_to(node, parent) ? rb_funcall(node, parent, 0) : Qnil) {
|
514
|
+
if (!RTEST(rb_funcall(node, element_, 0))) {
|
515
|
+
continue;
|
516
|
+
}
|
517
|
+
VALUE element_name = rb_funcall(node, name, 0);
|
518
|
+
if (RSTRING_LEN(element_name) == 4
|
519
|
+
&& !st_strcasecmp(RSTRING_PTR(element_name), "form")
|
520
|
+
&& lookup_namespace(node, false) == GUMBO_NAMESPACE_HTML) {
|
521
|
+
form = true;
|
522
|
+
break;
|
523
|
+
}
|
524
|
+
}
|
525
|
+
|
526
|
+
// Encoding.
|
527
|
+
if (RSTRING_LEN(tag_name) == 14
|
528
|
+
&& !st_strcasecmp(ctx_tag, "annotation-xml")) {
|
529
|
+
VALUE enc = rb_funcall(ctx, rb_intern_const("[]"),
|
530
|
+
rb_utf8_str_new_static("encoding", 8));
|
531
|
+
if (RTEST(enc)) {
|
532
|
+
Check_Type(enc, T_STRING);
|
533
|
+
encoding = StringValueCStr(enc);
|
534
|
+
}
|
535
|
+
}
|
536
|
+
}
|
537
|
+
|
538
|
+
// Quirks mode.
|
539
|
+
VALUE doc = rb_funcall(doc_fragment, rb_intern_const("document"), 0);
|
540
|
+
VALUE dtd = rb_funcall(doc, internal_subset, 0);
|
541
|
+
if (NIL_P(dtd)) {
|
542
|
+
quirks_mode = GUMBO_DOCTYPE_NO_QUIRKS;
|
543
|
+
} else {
|
544
|
+
VALUE dtd_name = rb_funcall(dtd, name, 0);
|
545
|
+
VALUE pubid = rb_funcall(dtd, rb_intern_const("external_id"), 0);
|
546
|
+
VALUE sysid = rb_funcall(dtd, rb_intern_const("system_id"), 0);
|
547
|
+
quirks_mode = gumbo_compute_quirks_mode(
|
548
|
+
NIL_P(dtd_name) ? NULL : StringValueCStr(dtd_name),
|
549
|
+
NIL_P(pubid) ? NULL : StringValueCStr(pubid),
|
550
|
+
NIL_P(sysid) ? NULL : StringValueCStr(sysid)
|
551
|
+
);
|
552
|
+
}
|
553
|
+
|
554
|
+
// Perform a fragment parse.
|
555
|
+
int depth = NUM2INT(max_depth);
|
556
|
+
GumboOptions options = kGumboDefaultOptions;
|
557
|
+
options.max_attributes = NUM2INT(max_attributes);
|
558
|
+
options.max_errors = NUM2INT(max_errors);
|
559
|
+
// Add one to account for the HTML element.
|
560
|
+
options.max_tree_depth = depth < 0 ? -1 : (depth + 1);
|
561
|
+
options.fragment_context = ctx_tag;
|
562
|
+
options.fragment_namespace = ctx_ns;
|
563
|
+
options.fragment_encoding = encoding;
|
564
|
+
options.quirks_mode = quirks_mode;
|
565
|
+
options.fragment_context_has_form_ancestor = form;
|
566
|
+
|
567
|
+
GumboOutput *output = perform_parse(&options, tags);
|
568
|
+
ParseArgs args = {
|
569
|
+
.output = output,
|
570
|
+
.input = tags,
|
571
|
+
.url_or_frag = doc_fragment,
|
572
|
+
.doc = (xmlDocPtr)extract_xml_node(doc),
|
573
|
+
};
|
574
|
+
VALUE parse_args = wrap_parse_args(&args);
|
575
|
+
rb_ensure(fragment_continue, parse_args, parse_cleanup, parse_args);
|
576
|
+
return Qnil;
|
577
|
+
}
|
578
|
+
|
579
|
+
static VALUE
|
580
|
+
fragment_continue(VALUE parse_args)
|
581
|
+
{
|
582
|
+
ParseArgs *args = unwrap_parse_args(parse_args);
|
583
|
+
GumboOutput *output = args->output;
|
584
|
+
VALUE doc_fragment = args->url_or_frag;
|
585
|
+
xmlDocPtr xml_doc = args->doc;
|
586
|
+
|
587
|
+
args->doc = NULL; // The Ruby runtime owns doc so make sure we don't delete it.
|
588
|
+
xmlNodePtr xml_frag = extract_xml_node(doc_fragment);
|
589
|
+
build_tree(xml_doc, xml_frag, output->root);
|
590
|
+
add_errors(output, doc_fragment, args->input, rb_utf8_str_new_static("#fragment", 9));
|
591
|
+
return Qnil;
|
592
|
+
}
|
593
|
+
|
594
|
+
// Initialize the Nokogumbo class and fetch constants we will use later.
|
595
|
+
void
|
596
|
+
noko_init_gumbo()
|
597
|
+
{
|
598
|
+
// Class constants.
|
599
|
+
cNokogiriHtml5Document = rb_define_class_under(mNokogiriHtml5, "Document", cNokogiriHtml4Document);
|
600
|
+
rb_gc_register_mark_object(cNokogiriHtml5Document);
|
601
|
+
|
602
|
+
// Interned symbols.
|
603
|
+
internal_subset = rb_intern_const("internal_subset");
|
604
|
+
parent = rb_intern_const("parent");
|
605
|
+
|
606
|
+
// Define Nokogumbo module with parse and fragment methods.
|
607
|
+
rb_define_singleton_method(mNokogiriGumbo, "parse", parse, 5);
|
608
|
+
rb_define_singleton_method(mNokogiriGumbo, "fragment", fragment, 6);
|
609
|
+
}
|
610
|
+
|
611
|
+
// vim: set shiftwidth=2 softtabstop=2 tabstop=8 expandtab:
|