nokogiri 1.19.4-java → 1.19.5-java

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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE-DEPENDENCIES.md +29 -0
  3. data/ext/java/nokogiri/XmlNode.java +69 -38
  4. data/ext/java/nokogiri/XmlReader.java +4 -1
  5. data/ext/java/nokogiri/XmlSchema.java +5 -74
  6. data/ext/java/nokogiri/XmlXpathContext.java +5 -15
  7. data/ext/java/nokogiri/internals/NokogiriEntityResolver.java +5 -7
  8. data/ext/java/nokogiri/internals/NokogiriHelpers.java +68 -0
  9. data/ext/java/nokogiri/internals/NokogiriXPathContext.java +37 -0
  10. data/ext/nokogiri/extconf.rb +1 -0
  11. data/ext/nokogiri/gumbo.c +83 -6
  12. data/ext/nokogiri/html4_document.c +10 -3
  13. data/ext/nokogiri/libxml2_polyfill.c +12 -0
  14. data/ext/nokogiri/nokogiri.c +84 -2
  15. data/ext/nokogiri/xml_document.c +42 -16
  16. data/ext/nokogiri/xml_node.c +73 -38
  17. data/ext/nokogiri/xml_node_set.c +9 -9
  18. data/ext/nokogiri/xml_reader.c +198 -107
  19. data/ext/nokogiri/xml_sax_parser.c +3 -3
  20. data/ext/nokogiri/xml_sax_parser_context.c +38 -7
  21. data/ext/nokogiri/xml_sax_push_parser.c +12 -0
  22. data/ext/nokogiri/xml_xpath_context.c +124 -26
  23. data/ext/nokogiri/xslt_stylesheet.c +123 -27
  24. data/lib/nokogiri/css/parser.rb +35 -18
  25. data/lib/nokogiri/css/parser.y +35 -17
  26. data/lib/nokogiri/css/xpath_visitor.rb +71 -28
  27. data/lib/nokogiri/html5/document.rb +9 -0
  28. data/lib/nokogiri/html5/document_fragment.rb +16 -1
  29. data/lib/nokogiri/html5/node.rb +1 -1
  30. data/lib/nokogiri/html5.rb +16 -0
  31. data/lib/nokogiri/nokogiri.jar +0 -0
  32. data/lib/nokogiri/version/constant.rb +1 -1
  33. data/lib/nokogiri/xml/attr.rb +2 -0
  34. data/lib/nokogiri/xml/attribute_decl.rb +2 -1
  35. data/lib/nokogiri/xml/builder.rb +24 -32
  36. data/lib/nokogiri/xml/document.rb +1 -0
  37. data/lib/nokogiri/xml/dtd.rb +2 -1
  38. data/lib/nokogiri/xml/element_decl.rb +2 -1
  39. data/lib/nokogiri/xml/entity_decl.rb +2 -1
  40. data/lib/nokogiri/xml/node.rb +8 -6
  41. metadata +3 -2
data/ext/nokogiri/gumbo.c CHANGED
@@ -113,6 +113,71 @@ set_line(xmlNodePtr node, size_t line)
113
113
  }
114
114
  }
115
115
 
116
+ // This function is essentially xmlNewNsProp, but we skip the full list traversal to append by
117
+ // providing the last property in the linked list as a parameter.
118
+ static xmlAttrPtr
119
+ append_property(xmlNodePtr node, xmlNsPtr ns, const xmlChar *name, const xmlChar *value, xmlAttrPtr last_prop)
120
+ {
121
+ xmlAttrPtr cur = (xmlAttrPtr) xmlMalloc(sizeof(xmlAttr));
122
+ if (cur == NULL) {
123
+ return NULL;
124
+ }
125
+ memset(cur, 0, sizeof(xmlAttr));
126
+ cur->type = XML_ATTRIBUTE_NODE;
127
+ cur->parent = node;
128
+ xmlDocPtr doc = node->doc;
129
+ cur->doc = doc;
130
+ cur->ns = ns;
131
+
132
+ if ((doc != NULL) && (doc->dict != NULL)) {
133
+ cur->name = (xmlChar *) xmlDictLookup(doc->dict, name, -1);
134
+ } else {
135
+ cur->name = xmlStrdup(name);
136
+ }
137
+ if (cur->name == NULL) {
138
+ goto error;
139
+ }
140
+
141
+ if (value != NULL) {
142
+ cur->children = xmlNewDocText(doc, value);
143
+ if (cur->children == NULL) {
144
+ goto error;
145
+ }
146
+ cur->last = NULL;
147
+ xmlNodePtr tmp = cur->children;
148
+ while (tmp != NULL) {
149
+ tmp->parent = (xmlNodePtr) cur;
150
+ if (tmp->next == NULL) {
151
+ cur->last = tmp;
152
+ }
153
+ tmp = tmp->next;
154
+ }
155
+
156
+ if (doc != NULL) {
157
+ int res = xmlIsID(doc, node, cur);
158
+ if (res < 0) {
159
+ goto error;
160
+ }
161
+ if ((res == 1) && (xmlAddIDSafe(cur, value) < 0)) {
162
+ goto error;
163
+ }
164
+ }
165
+ }
166
+
167
+ if (node->properties == NULL) {
168
+ node->properties = cur;
169
+ } else {
170
+ last_prop->next = cur;
171
+ cur->prev = last_prop;
172
+ }
173
+
174
+ return cur;
175
+
176
+ error:
177
+ xmlFreeProp(cur);
178
+ return (NULL);
179
+ }
180
+
116
181
  // Construct an XML tree rooted at xml_output_node from the Gumbo tree rooted
