Commits

Tim Brody committed 2cb2762

Added support for register_element() on stylesheet objects. The callback may leak memory though - nothing frees the _private attributes of the node + document owner for the passed arguments?

Comments (0)

Files changed (3)

                XML_LIBXSLT_CLOSE_CB => $self->{XML_LIBXSLT_CLOSE_CB},
                XML_LIBXSLT_SECPREFS => $self->{XML_LIBXSLT_SECPREFS},
 			   XML_LIBXSLT_FUNCTIONS => {},
+			   XML_LIBXSLT_ELEMENTS => {},
              };
 
     return bless $rv, "XML::LibXSLT::StylesheetWrapper";
                XML_LIBXSLT_CLOSE_CB => $self->{XML_LIBXSLT_CLOSE_CB},
                XML_LIBXSLT_SECPREFS => $self->{XML_LIBXSLT_SECPREFS},
 			   XML_LIBXSLT_FUNCTIONS => {},
+			   XML_LIBXSLT_ELEMENTS => {},
              };
 
     return bless $rv, "XML::LibXSLT::StylesheetWrapper";
 {
 	my $self = shift;
 
-	$self->{XML_LIBXSLT_FUNCTIONS}->{"{$_[0]}$_[1]"} = \@_;
+	$self->{XML_LIBXSLT_FUNCTIONS}->{"{$_[0]}$_[1]"} = [@_[0,1,2]];
+}
+
+sub register_element
+{
+	my $self = shift;
+
+	$self->{XML_LIBXSLT_ELEMENTS}->{"{$_[0]}$_[1]"} = [@_[0,1,2]];
 }
 
 sub output_string { shift->{XML_LIBXSLT_STYLESHEET}->_output_string($_[0],0) }
 But only a single return value is supported (a list is not converted to
 a nodelist).
 
+=item register_element
+
+	$stylesheet->register_element($uri, $name, $subref)
+
+Registers an XSLT extension element $name mapped to the given URI. For example:
+
+  $stylesheet->register_element("urn:foo", "hello", sub {
+	  my $name = $_[2]->getAttribute( "name" );
+	  return XML::LibXML::Text->new( "Hello, $name!" );
+  });
+
+Will register a C<hello> element in the C<urn:foo> namespace that returns a "Hello, X!" text node. You must define this namespace in your XSLT and include its prefix in the C<extension-element-prefixes> list:
+
+  <xsl:stylesheet version="1.0"
+    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+    xmlns:foo="urn:foo"
+	extension-element-prefixes="foo">
+  <xsl:template match="/">
+    <foo:hello name="bob"/>
+  </xsl:template>
+  </xsl:stylesheet>
+
+The callback is passed the input document node as $_[1] and the stylesheet node as $_[2]. $_[0] is reserved for future use.
+
 =back
 
 =head1 API
     LibXSLT__function (ctxt, nargs, *perl_function);
 }
 
+static void
+LibXSLT_context_element(xsltTransformContextPtr ctxt, xmlNodePtr node, xmlNodePtr inst, xsltElemPreCompPtr comp)
+{
+    SV *key, *wrapper, **ptr, **perl_function, *perlnode;
+	HV *elements;
+	AV *val;
+	char *strkey;
+	STRLEN len;
+    HE *ent;
+    int count;
+    xmlNodePtr result;
+
+    wrapper = (SV *) ctxt->_private;
+
+    key = newSVpvn("", 0);
+
+	sv_setpv(key, "XML_LIBXSLT_ELEMENTS");
+    strkey = SvPV(key, len);
+    ptr = hv_fetch((HV *) SvRV(wrapper), strkey, len, 0);
+	elements = (HV *) SvRV(*ptr);
+
+	sv_setpv(key, "{");
+    sv_catpv(key, (const char*)inst->ns->href);
+	sv_catpv(key, "}");
+	sv_catpv(key, (const char*)inst->name);
+    strkey = SvPV(key, len);
+    ptr = hv_fetch(elements, strkey, len, 0);
+    val = (AV *) SvRV(*ptr);
+
+    perl_function = av_fetch(val, 2, 0);
+
+    SvREFCNT_dec(key);
+
+    dSP;
+    
+    ENTER;
+    SAVETMPS;
+    PUSHMARK(SP);
+    EXTEND(SP, 3);
+    PUSHs(sv_setref_pv(sv_newmortal(), "XML::LibXSLT::TransformContext",
+                (void*)ctxt));
+    if (PmmPROXYNODE(node->doc) == NULL) {
+        node->doc->_private = x_PmmNewNode(INT2PTR(xmlNodePtr,node->doc));
+    }
+    PUSHs(x_PmmNodeToSv(node, PmmPROXYNODE(node->doc)));
+    if (PmmPROXYNODE(inst->doc) == NULL) {
+        inst->doc->_private = x_PmmNewNode(INT2PTR(xmlNodePtr,inst->doc));
+    }
+    PUSHs(x_PmmNodeToSv(inst, PmmPROXYNODE(inst->doc)));
+    PUTBACK;
+
+    count = call_sv(*perl_function, G_SCALAR);
+
+    SPAGAIN;
+
+    if (count != 1)
+        croak("LibXSLT: element callback did not return anything");
+
+    perlnode = POPs;
+
+    if (perlnode != &PL_sv_undef)
+    {
+        result = x_PmmSvNodeExt(perlnode, 0); 
+        if (result == NULL)
+            croak("LibXSLT: element callback did not return a XML::Node");
+
+        x_PmmREFCNT_inc(PmmPROXYNODE(result));
+
+        xmlAddChild(ctxt->insert, result);
+    }
+
+    FREETMPS;
+    LEAVE;
+}
+
 
 int
 LibXSLT_input_match(char const * filename)
     }
 }
 
