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
@@ -4,10 +4,25 @@ VALUE cNokogiriXmlSaxParserContext ;
4
4
 
5
5
  static ID id_read;
6
6
 
7
+ typedef struct {
8
+ xmlParserCtxtPtr ctxt;
9
+ VALUE rb_input;
10
+ } nokogiriSaxParserContextTuple;
11
+
12
+ static void
13
+ _noko_xml_sax_parser_context_mark(void *data)
14
+ {
15
+ nokogiriSaxParserContextTuple *tuple = data;
16
+ /* Pin the input because libxml2 retains the IO callback's Ruby handle. */
17
+ rb_gc_mark(tuple->rb_input);
18
+ }
19
+
7
20
  static void
8
21
  xml_sax_parser_context_type_free(void *data)
9
22
  {
10
- xmlParserCtxtPtr ctxt = data;
23
+ nokogiriSaxParserContextTuple *tuple = data;
24
+ xmlParserCtxtPtr ctxt = tuple->ctxt;
25
+
11
26
  ctxt->sax = NULL;
12
27
  if (ctxt->myDoc) {
13
28
  xmlFreeDoc(ctxt->myDoc);
@@ -15,6 +30,7 @@ xml_sax_parser_context_type_free(void *data)
15
30
  if (ctxt) {
16
31
  xmlFreeParserCtxt(ctxt);
17
32
  }
33
+ ruby_xfree(tuple);
18
34
  }
19
35
 
20
36
  /*
@@ -24,6 +40,7 @@ xml_sax_parser_context_type_free(void *data)
24
40
  static const rb_data_type_t xml_sax_parser_context_type = {
25
41
  .wrap_struct_name = "xmlParserCtxt",
26
42
  .function = {
43
+ .dmark = _noko_xml_sax_parser_context_mark,
27
44
  .dfree = xml_sax_parser_context_type_free,
28
45
  },
29
46
  .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
@@ -32,15 +49,29 @@ static const rb_data_type_t xml_sax_parser_context_type = {
32
49
  xmlParserCtxtPtr
33
50
  noko_xml_sax_parser_context_unwrap(VALUE rb_context)
34
51
  {
35
- xmlParserCtxtPtr c_context;
36
- TypedData_Get_Struct(rb_context, xmlParserCtxt, &xml_sax_parser_context_type, c_context);
37
- return c_context;
52
+ nokogiriSaxParserContextTuple *tuple;
53
+ TypedData_Get_Struct(rb_context, nokogiriSaxParserContextTuple, &xml_sax_parser_context_type, tuple);
54
+ return tuple->ctxt;
38
55
  }
39
56
 
40
57
  VALUE
41
58
  noko_xml_sax_parser_context_wrap(VALUE klass, xmlParserCtxtPtr c_context)
42
59
  {
43
- return TypedData_Wrap_Struct(klass, &xml_sax_parser_context_type, c_context);
60
+ nokogiriSaxParserContextTuple *tuple;
61
+ VALUE rb_context = TypedData_Make_Struct(klass, nokogiriSaxParserContextTuple, &xml_sax_parser_context_type, tuple);
62
+
63
+ tuple->ctxt = c_context;
64
+ tuple->rb_input = Qnil;
65
+
66
+ return rb_context;
67
+ }
68
+
69
+ static void
70
+ _noko_xml_sax_parser_context_set_input(VALUE rb_context, VALUE rb_input)
71
+ {
72
+ nokogiriSaxParserContextTuple *tuple;
73
+ TypedData_Get_Struct(rb_context, nokogiriSaxParserContextTuple, &xml_sax_parser_context_type, tuple);
74
+ RB_OBJ_WRITE(rb_context, &tuple->rb_input, rb_input);
44
75
  }
45
76
 
46
77
  void
@@ -103,7 +134,7 @@ noko_xml_sax_parser_context_s_native_io(VALUE rb_class, VALUE rb_io, VALUE rb_en
103
134
  }
104
135
 
105
136
  VALUE rb_context = noko_xml_sax_parser_context_wrap(rb_class, c_context);
106
- rb_iv_set(rb_context, "@input", rb_io);
137
+ _noko_xml_sax_parser_context_set_input(rb_context, rb_io);
107
138
 
108
139
  return rb_context;
109
140
  }
@@ -158,7 +189,7 @@ noko_xml_sax_parser_context_s_native_memory(VALUE rb_class, VALUE rb_input, VALU
158
189
  }
159
190
 
160
191
  VALUE rb_context = noko_xml_sax_parser_context_wrap(rb_class, c_context);
161
- rb_iv_set(rb_context, "@input", rb_input);
192
+ _noko_xml_sax_parser_context_set_input(rb_context, rb_input);
162
193
 
163
194
  return rb_context;
164
195
  }
@@ -14,10 +14,22 @@ xml_sax_push_parser_free(void *data)
14
14
  }
15
15
  }
16
16
 
17
+ /* The @sax_parser ivar marks the parser; libxml2 also retains its movable VALUE in _private. */
18
+ static void
19
+ _noko_xml_sax_push_parser_update_references(void *data)
20
+ {
21
+ xmlParserCtxtPtr ctx = data;
22
+
23
+ if (ctx->_private) {
24
+ ctx->_private = (void *)rb_gc_location((VALUE)ctx->_private);
25
+ }
26
+ }
27
+
17
28
  static const rb_data_type_t xml_sax_push_parser_type = {
18
29
  .wrap_struct_name = "xmlParserCtxt",
19
30
  .function = {
20
31
  .dfree = xml_sax_push_parser_free,
32
+ .dcompact = _noko_xml_sax_push_parser_update_references,
21
33
  },
22
34
  .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
23
35
  };
@@ -231,6 +231,7 @@ Nokogiri_marshal_xpath_funcall_and_return_values(
231
231
  {
232
232
  VALUE rb_retval;
233
233
  VALUE *argv;
234
+ VALUE argv_handle;
234
235
  VALUE rb_node_set = Qnil;
235
236
  xmlNodeSetPtr c_node_set = NULL;
236
237
  xmlXPathObjectPtr c_xpath_object;
@@ -238,10 +239,7 @@ Nokogiri_marshal_xpath_funcall_and_return_values(
238
239
  assert(ctxt->context->doc);
239
240
  assert(DOC_RUBY_OBJECT_TEST(ctxt->context->doc));
240
241
 
241
- argv = (VALUE *)ruby_xcalloc((size_t)argc, sizeof(VALUE));
242
- for (int j = 0 ; j < argc ; ++j) {
243
- rb_gc_register_address(&argv[j]);
244
- }
242
+ argv = ALLOCV_N(VALUE, argv_handle, argc);
245
243
 
246
244
  for (int j = argc - 1 ; j >= 0 ; --j) {
247
245
  c_xpath_object = valuePop(ctxt);
@@ -259,10 +257,7 @@ Nokogiri_marshal_xpath_funcall_and_return_values(
259
257
  argv
260
258
  );
261
259
 
262
- for (int j = 0 ; j < argc ; ++j) {
263
- rb_gc_unregister_address(&argv[j]);
264
- }
265
- ruby_xfree(argv);
260
+ ALLOCV_END(argv_handle);
266
261
 
267
262
  switch (TYPE(rb_retval)) {
268
263
  case T_FLOAT:
@@ -300,37 +295,116 @@ Nokogiri_marshal_xpath_funcall_and_return_values(
300
295
  }
301
296
  }
302
297
 
298
+ /*
299
+ * A Ruby exception must not unwind through libxml2's stack frames, because that skips libxml2's
300
+ * cleanup and leaks memory. So the handler is called under rb_protect, and the exception is
301
+ * re-raised after xmlXPathEvalExpression returns.
302
+ *
303
+ * Until then, no Ruby code may run: the pending exception lives only in the thread's errinfo,
304
+ * which a rescue elsewhere would overwrite.
305
+ */
306
+ typedef struct {
307
+ VALUE handler;
308
+ int exception_state;
309
+ } _noko_xml_xpath_context__handler_call_t;
310
+
311
+ typedef struct {
312
+ VALUE handler;
313
+ const xmlChar *name;
314
+ const xmlChar *ns_uri;
315
+ } _noko_xml_xpath_context__lookup_args_t;
316
+
317
+ typedef struct {
318
+ xmlXPathParserContextPtr ctxt;
319
+ int argc;
320
+ } _noko_xml_xpath_context__invoke_args_t;
321
+
322
+ PRINTFLIKE_DECL(2, 3)
323
+ static void
324
+ _noko_xml_xpath_context__ignore_error(void *data, const char *msg, ...)
325
+ {
326
+ }
327
+
328
+ static void
329
+ _noko_xml_xpath_context__abort_evaluation(xmlXPathParserContextPtr ctxt)
330
+ {
331
+ /* silence the error that some libxml2 versions report, which would run Ruby code */
332
+ xmlSetStructuredErrorFunc(NULL, NULL);
333
+ xmlSetGenericErrorFunc(NULL, _noko_xml_xpath_context__ignore_error);
334
+
335
+ ctxt->error = XPATH_EXPR_ERROR;
336
+ }
337
+
338
+ static VALUE
339
+ _noko_xml_xpath_context__invoke_handler(VALUE data)
340
+ {
341
+ _noko_xml_xpath_context__invoke_args_t *args = (_noko_xml_xpath_context__invoke_args_t *)data;
342
+ _noko_xml_xpath_context__handler_call_t *handler_call = args->ctxt->context->userData;
343
+
344
+ Nokogiri_marshal_xpath_funcall_and_return_values(
345
+ args->ctxt,
346
+ args->argc,
347
+ handler_call->handler,
348
+ (const char *)(args->ctxt->context->function)
349
+ );
350
+
351
+ return Qnil;
352
+ }
353
+
303
354
  static void
304
355
  _noko_xml_xpath_context__handler_invoker(xmlXPathParserContextPtr ctxt, int argc)
305
356
  {
306
- VALUE rb_xpath_handler = Qnil;
307
- const char *method_name = NULL ;
357
+ _noko_xml_xpath_context__handler_call_t *handler_call;
358
+ _noko_xml_xpath_context__invoke_args_t args = { ctxt, argc };
308
359
 
309
360
  assert(ctxt);
310
361
  assert(ctxt->context);
311
362
  assert(ctxt->context->userData);
312
363
  assert(ctxt->context->function);
313
364
 
314
- rb_xpath_handler = (VALUE)(ctxt->context->userData);
315
- method_name = (const char *)(ctxt->context->function);
365
+ handler_call = ctxt->context->userData;
316
366
 
317
- Nokogiri_marshal_xpath_funcall_and_return_values(
318
- ctxt,
319
- argc,
320
- rb_xpath_handler,
321
- method_name
322
- );
367
+ if (handler_call->exception_state == 0) {
368
+ rb_protect(_noko_xml_xpath_context__invoke_handler, (VALUE)&args, &handler_call->exception_state);
369
+ }
370
+
371
+ if (handler_call->exception_state != 0) {
372
+ _noko_xml_xpath_context__abort_evaluation(ctxt);
373
+ }
323
374
  }
324
375
 
325
- static xmlXPathFunction
326
- _noko_xml_xpath_context_handler_lookup(void *data, const xmlChar *c_name, const xmlChar *c_ns_uri)
376
+ static VALUE
377
+ _noko_xml_xpath_context__handler_responds_to(VALUE data)
327
378
  {
328
- VALUE rb_handler = (VALUE)data;
329
- if (rb_respond_to(rb_handler, rb_intern((const char *)c_name))) {
330
- if (c_ns_uri == NULL) {
379
+ _noko_xml_xpath_context__lookup_args_t *args = (_noko_xml_xpath_context__lookup_args_t *)data;
380
+
381
+ if (rb_respond_to(args->handler, rb_intern((const char *)args->name))) {
382
+ if (args->ns_uri == NULL) {
331
383
  NOKO_WARN_DEPRECATION("A custom XPath or CSS handler function named '%s' is being invoked without a namespace. Please update your query to reference this function as 'nokogiri:%s'. Invoking custom handler functions without a namespace is deprecated and will become an error in Nokogiri v1.17.0.",
332
- c_name, c_name); // TODO deprecated in v1.15.0, remove in v1.19.0
384
+ args->name, args->name); // TODO deprecated in v1.15.0, remove in v1.19.0
333
385
  }
386
+ return Qtrue;
387
+ }
388
+
389
+ return Qfalse;
390
+ }
391
+
392
+ static xmlXPathFunction
393
+ _noko_xml_xpath_context_handler_lookup(void *data, const xmlChar *c_name, const xmlChar *c_ns_uri)
394
+ {
395
+ _noko_xml_xpath_context__handler_call_t *handler_call = data;
396
+ _noko_xml_xpath_context__lookup_args_t args = { handler_call->handler, c_name, c_ns_uri };
397
+ VALUE responds_to = Qfalse;
398
+
399
+ if (handler_call->exception_state == 0) {
400
+ responds_to = rb_protect(_noko_xml_xpath_context__handler_responds_to, (VALUE)&args, &handler_call->exception_state);
401
+ }
402
+
403
+ /*
404
+ * After an exception, return the invoker anyway, so that it aborts the evaluation. Returning NULL
405
+ * would make libxml2 fall back to its own functions, or report an unknown function.
406
+ */
407
+ if (handler_call->exception_state != 0 || RTEST(responds_to)) {
334
408
  return _noko_xml_xpath_context__handler_invoker;
335
409
  }
336
410
 
@@ -381,6 +455,8 @@ noko_xml_xpath_context_evaluate(int argc, VALUE *argv, VALUE rb_context)
381
455
  VALUE rb_errors = rb_ary_new();
382
456
  xmlXPathObjectPtr c_xpath_object;
383
457
  VALUE rb_xpath_object = Qnil;
458
+ _noko_xml_xpath_context__handler_call_t handler_call = { Qnil, 0 };
459
+ void *previous_user_data;
384
460
 
385
461
  TypedData_Get_Struct(rb_context, xmlXPathContext, &_noko_xml_xpath_context_type, c_context);
386
462
 
@@ -388,13 +464,17 @@ noko_xml_xpath_context_evaluate(int argc, VALUE *argv, VALUE rb_context)
388
464
 
389
465
  c_expression_str = (xmlChar *)StringValueCStr(rb_expression);
390
466
 
467
+ /* restored afterwards, because a handler may call evaluate re-entrantly on this context */
468
+ previous_user_data = c_context->userData;
469
+
391
470
  if (Qnil != rb_function_lookup_handler) {
471
+ handler_call.handler = rb_function_lookup_handler;
392
472
  /* FIXME: not sure if this is the correct place to shove private data. */
393
- c_context->userData = (void *)rb_function_lookup_handler;
473
+ c_context->userData = &handler_call;
394
474
  xmlXPathRegisterFuncLookup(
395
475
  c_context,
396
476
  _noko_xml_xpath_context_handler_lookup,
397
- (void *)rb_function_lookup_handler
477
+ &handler_call
398
478
  );
399
479
  }
400
480
 
@@ -408,6 +488,12 @@ noko_xml_xpath_context_evaluate(int argc, VALUE *argv, VALUE rb_context)
408
488
  xmlSetGenericErrorFunc(NULL, NULL);
409
489
 
410
490
  xmlXPathRegisterFuncLookup(c_context, NULL, NULL);
491
+ c_context->userData = previous_user_data;
492
+
493
+ if (handler_call.exception_state != 0) {
494
+ xmlXPathFreeObject(c_xpath_object);
495
+ rb_jump_tag(handler_call.exception_state);
496
+ }
411
497
 
412
498
  if (c_xpath_object == NULL) {
413
499
  rb_exc_raise(rb_ary_entry(rb_errors, 0));
@@ -436,6 +522,12 @@ noko_xml_xpath_context_new(VALUE klass, VALUE rb_node)
436
522
  xmlXPathContextPtr c_context;
437
523
  VALUE rb_context;
438
524
 
525
+ if (!rb_obj_is_kind_of(rb_node, cNokogiriXmlNode)) {
526
+ rb_raise(rb_eTypeError,
527
+ "expected a Nokogiri::XML::Node, received %"PRIsVALUE,
528
+ rb_obj_class(rb_node));
529
+ }
530
+
439
531
  Noko_Node_Get_Struct(rb_node, xmlNode, c_node);
440
532
 
441
533
  #if LIBXML_VERSION < 21000
@@ -468,6 +560,12 @@ noko_xml_xpath_context_set_node(VALUE rb_context, VALUE rb_node)
468
560
  xmlNodePtr c_node;
469
561
  xmlXPathContextPtr c_context;
470
562
 
563
+ if (!rb_obj_is_kind_of(rb_node, cNokogiriXmlNode)) {
564
+ rb_raise(rb_eTypeError,
565
+ "expected a Nokogiri::XML::Node, received %"PRIsVALUE,
566
+ rb_obj_class(rb_node));
567
+ }
568
+
471
569
  TypedData_Get_Struct(rb_context, xmlXPathContext, &_noko_xml_xpath_context_type, c_context);
472
570
  Noko_Node_Get_Struct(rb_node, xmlNode, c_node);
473
571
 
@@ -6,7 +6,19 @@ static void
6
6
  mark(void *data)
7
7
  {
8
8
  nokogiriXsltStylesheetTuple *wrapper = (nokogiriXsltStylesheetTuple *)data;
9
+ long i;
10
+
11
+ /* GC can run while Nokogiri_wrap_xslt_stylesheet allocates func_instances. */
12
+ if (!RB_TYPE_P(wrapper->func_instances, T_ARRAY)) {
13
+ return;
14
+ }
15
+
9
16
  rb_gc_mark(wrapper->func_instances);
17
+
18
+ /* libxslt retains each extension handler instance's movable VALUE as module data. */
19
+ for (i = 0; i < RARRAY_LEN(wrapper->func_instances); i++) {
20
+ rb_gc_mark(RARRAY_AREF(wrapper->func_instances, i));
21
+ }
10
22
  }
11
23
 
12
24
  static void
@@ -18,11 +30,23 @@ dealloc(void *data)
18
30
  ruby_xfree(wrapper);
19
31
  }
20
32
 
33
+ /* libxslt retains this Stylesheet's own movable VALUE in _private. */
34
+ static void
35
+ update_references(void *data)
36
+ {
37
+ nokogiriXsltStylesheetTuple *wrapper = (nokogiriXsltStylesheetTuple *)data;
38
+
39
+ if (wrapper->ss && wrapper->ss->_private) {
40
+ wrapper->ss->_private = (void *)rb_gc_location((VALUE)wrapper->ss->_private);
41
+ }
42
+ }
43
+
21
44
  static const rb_data_type_t nokogiri_xslt_stylesheet_tuple_type = {
22
45
  .wrap_struct_name = "nokogiriXsltStylesheetTuple",
23
46
  .function = {
24
47
  .dmark = mark,
25
48
  .dfree = dealloc,
49
+ .dcompact = update_references,
26
50
  },
27
51
  .flags = RUBY_TYPED_FREE_IMMEDIATELY
28
52
  };
@@ -47,6 +71,29 @@ xslt_generic_error_handler(void *ctx, const char *msg, ...)
47
71
  rb_str_concat((VALUE)ctx, message);
48
72
  }
49
73
 
74
+ PRINTFLIKE_DECL(2, 3)
75
+ static void
76
+ ignore_error(void *ctx, const char *msg, ...)
77
+ {
78
+ }
79
+
80
+ /*
81
+ * A Ruby exception must not unwind through libxslt's stack frames, because that skips libxslt's
82
+ * cleanup and leaks memory, and skips the reset of the error handlers. So an extension function is
83
+ * called under rb_protect, and the exception is re-raised after the transform returns.
84
+ *
85
+ * Until then, no Ruby code may run: the pending exception lives only in the thread's errinfo,
86
+ * which a rescue elsewhere would overwrite.
87
+ */
88
+ typedef struct {
89
+ int exception_state;
90
+ } _noko_xslt_stylesheet__function_call_t;
91
+
92
+ typedef struct {
93
+ xmlXPathParserContextPtr ctxt;
94
+ int nargs;
95
+ } _noko_xslt_stylesheet__invoke_args_t;
96
+
50
97
  VALUE
51
98
  Nokogiri_wrap_xslt_stylesheet(xsltStylesheetPtr ss)
52
99
  {
@@ -137,15 +184,14 @@ rb_xslt_stylesheet_serialize(VALUE self, VALUE xmlobj)
137
184
  /*
138
185
  * Build the C-string params array passed to xsltApplyStylesheet.
139
186
  *
140
- * Note: params[j] is a raw pointer into a Ruby string's buffer, and we do not pin the underlying
141
- * VALUEs against GC compaction. This is safe (despite not pinning the VALUEs) because libxslt fully
142
- * processes params (interning names, evaluating values) before template execution begins, and Ruby
143
- * callbacks can only run during template execution. By the time GC compaction is reachable, libxslt
144
- * no longer reads params[].
187
+ * Note: params[j] is a copy of the Ruby string's bytes, not a pointer into its buffer, because GC
188
+ * compaction moves the bytes of short strings that are embedded in their object slots. Compaction
189
+ * can run during a #to_str conversion in this loop and, when libxml2 uses Ruby's allocator, during
190
+ * libxslt's own allocations. Free params with free_xslt_params.
145
191
  */
146
192
  typedef struct {
147
193
  VALUE rb_param;
148
- long param_len;
194
+ rb_len_t param_len;
149
195
  const char **params;
150
196
  } build_xslt_params_args_t;
151
197
 
@@ -154,14 +200,23 @@ build_xslt_params(VALUE args_ptr)
154
200
  {
155
201
  build_xslt_params_args_t *args = (build_xslt_params_args_t *)args_ptr;
156
202
 
157
- for (long j = 0; j < args->param_len; j++) {
203
+ for (rb_len_t j = 0; j < args->param_len; j++) {
158
204
  VALUE entry = rb_ary_entry(args->rb_param, j);
159
- args->params[j] = StringValueCStr(entry);
205
+ args->params[j] = ruby_strdup(StringValueCStr(entry));
160
206
  }
161
207
 
162
208
  return Qnil;
163
209
  }
164
210
 
211
+ static void
212
+ free_xslt_params(const char **params, rb_len_t param_len)
213
+ {
214
+ for (rb_len_t j = 0; j < param_len; j++) {
215
+ ruby_xfree((void *)params[j]);
216
+ }
217
+ ruby_xfree(params);
218
+ }
219
+
165
220
  /*
166
221
  * call-seq:
167
222
  * transform(document)
@@ -283,9 +338,10 @@ rb_xslt_stylesheet_transform(int argc, VALUE *argv, VALUE self)
283
338
  xmlDocPtr c_result_document ;
284
339
  nokogiriXsltStylesheetTuple *wrapper;
285
340
  const char **params ;
286
- long param_len ;
341
+ rb_len_t param_len ;
287
342
  int parse_error_occurred ;
288
343
  int defensive_copy_p = 0;
344
+ _noko_xslt_stylesheet__function_call_t function_call = { 0 };
289
345
 
290
346
  rb_scan_args(argc, argv, "11", &rb_document, &rb_param);
291
347
  if (NIL_P(rb_param)) { rb_param = rb_ary_new2(0L) ; }
@@ -314,7 +370,7 @@ rb_xslt_stylesheet_transform(int argc, VALUE *argv, VALUE self)
314
370
 
315
371
  rb_protect(build_xslt_params, (VALUE)&args, &state);
316
372
  if (state) {
317
- ruby_xfree(params);
373
+ free_xslt_params(params, param_len);
318
374
  rb_jump_tag(state);
319
375
  }
320
376
  }
@@ -329,13 +385,24 @@ rb_xslt_stylesheet_transform(int argc, VALUE *argv, VALUE self)
329
385
  }
330
386
  xsltFreeTransformContext(c_transform_context);
331
387
 
388
+ c_transform_context = xsltNewTransformContext(wrapper->ss, c_document);
389
+ if (c_transform_context == NULL) {
390
+ free_xslt_params(params, param_len);
391
+ if (defensive_copy_p) {
392
+ xmlFreeDoc(c_document);
393
+ }
394
+ rb_raise(rb_eRuntimeError, "could not create an XSLT transform context");
395
+ }
396
+ c_transform_context->_private = &function_call;
397
+
332
398
  rb_error_str = rb_str_new(0, 0);
333
399
  xsltSetGenericErrorFunc((void *)rb_error_str, xslt_generic_error_handler);
334
400
  xmlSetGenericErrorFunc((void *)rb_error_str, xslt_generic_error_handler);
335
401
 
336
- c_result_document = xsltApplyStylesheet(wrapper->ss, c_document, params);
402
+ c_result_document = xsltApplyStylesheetUser(wrapper->ss, c_document, params, NULL, NULL, c_transform_context);
403
+ xsltFreeTransformContext(c_transform_context);
337
404
 
338
- ruby_xfree(params);
405
+ free_xslt_params(params, param_len);
339
406
  if (defensive_copy_p) {
340
407
  xmlFreeDoc(c_document);
341
408
  c_document = NULL;
@@ -344,6 +411,11 @@ rb_xslt_stylesheet_transform(int argc, VALUE *argv, VALUE self)
344
411
  xsltSetGenericErrorFunc(NULL, NULL);
345
412
  xmlSetGenericErrorFunc(NULL, NULL);
346
413
 
414
+ if (function_call.exception_state != 0) {
415
+ xmlFreeDoc(c_result_document);
416
+ rb_jump_tag(function_call.exception_state);
417
+ }
418
+
347
419
  parse_error_occurred = (Qfalse == rb_funcall(rb_error_str, rb_intern("empty?"), 0));
348
420
 
349
421
  if (parse_error_occurred) {
@@ -353,25 +425,42 @@ rb_xslt_stylesheet_transform(int argc, VALUE *argv, VALUE self)
353
425
  return noko_xml_document_wrap((VALUE)0, c_result_document) ;
354
426
  }
355
427
 
356
- static void
357
- method_caller(xmlXPathParserContextPtr ctxt, int nargs)
428
+ static VALUE
429
+ invoke_function(VALUE data)
358
430
  {
359
- VALUE handler;
360
- const char *function_name;
361
- xsltTransformContextPtr transform;
362
- const xmlChar *functionURI;
363
-
364
- transform = xsltXPathGetTransformContext(ctxt);
365
- functionURI = ctxt->context->functionURI;
366
- handler = (VALUE)xsltGetExtData(transform, functionURI);
367
- function_name = (const char *)(ctxt->context->function);
431
+ _noko_xslt_stylesheet__invoke_args_t *args = (_noko_xslt_stylesheet__invoke_args_t *)data;
432
+ xsltTransformContextPtr transform = xsltXPathGetTransformContext(args->ctxt);
433
+ VALUE handler = (VALUE)xsltGetExtData(transform, args->ctxt->context->functionURI);
368
434
 
369
435
  Nokogiri_marshal_xpath_funcall_and_return_values(
370
- ctxt,
371
- nargs,
436
+ args->ctxt,
437
+ args->nargs,
372
438
  handler,
373
- (const char *)function_name
439
+ (const char *)(args->ctxt->context->function)
374
440
  );
441
+
442
+ return Qnil;
443
+ }
444
+
445
+ static void
446
+ method_caller(xmlXPathParserContextPtr ctxt, int nargs)
447
+ {
448
+ xsltTransformContextPtr transform = xsltXPathGetTransformContext(ctxt);
449
+ _noko_xslt_stylesheet__function_call_t *function_call = transform->_private;
450
+ _noko_xslt_stylesheet__invoke_args_t args = { ctxt, nargs };
451
+
452
+ if (function_call->exception_state == 0) {
453
+ rb_protect(invoke_function, (VALUE)&args, &function_call->exception_state);
454
+ }
455
+
456
+ if (function_call->exception_state != 0) {
457
+ /* silence the errors libxslt reports while it stops, which would run Ruby code */
458
+ xsltSetGenericErrorFunc(NULL, ignore_error);
459
+ xmlSetGenericErrorFunc(NULL, ignore_error);
460
+
461
+ ctxt->error = XPATH_EXPR_ERROR;
462
+ transform->state = XSLT_STATE_STOPPED;
463
+ }
375
464
  }
376
465
 
377
466
  static void *
@@ -412,6 +501,7 @@ shutdownFunc(xsltTransformContextPtr ctxt,
412
501
  const xmlChar *uri, void *data)
413
502
  {
414
503
  nokogiriXsltStylesheetTuple *wrapper;
504
+ long i;
415
505
 
416
506
  TypedData_Get_Struct(
417
507
  (VALUE)ctxt->style->_private,
@@ -420,7 +510,13 @@ shutdownFunc(xsltTransformContextPtr ctxt,
420
510
  wrapper
421
511
  );
422
512
 
423
- rb_ary_clear(wrapper->func_instances);
513
+ /* Other transform contexts, such as a nested transform's, may still use their instances. */
514
+ for (i = 0; i < RARRAY_LEN(wrapper->func_instances); i++) {
515
+ if (RARRAY_AREF(wrapper->func_instances, i) == (VALUE)data) {
516
+ rb_ary_delete_at(wrapper->func_instances, i);
517
+ break;
518
+ }
519
+ }
424
520
  }
425
521
 
426
522
  /* docstring is in lib/nokogiri/xslt.rb */