117
182
  // at gumbo_node.
118
183
  static void
@@ -200,6 +265,7 @@ build_tree(
200
265
  xmlAddChild(xml_node, xml_child);
201
266
 
202
267
  // Add the attributes.
268
+ xmlAttrPtr last_prop = NULL;
203
269
  const GumboVector *attrs = &gumbo_child->v.element.attributes;
204
270
  for (size_t i = 0; i < attrs->length; i++) {
205
271
  const GumboAttribute *attr = attrs->data[i];
@@ -220,7 +286,9 @@ build_tree(
220
286
  default:
221
287
  ns = NULL;
222
288
  }
223
- xmlNewNsProp(xml_child, ns, (const xmlChar *)attr->name, (const xmlChar *)attr->value);
289
+
290
+ // We micromanage the attribute list for performance reasons.
291
+ last_prop = append_property(xml_child, ns, (const xmlChar *)attr->name, (const xmlChar *)attr->value, last_prop);
224
292
  }
225
293
 
226
294
  // Add children for this element.
@@ -373,12 +441,20 @@ parse_continue(VALUE parse_args)
373
441
  } else {
374
442
  doc = new_html_doc(NULL, NULL, NULL);
375
443
  }
376
- args->doc = doc; // Make sure doc gets cleaned up if an error is thrown.
444
+
445
+ // We're about to build the libxml2 tree structure from the Gumbo tree
446
+ // structure. If an exception is raised during tree building, control passes
447
+ // to `parse_cleanup()` which needs to free the newly allocated `xmlDoc`,
448
+ // `doc`. If the tree is successfully constructed, then
449
+ // `noko_xml_document_wrap()` will create a new Ruby object which will own
450
+ // `doc`. In that case, `parse_cleanup()` should not free `doc`.
451
+ args->doc = doc;
377
452
  build_tree(doc, (xmlNodePtr)doc, output->document);
453
+ args->doc = NULL;
378
454
  VALUE rdoc = noko_xml_document_wrap(args->klass, doc);
455
+
379
456
  rb_iv_set(rdoc, "@url", args->url_or_frag);
380
457
  rb_iv_set(rdoc, "@quirks_mode", INT2NUM(output->document->v.document.doc_type_quirks_mode));
381
- args->doc = NULL; // The Ruby runtime now owns doc so don't delete it.
382
458
  add_errors(output, rdoc, args->input, args->url_or_frag);
383
459
  return rdoc;
384
460
  }
@@ -512,6 +588,7 @@ error:
512
588
  continue;
513
589
  }
514
590
  VALUE element_name = rb_funcall(node, name, 0);
591
+ Check_Type(element_name, T_STRING);
515
592
  if (RSTRING_LEN(element_name) == 4
516
593
  && !st_strcasecmp(RSTRING_PTR(element_name), "form")
517
594
  && lookup_namespace(node, false) == GUMBO_NAMESPACE_HTML) {
@@ -568,7 +645,7 @@ error:
568
645
  .output = output,
569
646
  .input = tags,
570
647
  .url_or_frag = doc_fragment,
571
- .doc = (xmlDocPtr)extract_xml_node(doc),
648
+ .doc = NULL,
572
649
  };
573
650
  rb_ensure(fragment_continue, (VALUE)(&args), parse_cleanup, (VALUE)(&args));
574
651
  return Qnil;
@@ -580,9 +657,9 @@ fragment_continue(VALUE parse_args)
580
657
  ParseArgs *args = (ParseArgs *)parse_args;
