    /*
     *  call-seq:
     *    transform(document, params = [])
     *
     *  Apply an XSLT stylesheet to an XML::Document.
     *  +params+ is an array of strings used as XSLT parameters.
     *  returns Nokogiri::XML::Document
     *
     *  Example:
     *
     *    doc   = Nokogiri::XML(File.read(ARGV[0]))
     *    xslt  = Nokogiri::XSLT(File.read(ARGV[1]))
     *    puts xslt.transform(doc, ['key', 'value'])
     *
     */
    static VALUE transform(int argc, VALUE* argv, VALUE self)
    {
        VALUE xmldoc, paramobj ;
        xmlDocPtr xml ;
        xmlDocPtr result ;
        xsltStylesheetPtr ss ;
        const char** params ;
        long param_len, j ;

        rb_scan_args(argc, argv, "11", &xmldoc, &paramobj);
        if (NIL_P(paramobj)) { paramobj = rb_ary_new2(0L) ; }
        if (!rb_obj_is_kind_of(xmldoc, cNokogiriXmlDocument))
          rb_raise(rb_eArgError, "argument must be a Nokogiri::XML::Document");

        /* handle hashes as arguments. */
        if(T_HASH == TYPE(paramobj)) {
          paramobj = rb_funcall(paramobj, rb_intern("to_a"), 0);
          paramobj = rb_funcall(paramobj, rb_intern("flatten"), 0);
        }

        Check_Type(paramobj, T_ARRAY);

        Data_Get_Struct(xmldoc, xmlDoc, xml);
        Data_Get_Struct(self, xsltStylesheet, ss);

        param_len = RARRAY_LEN(paramobj);
        params = calloc((size_t)param_len+1, sizeof(char*));
        for (j = 0 ; j < param_len ; j++) {
          VALUE entry = rb_ary_entry(paramobj, j);
          const char * ptr = StringValuePtr(entry);
          params[j] = ptr;
        }
        params[param_len] = 0 ;

        result = xsltApplyStylesheet(ss, xml, params);
        free(params);

        if (!result) rb_raise(rb_eRuntimeError, "could not perform xslt transform on document");

        return Nokogiri_wrap_xml_document((VALUE)0, result) ;
    }