+void
+LibXSLT_init_elements(xsltTransformContextPtr ctxt, SV *wrapper)
+{
+    SV **ptr;
+    HV *functions;
+    HE *key;
+    AV *val;
+    char *uri, *name;
+    const char strkey[] = "XML_LIBXSLT_ELEMENTS";
+
+    ptr = hv_fetch((HV *) SvRV(wrapper), strkey, strlen(strkey), 0);
+	/* make sure the user hasn't screwed up our StylesheetWrapper object */
+    if (ptr == NULL)
+        croak("XML_LIBXSLT_ELEMENTS is undef in StylesheetWrapper");
+    if (SvTYPE(SvRV(*ptr)) != SVt_PVHV)
+        croak("XML_LIBXSLT_ELEMENTS is not a HASHREF in StylesheetWrapper");
+
+    functions = (HV *) SvRV(*ptr);
+    hv_iterinit(functions);
+    while (key = hv_iternext(functions))
+    {
+        val = (AV *) SvRV(HeVAL(key)); /* [uri, name, callback] */
+        uri = SvPV_nolen (*av_fetch (val, 0, 0));
+        name = SvPV_nolen (*av_fetch (val, 1, 0));
+        xsltRegisterExtElement (ctxt,
+                (const xmlChar *)name,
+                (const xmlChar *)uri,
+                LibXSLT_context_element
+                );
+    }
+}
+
 MODULE = XML::LibXSLT         PACKAGE = XML::LibXSLT
 
 PROTOTYPES: DISABLE
         ctxt->_private = (void *) wrapper;
         sec = LibXSLT_init_security_prefs(ctxt);
         LibXSLT_init_functions(ctxt, wrapper);
+        LibXSLT_init_elements(ctxt, wrapper);
 
         if (doc->intSubset != NULL) {
 	  /* Note: libxslt will unlink intSubset, we
            ctxt->_private = (void *) wrapper;
            sec = LibXSLT_init_security_prefs(ctxt);
            LibXSLT_init_functions(ctxt, wrapper);
+           LibXSLT_init_elements(ctxt, wrapper);
 	   real_dom = xsltApplyStylesheetUser(self, source_dom, xslt_params,
 					      NULL, NULL, ctxt);
 	   if ((ctxt->state != XSLT_STATE_OK) && real_dom) {
 use Test;
-BEGIN { plan tests => 36 }
+BEGIN { plan tests => 38 }
 use XML::LibXSLT;
 
 {
   my $val = $result->findvalue("/root");
   ok($val, 20, "contextual register_function");
 }
+
+{
+  my $ns = "http://foo";
+
+  my $p = XML::LibXML->new;
+  my $xsltproc = XML::LibXSLT->new;
+
+  my $xsltdoc = $p->parse_string(<<EOF);
+<?xml version="1.0" encoding="utf-8"?>
+<xsl:stylesheet version="1.0"
+     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"  
+     xmlns:foo="$ns"
+	 extension-element-prefixes="foo"
+>
+
+<xsl:template match="root">
+<root>
+<foo:bar value="10"/>
+</root>
+</xsl:template>
+
+</xsl:stylesheet>
+EOF
+
+  my $doc = $p->parse_string(<<EOF);
+<root></root>
+EOF
+
+  my $stylesheet = $xsltproc->parse_stylesheet($xsltdoc);
+  $stylesheet->register_element($ns, "bar", sub {
+	  return XML::LibXML::Text->new( $_[2]->getAttribute( "value" ) );
+  });
+  my $result = $stylesheet->transform($doc);
+  my $val = $result->findvalue("/root");
+  ok($val, 10, "contextual register_element");
+}
+
+ok(1);