581
658
  GumboOutput *output = args->output;
582
659
  VALUE doc_fragment = args->url_or_frag;
583
- xmlDocPtr xml_doc = args->doc;
660
+ VALUE doc = rb_funcall(doc_fragment, rb_intern_const("document"), 0);
661
+ xmlDocPtr xml_doc = (xmlDocPtr)extract_xml_node(doc);
584
662
 
585
- args->doc = NULL; // The Ruby runtime owns doc so make sure we don't delete it.
586
663
  xmlNodePtr xml_frag = extract_xml_node(doc_fragment);
587
664
  build_tree(xml_doc, xml_frag, output->root);
588
665
  rb_iv_set(doc_fragment, "@quirks_mode", INT2NUM(output->document->v.document.doc_type_quirks_mode));
@@ -18,8 +18,8 @@ rb_html_document_s_new(int argc, VALUE *argv, VALUE klass)
18
18
  htmlDocPtr doc;
19
19
 
20
20
  rb_scan_args(argc, argv, "0*", &rest);
21
- uri = rb_ary_entry(rest, (long)0);
22
- external_id = rb_ary_entry(rest, (long)1);
21
+ uri = rb_ary_entry(rest, (rb_len_t)0);
22
+ external_id = rb_ary_entry(rest, (rb_len_t)1);
23
23
 
