#include VALUE cNokogiriXsltStylesheet ; static void mark(void *data) { nokogiriXsltStylesheetTuple *wrapper = (nokogiriXsltStylesheetTuple *)data; rb_gc_mark(wrapper->func_instances); } static void dealloc(void *data) { nokogiriXsltStylesheetTuple *wrapper = (nokogiriXsltStylesheetTuple *)data; xsltStylesheetPtr doc = wrapper->ss; xsltFreeStylesheet(doc); ruby_xfree(wrapper); } static const rb_data_type_t xslt_stylesheet_type = { .wrap_struct_name = "Nokogiri::XSLT::Stylesheet", .function = { .dmark = mark, .dfree = dealloc, }, .flags = RUBY_TYPED_FREE_IMMEDIATELY }; PRINTFLIKE_DECL(2, 3) static void xslt_generic_error_handler(void *ctx, const char *msg, ...) { VALUE message; #ifdef TRUFFLERUBY_NOKOGIRI_SYSTEM_LIBRARIES /* It is not currently possible to pass var args from native functions to sulong, so we work around the issue here. */ message = rb_sprintf("xslt_generic_error_handler: %s", msg); #else va_list args; va_start(args, msg); message = rb_vsprintf(msg, args); va_end(args); #endif rb_str_concat((VALUE)ctx, message); } VALUE Nokogiri_wrap_xslt_stylesheet(xsltStylesheetPtr ss) { VALUE self; nokogiriXsltStylesheetTuple *wrapper; self = TypedData_Make_Struct( cNokogiriXsltStylesheet, nokogiriXsltStylesheetTuple, &xslt_stylesheet_type, wrapper ); ss->_private = (void *)self; wrapper->ss = ss; wrapper->func_instances = rb_ary_new(); return self; } /* * call-seq: * parse_stylesheet_doc(document) * * Parse an XSLT::Stylesheet from +document+. * * [Parameters] * - +document+ (Nokogiri::XML::Document) the document to be parsed. * * [Returns] Nokogiri::XSLT::Stylesheet */ static VALUE parse_stylesheet_doc(VALUE klass, VALUE xmldocobj) { xmlDocPtr xml, xml_cpy; VALUE errstr, exception; xsltStylesheetPtr ss ; xml = noko_xml_document_unwrap(xmldocobj); errstr = rb_str_new(0, 0); xsltSetGenericErrorFunc((void *)errstr, xslt_generic_error_handler); xml_cpy = xmlCopyDoc(xml, 1); /* 1 => recursive */ ss = xsltParseStylesheetDoc(xml_cpy); xsltSetGenericErrorFunc(NULL, NULL); if (!ss) { xmlFreeDoc(xml_cpy); exception = rb_exc_new3(rb_eRuntimeError, errstr); rb_exc_raise(exception); } return Nokogiri_wrap_xslt_stylesheet(ss); } /* * call-seq: * serialize(document) * * Serialize +document+ to an xml string, as specified by the +method+ parameter in the Stylesheet. */ static VALUE rb_xslt_stylesheet_serialize(VALUE self, VALUE xmlobj) { xmlDocPtr xml ; nokogiriXsltStylesheetTuple *wrapper; xmlChar *doc_ptr ; int doc_len ; VALUE rval ; xml = noko_xml_document_unwrap(xmlobj); TypedData_Get_Struct( self, nokogiriXsltStylesheetTuple, &xslt_stylesheet_type, wrapper ); xsltSaveResultToString(&doc_ptr, &doc_len, xml, wrapper->ss); rval = NOKOGIRI_STR_NEW(doc_ptr, doc_len); xmlFree(doc_ptr); return rval ; } /* * call-seq: * transform(document) * transform(document, params = {}) * * Transform an XML::Document as defined by an XSLT::Stylesheet. * * [Parameters] * - +document+ (Nokogiri::XML::Document) the document to be transformed. * - +params+ (Hash, Array) strings used as XSLT parameters. * * [Returns] Nokogiri::XML::Document * * *Example* of basic transformation: * * xslt = <<~XSLT * * * * * * * *

*
    * *
  1. *
    *
