diff options
Diffstat (limited to 'libxslt/xsltutils.c')
-rw-r--r-- | libxslt/xsltutils.c | 2483 |
1 files changed, 2483 insertions, 0 deletions
diff --git a/libxslt/xsltutils.c b/libxslt/xsltutils.c new file mode 100644 index 0000000..c250ccf --- /dev/null +++ b/libxslt/xsltutils.c @@ -0,0 +1,2483 @@ +/* + * 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> +#include <time.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); + + if (nameSpace == NULL) + return xmlGetProp(node, name); + + if (node->type == XML_NAMESPACE_DECL) + return(NULL); + if (node->type == XML_ELEMENT_NODE) + prop = node->properties; + else + prop = NULL; + 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); + + if (nameSpace == NULL) + return xmlGetProp(node, name); + + if (node->type == XML_NAMESPACE_DECL) + return(NULL); + if (node->type == XML_ELEMENT_NODE) + prop = node->properties; + else + prop = NULL; + /* + * 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. + */ + 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 { + xsltTransformError(ctxt, NULL, inst, + "xsl:message : terminate expecting 'yes' or 'no'\n"); + } + 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 LIBXSLT_ATTR_FORMAT(2,3) +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 LIBXSLT_ATTR_FORMAT(2,3) +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) { + if (ctxt->state == XSLT_STATE_OK) + 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) { + if (ctxt->state == XSLT_STATE_OK) + 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) { + xmlOutputBufferWriteString(buf, "\""); + xmlOutputBufferWriteString(buf, (const char *)result->version); + xmlOutputBufferWriteString(buf, "\""); + } 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="); + xmlOutputBufferWriteString(buf, "\""); + xmlOutputBufferWriteString(buf, (const char *) encoding); + xmlOutputBufferWriteString(buf, "\""); + } + 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 (indent && ((child->type == XML_DTD_NODE) || + ((child->type == XML_COMMENT_NODE) && + (child->next != NULL)))) + xmlOutputBufferWriteString(buf, "\n"); + child = child->next; + } + if (indent) + 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); +#ifdef LIBXML2_NEW_BUFFER + if (buf->conv != NULL) { + *doc_txt_len = xmlBufUse(buf->conv); + *doc_txt_ptr = xmlStrndup(xmlBufContent(buf->conv), *doc_txt_len); + } else { + *doc_txt_len = xmlBufUse(buf->buffer); + *doc_txt_ptr = xmlStrndup(xmlBufContent(buf->buffer), *doc_txt_len); + } +#else + 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); + } +#endif + (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_CLOCK_GETTIME +# if defined(CLOCK_MONOTONIC) +# define XSLT_CLOCK CLOCK_MONOTONIC +# elif defined(CLOCK_HIGHRES) +# define XSLT_CLOCK CLOCK_HIGHRES +# else +# define XSLT_CLOCK CLOCK_REALTIME +# endif + static struct timespec startup; + struct timespec cur; + long tics; + + if (calibration < 0) { + clock_gettime(XSLT_CLOCK, &startup); + calibration = 0; + calibration = xsltCalibrateTimestamps(); + clock_gettime(XSLT_CLOCK, &startup); + return (0); + } + + clock_gettime(XSLT_CLOCK, &cur); + tics = (cur.tv_sec - startup.tv_sec) * XSLT_TIMESTAMP_TICS_PER_SEC; + tics += (cur.tv_nsec - startup.tv_nsec) / + (1000000000l / XSLT_TIMESTAMP_TICS_PER_SEC); + + tics -= calibration; + return(tics); + +#elif 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 */ +} + +static char * +pretty_templ_match(xsltTemplatePtr templ) { + static char dst[1001]; + char *src = (char *)templ->match; + int i=0,j; + + /* strip white spaces */ + for (j=0; i<1000 && src[j]; i++,j++) { + for(;src[j]==' ';j++); + dst[i]=src[j]; + } + if(i<998 && templ->mode) { + /* append [mode] */ + dst[i++]='['; + src=(char *)templ->mode; + for (j=0; i<999 && src[j]; i++,j++) { + dst[i]=src[j]; + } + dst[i++]=']'; + } + dst[i]='\0'; + return dst; +} + +#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,k,l; + int max; + int total; + unsigned long totalt; + xsltTemplatePtr *templates; + xsltStylesheetPtr style; + xsltTemplatePtr templ1,templ2; + int *childt; + + 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) { + templ1 = style->templates; + while (templ1 != NULL) { + if (nb >= max) + break; + + if (templ1->nbCalls > 0) + templates[nb++] = templ1; + templ1 = templ1->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))) { + templ1 = templates[j]; + templates[j] = templates[i]; + templates[i] = templ1; + } + } + } + + + /* print flat profile */ + + 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++) { + templ1 = templates[i]; + fprintf(output, "%5d ", i); + if (templ1->match != NULL) { + if (xmlStrlen(templ1->match) > 20) + fprintf(output, "%s\n%26s", templ1->match, ""); + else + fprintf(output, "%20s", templ1->match); + } else { + fprintf(output, "%20s", ""); + } + if (templ1->name != NULL) { + if (xmlStrlen(templ1->name) > 20) + fprintf(output, "%s\n%46s", templ1->name, ""); + else + fprintf(output, "%20s", templ1->name); + } else { + fprintf(output, "%20s", ""); + } + if (templ1->mode != NULL) { + if (xmlStrlen(templ1->mode) > 10) + fprintf(output, "%s\n%56s", templ1->mode, ""); + else + fprintf(output, "%10s", templ1->mode); + } else { + fprintf(output, "%10s", ""); + } + fprintf(output, " %6d", templ1->nbCalls); + fprintf(output, " %6ld %6ld\n", templ1->time, + templ1->time / templ1->nbCalls); + total += templ1->nbCalls; + totalt += templ1->time; + } + fprintf(output, "\n%30s%26s %6d %6ld\n", "Total", "", total, totalt); + + + /* print call graph */ + + childt = xmlMalloc((nb + 1) * sizeof(int)); + if (childt == NULL) + return; + + /* precalculate children times */ + for (i = 0; i < nb; i++) { + templ1 = templates[i]; + + childt[i] = 0; + for (k = 0; k < nb; k++) { + templ2 = templates[k]; + for (l = 0; l < templ2->templNr; l++) { + if (templ2->templCalledTab[l] == templ1) { + childt[i] +=templ2->time; + } + } + } + } + childt[i] = 0; + + fprintf(output, "\nindex %% time self children called name\n"); + + for (i = 0; i < nb; i++) { + char ix_str[20], timep_str[20], times_str[20], timec_str[20], called_str[20]; + unsigned long t; + + templ1 = templates[i]; + /* callers */ + for (j = 0; j < templ1->templNr; j++) { + templ2 = templ1->templCalledTab[j]; + for (k = 0; k < nb; k++) { + if (templates[k] == templ2) + break; + } + t=templ2?templ2->time:totalt; + snprintf(times_str,sizeof(times_str),"%8.3f",(float)t/XSLT_TIMESTAMP_TICS_PER_SEC); + snprintf(timec_str,sizeof(timec_str),"%8.3f",(float)childt[k]/XSLT_TIMESTAMP_TICS_PER_SEC); + snprintf(called_str,sizeof(called_str),"%6d/%d", + templ1->templCountTab[j], /* number of times caller calls 'this' */ + templ1->nbCalls); /* total number of calls to 'this' */ + + fprintf(output, " %-8s %-8s %-12s %s [%d]\n", + times_str,timec_str,called_str, + (templ2?(templ2->name?(char *)templ2->name:pretty_templ_match(templ2)):"-"),k); + } + /* this */ + snprintf(ix_str,sizeof(ix_str),"[%d]",i); + snprintf(timep_str,sizeof(timep_str),"%6.2f",(float)templ1->time*100.0/totalt); + snprintf(times_str,sizeof(times_str),"%8.3f",(float)templ1->time/XSLT_TIMESTAMP_TICS_PER_SEC); + snprintf(timec_str,sizeof(timec_str),"%8.3f",(float)childt[i]/XSLT_TIMESTAMP_TICS_PER_SEC); + fprintf(output, "%-5s %-6s %-8s %-8s %6d %s [%d]\n", + ix_str, timep_str,times_str,timec_str, + templ1->nbCalls, + templ1->name?(char *)templ1->name:pretty_templ_match(templ1),i); + /* callees + * - go over templates[0..nb] and their templCalledTab[] + * - print those where we in the the call-stack + */ + total = 0; + for (k = 0; k < nb; k++) { + templ2 = templates[k]; + for (l = 0; l < templ2->templNr; l++) { + if (templ2->templCalledTab[l] == templ1) { + total+=templ2->templCountTab[l]; + } + } + } + for (k = 0; k < nb; k++) { + templ2 = templates[k]; + for (l = 0; l < templ2->templNr; l++) { + if (templ2->templCalledTab[l] == templ1) { + snprintf(times_str,sizeof(times_str),"%8.3f",(float)templ2->time/XSLT_TIMESTAMP_TICS_PER_SEC); + snprintf(timec_str,sizeof(timec_str),"%8.3f",(float)childt[k]/XSLT_TIMESTAMP_TICS_PER_SEC); + snprintf(called_str,sizeof(called_str),"%6d/%d", + templ2->templCountTab[l], /* number of times 'this' calls callee */ + total); /* total number of calls from 'this' */ + fprintf(output, " %-8s %-8s %-12s %s [%d]\n", + times_str,timec_str,called_str, + templ2->name?(char *)templ2->name:pretty_templ_match(templ2),k); + } + } + } + fprintf(output, "-----------------------------------------------\n"); + } + + fprintf(output, "\f\nIndex by function name\n"); + for (i = 0; i < nb; i++) { + templ1 = templates[i]; + fprintf(output, "[%d] %s (%s:%d)\n", + i, templ1->name?(char *)templ1->name:pretty_templ_match(templ1), + templ1->style->doc->URL,templ1->elem->line); + } + + fprintf(output, "\f\n"); + xmlFree(childt); + + 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); + snprintf(buf, sizeof(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); + + snprintf(buf, sizeof(buf), "%d", templates[i]->nbCalls); + xmlSetProp(child, BAD_CAST "calls", BAD_CAST buf); + + snprintf(buf, sizeof(buf), "%ld", templates[i]->time); + xmlSetProp(child, BAD_CAST "time", BAD_CAST buf); + + snprintf(buf, sizeof(buf), "%ld", templates[i]->time / templates[i]->nbCalls); + xmlSetProp(child, BAD_CAST "average", BAD_CAST buf); + }; + + xmlFree(templates); + + return ret; +} + +/************************************************************************ + * * + * Hooks for libxml2 XPath * + * * + ************************************************************************/ + +/** + * xsltXPathCompileFlags: + * @style: the stylesheet + * @str: the XPath expression + * @flags: extra compilation flags to pass down to libxml2 XPath + * + * Compile an XPath expression + * + * Returns the xmlXPathCompExprPtr resulting from the compilation or NULL. + * the caller has to free the object. + */ +xmlXPathCompExprPtr +xsltXPathCompileFlags(xsltStylesheetPtr style, const xmlChar *str, int flags) { + 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; + } + xpathCtxt->flags = flags; + + /* + * 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); +} + +/** + * 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) { + return(xsltXPathCompileFlags(style, str, 0)); +} + +/************************************************************************ + * * + * 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(); +} + |