24
24
  doc = htmlNewDoc(
25
25
  RTEST(uri) ? (const xmlChar *)StringValueCStr(uri) : NULL,
@@ -48,10 +48,17 @@ rb_html_document_s_read_io(VALUE klass, VALUE rb_io, VALUE rb_url, VALUE rb_enco
48
48
 
49
49
  xmlSetStructuredErrorFunc((void *)rb_error_list, noko__error_array_pusher);
50
50
 
51
- c_doc = htmlReadIO(noko_io_read, noko_io_close, (void *)rb_io, c_url, c_encoding, options);
51
+ noko_io_read_reset_exception();
52
+ c_doc = htmlReadIO(noko_io_read_protected, noko_io_close, (void *)rb_io, c_url, c_encoding, options);
53
+ VALUE rb_io_read_exception = noko_io_read_take_exception();
52
54
 
53
55
  xmlSetStructuredErrorFunc(NULL, NULL);
54
56
 
57
+ if (!NIL_P(rb_io_read_exception)) {
58
+ xmlFreeDoc(c_doc);
59
+ noko_io_read_raise_exception(rb_io_read_exception);
60
+ }
61
+
55
62
  /*
56
63
  * If EncodingFound has occurred in EncodingReader, make sure to do
57
64
  * a cleanup and propagate the error.
@@ -112,3 +112,15 @@ xmlSwitchEncodingName(xmlParserCtxtPtr ctxt, const char *encoding)
112
112
  return (xmlSwitchToEncoding(ctxt, handler));
113
113
  }
114
114
  #endif
115
+
116
+ #ifndef HAVE_XMLADDIDSAFE
117
+ int
118
+ xmlAddIDSafe(xmlAttrPtr attr, const xmlChar *value)
119
+ {
120
+ xmlIDPtr id = xmlAddID(NULL, attr->doc, value, attr);
121
+ if (id) {
122
+ return 1;
123
+ }
124
+ return 0;
125
+ }
126
+ #endif
@@ -50,7 +50,7 @@ void noko_init_html4_sax_parser(void);
50
50
  void noko_init_gumbo(void);
51
51
  void noko_init_test_global_handlers(void);
52
52
 
53
- static ID id_read, id_write, id_external_encoding;
53
+ static ID id_read, id_write, id_external_encoding, id_io_read_exception;
54
54
 
55
55
 
56
56
  static VALUE
@@ -92,6 +92,87 @@ noko_io_read(void *io, char *c_buffer, int c_buffer_len)
92
92
  }
93
93
 
94
94
 
95
+ typedef struct {
96
+ void *io;
97
+ char *c_buffer;
98
+ int c_buffer_len;
99
+ int n_bytes_read;
100
+ } noko_io_read_args_t;
101
+
102
+ static VALUE
103
+ noko_io_read_call(VALUE data)
104
+ {
105
+ noko_io_read_args_t *args = (noko_io_read_args_t *)data;
106
+ args->n_bytes_read = noko_io_read(args->io, args->c_buffer, args->c_buffer_len);
107
+ return Qnil;
108
+ }
109
+
110
+
111
+ /*
112
+ * Like noko_io_read, a StandardError raised by IO#read is swallowed, and libxml2 sees a read
113
+ * error. Any other exception (e.g., Interrupt or Timeout::ExitException) must not unwind through
114
+ * libxml2's stack frames, because that skips libxml2's cleanup and leaks memory, and skips the
115
+ * reset of the error handlers. So it is saved in a fiber-local variable, and $! is cleared until
116
+ * it is re-raised.
117
+ *
118
+ * A caller of libxml2 that passes this callback must call noko_io_read_reset_exception before
119
+ * calling libxml2, and noko_io_read_take_exception after libxml2 returns, and must re-raise a
120
+ * non-nil result with noko_io_read_raise_exception.
121
+ */
122
+ int
123
+ noko_io_read_protected(void *io, char *c_buffer, int c_buffer_len)
124
+ {
125
+ noko_io_read_args_t args = { io, c_buffer, c_buffer_len, -1 };
126
+ int state = 0;
127
+
128
+ rb_protect(noko_io_read_call, (VALUE)&args, &state);
129
+
130
+ if (state) {
131
+ VALUE rb_errinfo_value = rb_errinfo();
132
+ if (RB_TYPE_P(rb_errinfo_value, T_OBJECT) && rb_obj_is_kind_of(rb_errinfo_value, rb_eException)) {
133
+ rb_set_errinfo(Qnil);
134
+ } else {
135
+ rb_errinfo_value = Qnil;
136
+ }
137
+ rb_thread_local_aset(rb_thread_current(), id_io_read_exception,
138
+ rb_assoc_new(INT2FIX(state), rb_errinfo_value));
139
+ return -1;
140
+ }
141
+
142
+ return args.n_bytes_read;
143
+ }
144
+
145
+
146
+ void
147
+ noko_io_read_reset_exception(void)
148
+ {
149
+ rb_thread_local_aset(rb_thread_current(), id_io_read_exception, Qnil);
150
+ }
151
+
152
+
153
+ VALUE
154
+ noko_io_read_take_exception(void)
155
+ {
156
+ VALUE rb_thread = rb_thread_current();
157
+ VALUE rb_exception = rb_thread_local_aref(rb_thread, id_io_read_exception);
158
+
159
+ rb_thread_local_aset(rb_thread, id_io_read_exception, Qnil);
160
+ return rb_exception;
161
+ }
162
+
163
+
164
+ void
165
+ noko_io_read_raise_exception(VALUE rb_exception)
166
+ {
167
+ VALUE rb_errinfo_value = rb_ary_entry(rb_exception, 1);
168
+
169
+ if (!NIL_P(rb_errinfo_value)) {
170
+ rb_set_errinfo(rb_errinfo_value);
171
+ }
172
+ rb_jump_tag(FIX2INT(rb_ary_entry(rb_exception, 0)));
173
+ }
174
+
175
+
95
176
  static VALUE
96
177
  noko_io_write_check(VALUE rb_args)
97
178
  {
@@ -122,7 +203,7 @@ noko_io_write(void *io, char *c_buffer, int c_buffer_len)
122
203
  io_encoding = RB_NIL_P(rb_enc) ? rb_ascii8bit_encoding() : rb_to_encoding(rb_enc);
123
204
 
124
205
  rb_args[0] = rb_io;
125
- rb_args[1] = rb_enc_str_new(c_buffer, (long)c_buffer_len, io_encoding);
206
+ rb_args[1] = rb_enc_str_new(c_buffer, (rb_len_t)c_buffer_len, io_encoding);
126
207
 
127
208
  rb_n_bytes_written = rb_rescue(noko_io_write_check, (VALUE)rb_args, noko_io_write_failed, 0);
128
209
  if (rb_n_bytes_written == Qundef) { return -1; }
@@ -292,6 +373,7 @@ Init_nokogiri(void)
292
373
  noko_init_test_global_handlers();
293
374
 
294
375
  id_read = rb_intern("read");
376
+ id_io_read_exception = rb_intern("__nokogiri_io_read_exception");
295
377
  id_write = rb_intern("write");
296
378
  id_external_encoding = rb_intern("external_encoding");
297
379
  }
@@ -384,17 +384,24 @@ noko_xml_document_s_read_io(VALUE rb_class,
384
384
 
385
385
  const char *c_url = NIL_P(rb_url) ? NULL : StringValueCStr(rb_url);
386
386
  const char *c_enc = NIL_P(rb_encoding) ? NULL : StringValueCStr(rb_encoding);
387
+ noko_io_read_reset_exception();
387
388
  xmlDocPtr c_document = xmlReadIO(
388
- (xmlInputReadCallback)noko_io_read,
389
+ (xmlInputReadCallback)noko_io_read_protected,
389
390
  (xmlInputCloseCallback)noko_io_close,
390
391
  (void *)rb_io,
391
392
  c_url,
392
393
  c_enc,
393
394
  (int)NUM2INT(rb_options)
394
395
  );
396
+ VALUE rb_io_read_exception = noko_io_read_take_exception();
395
397
 
396
398
  noko__structured_error_func_restore(&handler_state);
397
399
 
400
+ if (!NIL_P(rb_io_read_exception)) {
401
+ xmlFreeDoc(c_document);
402
+ noko_io_read_raise_exception(rb_io_read_exception);
403
+ }
404
+
398
405
  if (c_document == NULL) {
399
406
  xmlFreeDoc(c_document);
400
407
 
@@ -464,7 +471,7 @@ new (int argc, VALUE *argv, VALUE klass)
464
471
  VALUE version, rest, rb_doc ;
465
472
 
466
473
  rb_scan_args(argc, argv, "0*", &rest);
467
- version = rb_ary_entry(rest, (long)0);
474
+ version = rb_ary_entry(rest, (rb_len_t)0);
468
475
  if (NIL_P(version)) { version = rb_str_new2("1.0"); }
469
476
 
470
477
  doc = xmlNewDoc((xmlChar *)StringValueCStr(version));
@@ -609,7 +616,8 @@ rb_xml_document_canonicalize(int argc, VALUE *argv, VALUE self)
609
616
  VALUE rb_namespaces;
610
617
  VALUE rb_comments_p;
611
618
  int c_mode = 0;
612
- xmlChar **c_namespaces;
619
+ xmlChar **c_namespaces = NULL;
620
+ VALUE rb_namespaces_buffer = 0;
613
621
 
614
622
  xmlDocPtr c_doc;
615
623
  xmlOutputBufferPtr c_obuf;
@@ -629,6 +637,35 @@ rb_xml_document_canonicalize(int argc, VALUE *argv, VALUE self)
629
637
  if (c_mode == XML_C14N_1_0 || c_mode == XML_C14N_1_1) {
630
638
  rb_raise(rb_eRuntimeError, "This canonicalizer does not support this operation");
631
639
  }
640
+
641
+ // Copy every prefix into one temporary buffer before libxml2 sees it. Converting first means a
642
+ // raise from #to_str or a null byte leaks nothing. Copying means GC compaction, which moves the
643
+ // bytes of embedded strings, cannot move a prefix while libxml2 reads it. No allocation may
644
+ // happen between reading a prefix's pointer and copying its bytes.
645
+ rb_len_t ns_len = RARRAY_LEN(rb_namespaces);
646
+ VALUE rb_prefixes = rb_ary_new_capa(ns_len);
647
+ for (rb_len_t j = 0; j < ns_len; j++) {
648
+ VALUE entry = rb_ary_entry(rb_namespaces, j);
649
+ StringValueCStr(entry);
650
+ rb_ary_push(rb_prefixes, entry);
651
+ }
652
+
653
+ size_t size = ((size_t)ns_len + 1) * sizeof(xmlChar *);
654
+ for (rb_len_t j = 0; j < ns_len; j++) {
655
+ size += (size_t)RSTRING_LEN(RARRAY_AREF(rb_prefixes, j)) + 1;
656
+ }
657
+ c_namespaces = ALLOCV(rb_namespaces_buffer, size);
658
+
659
+ xmlChar *c_prefix = (xmlChar *)(c_namespaces + ns_len + 1);
660
+ for (rb_len_t j = 0; j < ns_len; j++) {
661
+ VALUE prefix = RARRAY_AREF(rb_prefixes, j);
662
+ size_t prefix_len = (size_t)RSTRING_LEN(prefix);
663
+ memcpy(c_prefix, RSTRING_PTR(prefix), prefix_len);
664
+ c_prefix[prefix_len] = '\0';
665
+ c_namespaces[j] = c_prefix;
666
+ c_prefix += prefix_len + 1;
667
+ }
668
+ c_namespaces[ns_len] = NULL;
632
669
  }
633
670
 
634
671
  c_doc = noko_xml_document_unwrap(self);
@@ -646,24 +683,13 @@ rb_xml_document_canonicalize(int argc, VALUE *argv, VALUE self)
646
683
  rb_callback = (void *)rb_block_proc();
647
684
  }
648
685
 
649
- if (NIL_P(rb_namespaces)) {
650
- c_namespaces = NULL;
651
- } else {
652
- long ns_len = RARRAY_LEN(rb_namespaces);
653
- c_namespaces = ruby_xcalloc((size_t)ns_len + 1, sizeof(xmlChar *));
654
- for (int j = 0 ; j < ns_len ; j++) {
655
- VALUE entry = rb_ary_entry(rb_namespaces, j);
656
- c_namespaces[j] = (xmlChar *)StringValueCStr(entry);
657
- }
658
- }
659
-
660
686
  int ret = xmlC14NExecute(c_doc, c_callback_wrapper, rb_callback,
661
687
  c_mode,
662
688
  c_namespaces,
663
689
  (int)RTEST(rb_comments_p),
664
690
  c_obuf);
665
691
 
666
- ruby_xfree(c_namespaces);
692
+ ALLOCV_END(rb_namespaces_buffer);
667
693
  xmlOutputBufferClose(c_obuf);
668
694
 
669
695
  if (ret < 0) {
@@ -733,7 +759,7 @@ noko_xml_document_has_wrapped_blank_nodes_p(xmlDocPtr c_document)
733
759
  return 0;
734
760
  }
735
761
 
736
- for (long jnode = 0; jnode < RARRAY_LEN(cache); jnode++) {
762
+ for (rb_len_t jnode = 0; jnode < RARRAY_LEN(cache); jnode++) {
737
763
  xmlNodePtr node;
738
764
  VALUE element = rb_ary_entry(cache, jnode);
739
765
 
@@ -115,14 +115,10 @@ relink_namespace(xmlNodePtr reparented)
115
115
  xmlNsPtr prev = NULL;
116
116
 
117
117
  while (curr) {
118
- xmlNsPtr ns = xmlSearchNsByHref(
119
- reparented->doc,
120
- reparented->parent,
121
- curr->href
122
- );
123
- /* If we find the namespace is already declared, remove it from this
124
- * definition list. */
125
- if (ns && ns != curr && xmlStrEqual(ns->prefix, curr->prefix)) {
118
+ xmlNsPtr ns = xmlSearchNs(reparented->doc, reparented->parent, curr->prefix);
119
+ /* If the parent already binds this prefix to the same href, remove it
120
+ * from this definition list. */
121
+ if (ns && ns != curr && xmlStrEqual(ns->href, curr->href)) {
126
122
  if (prev) {
127
123
  prev->next = curr->next;
128
124
  } else {
@@ -1232,6 +1228,29 @@ namespaced_key_eh(VALUE self, VALUE attribute, VALUE namespace)
1232
1228
  return Qfalse;
1233
1229
  }
1234
1230
 
1231
+ /* If a matching attribute node already exists, then xmlSetNsProp will destroy
1232
+ * the existing node's children. However, if Nokogiri has a node object
1233
+ * pointing to one of those children, we are left with a broken reference.
1234
+ *
1235
+ * We can avoid this by unlinking these nodes first.
1236
+ */
1237
+ static void
1238
+ unlink_wrapped_attr_children(xmlAttrPtr attr)
1239
+ {
1240
+ xmlNodePtr cur, next;
1241
+
1242
+ if (!attr || attr->type != XML_ATTRIBUTE_NODE) {
1243
+ return;
1244
+ }
1245
+ for (cur = attr->children; cur; cur = next) {
1246
+ next = cur->next;
1247
+ if (cur->_private) {
1248
+ noko_xml_document_pin_node(cur);
1249
+ xmlUnlinkNode(cur);
1250
+ }
1251
+ }
1252
+ }
1253
+
1235
1254
  /*
1236
1255
  * call-seq:
1237
1256
  * []=(property, value)
@@ -1241,31 +1260,36 @@ namespaced_key_eh(VALUE self, VALUE attribute, VALUE namespace)
1241
1260
  static VALUE
1242
1261
  set(VALUE self, VALUE property, VALUE value)
1243
1262
  {
1244
- xmlNodePtr node, cur;
1245
- xmlAttrPtr prop;
1263
+ xmlNodePtr node;
1264
+ xmlNsPtr ns = NULL;
1265
+ const xmlChar *name, *local_name, *c_value;
1266
+ xmlChar *prefix;
1267
+ int prefix_len;
1246
1268
  Noko_Node_Get_Struct(self, xmlNode, node);
1247
1269
 
1248
- /* If a matching attribute node already exists, then xmlSetProp will destroy
1249
- * the existing node's children. However, if Nokogiri has a node object
1250
- * pointing to one of those children, we are left with a broken reference.
1251
- *
1252
- * We can avoid this by unlinking these nodes first.
1253
- */
1254
1270
  if (node->type != XML_ELEMENT_NODE) {
1255
1271
  return (Qnil);
1256
1272
  }
1257
- prop = xmlHasProp(node, (xmlChar *)StringValueCStr(property));
1258
- if (prop && prop->children) {
1259
- for (cur = prop->children; cur; cur = cur->next) {
1260
- if (cur->_private) {
1261
- noko_xml_document_pin_node(cur);
1262
- xmlUnlinkNode(cur);
1263
- }
1273
+ name = (const xmlChar *)StringValueCStr(property);
1274
+ c_value = (const xmlChar *)StringValueCStr(value);
1275
+
1276
+ /* Resolve a QName the way xmlSetProp does, so that we find the same attribute it would replace. */
1277
+ local_name = xmlSplitQName3(name, &prefix_len);
1278
+ if (local_name) {
1279
+ prefix = xmlStrndup(name, prefix_len);
1280
+ if (!prefix) {
1281
+ rb_memerror();
1264
1282
  }
1283
+ ns = xmlSearchNs(node->doc, node, prefix);
1284
+ xmlFree(prefix);
1285
+ }
1286
+ if (!ns) {
1287
+ local_name = name;
1265
1288
  }
1266
1289
 
1267
- xmlSetProp(node, (xmlChar *)StringValueCStr(property),
1268
- (xmlChar *)StringValueCStr(value));
1290
+ unlink_wrapped_attr_children(xmlHasNsProp(node, local_name, ns ? ns->href : NULL));
1291
+
1292
+ xmlSetNsProp(node, ns, local_name, c_value);
1269
1293
 
1270
1294
  return value;
1271
1295
  }
@@ -1338,6 +1362,11 @@ set_namespace(VALUE self, VALUE namespace)
1338
1362
 
1339
1363
  xmlSetNs(node, ns);
1340
1364
 
1365
+ if (ns && node->type == XML_ELEMENT_NODE && !xmlSearchNs(node->doc, node, ns->prefix)) {
1366
+ xmlNsPtr definition = xmlNewNs(node, ns->href, ns->prefix);
1367
+ if (definition) { xmlSetNs(node, definition); }
1368
+ }
1369
+
1341
1370
  return self;
1342
1371
  }
1343
1372
 
@@ -1566,6 +1595,7 @@ set_lang(VALUE self_rb, VALUE lang_rb)
1566
1595
  Noko_Node_Get_Struct(self_rb, xmlNode, self);
1567
1596
  lang = (xmlChar *)StringValueCStr(lang_rb);
1568
1597
 
1598
+ unlink_wrapped_attr_children(xmlHasNsProp(self, BAD_CAST "lang", XML_XML_NAMESPACE));
1569
1599
  xmlNodeSetLang(self, lang);
1570
1600
 
1571
1601
  return Qnil ;
@@ -1744,7 +1774,7 @@ static inline void
1744
1774
  output_partial_string(VALUE out, char const *str, size_t length)
1745
1775
  {
1746
1776
  if (length) {
1747
- rb_enc_str_buf_cat(out, str, (long)length, rb_utf8_encoding());
1777
+ rb_enc_str_buf_cat(out, str, (rb_len_t)length, rb_utf8_encoding());
1748
1778
  }
1749
1779
  }
1750
1780
 
@@ -1834,9 +1864,9 @@ output_escaped_string(VALUE out, xmlChar const *start, bool attr)
1834
1864
  replaced_bytes = 2;
1835
1865
  } else if (attr && ch == '"') {
1836
1866
  replacement = "&quot;";
1837
- } else if (!attr && ch == '<') {
1867
+ } else if (ch == '<') {
1838
1868
  replacement = "&lt;";
1839
- } else if (!attr && ch == '>') {
1869
+ } else if (ch == '>') {
1840
1870
  replacement = "&gt;";
1841
1871
  } else {
1842
1872
  ++next;
@@ -1897,7 +1927,8 @@ static void
1897
1927
  output_node(
1898
1928
  VALUE out,
1899
1929
  xmlNodePtr node,
1900
- bool preserve_newline
1930
+ bool preserve_newline,
1931
+ bool scripting_enabled
1901
1932
  )
1902
1933
  {
1903
1934
  static char const *const VOID_ELEMENTS[] = {
@@ -1906,9 +1937,11 @@ output_node(
1906
1937
  };
1907
1938
 
1908
1939
  static char const *const UNESCAPED_TEXT_ELEMENTS[] = {
1909
- "style", "script", "xmp", "iframe", "noembed", "noframes", "plaintext", "noscript",
1940
+ "style", "script", "xmp", "iframe", "noembed", "noframes", "plaintext",
1910
1941
  };
1911
1942
 
1943
+ static char const *const NOSCRIPT_ELEMENT[] = { "noscript" };
1944
+
1912
1945
  switch (node->type) {
1913
1946
  case XML_ELEMENT_NODE:
1914
1947
  // Serialize the start tag.
@@ -1918,7 +1951,7 @@ output_node(
1918
1951
  // Add attributes.
1919
1952
  for (xmlAttrPtr attr = node->properties; attr; attr = attr->next) {
1920
1953
  output_char(out, ' ');
1921
- output_node(out, (xmlNodePtr)attr, preserve_newline);
1954
+ output_node(out, (xmlNodePtr)attr, preserve_newline, scripting_enabled);
1922
1955
  }
1923
1956
  output_char(out, '>');
1924
1957
 
@@ -1928,7 +1961,7 @@ output_node(
1928
1961
  output_char(out, '\n');
1929
1962
  }
1930
1963
  for (xmlNodePtr child = node->children; child; child = child->next) {
1931
- output_node(out, child, preserve_newline);
1964
+ output_node(out, child, preserve_newline, scripting_enabled);
1932
1965
  }
1933
1966
  output_string(out, "</");
1934
1967
  output_tagname(out, node);
@@ -1954,8 +1987,9 @@ output_node(
1954
1987
 
1955
1988
  case XML_TEXT_NODE:
1956
1989
  if (node->parent
1957
- && is_one_of(node->parent, UNESCAPED_TEXT_ELEMENTS,
1958
- sizeof UNESCAPED_TEXT_ELEMENTS / sizeof UNESCAPED_TEXT_ELEMENTS[0])) {
1990
+ && (is_one_of(node->parent, UNESCAPED_TEXT_ELEMENTS,
1991
+ sizeof UNESCAPED_TEXT_ELEMENTS / sizeof UNESCAPED_TEXT_ELEMENTS[0])
1992
+ || (scripting_enabled && is_one_of(node->parent, NOSCRIPT_ELEMENT, 1)))) {
1959
1993
  output_string(out, (char const *)node->content);
1960
1994
  } else {
1961
1995
  output_escaped_string(out, node->content, false);
@@ -1991,7 +2025,7 @@ output_node(
1991
2025
  case XML_DOCUMENT_FRAG_NODE:
1992
2026
  case XML_HTML_DOCUMENT_NODE:
1993
2027
  for (xmlNodePtr child = node->children; child; child = child->next) {
1994
- output_node(out, child, preserve_newline);
2028
+ output_node(out, child, preserve_newline, scripting_enabled);
1995
2029
  }
1996
2030
  break;
1997
2031
 
@@ -2004,13 +2038,14 @@ output_node(
2004
2038
  static VALUE
2005
2039
  html_standard_serialize(
2006
2040
  VALUE self,
2007
- VALUE preserve_newline
2041
+ VALUE preserve_newline,
2042
+ VALUE scripting_enabled
2008
2043
  )
2009
2044
  {
2010
2045
  xmlNodePtr node;
2011
2046
  Noko_Node_Get_Struct(self, xmlNode, node);
2012
2047
  VALUE output = rb_str_buf_new(4096);
2013
- output_node(output, node, RTEST(preserve_newline));
2048
+ output_node(output, node, RTEST(preserve_newline), RTEST(scripting_enabled));
2014
2049
  return output;
2015
2050
  }
2016
2051
 
@@ -2512,7 +2547,7 @@ noko_init_xml_node(void)
2512
2547
  rb_define_private_method(cNokogiriXmlNode, "in_context", in_context, 2);
2513
2548
  rb_define_private_method(cNokogiriXmlNode, "native_write_to", native_write_to, 4);
2514
2549
  rb_define_private_method(cNokogiriXmlNode, "prepend_newline?", rb_prepend_newline, 0);
2515
- rb_define_private_method(cNokogiriXmlNode, "html_standard_serialize", html_standard_serialize, 1);
2550
+ rb_define_private_method(cNokogiriXmlNode, "html_standard_serialize", html_standard_serialize, 2);
2516
2551
  rb_define_private_method(cNokogiriXmlNode, "process_xincludes", noko_xml_node__process_xincludes, 1);
2517
2552
  rb_define_private_method(cNokogiriXmlNode, "replace_node", replace, 1);
2518
2553
  rb_define_private_method(cNokogiriXmlNode, "set", set, 2);