* * *
* XSLT * * xml = <<~XML * * * * EMP0001 * Accountant * * * EMP0002 * Developer * * * XML * * doc = Nokogiri::XML::Document.parse(xml) * stylesheet = Nokogiri::XSLT.parse(xslt) * * ⚠ Note that the +h1+ element is empty because no param has been provided! * * stylesheet.transform(doc).to_xml * # => "\n" + * # "

\n" + * # "
    \n" + * # "
  1. EMP0001
  2. \n" + * # "
  3. EMP0002
  4. \n" + * # "
\n" + * # "\n" * * *Example* of using an input parameter hash: * * ⚠ The title is populated, but note how we need to quote-escape the value. * * stylesheet.transform(doc, { "title" => "'Employee List'" }).to_xml * # => "\n" + * # "

Employee List

\n" + * # "
    \n" + * # "
  1. EMP0001
  2. \n" + * # "
  3. EMP0002
  4. \n" + * # "
\n" + * # "\n" * * *Example* using the XSLT.quote_params helper method to safely quote-escape strings: * * stylesheet.transform(doc, Nokogiri::XSLT.quote_params({ "title" => "Aaron's List" })).to_xml * # => "\n" + * # "

Aaron's List

\n" + * # "
    \n" + * # "
  1. EMP0001
  2. \n" + * # "
  3. EMP0002
  4. \n" + * # "
\n" + * # "\n" * * *Example* using an array of XSLT parameters * * You can also use an array if you want to. * * stylesheet.transform(doc, ["title", "'Employee List'"]).to_xml * # => "\n" + * # "

Employee List

\n" + * # "
    \n" + * # "
  1. EMP0001
  2. \n" + * # "
  3. EMP0002
  4. \n" + * # "
\n" + * # "\n" * * Or pass an array to XSLT.quote_params: * * stylesheet.transform(doc, Nokogiri::XSLT.quote_params(["title", "Aaron's List"])).to_xml * # => "\n" + * # "

Aaron's List

\n" + * # "
    \n" + * # "
  1. EMP0001
  2. \n" + * # "
  3. EMP0002
  4. \n" + * # "
