This commit is contained in:
@@ -0,0 +1,421 @@
|
||||
#include <nokogiri.h>
|
||||
|
||||
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 nokogiri_xslt_stylesheet_tuple_type = {
|
||||
.wrap_struct_name = "nokogiriXsltStylesheetTuple",
|
||||
.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,
|
||||
&nokogiri_xslt_stylesheet_tuple_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,
|
||||
&nokogiri_xslt_stylesheet_tuple_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
|
||||
* <xsl:stylesheet version="1.0"
|
||||
* xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
||||
*
|
||||
* <xsl:param name="title"/>
|
||||
*
|
||||
* <xsl:template match="/">
|
||||
* <html>
|
||||
* <body>
|
||||
* <h1><xsl:value-of select="$title"/></h1>
|
||||
* <ol>
|
||||
* <xsl:for-each select="staff/employee">
|
||||
* <li><xsl:value-of select="employeeId"></li>
|
||||
* </xsl:for-each>
|
||||
* </ol>
|
||||
* </body>
|
||||
* </html>
|
||||
* </xsl:stylesheet>
|
||||
* XSLT
|
||||
*
|
||||
* xml = <<~XML
|
||||
* <?xml version="1.0"?>
|
||||
* <staff>
|
||||
* <employee>
|
||||
* <employeeId>EMP0001</employeeId>
|
||||
* <position>Accountant</position>
|
||||
* </employee>
|
||||
* <employee>
|
||||
* <employeeId>EMP0002</employeeId>
|
||||
* <position>Developer</position>
|
||||
* </employee>
|
||||
* </staff>
|
||||
* 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
|
||||
* # => "<html><body>\n" +
|
||||
* # "<h1></h1>\n" +
|
||||
* # "<ol>\n" +
|
||||
* # "<li>EMP0001</li>\n" +
|
||||
* # "<li>EMP0002</li>\n" +
|
||||
* # "</ol>\n" +
|
||||
* # "</body></html>\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
|
||||
* # => "<html><body>\n" +
|
||||
* # "<h1>Employee List</h1>\n" +
|
||||
* # "<ol>\n" +
|
||||
* # "<li>EMP0001</li>\n" +
|
||||
* # "<li>EMP0002</li>\n" +
|
||||
* # "</ol>\n" +
|
||||
* # "</body></html>\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
|
||||
* # => "<html><body>\n" +
|
||||
* # "<h1>Aaron's List</h1>\n" +
|
||||
* # "<ol>\n" +
|
||||
* # "<li>EMP0001</li>\n" +
|
||||
* # "<li>EMP0002</li>\n" +
|
||||
* # "</ol>\n" +
|
||||
* # "</body></html>\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
|
||||
* # => "<html><body>\n" +
|
||||
* # "<h1>Employee List</h1>\n" +
|
||||
* # "<ol>\n" +
|
||||
* # "<li>EMP0001</li>\n" +
|
||||
* # "<li>EMP0002</li>\n" +
|
||||
* # "</ol>\n" +
|
||||
* # "</body></html>\n"
|
||||
*
|
||||
* Or pass an array to XSLT.quote_params:
|
||||
*
|
||||
* stylesheet.transform(doc, Nokogiri::XSLT.quote_params(["title", "Aaron's List"])).to_xml
|
||||
* # => "<html><body>\n" +
|
||||
* # "<h1>Aaron's List</h1>\n" +
|
||||
* # "<ol>\n" +
|
||||
* # "<li>EMP0001</li>\n" +
|
||||
* # "<li>EMP0002</li>\n" +
|
||||
* # "</ol>\n" +
|
||||
* # "</body></html>\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, &nokogiri_xslt_stylesheet_tuple_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,
|
||||
&nokogiri_xslt_stylesheet_tuple_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,
|
||||
&nokogiri_xslt_stylesheet_tuple_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);
|
||||
}
|
||||
Reference in New Issue
Block a user