Table of contentThis document describes the work needed to write extensions to thestandard
XSLT library for use with libxslt, the
XSLTC library developed for the Gnomeproject. Before starting reading this document it is highly recommended to
getfamiliar with the libxslt internals. Note: this documentation is by definition incomplete and I am not good
atspelling, grammar, so patches and suggestions are really welcome. The XSLT specificationprovidestwo
ways to extend an XSLT engine: In both cases the extensions need to be associated to a new namespace,i.e.
an URI used as the name for the extension's namespace (there is no needto
have a resource there for this to work). libxslt provides a few extensions itself, either in the libxslt
namespace"http://xmlsoft.org/XSLT/namespace" or in namespaces for other well
knownextensions provided by other XSLT processors like Saxon, Xalan or XT. Since extensions are bound to a namespace name, usually sets of
extensionscoming from a given source are using the same namespace name
defining inpractice a group of extensions providing elements, functions or
both. Fromthe libxslt point of view those are considered as an "extension
module", andmost of the APIs work at a module point of view. Registration of new functions or elements are bound to the activation
ofthe module. This is currently done by declaring the namespace as an
extensionby using the attribute extension-element-prefixes on
thexsl:stylesheet element. An extension module is defined by 3 objects: - the namespace name associated
- an initialization function
- a shutdown function
Currently a libxslt module has to be compiled within the application
usinglibxslt. There is no code to load dynamically shared libraries
associated toa namespace (this may be added but is likely to become a
portabilitynightmare). The current way to register a module is to link the code implementing
itwith the application and to call a registration function: int xsltRegisterExtModule(const xmlChar *URI,
xsltExtInitFunction initFunc,
xsltExtShutdownFunction shutdownFunc); The associated header is read by: #include<libxslt/extensions.h> which also defines the type for the initialization and
shutdownfunctions Once the module URI has been registered and if the XSLT processor
detectsthat a given stylesheet needs the functionalities of an extended
module, thisone is initialized. The xsltExtInitFunction type defines the interface for an
initializationfunction: /**
* xsltExtInitFunction:
* @ctxt: an XSLT transformation context
* @URI: the namespace URI for the extension
*
* A function called at initialization time of an XSLT
* extension module
*
* Returns a pointer to the module specific data for this
* transformation
*/
typedef void *(*xsltExtInitFunction)(xsltTransformContextPtr ctxt,
const xmlChar *URI); There are 3 things to notice: - The function gets passed the namespace name URI as an argument.
Thisallows a single function to provide the initialization for
multiplelogical modules.
- It also gets passed a transformation context. The initialization isdone
at run time before any processing occurs on the stylesheet but itwill be
invoked separately each time for each transformation.
- It returns a pointer. This can be used to store module
specificinformation which can be retrieved later when a function or an
elementfrom the extension is used. An obvious example is a connection to
adatabase which should be kept and reused along with the
transformation.NULL is a perfectly valid return; there is no way to
indicate a failureat this level
What this function is expected to do is: - prepare the context for this module (like opening the
databaseconnection)
- register the extensions specific to this module
There is a single call to do this registration: int xsltRegisterExtFunction(xsltTransformContextPtr ctxt,
const xmlChar *name,
const xmlChar *URI,
xmlXPathEvalFunc function); The registration is bound to a single transformation instance referred
byctxt, name is the UTF8 encoded name for the NCName of the function, and
URIis the namespace name for the extension (no checking is done, a module
couldregister functions or elements from a different namespace, but it is
notrecommended). The implementation of the function must have the signature of a
libxmlXPath function: /**
* xmlXPathEvalFunc:
* @ctxt: an XPath parser context
* @nargs: the number of arguments passed to the function
*
* an XPath evaluation function, the parameters are on the
* XPath context stack
*/
typedef void (*xmlXPathEvalFunc)(xmlXPathParserContextPtr ctxt,
int nargs); The context passed to an XPath function is not an XSLT context but an XPath context. However it is possible tofind
one from the other: The first thing an extension function may want to do is to check
thearguments passed on the stack, the nargs parameter will tell
howmany of them were provided on the XPath expression. The macro valuePop
willextract them from the XPath stack: #include <libxml/xpath.h>
#include <libxml/xpathInternals.h>
xmlXPathObjectPtr obj = valuePop(ctxt); Note that ctxt is the XPath context not the XSLT one. It
isthen possible to examine the content of the value. Check the description of XPath
objectsifnecessary. The following is a common sequence checking whether
the argumentpassed is a string and converting it using the built-in
XPathstring() function if this is not the case: if (obj->type != XPATH_STRING) {
valuePush(ctxt, obj);
xmlXPathStringFunction(ctxt, 1);
obj = valuePop(ctxt);
} Most common XPath functions are available directly at the C level and
areexported either in <libxml/xpath.h> or
in<libxml/xpathInternals.h> . The extension function may also need to retrieve the data associated
tothis module instance (the database connection in the previous example)
thiscan be done using the xsltGetExtData: void * xsltGetExtData(xsltTransformContextPtr ctxt,
const xmlChar *URI); Again the URI to be provided is the one which was used when registeringthe
module. Once the function finishes, don't forget to: - push the return value on the stack using
valuePush(ctxt,obj)
- deallocate the parameters passed to the function
using
xmlXPathFreeObject(obj)
The module libxslt/functions.c contains the sources of the XSLT
built-infunctions, including document(), key(), generate-id(), etc. as well
as a fullexample module at the end. Here is the test function implementation
for thelibxslt:test function: /**
* xsltExtFunctionTest:
* @ctxt: the XPath Parser context
* @nargs: the number of arguments
*
* function libxslt:test() for testing the extensions support.
*/
static void
xsltExtFunctionTest(xmlXPathParserContextPtr ctxt, int nargs)
{
xsltTransformContextPtr tctxt;
void *data;
tctxt = xsltXPathGetTransformContext(ctxt);
if (tctxt == NULL) {
xsltGenericError(xsltGenericErrorContext,
"xsltExtFunctionTest: failed to get the transformation context\n");
return;
}
data = xsltGetExtData(tctxt, (const xmlChar *) XSLT_DEFAULT_URL);
if (data == NULL) {
xsltGenericError(xsltGenericErrorContext,
"xsltExtFunctionTest: failed to get module data\n");
return;
}
#ifdef WITH_XSLT_DEBUG_FUNCTION
xsltGenericDebug(xsltGenericDebugContext,
"libxslt:test() called with %d args\n", nargs);
#endif
} There is a single call to do this registration: int xsltRegisterExtElement(xsltTransformContextPtr ctxt,
const xmlChar *name,
const xmlChar *URI,
xsltTransformFunction function); It is similar to the mechanism used to register an extension
function,except that the signature of an extension element implementation
isdifferent. The registration is bound to a single transformation instance referred
toby ctxt, name is the UTF8 encoded name for the NCName of the element, and
URIis the namespace name for the extension (no checking is done, a module
couldregister elements for a different namespace, but it is not
recommended). The implementation of the element must have the signature of an
XSLTtransformation function: /**
* xsltTransformFunction:
* @ctxt: the XSLT transformation context
* @node: the input node
* @inst: the stylesheet node
* @comp: the compiled information from the stylesheet
*
* signature of the function associated to elements part of the
* stylesheet language like xsl:if or xsl:apply-templates.
*/
typedef void (*xsltTransformFunction)
(xsltTransformContextPtr ctxt,
xmlNodePtr node,
xmlNodePtr inst,
xsltStylePreCompPtr comp); The first argument is the XSLT transformation context. The second andthird
arguments are xmlNodePtr i.e. internal memory representation of XML nodes. They
arerespectively node from the the input document being
transformedby the stylesheet and inst the extension element in
thestylesheet. The last argument is comp a pointer to a
precompiledrepresentation of inst but usually for an extension
functionthis value is NULL by default (it could be added and
associatedto the instruction in inst->_private ). The same functions are available from a function implementing an
extensionelement as in an extension function,
includingxsltGetExtData() . The goal of an extension element being usually to enrich the
generatedoutput, it is expected that they will grow the currently generated
outputtree. This can be done by grabbing ctxt->insert which is the
currentlibxml node being generated (Note this can also be the intermediate
valuetree being built for example to initialize a variable, the processing
shouldbe similar). The functions for libxml tree manipulation from <libxml/tree.h>canbe
employed to extend or modify the tree, but it is required to preserve
theinsertion node and its ancestors since there are existing pointers to
thoseelements still in use in the XSLT template execution stack. The module libxslt/transform.c contains the sources of the XSLT
built-inelements, including xsl:element, xsl:attribute, xsl:if, etc. There is
a smallbut full example in functions.c providing the implementation for
thelibxslt:test element, it will output a comment in the result tree: /**
* xsltExtElementTest:
* @ctxt: an XSLT processing context
* @node: The current node
* @inst: the instruction in the stylesheet
* @comp: precomputed informations
*
* Process a libxslt:test node
*/
static void
xsltExtElementTest(xsltTransformContextPtr ctxt, xmlNodePtr node,
xmlNodePtr inst,
xsltStylePreCompPtr comp)
{
xmlNodePtr comment;
if (ctxt == NULL) {
xsltGenericError(xsltGenericErrorContext,
"xsltExtElementTest: no transformation context\n");
return;
}
if (node == NULL) {
xsltGenericError(xsltGenericErrorContext,
"xsltExtElementTest: no current node\n");
return;
}
if (inst == NULL) {
xsltGenericError(xsltGenericErrorContext,
"xsltExtElementTest: no instruction\n");
return;
}
if (ctxt->insert == NULL) {
xsltGenericError(xsltGenericErrorContext,
"xsltExtElementTest: no insertion point\n");
return;
}
comment =
xmlNewComment((const xmlChar *)
"libxslt:test element test worked");
xmlAddChild(ctxt->insert, comment);
} When the XSLT processor ends a transformation, the shutdown function (ifit
exists) for each of the modules initialized is called.
ThexsltExtShutdownFunction type defines the interface for a
shutdownfunction: /**
* xsltExtShutdownFunction:
* @ctxt: an XSLT transformation context
* @URI: the namespace URI for the extension
* @data: the data associated to this module
*
* A function called at shutdown time of an XSLT extension module
*/
typedef void (*xsltExtShutdownFunction) (xsltTransformContextPtr ctxt,
const xmlChar *URI,
void *data); This is really similar to a module initialization function except a
thirdargument is passed, it's the value that was returned by the
initializationfunction. This allows the routine to deallocate resources from
the module forexample close the connection to the database to keep the same
example. Well, some of the pieces missing: - a way to load shared libraries to instantiate new modules
- a better detection of extension functions usage and their
registrationwithout having to use the extension prefix which ought to be
reserved toelement extensions.
- more examples
- implementations of the EXSLTcommonextension libraries, Thomas
Broyer nearly finished implementing them.
Daniel Veillard |