\n" + * # "\n" * * See: Nokogiri::XSLT.quote_params */ static VALUE rb_xslt_stylesheet_transform(int argc, VALUE *argv, VALUE self) { VALUE rb_document, rb_param, rb_error_str; xmlDocPtr c_document ; xmlDocPtr c_result_document ; nokogiriXsltStylesheetTuple *wrapper; const char **params ; long param_len, j ; int parse_error_occurred ; int defensive_copy_p = 0; rb_scan_args(argc, argv, "11", &rb_document, &rb_param); if (NIL_P(rb_param)) { rb_param = rb_ary_new2(0L) ; } if (!rb_obj_is_kind_of(rb_document, cNokogiriXmlDocument)) { rb_raise(rb_eArgError, "argument must be a Nokogiri::XML::Document"); } /* handle hashes as arguments. */ if (T_HASH == TYPE(rb_param)) { rb_param = rb_funcall(rb_param, rb_intern("to_a"), 0); rb_param = rb_funcall(rb_param, rb_intern("flatten"), 0); } Check_Type(rb_param, T_ARRAY); c_document = noko_xml_document_unwrap(rb_document); TypedData_Get_Struct(self, nokogiriXsltStylesheetTuple, &xslt_stylesheet_type, wrapper); param_len = RARRAY_LEN(rb_param); params = ruby_xcalloc((size_t)param_len + 1, sizeof(char *)); for (j = 0 ; j < param_len ; j++) { VALUE entry = rb_ary_entry(rb_param, j); const char *ptr = StringValueCStr(entry); params[j] = ptr; } params[param_len] = 0 ; xsltTransformContextPtr c_transform_context = xsltNewTransformContext(wrapper->ss, c_document); if (xsltNeedElemSpaceHandling(c_transform_context) && noko_xml_document_has_wrapped_blank_nodes_p(c_document)) { // see https://github.com/sparklemotion/nokogiri/issues/2800 c_document = xmlCopyDoc(c_document, 1); defensive_copy_p = 1; } xsltFreeTransformContext(c_transform_context); rb_error_str = rb_str_new(0, 0); xsltSetGenericErrorFunc((void *)rb_error_str, xslt_generic_error_handler); xmlSetGenericErrorFunc((void *)rb_error_str, xslt_generic_error_handler); c_result_document = xsltApplyStylesheet(wrapper->ss, c_document, params); ruby_xfree(params); if (defensive_copy_p) { xmlFreeDoc(c_document); c_document = NULL; } xsltSetGenericErrorFunc(NULL, NULL); xmlSetGenericErrorFunc(NULL, NULL); parse_error_occurred = (Qfalse == rb_funcall(rb_error_str, rb_intern("empty?"), 0)); if (parse_error_occurred) { rb_exc_raise(rb_exc_new3(rb_eRuntimeError, rb_error_str)); } return noko_xml_document_wrap((VALUE)0, c_result_document) ; } static void method_caller(xmlXPathParserContextPtr ctxt, int nargs) { VALUE handler; const char *function_name; xsltTransformContextPtr transform; const xmlChar *functionURI; transform = xsltXPathGetTransformContext(ctxt); functionURI = ctxt->context->functionURI; handler = (VALUE)xsltGetExtData(transform, functionURI); function_name = (const char *)(ctxt->context->function); Nokogiri_marshal_xpath_funcall_and_return_values( ctxt, nargs, handler, (const char *)function_name ); } static void * initFunc(xsltTransformContextPtr ctxt, const xmlChar *uri) { VALUE modules = rb_iv_get(mNokogiriXslt, "@modules"); VALUE obj = rb_hash_aref(modules, rb_str_new2((const char *)uri)); VALUE args = { Qfalse }; VALUE methods = rb_funcall(obj, rb_intern("instance_methods"), 1, args); VALUE inst; nokogiriXsltStylesheetTuple *wrapper; int i; for (i = 0; i < RARRAY_LEN(methods); i++) { VALUE method_name = rb_obj_as_string(rb_ary_entry(methods, i)); xsltRegisterExtFunction( ctxt, (unsigned char *)StringValueCStr(method_name), uri, method_caller ); } TypedData_Get_Struct( (VALUE)ctxt->style->_private, nokogiriXsltStylesheetTuple, &xslt_stylesheet_type, wrapper ); inst = rb_class_new_instance(0, NULL, obj); rb_ary_push(wrapper->func_instances, inst); return (void *)inst; } static void shutdownFunc(xsltTransformContextPtr ctxt, const xmlChar *uri, void *data) { nokogiriXsltStylesheetTuple *wrapper; TypedData_Get_Struct( (VALUE)ctxt->style->_private, nokogiriXsltStylesheetTuple, &xslt_stylesheet_type, wrapper ); rb_ary_clear(wrapper->func_instances); } /* docstring is in lib/nokogiri/xslt.rb */ static VALUE rb_xslt_s_register(VALUE self, VALUE uri, VALUE obj) { VALUE modules = rb_iv_get(self, "@modules"); if (NIL_P(modules)) { rb_raise(rb_eRuntimeError, "internal error: @modules not set"); } rb_hash_aset(modules, uri, obj); xsltRegisterExtModule( (unsigned char *)StringValueCStr(uri), initFunc, shutdownFunc ); return self; } void noko_init_xslt_stylesheet(void) { rb_define_singleton_method(mNokogiriXslt, "register", rb_xslt_s_register, 2); rb_iv_set(mNokogiriXslt, "@modules", rb_hash_new()); cNokogiriXsltStylesheet = rb_define_class_under(mNokogiriXslt, "Stylesheet", rb_cObject); rb_undef_alloc_func(cNokogiriXsltStylesheet); rb_define_singleton_method(cNokogiriXsltStylesheet, "parse_stylesheet_doc", parse_stylesheet_doc, 1); rb_define_method(cNokogiriXsltStylesheet, "serialize", rb_xslt_stylesheet_serialize, 1); rb_define_method(cNokogiriXsltStylesheet, "transform", rb_xslt_stylesheet_transform, -1); }