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.

Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE-DEPENDENCIES.md +243 -22
  3. data/LICENSE.md +1 -1
  4. data/README.md +6 -5
  5. data/ext/nokogiri/depend +35 -34
  6. data/ext/nokogiri/extconf.rb +181 -103
  7. data/ext/nokogiri/gumbo.c +611 -0
  8. data/ext/nokogiri/{html_document.c → html4_document.c} +8 -8
  9. data/ext/nokogiri/{html_element_description.c → html4_element_description.c} +20 -18
  10. data/ext/nokogiri/{html_entity_lookup.c → html4_entity_lookup.c} +7 -7
  11. data/ext/nokogiri/{html_sax_parser_context.c → html4_sax_parser_context.c} +5 -5
  12. data/ext/nokogiri/{html_sax_push_parser.c → html4_sax_push_parser.c} +4 -4
  13. data/ext/nokogiri/libxml2_backwards_compat.c +30 -30
  14. data/ext/nokogiri/nokogiri.c +51 -38
  15. data/ext/nokogiri/nokogiri.h +16 -9
  16. data/ext/nokogiri/xml_document.c +13 -13
  17. data/ext/nokogiri/xml_element_content.c +2 -0
  18. data/ext/nokogiri/xml_encoding_handler.c +11 -6
  19. data/ext/nokogiri/xml_namespace.c +2 -0
  20. data/ext/nokogiri/xml_node.c +102 -102
  21. data/ext/nokogiri/xml_node_set.c +20 -20
  22. data/ext/nokogiri/xml_reader.c +2 -0
  23. data/ext/nokogiri/xml_sax_parser.c +6 -6
  24. data/ext/nokogiri/xml_sax_parser_context.c +2 -0
  25. data/ext/nokogiri/xml_schema.c +2 -0
  26. data/ext/nokogiri/xml_xpath_context.c +67 -65
  27. data/ext/nokogiri/xslt_stylesheet.c +2 -1
  28. data/gumbo-parser/CHANGES.md +63 -0
  29. data/gumbo-parser/Makefile +101 -0
  30. data/gumbo-parser/THANKS +27 -0
  31. data/gumbo-parser/src/Makefile +17 -0
  32. data/gumbo-parser/src/README.md +41 -0
  33. data/gumbo-parser/src/ascii.c +75 -0
  34. data/gumbo-parser/src/ascii.h +115 -0
  35. data/gumbo-parser/src/attribute.c +42 -0
  36. data/gumbo-parser/src/attribute.h +17 -0
  37. data/gumbo-parser/src/char_ref.c +22225 -0
  38. data/gumbo-parser/src/char_ref.h +29 -0
  39. data/gumbo-parser/src/char_ref.rl +2154 -0
  40. data/gumbo-parser/src/error.c +626 -0
  41. data/gumbo-parser/src/error.h +148 -0
  42. data/gumbo-parser/src/foreign_attrs.c +104 -0
  43. data/gumbo-parser/src/foreign_attrs.gperf +27 -0
  44. data/gumbo-parser/src/gumbo.h +943 -0
  45. data/gumbo-parser/src/insertion_mode.h +33 -0
  46. data/gumbo-parser/src/macros.h +91 -0
  47. data/gumbo-parser/src/parser.c +4886 -0
  48. data/gumbo-parser/src/parser.h +41 -0
  49. data/gumbo-parser/src/replacement.h +33 -0
  50. data/gumbo-parser/src/string_buffer.c +103 -0
  51. data/gumbo-parser/src/string_buffer.h +68 -0
  52. data/gumbo-parser/src/string_piece.c +48 -0
  53. data/gumbo-parser/src/svg_attrs.c +174 -0
  54. data/gumbo-parser/src/svg_attrs.gperf +77 -0
  55. data/gumbo-parser/src/svg_tags.c +137 -0
  56. data/gumbo-parser/src/svg_tags.gperf +55 -0
  57. data/gumbo-parser/src/tag.c +222 -0
  58. data/gumbo-parser/src/tag_lookup.c +382 -0
  59. data/gumbo-parser/src/tag_lookup.gperf +169 -0
  60. data/gumbo-parser/src/tag_lookup.h +13 -0
  61. data/gumbo-parser/src/token_buffer.c +79 -0
  62. data/gumbo-parser/src/token_buffer.h +71 -0
  63. data/gumbo-parser/src/token_type.h +17 -0
  64. data/gumbo-parser/src/tokenizer.c +3463 -0
  65. data/gumbo-parser/src/tokenizer.h +112 -0
  66. data/gumbo-parser/src/tokenizer_states.h +339 -0
  67. data/gumbo-parser/src/utf8.c +245 -0
  68. data/gumbo-parser/src/utf8.h +164 -0
  69. data/gumbo-parser/src/util.c +68 -0
  70. data/gumbo-parser/src/util.h +30 -0
  71. data/gumbo-parser/src/vector.c +111 -0
  72. data/gumbo-parser/src/vector.h +45 -0
  73. data/lib/nokogiri.rb +31 -29
  74. data/lib/nokogiri/css.rb +14 -14
  75. data/lib/nokogiri/css/parser.rb +1 -1
  76. data/lib/nokogiri/css/parser.y +1 -1
  77. data/lib/nokogiri/css/syntax_error.rb +1 -1
  78. data/lib/nokogiri/extension.rb +2 -2
  79. data/lib/nokogiri/gumbo.rb +14 -0
  80. data/lib/nokogiri/html.rb +31 -27
  81. data/lib/nokogiri/html4.rb +40 -0
  82. data/lib/nokogiri/{html → html4}/builder.rb +2 -2
  83. data/lib/nokogiri/{html → html4}/document.rb +4 -4
  84. data/lib/nokogiri/{html → html4}/document_fragment.rb +3 -3
  85. data/lib/nokogiri/{html → html4}/element_description.rb +1 -1
  86. data/lib/nokogiri/{html → html4}/element_description_defaults.rb +1 -1
  87. data/lib/nokogiri/{html → html4}/entity_lookup.rb +1 -1
  88. data/lib/nokogiri/{html → html4}/sax/parser.rb +11 -14
  89. data/lib/nokogiri/html4/sax/parser_context.rb +19 -0
  90. data/lib/nokogiri/{html → html4}/sax/push_parser.rb +5 -5
  91. data/lib/nokogiri/html5.rb +473 -0
  92. data/lib/nokogiri/html5/document.rb +74 -0
  93. data/lib/nokogiri/html5/document_fragment.rb +80 -0
  94. data/lib/nokogiri/html5/node.rb +93 -0
  95. data/lib/nokogiri/version/constant.rb +1 -1
  96. data/lib/nokogiri/version/info.rb +11 -2
  97. data/lib/nokogiri/xml.rb +35 -36
  98. data/lib/nokogiri/xml/node.rb +6 -5
  99. data/lib/nokogiri/xml/parse_options.rb +2 -0
  100. data/lib/nokogiri/xml/pp.rb +2 -2
  101. data/lib/nokogiri/xml/sax.rb +4 -4
  102. data/lib/nokogiri/xml/sax/document.rb +24 -30
  103. data/lib/nokogiri/xml/xpath.rb +2 -2
  104. data/lib/nokogiri/xslt.rb +16 -16
  105. data/lib/nokogiri/xslt/stylesheet.rb +1 -1
  106. metadata +102 -60
  107. 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: