blob: 9565e152fbfd1791c7c96b47fe0ee8c1d9841d17 [file] [log] [blame]
/*
* xsltutils.c: Utilities for the XSL Transformation 1.0 engine
*
* Reference:
* http://www.w3.org/TR/1999/REC-xslt-19991116
*
* See Copyright for the status of this software.
*
* daniel@veillard.com
*/
#define IN_LIBXSLT
#include "libxslt.h"
#ifndef XSLT_NEED_TRIO
#include <stdio.h>
#else
#include <trio.h>
#endif
#include <string.h>
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#include <stdarg.h>
#include <libxml/xmlmemory.h>
#include <libxml/tree.h>
#include <libxml/HTMLtree.h>
#include <libxml/xmlerror.h>
#include <libxml/xmlIO.h>
#include "xsltutils.h"
#include "templates.h"
#include "xsltInternals.h"
#include "imports.h"
#include "transform.h"
/* gettimeofday on Windows ??? */
#if defined(WIN32) && !defined(__CYGWIN__)
#ifdef _MSC_VER
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
#define gettimeofday(p1,p2)
#define HAVE_GETTIMEOFDAY
#define XSLT_WIN32_PERFORMANCE_COUNTER
#endif /* _MS_VER */
#endif /* WIN32 */
/************************************************************************
* *
* Convenience function *
* *
************************************************************************/
/**
* xsltGetCNsProp:
* @style: the stylesheet
* @node: the node
* @name: the attribute name
* @nameSpace: the URI of the namespace
*
* Similar to xmlGetNsProp() but with a slightly different semantic
*
* Search and get the value of an attribute associated to a node
* This attribute has to be anchored in the namespace specified,
* or has no namespace and the element is in that namespace.
*
* This does the entity substitution.
* This function looks in DTD attribute declaration for #FIXED or
* default declaration values unless DTD use has been turned off.
*
* Returns the attribute value or NULL if not found. The string is allocated
* in the stylesheet dictionary.
*/
const xmlChar *
xsltGetCNsProp(xsltStylesheetPtr style, xmlNodePtr node,
const xmlChar *name, const xmlChar *nameSpace) {
xmlAttrPtr prop;
xmlDocPtr doc;
xmlNsPtr ns;
xmlChar *tmp;
const xmlChar *ret;
if ((node == NULL) || (style == NULL) || (style->dict == NULL))
return(NULL);
prop = node->properties;
if (nameSpace == NULL) {
return xmlGetProp(node, name);
}
while (prop != NULL) {
/*
* One need to have
* - same attribute names
* - and the attribute carrying that namespace
*/
if ((xmlStrEqual(prop->name, name)) &&
(((prop->ns == NULL) && (node->ns != NULL) &&
(xmlStrEqual(node->ns->href, nameSpace))) ||
((prop->ns != NULL) &&
(xmlStrEqual(prop->ns->href, nameSpace))))) {
tmp = xmlNodeListGetString(node->doc, prop->children, 1);
if (tmp == NULL)
ret = xmlDictLookup(style->dict, BAD_CAST "", 0);
else {
ret = xmlDictLookup(style->dict, tmp, -1);
xmlFree(tmp);
}
return ret;
}
prop = prop->next;
}
tmp = NULL;
/*
* Check if there is a default declaration in the internal
* or external subsets
*/
doc = node->doc;
if (doc != NULL) {
if (doc->intSubset != NULL) {
xmlAttributePtr attrDecl;
attrDecl = xmlGetDtdAttrDesc(doc->intSubset, node->name, name);
if ((attrDecl == NULL) && (doc->extSubset != NULL))
attrDecl = xmlGetDtdAttrDesc(doc->extSubset, node->name, name);
if ((attrDecl != NULL) && (attrDecl->prefix != NULL)) {
/*
* The DTD declaration only allows a prefix search
*/
ns = xmlSearchNs(doc, node, attrDecl->prefix);
if ((ns != NULL) && (xmlStrEqual(ns->href, nameSpace)))
return(xmlDictLookup(style->dict,
attrDecl->defaultValue, -1));
}
}
}
return(NULL);
}
/**
* xsltGetNsProp:
* @node: the node
* @name: the attribute name
* @nameSpace: the URI of the namespace
*
* Similar to xmlGetNsProp() but with a slightly different semantic
*
* Search and get the value of an attribute associated to a node
* This attribute has to be anchored in the namespace specified,
* or has no namespace and the element is in that namespace.
*
* This does the entity substitution.
* This function looks in DTD attribute declaration for #FIXED or
* default declaration values unless DTD use has been turned off.
*
* Returns the attribute value or NULL if not found.
* It's up to the caller to free the memory.
*/
xmlChar *
xsltGetNsProp(xmlNodePtr node, const xmlChar *name, const xmlChar *nameSpace) {
xmlAttrPtr prop;
xmlDocPtr doc;
xmlNsPtr ns;
if (node == NULL)
return(NULL);
prop = node->properties;
/*
* TODO: Substitute xmlGetProp() for xmlGetNsProp(), since the former
* is not namespace-aware and will return an attribute with equal
* name regardless of its namespace.
* Example:
* <xsl:element foo:name="myName"/>
* So this would return "myName" even if an attribute @name
* in the XSLT was requested.
*/
if (nameSpace == NULL)
return(xmlGetProp(node, name));
while (prop != NULL) {
/*
* One need to have
* - same attribute names
* - and the attribute carrying that namespace
*/
if ((xmlStrEqual(prop->name, name)) &&
(((prop->ns == NULL) && (node->ns != NULL) &&
(xmlStrEqual(node->ns->href, nameSpace))) ||
((prop->ns != NULL) &&
(xmlStrEqual(prop->ns->href, nameSpace))))) {
xmlChar *ret;
ret = xmlNodeListGetString(node->doc, prop->children, 1);
if (ret == NULL) return(xmlStrdup((xmlChar *)""));
return(ret);
}
prop = prop->next;
}
/*
* Check if there is a default declaration in the internal
* or external subsets
*/
doc = node->doc;
if (doc != NULL) {
if (doc->intSubset != NULL) {
xmlAttributePtr attrDecl;
attrDecl = xmlGetDtdAttrDesc(doc->intSubset, node->name, name);
if ((attrDecl == NULL) && (doc->extSubset != NULL))
attrDecl = xmlGetDtdAttrDesc(doc->extSubset, node->name, name);
if ((attrDecl != NULL) && (attrDecl->prefix != NULL)) {
/*
* The DTD declaration only allows a prefix search
*/
ns = xmlSearchNs(doc, node, attrDecl->prefix);
if ((ns != NULL) && (xmlStrEqual(ns->href, nameSpace)))
return(xmlStrdup(attrDecl->defaultValue));
}
}
}
return(NULL);
}
/**
* xsltGetUTF8Char:
* @utf: a sequence of UTF-8 encoded bytes
* @len: a pointer to @bytes len
*
* Read one UTF8 Char from @utf
* Function copied from libxml2 xmlGetUTF8Char() ... to discard ultimately
* and use the original API
*
* Returns the char value or -1 in case of error and update @len with the
* number of bytes used
*/
int
xsltGetUTF8Char(const unsigned char *utf, int *len) {
unsigned int c;
if (utf == NULL)
goto error;
if (len == NULL)
goto error;
if (*len < 1)
goto error;
c = utf[0];
if (c & 0x80) {
if (*len < 2)
goto error;
if ((utf[1] & 0xc0) != 0x80)
goto error;
if ((c & 0xe0) == 0xe0) {
if (*len < 3)
goto error;
if ((utf[2] & 0xc0) != 0x80)
goto error;
if ((c & 0xf0) == 0xf0) {
if (*len < 4)
goto error;
if ((c & 0xf8) != 0xf0 || (utf[3] & 0xc0) != 0x80)
goto error;
*len = 4;
/* 4-byte code */
c = (utf[0] & 0x7) << 18;
c |= (utf[1] & 0x3f) << 12;
c |= (utf[2] & 0x3f) << 6;
c |= utf[3] & 0x3f;
} else {
/* 3-byte code */
*len = 3;
c = (utf[0] & 0xf) << 12;
c |= (utf[1] & 0x3f) << 6;
c |= utf[2] & 0x3f;
}
} else {
/* 2-byte code */
*len = 2;
c = (utf[0] & 0x1f) << 6;
c |= utf[1] & 0x3f;
}
} else {
/* 1-byte code */
*len = 1;
}
return(c);
error:
if (len != NULL)
*len = 0;
return(-1);
}
#ifdef XSLT_REFACTORED
/**
* xsltPointerListAddSize:
* @list: the pointer list structure
* @item: the item to be stored
* @initialSize: the initial size of the list
*
* Adds an item to the list.
*
* Returns the position of the added item in the list or
* -1 in case of an error.
*/
int
xsltPointerListAddSize(xsltPointerListPtr list,
void *item,
int initialSize)
{
if (list->items == NULL) {
if (initialSize <= 0)
initialSize = 1;
list->items = (void **) xmlMalloc(
initialSize * sizeof(void *));
if (list->items == NULL) {
xsltGenericError(xsltGenericErrorContext,
"xsltPointerListAddSize: memory allocation failure.\n");
return(-1);
}
list->number = 0;
list->size = initialSize;
} else if (list->size <= list->number) {
list->size *= 2;
list->items = (void **) xmlRealloc(list->items,
list->size * sizeof(void *));
if (list->items == NULL) {
xsltGenericError(xsltGenericErrorContext,
"xsltPointerListAddSize: memory re-allocation failure.\n");
list->size = 0;
return(-1);
}
}
list->items[list->number++] = item;
return(0);
}
/**
* xsltPointerListCreate:
* @initialSize: the initial size for the list
*
* Creates an xsltPointerList structure.
*
* Returns a xsltPointerList structure or NULL in case of an error.
*/
xsltPointerListPtr
xsltPointerListCreate(int initialSize)
{
xsltPointerListPtr ret;
ret = xmlMalloc(sizeof(xsltPointerList));
if (ret == NULL) {
xsltGenericError(xsltGenericErrorContext,
"xsltPointerListCreate: memory allocation failure.\n");
return (NULL);
}
memset(ret, 0, sizeof(xsltPointerList));
if (initialSize > 0) {
xsltPointerListAddSize(ret, NULL, initialSize);
ret->number = 0;
}
return (ret);
}
/**
* xsltPointerListFree:
* @list: pointer to the list to be freed
*
* Frees the xsltPointerList structure. This does not free
* the content of the list.
*/
void
xsltPointerListFree(xsltPointerListPtr list)
{
if (list == NULL)
return;
if (list->items != NULL)
xmlFree(list->items);
xmlFree(list);
}
/**
* xsltPointerListClear:
* @list: pointer to the list to be cleared
*
* Resets the list, but does not free the allocated array
* and does not free the content of the list.
*/
void
xsltPointerListClear(xsltPointerListPtr list)
{
if (list->items != NULL) {
xmlFree(list->items);
list->items = NULL;
}
list->number = 0;
list->size = 0;
}
#endif /* XSLT_REFACTORED */
/************************************************************************
* *
* Handling of XSLT stylesheets messages *
* *
************************************************************************/
/**
* xsltMessage:
* @ctxt: an XSLT processing context
* @node: The current node
* @inst: The node containing the message instruction
*
* Process and xsl:message construct
*/
void
xsltMessage(xsltTransformContextPtr ctxt, xmlNodePtr node, xmlNodePtr inst) {
xmlGenericErrorFunc error = xsltGenericError;
void *errctx = xsltGenericErrorContext;
xmlChar *prop, *message;
int terminate = 0;
if ((ctxt == NULL) || (inst == NULL))
return;
if (ctxt->error != NULL) {
error = ctxt->error;
errctx = ctxt->errctx;
}
prop = xmlGetNsProp(inst, (const xmlChar *)"terminate", NULL);
if (prop != NULL) {
if (xmlStrEqual(prop, (const xmlChar *)"yes")) {
terminate = 1;
} else if (xmlStrEqual(prop, (const xmlChar *)"no")) {
terminate = 0;
} else {
error(errctx,
"xsl:message : terminate expecting 'yes' or 'no'\n");
ctxt->state = XSLT_STATE_ERROR;
}
xmlFree(prop);
}
message = xsltEvalTemplateString(ctxt, node, inst);
if (message != NULL) {
int len = xmlStrlen(message);
error(errctx, "%s", (const char *)message);
if ((len > 0) && (message[len - 1] != '\n'))
error(errctx, "\n");
xmlFree(message);
}
if (terminate)
ctxt->state = XSLT_STATE_STOPPED;
}
/************************************************************************
* *
* Handling of out of context errors *
* *
************************************************************************/
#define XSLT_GET_VAR_STR(msg, str) { \
int size; \
int chars; \
char *larger; \
va_list ap; \
\
str = (char *) xmlMalloc(150); \
if (str == NULL) \
return; \
\
size = 150; \
\
while (size < 64000) { \
va_start(ap, msg); \
chars = vsnprintf(str, size, msg, ap); \
va_end(ap); \
if ((chars > -1) && (chars < size)) \
break; \
if (chars > -1) \
size += chars + 1; \
else \
size += 100; \
if ((larger = (char *) xmlRealloc(str, size)) == NULL) {\
xmlFree(str); \
return; \
} \
str = larger; \
} \
}
/**
* xsltGenericErrorDefaultFunc:
* @ctx: an error context
* @msg: the message to display/transmit
* @...: extra parameters for the message display
*
* Default handler for out of context error messages.
*/
static void
xsltGenericErrorDefaultFunc(void *ctx ATTRIBUTE_UNUSED, const char *msg, ...) {
va_list args;
if (xsltGenericErrorContext == NULL)
xsltGenericErrorContext = (void *) stderr;
va_start(args, msg);
vfprintf((FILE *)xsltGenericErrorContext, msg, args);
va_end(args);
}
xmlGenericErrorFunc xsltGenericError = xsltGenericErrorDefaultFunc;
void *xsltGenericErrorContext = NULL;
/**
* xsltSetGenericErrorFunc:
* @ctx: the new error handling context
* @handler: the new handler function
*
* Function to reset the handler and the error context for out of
* context error messages.
* This simply means that @handler will be called for subsequent
* error messages while not parsing nor validating. And @ctx will
* be passed as first argument to @handler
* One can simply force messages to be emitted to another FILE * than
* stderr by setting @ctx to this file handle and @handler to NULL.
*/
void
xsltSetGenericErrorFunc(void *ctx, xmlGenericErrorFunc handler) {
xsltGenericErrorContext = ctx;
if (handler != NULL)
xsltGenericError = handler;
else
xsltGenericError = xsltGenericErrorDefaultFunc;
}
/**
* xsltGenericDebugDefaultFunc:
* @ctx: an error context
* @msg: the message to display/transmit
* @...: extra parameters for the message display
*
* Default handler for out of context error messages.
*/
static void
xsltGenericDebugDefaultFunc(void *ctx ATTRIBUTE_UNUSED, const char *msg, ...) {
va_list args;
if (xsltGenericDebugContext == NULL)
return;
va_start(args, msg);
vfprintf((FILE *)xsltGenericDebugContext, msg, args);
va_end(args);
}
xmlGenericErrorFunc xsltGenericDebug = xsltGenericDebugDefaultFunc;
void *xsltGenericDebugContext = NULL;
/**
* xsltSetGenericDebugFunc:
* @ctx: the new error handling context
* @handler: the new handler function
*
* Function to reset the handler and the error context for out of
* context error messages.
* This simply means that @handler will be called for subsequent
* error messages while not parsing or validating. And @ctx will
* be passed as first argument to @handler
* One can simply force messages to be emitted to another FILE * than
* stderr by setting @ctx to this file handle and @handler to NULL.
*/
void
xsltSetGenericDebugFunc(void *ctx, xmlGenericErrorFunc handler) {
xsltGenericDebugContext = ctx;
if (handler != NULL)
xsltGenericDebug = handler;
else
xsltGenericDebug = xsltGenericDebugDefaultFunc;
}
/**
* xsltPrintErrorContext:
* @ctxt: the transformation context
* @style: the stylesheet
* @node: the current node being processed
*
* Display the context of an error.
*/
void
xsltPrintErrorContext(xsltTransformContextPtr ctxt,
xsltStylesheetPtr style, xmlNodePtr node) {
int line = 0;
const xmlChar *file = NULL;
const xmlChar *name = NULL;
const char *type = "error";
xmlGenericErrorFunc error = xsltGenericError;
void *errctx = xsltGenericErrorContext;
if (ctxt != NULL) {
ctxt->state = XSLT_STATE_ERROR;
if (ctxt->error != NULL) {
error = ctxt->error;
errctx = ctxt->errctx;
}
}
if ((node == NULL) && (ctxt != NULL))
node = ctxt->inst;
if (node != NULL) {
if ((node->type == XML_DOCUMENT_NODE) ||
(node->type == XML_HTML_DOCUMENT_NODE)) {
xmlDocPtr doc = (xmlDocPtr) node;
file = doc->URL;
} else {
line = xmlGetLineNo(node);
if ((node->doc != NULL) && (node->doc->URL != NULL))
file = node->doc->URL;
if (node->name != NULL)
name = node->name;
}
}
if (ctxt != NULL)
type = "runtime error";
else if (style != NULL) {
#ifdef XSLT_REFACTORED
if (XSLT_CCTXT(style)->errSeverity == XSLT_ERROR_SEVERITY_WARNING)
type = "compilation warning";
else
type = "compilation error";
#else
type = "compilation error";
#endif
}
if ((file != NULL) && (line != 0) && (name != NULL))
error(errctx, "%s: file %s line %d element %s\n",
type, file, line, name);
else if ((file != NULL) && (name != NULL))
error(errctx, "%s: file %s element %s\n", type, file, name);
else if ((file != NULL) && (line != 0))
error(errctx, "%s: file %s line %d\n", type, file, line);
else if (file != NULL)
error(errctx, "%s: file %s\n", type, file);
else if (name != NULL)
error(errctx, "%s: element %s\n", type, name);
else
error(errctx, "%s\n", type);
}
/**
* xsltSetTransformErrorFunc:
* @ctxt: the XSLT transformation context
* @ctx: the new error handling context
* @handler: the new handler function
*
* Function to reset the handler and the error context for out of
* context error messages specific to a given XSLT transromation.
*
* This simply means that @handler will be called for subsequent
* error messages while running the transformation.
*/
void
xsltSetTransformErrorFunc(xsltTransformContextPtr ctxt,
void *ctx, xmlGenericErrorFunc handler)
{
ctxt->error = handler;
ctxt->errctx = ctx;
}
/**
* xsltTransformError:
* @ctxt: an XSLT transformation context
* @style: the XSLT stylesheet used
* @node: the current node in the stylesheet
* @msg: the message to display/transmit
* @...: extra parameters for the message display
*
* Display and format an error messages, gives file, line, position and
* extra parameters, will use the specific transformation context if available
*/
void
xsltTransformError(xsltTransformContextPtr ctxt,
xsltStylesheetPtr style,
xmlNodePtr node,
const char *msg, ...) {
xmlGenericErrorFunc error = xsltGenericError;
void *errctx = xsltGenericErrorContext;
char * str;
if (ctxt != NULL) {
ctxt->state = XSLT_STATE_ERROR;
if (ctxt->error != NULL) {
error = ctxt->error;
errctx = ctxt->errctx;
}
}
if ((node == NULL) && (ctxt != NULL))
node = ctxt->inst;
xsltPrintErrorContext(ctxt, style, node);
XSLT_GET_VAR_STR(msg, str);
error(errctx, "%s", str);
if (str != NULL)
xmlFree(str);
}
/************************************************************************
* *
* QNames *
* *
************************************************************************/
/**
* xsltSplitQName:
* @dict: a dictionary
* @name: the full QName
* @prefix: the return value
*
* Split QNames into prefix and local names, both allocated from a dictionary.
*
* Returns: the localname or NULL in case of error.
*/
const xmlChar *
xsltSplitQName(xmlDictPtr dict, const xmlChar *name, const xmlChar **prefix) {
int len = 0;
const xmlChar *ret = NULL;
*prefix = NULL;
if ((name == NULL) || (dict == NULL)) return(NULL);
if (name[0] == ':')
return(xmlDictLookup(dict, name, -1));
while ((name[len] != 0) && (name[len] != ':')) len++;
if (name[len] == 0) return(xmlDictLookup(dict, name, -1));
*prefix = xmlDictLookup(dict, name, len);
ret = xmlDictLookup(dict, &name[len + 1], -1);
return(ret);
}
/**
* xsltGetQNameURI:
* @node: the node holding the QName
* @name: pointer to the initial QName value
*
* This function analyzes @name, if the name contains a prefix,
* the function seaches the associated namespace in scope for it.
* It will also replace @name value with the NCName, the old value being
* freed.
* Errors in the prefix lookup are signalled by setting @name to NULL.
*
* NOTE: the namespace returned is a pointer to the place where it is
* defined and hence has the same lifespan as the document holding it.
*
* Returns the namespace URI if there is a prefix, or NULL if @name is
* not prefixed.
*/
const xmlChar *
xsltGetQNameURI(xmlNodePtr node, xmlChar ** name)
{
int len = 0;
xmlChar *qname;
xmlNsPtr ns;
if (name == NULL)
return(NULL);
qname = *name;
if ((qname == NULL) || (*qname == 0))
return(NULL);
if (node == NULL) {
xsltGenericError(xsltGenericErrorContext,
"QName: no element for namespace lookup %s\n",
qname);
xmlFree(qname);
*name = NULL;
return(NULL);
}
/* nasty but valid */
if (qname[0] == ':')
return(NULL);
/*
* we are not trying to validate but just to cut, and yes it will
* work even if this is a set of UTF-8 encoded chars
*/
while ((qname[len] != 0) && (qname[len] != ':'))
len++;
if (qname[len] == 0)
return(NULL);
/*
* handle xml: separately, this one is magical
*/
if ((qname[0] == 'x') && (qname[1] == 'm') &&
(qname[2] == 'l') && (qname[3] == ':')) {
if (qname[4] == 0)
return(NULL);
*name = xmlStrdup(&qname[4]);
xmlFree(qname);
return(XML_XML_NAMESPACE);
}
qname[len] = 0;
ns = xmlSearchNs(node->doc, node, qname);
if (ns == NULL) {
xsltGenericError(xsltGenericErrorContext,
"%s:%s : no namespace bound to prefix %s\n",
qname, &qname[len + 1], qname);
*name = NULL;
xmlFree(qname);
return(NULL);
}
*name = xmlStrdup(&qname[len + 1]);
xmlFree(qname);
return(ns->href);
}
/**
* xsltGetQNameURI2:
* @style: stylesheet pointer
* @node: the node holding the QName
* @name: pointer to the initial QName value
*
* This function is similar to xsltGetQNameURI, but is used when
* @name is a dictionary entry.
*
* Returns the namespace URI if there is a prefix, or NULL if @name is
* not prefixed.
*/
const xmlChar *
xsltGetQNameURI2(xsltStylesheetPtr style, xmlNodePtr node,
const xmlChar **name) {
int len = 0;
xmlChar *qname;
xmlNsPtr ns;
if (name == NULL)
return(NULL);
qname = (xmlChar *)*name;
if ((qname == NULL) || (*qname == 0))
return(NULL);
if (node == NULL) {
xsltGenericError(xsltGenericErrorContext,
"QName: no element for namespace lookup %s\n",
qname);
*name = NULL;
return(NULL);
}
/*
* we are not trying to validate but just to cut, and yes it will
* work even if this is a set of UTF-8 encoded chars
*/
while ((qname[len] != 0) && (qname[len] != ':'))
len++;
if (qname[len] == 0)
return(NULL);
/*
* handle xml: separately, this one is magical
*/
if ((qname[0] == 'x') && (qname[1] == 'm') &&
(qname[2] == 'l') && (qname[3] == ':')) {
if (qname[4] == 0)
return(NULL);
*name = xmlDictLookup(style->dict, &qname[4], -1);
return(XML_XML_NAMESPACE);
}
qname = xmlStrndup(*name, len);
ns = xmlSearchNs(node->doc, node, qname);
if (ns == NULL) {
if (style) {
xsltTransformError(NULL, style, node,
"No namespace bound to prefix '%s'.\n",
qname);
style->errors++;
} else {
xsltGenericError(xsltGenericErrorContext,
"%s : no namespace bound to prefix %s\n",
*name, qname);
}
*name = NULL;
xmlFree(qname);
return(NULL);
}
*name = xmlDictLookup(style->dict, (*name)+len+1, -1);
xmlFree(qname);
return(ns->href);
}
/************************************************************************
* *
* Sorting *
* *
************************************************************************/
/**
* xsltDocumentSortFunction:
* @list: the node set
*
* reorder the current node list @list accordingly to the document order
* This function is slow, obsolete and should not be used anymore.
*/
void
xsltDocumentSortFunction(xmlNodeSetPtr list) {
int i, j;
int len, tst;
xmlNodePtr node;
if (list == NULL)
return;
len = list->nodeNr;
if (len <= 1)
return;
/* TODO: sort is really not optimized, does it needs to ? */
for (i = 0;i < len -1;i++) {
for (j = i + 1; j < len; j++) {
tst = xmlXPathCmpNodes(list->nodeTab[i], list->nodeTab[j]);
if (tst == -1) {
node = list->nodeTab[i];
list->nodeTab[i] = list->nodeTab[j];
list->nodeTab[j] = node;
}
}
}
}
/**
* xsltComputeSortResult:
* @ctxt: a XSLT process context
* @sort: node list
*
* reorder the current node list accordingly to the set of sorting
* requirement provided by the array of nodes.
*
* Returns a ordered XPath nodeset or NULL in case of error.
*/
xmlXPathObjectPtr *
xsltComputeSortResult(xsltTransformContextPtr ctxt, xmlNodePtr sort) {
#ifdef XSLT_REFACTORED
xsltStyleItemSortPtr comp;
#else
xsltStylePreCompPtr comp;
#endif
xmlXPathObjectPtr *results = NULL;
xmlNodeSetPtr list = NULL;
xmlXPathObjectPtr res;
int len = 0;
int i;
xmlNodePtr oldNode;
xmlNodePtr oldInst;
int oldPos, oldSize ;
int oldNsNr;
xmlNsPtr *oldNamespaces;
comp = sort->psvi;
if (comp == NULL) {
xsltGenericError(xsltGenericErrorContext,
"xsl:sort : compilation failed\n");
return(NULL);
}
if ((comp->select == NULL) || (comp->comp == NULL))
return(NULL);
list = ctxt->nodeList;
if ((list == NULL) || (list->nodeNr <= 1))
return(NULL);
len = list->nodeNr;
/* TODO: xsl:sort lang attribute */
/* TODO: xsl:sort case-order attribute */
results = xmlMalloc(len * sizeof(xmlXPathObjectPtr));
if (results == NULL) {
xsltGenericError(xsltGenericErrorContext,
"xsltComputeSortResult: memory allocation failure\n");
return(NULL);
}
oldNode = ctxt->node;
oldInst = ctxt->inst;
oldPos = ctxt->xpathCtxt->proximityPosition;
oldSize = ctxt->xpathCtxt->contextSize;
oldNsNr = ctxt->xpathCtxt->nsNr;
oldNamespaces = ctxt->xpathCtxt->namespaces;
for (i = 0;i < len;i++) {
ctxt->inst = sort;
ctxt->xpathCtxt->contextSize = len;
ctxt->xpathCtxt->proximityPosition = i + 1;
ctxt->node = list->nodeTab[i];
ctxt->xpathCtxt->node = ctxt->node;
#ifdef XSLT_REFACTORED
if (comp->inScopeNs != NULL) {
ctxt->xpathCtxt->namespaces = comp->inScopeNs->list;
ctxt->xpathCtxt->nsNr = comp->inScopeNs->xpathNumber;
} else {
ctxt->xpathCtxt->namespaces = NULL;
ctxt->xpathCtxt->nsNr = 0;
}
#else
ctxt->xpathCtxt->namespaces = comp->nsList;
ctxt->xpathCtxt->nsNr = comp->nsNr;
#endif
res = xmlXPathCompiledEval(comp->comp, ctxt->xpathCtxt);
if (res != NULL) {
if (res->type != XPATH_STRING)
res = xmlXPathConvertString(res);
if (comp->number)
res = xmlXPathConvertNumber(res);
res->index = i; /* Save original pos for dupl resolv */
if (comp->number) {
if (res->type == XPATH_NUMBER) {
results[i] = res;
} else {
#ifdef WITH_XSLT_DEBUG_PROCESS
xsltGenericDebug(xsltGenericDebugContext,
"xsltComputeSortResult: select didn't evaluate to a number\n");
#endif
results[i] = NULL;
}
} else {
if (res->type == XPATH_STRING) {
if (comp->locale != (xsltLocale)0) {
xmlChar *str = res->stringval;
res->stringval = (xmlChar *) xsltStrxfrm(comp->locale, str);
xmlFree(str);
}
results[i] = res;
} else {
#ifdef WITH_XSLT_DEBUG_PROCESS
xsltGenericDebug(xsltGenericDebugContext,
"xsltComputeSortResult: select didn't evaluate to a string\n");
#endif
results[i] = NULL;
}
}
} else {
ctxt->state = XSLT_STATE_STOPPED;
results[i] = NULL;
}
}
ctxt->node = oldNode;
ctxt->inst = oldInst;
ctxt->xpathCtxt->contextSize = oldSize;
ctxt->xpathCtxt->proximityPosition = oldPos;
ctxt->xpathCtxt->nsNr = oldNsNr;
ctxt->xpathCtxt->namespaces = oldNamespaces;
return(results);
}
/**
* xsltDefaultSortFunction:
* @ctxt: a XSLT process context
* @sorts: array of sort nodes
* @nbsorts: the number of sorts in the array
*
* reorder the current node list accordingly to the set of sorting
* requirement provided by the arry of nodes.
*/
void
xsltDefaultSortFunction(xsltTransformContextPtr ctxt, xmlNodePtr *sorts,
int nbsorts) {
#ifdef XSLT_REFACTORED
xsltStyleItemSortPtr comp;
#else
xsltStylePreCompPtr comp;
#endif
xmlXPathObjectPtr *resultsTab[XSLT_MAX_SORT];
xmlXPathObjectPtr *results = NULL, *res;
xmlNodeSetPtr list = NULL;
int descending, number, desc, numb;
int len = 0;
int i, j, incr;
int tst;
int depth;
xmlNodePtr node;
xmlXPathObjectPtr tmp;
int tempstype[XSLT_MAX_SORT], temporder[XSLT_MAX_SORT];
if ((ctxt == NULL) || (sorts == NULL) || (nbsorts <= 0) ||
(nbsorts >= XSLT_MAX_SORT))
return;
if (sorts[0] == NULL)
return;
comp = sorts[0]->psvi;
if (comp == NULL)
return;
list = ctxt->nodeList;
if ((list == NULL) || (list->nodeNr <= 1))
return; /* nothing to do */
for (j = 0; j < nbsorts; j++) {
comp = sorts[j]->psvi;
tempstype[j] = 0;
if ((comp->stype == NULL) && (comp->has_stype != 0)) {
comp->stype =
xsltEvalAttrValueTemplate(ctxt, sorts[j],
(const xmlChar *) "data-type",
XSLT_NAMESPACE);
if (comp->stype != NULL) {
tempstype[j] = 1;
if (xmlStrEqual(comp->stype, (const xmlChar *) "text"))
comp->number = 0;
else if (xmlStrEqual(comp->stype, (const xmlChar *) "number"))
comp->number = 1;
else {
xsltTransformError(ctxt, NULL, sorts[j],
"xsltDoSortFunction: no support for data-type = %s\n",
comp->stype);
comp->number = 0; /* use default */
}
}
}
temporder[j] = 0;
if ((comp->order == NULL) && (comp->has_order != 0)) {
comp->order = xsltEvalAttrValueTemplate(ctxt, sorts[j],
(const xmlChar *) "order",
XSLT_NAMESPACE);
if (comp->order != NULL) {
temporder[j] = 1;
if (xmlStrEqual(comp->order, (const xmlChar *) "ascending"))
comp->descending = 0;
else if (xmlStrEqual(comp->order,
(const xmlChar *) "descending"))
comp->descending = 1;
else {
xsltTransformError(ctxt, NULL, sorts[j],
"xsltDoSortFunction: invalid value %s for order\n",
comp->order);
comp->descending = 0; /* use default */
}
}
}
}
len = list->nodeNr;
resultsTab[0] = xsltComputeSortResult(ctxt, sorts[0]);
for (i = 1;i < XSLT_MAX_SORT;i++)
resultsTab[i] = NULL;
results = resultsTab[0];
comp = sorts[0]->psvi;
descending = comp->descending;
number = comp->number;
if (results == NULL)
return;
/* Shell's sort of node-set */
for (incr = len / 2; incr > 0; incr /= 2) {
for (i = incr; i < len; i++) {
j = i - incr;
if (results[i] == NULL)
continue;
while (j >= 0) {
if (results[j] == NULL)
tst = 1;
else {
if (number) {
/* We make NaN smaller than number in accordance
with XSLT spec */
if (xmlXPathIsNaN(results[j]->floatval)) {
if (xmlXPathIsNaN(results[j + incr]->floatval))
tst = 0;
else
tst = -1;
} else if (xmlXPathIsNaN(results[j + incr]->floatval))
tst = 1;
else if (results[j]->floatval ==
results[j + incr]->floatval)
tst = 0;
else if (results[j]->floatval >
results[j + incr]->floatval)
tst = 1;
else tst = -1;
} else if(comp->locale != (xsltLocale)0) {
tst = xsltLocaleStrcmp(
comp->locale,
(xsltLocaleChar *) results[j]->stringval,
(xsltLocaleChar *) results[j + incr]->stringval);
} else {
tst = xmlStrcmp(results[j]->stringval,
results[j + incr]->stringval);
}
if (descending)
tst = -tst;
}
if (tst == 0) {
/*
* Okay we need to use multi level sorts
*/
depth = 1;
while (depth < nbsorts) {
if (sorts[depth] == NULL)
break;
comp = sorts[depth]->psvi;
if (comp == NULL)
break;
desc = comp->descending;
numb = comp->number;
/*
* Compute the result of the next level for the
* full set, this might be optimized ... or not
*/
if (resultsTab[depth] == NULL)
resultsTab[depth] = xsltComputeSortResult(ctxt,
sorts[depth]);
res = resultsTab[depth];
if (res == NULL)
break;
if (res[j] == NULL) {
if (res[j+incr] != NULL)
tst = 1;
} else {
if (numb) {
/* We make NaN smaller than number in
accordance with XSLT spec */
if (xmlXPathIsNaN(res[j]->floatval)) {
if (xmlXPathIsNaN(res[j +
incr]->floatval))
tst = 0;
else
tst = -1;
} else if (xmlXPathIsNaN(res[j + incr]->
floatval))
tst = 1;
else if (res[j]->floatval == res[j + incr]->
floatval)
tst = 0;
else if (res[j]->floatval >
res[j + incr]->floatval)
tst = 1;
else tst = -1;
} else if(comp->locale != (xsltLocale)0) {
tst = xsltLocaleStrcmp(
comp->locale,
(xsltLocaleChar *) res[j]->stringval,
(xsltLocaleChar *) res[j + incr]->stringval);
} else {
tst = xmlStrcmp(res[j]->stringval,
res[j + incr]->stringval);
}
if (desc)
tst = -tst;
}
/*
* if we still can't differenciate at this level
* try one level deeper.
*/
if (tst != 0)
break;
depth++;
}
}
if (tst == 0) {
tst = results[j]->index > results[j + incr]->index;
}
if (tst > 0) {
tmp = results[j];
results[j] = results[j + incr];
results[j + incr] = tmp;
node = list->nodeTab[j];
list->nodeTab[j] = list->nodeTab[j + incr];
list->nodeTab[j + incr] = node;
depth = 1;
while (depth < nbsorts) {
if (sorts[depth] == NULL)
break;
if (resultsTab[depth] == NULL)
break;
res = resultsTab[depth];
tmp = res[j];
res[j] = res[j + incr];
res[j + incr] = tmp;
depth++;
}
j -= incr;
} else
break;
}
}
}
for (j = 0; j < nbsorts; j++) {
comp = sorts[j]->psvi;
if (tempstype[j] == 1) {
/* The data-type needs to be recomputed each time */
xmlFree((void *)(comp->stype));
comp->stype = NULL;
}
if (temporder[j] == 1) {
/* The order needs to be recomputed each time */
xmlFree((void *)(comp->order));
comp->order = NULL;
}
if (resultsTab[j] != NULL) {
for (i = 0;i < len;i++)
xmlXPathFreeObject(resultsTab[j][i]);
xmlFree(resultsTab[j]);
}
}
}
static xsltSortFunc xsltSortFunction = xsltDefaultSortFunction;
/**
* xsltDoSortFunction:
* @ctxt: a XSLT process context
* @sorts: array of sort nodes
* @nbsorts: the number of sorts in the array
*
* reorder the current node list accordingly to the set of sorting
* requirement provided by the arry of nodes.
* This is a wrapper function, the actual function used is specified
* using xsltSetCtxtSortFunc() to set the context specific sort function,
* or xsltSetSortFunc() to set the global sort function.
* If a sort function is set on the context, this will get called.
* Otherwise the global sort function is called.
*/
void
xsltDoSortFunction(xsltTransformContextPtr ctxt, xmlNodePtr * sorts,
int nbsorts)
{
if (ctxt->sortfunc != NULL)
(ctxt->sortfunc)(ctxt, sorts, nbsorts);
else if (xsltSortFunction != NULL)
xsltSortFunction(ctxt, sorts, nbsorts);
}
/**
* xsltSetSortFunc:
* @handler: the new handler function
*
* Function to reset the global handler for XSLT sorting.
* If the handler is NULL, the default sort function will be used.
*/
void
xsltSetSortFunc(xsltSortFunc handler) {
if (handler != NULL)
xsltSortFunction = handler;
else
xsltSortFunction = xsltDefaultSortFunction;
}
/**
* xsltSetCtxtSortFunc:
* @ctxt: a XSLT process context
* @handler: the new handler function
*
* Function to set the handler for XSLT sorting
* for the specified context.
* If the handler is NULL, then the global
* sort function will be called
*/
void
xsltSetCtxtSortFunc(xsltTransformContextPtr ctxt, xsltSortFunc handler) {
ctxt->sortfunc = handler;
}
/************************************************************************
* *
* Parsing options *
* *
************************************************************************/
/**
* xsltSetCtxtParseOptions:
* @ctxt: a XSLT process context
* @options: a combination of libxml2 xmlParserOption
*
* Change the default parser option passed by the XSLT engine to the
* parser when using document() loading.
*
* Returns the previous options or -1 in case of error
*/
int
xsltSetCtxtParseOptions(xsltTransformContextPtr ctxt, int options)
{
int oldopts;
if (ctxt == NULL)
return(-1);
oldopts = ctxt->parserOptions;
if (ctxt->xinclude)
oldopts |= XML_PARSE_XINCLUDE;
ctxt->parserOptions = options;
if (options & XML_PARSE_XINCLUDE)
ctxt->xinclude = 1;
else
ctxt->xinclude = 0;
return(oldopts);
}
/************************************************************************
* *
* Output *
* *
************************************************************************/
/**
* xsltSaveResultTo:
* @buf: an output buffer
* @result: the result xmlDocPtr
* @style: the stylesheet
*
* Save the result @result obtained by applying the @style stylesheet
* to an I/O output channel @buf
*
* Returns the number of byte written or -1 in case of failure.
*/
int
xsltSaveResultTo(xmlOutputBufferPtr buf, xmlDocPtr result,
xsltStylesheetPtr style) {
const xmlChar *encoding;
int base;
const xmlChar *method;
int indent;
if ((buf == NULL) || (result == NULL) || (style == NULL))
return(-1);
if ((result->children == NULL) ||
((result->children->type == XML_DTD_NODE) &&
(result->children->next == NULL)))
return(0);
if ((style->methodURI != NULL) &&
((style->method == NULL) ||
(!xmlStrEqual(style->method, (const xmlChar *) "xhtml")))) {
xsltGenericError(xsltGenericErrorContext,
"xsltSaveResultTo : unknown ouput method\n");
return(-1);
}
base = buf->written;
XSLT_GET_IMPORT_PTR(method, style, method)
XSLT_GET_IMPORT_PTR(encoding, style, encoding)
XSLT_GET_IMPORT_INT(indent, style, indent);
if ((method == NULL) && (result->type == XML_HTML_DOCUMENT_NODE))
method = (const xmlChar *) "html";
if ((method != NULL) &&
(xmlStrEqual(method, (const xmlChar *) "html"))) {
if (encoding != NULL) {
htmlSetMetaEncoding(result, (const xmlChar *) encoding);
} else {
htmlSetMetaEncoding(result, (const xmlChar *) "UTF-8");
}
if (indent == -1)
indent = 1;
htmlDocContentDumpFormatOutput(buf, result, (const char *) encoding,
indent);
xmlOutputBufferFlush(buf);
} else if ((method != NULL) &&
(xmlStrEqual(method, (const xmlChar *) "xhtml"))) {
if (encoding != NULL) {
htmlSetMetaEncoding(result, (const xmlChar *) encoding);
} else {
htmlSetMetaEncoding(result, (const xmlChar *) "UTF-8");
}
htmlDocContentDumpOutput(buf, result, (const char *) encoding);
xmlOutputBufferFlush(buf);
} else if ((method != NULL) &&
(xmlStrEqual(method, (const xmlChar *) "text"))) {
xmlNodePtr cur;
cur = result->children;
while (cur != NULL) {
if (cur->type == XML_TEXT_NODE)
xmlOutputBufferWriteString(buf, (const char *) cur->content);
/*
* Skip to next node
*/
if (cur->children != NULL) {
if ((cur->children->type != XML_ENTITY_DECL) &&
(cur->children->type != XML_ENTITY_REF_NODE) &&
(cur->children->type != XML_ENTITY_NODE)) {
cur = cur->children;
continue;
}
}
if (cur->next != NULL) {
cur = cur->next;
continue;
}
do {
cur = cur->parent;
if (cur == NULL)
break;
if (cur == (xmlNodePtr) style->doc) {
cur = NULL;
break;
}
if (cur->next != NULL) {
cur = cur->next;
break;
}
} while (cur != NULL);
}
xmlOutputBufferFlush(buf);
} else {
int omitXmlDecl;
int standalone;
XSLT_GET_IMPORT_INT(omitXmlDecl, style, omitXmlDeclaration);
XSLT_GET_IMPORT_INT(standalone, style, standalone);
if (omitXmlDecl != 1) {
xmlOutputBufferWriteString(buf, "<?xml version=");
if (result->version != NULL)
xmlBufferWriteQuotedString(buf->buffer, result->version);
else
xmlOutputBufferWriteString(buf, "\"1.0\"");
if (encoding == NULL) {
if (result->encoding != NULL)
encoding = result->encoding;
else if (result->charset != XML_CHAR_ENCODING_UTF8)
encoding = (const xmlChar *)
xmlGetCharEncodingName((xmlCharEncoding)
result->charset);
}
if (encoding != NULL) {
xmlOutputBufferWriteString(buf, " encoding=");
xmlBufferWriteQuotedString(buf->buffer, (xmlChar *) encoding);
}
switch (standalone) {
case 0:
xmlOutputBufferWriteString(buf, " standalone=\"no\"");
break;
case 1:
xmlOutputBufferWriteString(buf, " standalone=\"yes\"");
break;
default:
break;
}
xmlOutputBufferWriteString(buf, "?>\n");
}
if (result->children != NULL) {
xmlNodePtr child = result->children;
while (child != NULL) {
xmlNodeDumpOutput(buf, result, child, 0, (indent == 1),
(const char *) encoding);
if ((child->type == XML_DTD_NODE) ||
((child->type == XML_COMMENT_NODE) &&
(child->next != NULL)))
xmlOutputBufferWriteString(buf, "\n");
child = child->next;
}
xmlOutputBufferWriteString(buf, "\n");
}
xmlOutputBufferFlush(buf);
}
return(buf->written - base);
}
/**
* xsltSaveResultToFilename:
* @URL: a filename or URL
* @result: the result xmlDocPtr
* @style: the stylesheet
* @compression: the compression factor (0 - 9 included)
*
* Save the result @result obtained by applying the @style stylesheet
* to a file or @URL
*
* Returns the number of byte written or -1 in case of failure.
*/
int
xsltSaveResultToFilename(const char *URL, xmlDocPtr result,
xsltStylesheetPtr style, int compression) {
xmlOutputBufferPtr buf;
const xmlChar *encoding;
int ret;
if ((URL == NULL) || (result == NULL) || (style == NULL))
return(-1);
if (result->children == NULL)
return(0);
XSLT_GET_IMPORT_PTR(encoding, style, encoding)
if (encoding != NULL) {
xmlCharEncodingHandlerPtr encoder;
encoder = xmlFindCharEncodingHandler((char *)encoding);
if ((encoder != NULL) &&
(xmlStrEqual((const xmlChar *)encoder->name,
(const xmlChar *) "UTF-8")))
encoder = NULL;
buf = xmlOutputBufferCreateFilename(URL, encoder, compression);
} else {
buf = xmlOutputBufferCreateFilename(URL, NULL, compression);
}
if (buf == NULL)
return(-1);
xsltSaveResultTo(buf, result, style);
ret = xmlOutputBufferClose(buf);
return(ret);
}
/**
* xsltSaveResultToFile:
* @file: a FILE * I/O
* @result: the result xmlDocPtr
* @style: the stylesheet
*
* Save the result @result obtained by applying the @style stylesheet
* to an open FILE * I/O.
* This does not close the FILE @file
*
* Returns the number of bytes written or -1 in case of failure.
*/
int
xsltSaveResultToFile(FILE *file, xmlDocPtr result, xsltStylesheetPtr style) {
xmlOutputBufferPtr buf;
const xmlChar *encoding;
int ret;
if ((file == NULL) || (result == NULL) || (style == NULL))
return(-1);
if (result->children == NULL)
return(0);
XSLT_GET_IMPORT_PTR(encoding, style, encoding)
if (encoding != NULL) {
xmlCharEncodingHandlerPtr encoder;
encoder = xmlFindCharEncodingHandler((char *)encoding);
if ((encoder != NULL) &&
(xmlStrEqual((const xmlChar *)encoder->name,
(const xmlChar *) "UTF-8")))
encoder = NULL;
buf = xmlOutputBufferCreateFile(file, encoder);
} else {
buf = xmlOutputBufferCreateFile(file, NULL);
}
if (buf == NULL)
return(-1);
xsltSaveResultTo(buf, result, style);
ret = xmlOutputBufferClose(buf);
return(ret);
}
/**
* xsltSaveResultToFd:
* @fd: a file descriptor
* @result: the result xmlDocPtr
* @style: the stylesheet
*
* Save the result @result obtained by applying the @style stylesheet
* to an open file descriptor
* This does not close the descriptor.
*
* Returns the number of bytes written or -1 in case of failure.
*/
int
xsltSaveResultToFd(int fd, xmlDocPtr result, xsltStylesheetPtr style) {
xmlOutputBufferPtr buf;
const xmlChar *encoding;
int ret;
if ((fd < 0) || (result == NULL) || (style == NULL))
return(-1);
if (result->children == NULL)
return(0);
XSLT_GET_IMPORT_PTR(encoding, style, encoding)
if (encoding != NULL) {
xmlCharEncodingHandlerPtr encoder;
encoder = xmlFindCharEncodingHandler((char *)encoding);
if ((encoder != NULL) &&
(xmlStrEqual((const xmlChar *)encoder->name,
(const xmlChar *) "UTF-8")))
encoder = NULL;
buf = xmlOutputBufferCreateFd(fd, encoder);
} else {
buf = xmlOutputBufferCreateFd(fd, NULL);
}
if (buf == NULL)
return(-1);
xsltSaveResultTo(buf, result, style);
ret = xmlOutputBufferClose(buf);
return(ret);
}
/**
* xsltSaveResultToString:
* @doc_txt_ptr: Memory pointer for allocated XML text
* @doc_txt_len: Length of the generated XML text
* @result: the result xmlDocPtr
* @style: the stylesheet
*
* Save the result @result obtained by applying the @style stylesheet
* to a new allocated string.
*
* Returns 0 in case of success and -1 in case of error
*/
int
xsltSaveResultToString(xmlChar **doc_txt_ptr, int * doc_txt_len,
xmlDocPtr result, xsltStylesheetPtr style) {
xmlOutputBufferPtr buf;
const xmlChar *encoding;
*doc_txt_ptr = NULL;
*doc_txt_len = 0;
if (result->children == NULL)
return(0);
XSLT_GET_IMPORT_PTR(encoding, style, encoding)
if (encoding != NULL) {
xmlCharEncodingHandlerPtr encoder;
encoder = xmlFindCharEncodingHandler((char *)encoding);
if ((encoder != NULL) &&
(xmlStrEqual((const xmlChar *)encoder->name,
(const xmlChar *) "UTF-8")))
encoder = NULL;
buf = xmlAllocOutputBuffer(encoder);
} else {
buf = xmlAllocOutputBuffer(NULL);
}
if (buf == NULL)
return(-1);
xsltSaveResultTo(buf, result, style);
if (buf->conv != NULL) {
*doc_txt_len = buf->conv->use;
*doc_txt_ptr = xmlStrndup(buf->conv->content, *doc_txt_len);
} else {
*doc_txt_len = buf->buffer->use;
*doc_txt_ptr = xmlStrndup(buf->buffer->content, *doc_txt_len);
}
(void)xmlOutputBufferClose(buf);
return 0;
}
/************************************************************************
* *
* Generating profiling informations *
* *
************************************************************************/
static long calibration = -1;
/**
* xsltCalibrateTimestamps:
*
* Used for to calibrate the xsltTimestamp() function
* Should work if launched at startup and we don't loose our quantum :-)
*
* Returns the number of milliseconds used by xsltTimestamp()
*/
static long
xsltCalibrateTimestamps(void) {
register int i;
for (i = 0;i < 999;i++)
xsltTimestamp();
return(xsltTimestamp() / 1000);
}
/**
* xsltCalibrateAdjust:
* @delta: a negative dealy value found
*
* Used for to correct the calibration for xsltTimestamp()
*/
void
xsltCalibrateAdjust(long delta) {
calibration += delta;
}
/**
* xsltTimestamp:
*
* Used for gathering profiling data
*
* Returns the number of tenth of milliseconds since the beginning of the
* profiling
*/
long
xsltTimestamp(void)
{
#ifdef XSLT_WIN32_PERFORMANCE_COUNTER
BOOL ok;
LARGE_INTEGER performanceCount;
LARGE_INTEGER performanceFrequency;
LONGLONG quadCount;
double seconds;
static LONGLONG startupQuadCount = 0;
static LONGLONG startupQuadFreq = 0;
ok = QueryPerformanceCounter(&performanceCount);
if (!ok)
return 0;
quadCount = performanceCount.QuadPart;
if (calibration < 0) {
calibration = 0;
ok = QueryPerformanceFrequency(&performanceFrequency);
if (!ok)
return 0;
startupQuadFreq = performanceFrequency.QuadPart;
startupQuadCount = quadCount;
return (0);
}
if (startupQuadFreq == 0)
return 0;
seconds = (quadCount - startupQuadCount) / (double) startupQuadFreq;
return (long) (seconds * XSLT_TIMESTAMP_TICS_PER_SEC);
#else /* XSLT_WIN32_PERFORMANCE_COUNTER */
#ifdef HAVE_GETTIMEOFDAY
static struct timeval startup;
struct timeval cur;
long tics;
if (calibration < 0) {
gettimeofday(&startup, NULL);
calibration = 0;
calibration = xsltCalibrateTimestamps();
gettimeofday(&startup, NULL);
return (0);
}
gettimeofday(&cur, NULL);
tics = (cur.tv_sec - startup.tv_sec) * XSLT_TIMESTAMP_TICS_PER_SEC;
tics += (cur.tv_usec - startup.tv_usec) /
(1000000l / XSLT_TIMESTAMP_TICS_PER_SEC);
tics -= calibration;
return(tics);
#else
/* Neither gettimeofday() nor Win32 performance counter available */
return (0);
#endif /* HAVE_GETTIMEOFDAY */
#endif /* XSLT_WIN32_PERFORMANCE_COUNTER */
}
#define MAX_TEMPLATES 10000
/**
* xsltSaveProfiling:
* @ctxt: an XSLT context
* @output: a FILE * for saving the informations
*
* Save the profiling informations on @output
*/
void
xsltSaveProfiling(xsltTransformContextPtr ctxt, FILE *output) {
int nb, i,j;
int max;
int total;
long totalt;
xsltTemplatePtr *templates;
xsltStylesheetPtr style;
xsltTemplatePtr template;
if ((output == NULL) || (ctxt == NULL))
return;
if (ctxt->profile == 0)
return;
nb = 0;
max = MAX_TEMPLATES;
templates = xmlMalloc(max * sizeof(xsltTemplatePtr));
if (templates == NULL)
return;
style = ctxt->style;
while (style != NULL) {
template = style->templates;
while (template != NULL) {
if (nb >= max)
break;
if (template->nbCalls > 0)
templates[nb++] = template;
template = template->next;
}
style = xsltNextImport(style);
}
for (i = 0;i < nb -1;i++) {
for (j = i + 1; j < nb; j++) {
if ((templates[i]->time <= templates[j]->time) ||
((templates[i]->time == templates[j]->time) &&
(templates[i]->nbCalls <= templates[j]->nbCalls))) {
template = templates[j];
templates[j] = templates[i];
templates[i] = template;
}
}
}
fprintf(output, "%6s%20s%20s%10s Calls Tot 100us Avg\n\n",
"number", "match", "name", "mode");
total = 0;
totalt = 0;
for (i = 0;i < nb;i++) {
fprintf(output, "%5d ", i);
if (templates[i]->match != NULL) {
if (xmlStrlen(templates[i]->match) > 20)
fprintf(output, "%s\n%26s", templates[i]->match, "");
else
fprintf(output, "%20s", templates[i]->match);
} else {
fprintf(output, "%20s", "");
}
if (templates[i]->name != NULL) {
if (xmlStrlen(templates[i]->name) > 20)
fprintf(output, "%s\n%46s", templates[i]->name, "");
else
fprintf(output, "%20s", templates[i]->name);
} else {
fprintf(output, "%20s", "");
}
if (templates[i]->mode != NULL) {
if (xmlStrlen(templates[i]->mode) > 10)
fprintf(output, "%s\n%56s", templates[i]->mode, "");
else
fprintf(output, "%10s", templates[i]->mode);
} else {
fprintf(output, "%10s", "");
}
fprintf(output, " %6d", templates[i]->nbCalls);
fprintf(output, " %6ld %6ld\n", templates[i]->time,
templates[i]->time / templates[i]->nbCalls);
total += templates[i]->nbCalls;
totalt += templates[i]->time;
}
fprintf(output, "\n%30s%26s %6d %6ld\n", "Total", "", total, totalt);
xmlFree(templates);
}
/************************************************************************
* *
* Fetching profiling informations *
* *
************************************************************************/
/**
* xsltGetProfileInformation:
* @ctxt: a transformation context
*
* This function should be called after the transformation completed
* to extract template processing profiling informations if availble.
* The informations are returned as an XML document tree like
* <?xml version="1.0"?>
* <profile>
* <template rank="1" match="*" name=""
* mode="" calls="6" time="48" average="8"/>
* <template rank="2" match="item2|item3" name=""
* mode="" calls="10" time="30" average="3"/>
* <template rank="3" match="item1" name=""
* mode="" calls="5" time="17" average="3"/>
* </profile>
* The caller will need to free up the returned tree with xmlFreeDoc()
*
* Returns the xmlDocPtr corresponding to the result or NULL if not available.
*/
xmlDocPtr
xsltGetProfileInformation(xsltTransformContextPtr ctxt)
{
xmlDocPtr ret = NULL;
xmlNodePtr root, child;
char buf[100];
xsltStylesheetPtr style;
xsltTemplatePtr *templates;
xsltTemplatePtr templ;
int nb = 0, max = 0, i, j;
if (!ctxt)
return NULL;
if (!ctxt->profile)
return NULL;
nb = 0;
max = 10000;
templates =
(xsltTemplatePtr *) xmlMalloc(max * sizeof(xsltTemplatePtr));
if (templates == NULL)
return NULL;
/*
* collect all the templates in an array
*/
style = ctxt->style;
while (style != NULL) {
templ = style->templates;
while (templ != NULL) {
if (nb >= max)
break;
if (templ->nbCalls > 0)
templates[nb++] = templ;
templ = templ->next;
}
style = (xsltStylesheetPtr) xsltNextImport(style);
}
/*
* Sort the array by time spent
*/
for (i = 0; i < nb - 1; i++) {
for (j = i + 1; j < nb; j++) {
if ((templates[i]->time <= templates[j]->time) ||
((templates[i]->time == templates[j]->time) &&
(templates[i]->nbCalls <= templates[j]->nbCalls))) {
templ = templates[j];
templates[j] = templates[i];
templates[i] = templ;
}
}
}
/*
* Generate a document corresponding to the results.
*/
ret = xmlNewDoc(BAD_CAST "1.0");
root = xmlNewDocNode(ret, NULL, BAD_CAST "profile", NULL);
xmlDocSetRootElement(ret, root);
for (i = 0; i < nb; i++) {
child = xmlNewChild(root, NULL, BAD_CAST "template", NULL);
sprintf(buf, "%d", i + 1);
xmlSetProp(child, BAD_CAST "rank", BAD_CAST buf);
xmlSetProp(child, BAD_CAST "match", BAD_CAST templates[i]->match);
xmlSetProp(child, BAD_CAST "name", BAD_CAST templates[i]->name);
xmlSetProp(child, BAD_CAST "mode", BAD_CAST templates[i]->mode);
sprintf(buf, "%d", templates[i]->nbCalls);
xmlSetProp(child, BAD_CAST "calls", BAD_CAST buf);
sprintf(buf, "%ld", templates[i]->time);
xmlSetProp(child, BAD_CAST "time", BAD_CAST buf);
sprintf(buf, "%ld", templates[i]->time / templates[i]->nbCalls);
xmlSetProp(child, BAD_CAST "average", BAD_CAST buf);
};
xmlFree(templates);
return ret;
}
/************************************************************************
* *
* Hooks for libxml2 XPath *
* *
************************************************************************/
/**
* xsltXPathCompile:
* @style: the stylesheet
* @str: the XPath expression
*
* Compile an XPath expression
*
* Returns the xmlXPathCompExprPtr resulting from the compilation or NULL.
* the caller has to free the object.
*/
xmlXPathCompExprPtr
xsltXPathCompile(xsltStylesheetPtr style, const xmlChar *str) {
xmlXPathContextPtr xpathCtxt;
xmlXPathCompExprPtr ret;
if (style != NULL) {
#ifdef XSLT_REFACTORED_XPATHCOMP
if (XSLT_CCTXT(style)) {
/*
* Proposed by Jerome Pesenti
* --------------------------
* For better efficiency we'll reuse the compilation
* context's XPath context. For the common stylesheet using
* XPath expressions this will reduce compilation time to
* about 50%.
*
* See http://mail.gnome.org/archives/xslt/2006-April/msg00037.html
*/
xpathCtxt = XSLT_CCTXT(style)->xpathCtxt;
xpathCtxt->doc = style->doc;
} else
xpathCtxt = xmlXPathNewContext(style->doc);
#else
xpathCtxt = xmlXPathNewContext(style->doc);
#endif
if (xpathCtxt == NULL)
return NULL;
xpathCtxt->dict = style->dict;
} else {
xpathCtxt = xmlXPathNewContext(NULL);
if (xpathCtxt == NULL)
return NULL;
}
/*
* Compile the expression.
*/
ret = xmlXPathCtxtCompile(xpathCtxt, str);
#ifdef XSLT_REFACTORED_XPATHCOMP
if ((style == NULL) || (! XSLT_CCTXT(style))) {
xmlXPathFreeContext(xpathCtxt);
}
#else
xmlXPathFreeContext(xpathCtxt);
#endif
/*
* TODO: there is a lot of optimizations which should be possible
* like variable slot precomputations, function precomputations, etc.
*/
return(ret);
}
/************************************************************************
* *
* Hooks for the debugger *
* *
************************************************************************/
/*
* There is currently only 3 debugging callback defined
* Debugger callbacks are disabled by default
*/
#define XSLT_CALLBACK_NUMBER 3
typedef struct _xsltDebuggerCallbacks xsltDebuggerCallbacks;
typedef xsltDebuggerCallbacks *xsltDebuggerCallbacksPtr;
struct _xsltDebuggerCallbacks {
xsltHandleDebuggerCallback handler;
xsltAddCallCallback add;
xsltDropCallCallback drop;
};
static xsltDebuggerCallbacks xsltDebuggerCurrentCallbacks = {
NULL, /* handler */
NULL, /* add */
NULL /* drop */
};
int xslDebugStatus;
/**
* xsltSetDebuggerStatus:
* @value : the value to be set
*
* This function sets the value of xslDebugStatus.
*/
void
xsltSetDebuggerStatus(int value)
{
xslDebugStatus = value;
}
/**
* xsltGetDebuggerStatus:
*
* Get xslDebugStatus.
*
* Returns the value of xslDebugStatus.
*/
int
xsltGetDebuggerStatus(void)
{
return(xslDebugStatus);
}
/**
* xsltSetDebuggerCallbacks:
* @no : number of callbacks
* @block : the block of callbacks
*
* This function allow to plug a debugger into the XSLT library
* @block points to a block of memory containing the address of @no
* callback routines.
*
* Returns 0 in case of success and -1 in case of error
*/
int
xsltSetDebuggerCallbacks(int no, void *block)
{
xsltDebuggerCallbacksPtr callbacks;
if ((block == NULL) || (no != XSLT_CALLBACK_NUMBER))
return(-1);
callbacks = (xsltDebuggerCallbacksPtr) block;
xsltDebuggerCurrentCallbacks.handler = callbacks->handler;
xsltDebuggerCurrentCallbacks.add = callbacks->add;
xsltDebuggerCurrentCallbacks.drop = callbacks->drop;
return(0);
}
/**
* xslHandleDebugger:
* @cur : source node being executed
* @node : data node being processed
* @templ : temlate that applies to node
* @ctxt : the xslt transform context
*
* If either cur or node are a breakpoint, or xslDebugStatus in state
* where debugging must occcur at this time then transfer control
* to the xslDebugBreak function
*/
void
xslHandleDebugger(xmlNodePtr cur, xmlNodePtr node, xsltTemplatePtr templ,
xsltTransformContextPtr ctxt)
{
if (xsltDebuggerCurrentCallbacks.handler != NULL)
xsltDebuggerCurrentCallbacks.handler(cur, node, templ, ctxt);
}
/**
* xslAddCall:
* @templ : current template being applied
* @source : the source node being processed
*
* Add template "call" to call stack
* Returns : 1 on sucess 0 otherwise an error may be printed if
* WITH_XSLT_DEBUG_BREAKPOINTS is defined
*/
int
xslAddCall(xsltTemplatePtr templ, xmlNodePtr source)
{
if (xsltDebuggerCurrentCallbacks.add != NULL)
return(xsltDebuggerCurrentCallbacks.add(templ, source));
return(0);
}
/**
* xslDropCall:
*
* Drop the topmost item off the call stack
*/
void
xslDropCall(void)
{
if (xsltDebuggerCurrentCallbacks.drop != NULL)
xsltDebuggerCurrentCallbacks.drop();
}