/* * 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 */ #include "precomp.h" #ifdef HAVE_SYS_TIME_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #if defined(_WIN32) && !defined(__CYGWIN__) #define XSLT_WIN32_PERFORMANCE_COUNTER #endif /************************************************************************ * * * 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: * * 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 (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 output 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, "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 information * * * ************************************************************************/ 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() */ #if !defined(XSLT_WIN32_PERFORMANCE_COUNTER) && \ (defined(HAVE_CLOCK_GETTIME) || defined(HAVE_GETTIMEOFDAY)) static long xsltCalibrateTimestamps(void) { register int i; for (i = 0;i < 999;i++) xsltTimestamp(); return(xsltTimestamp() / 1000); } #endif /** * 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 information * * Save the profiling information 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 information * * * ************************************************************************/ /** * xsltGetProfileInformation: * @ctxt: a transformation context * * This function should be called after the transformation completed * to extract template processing profiling information if available. * The information is returned as an XML document tree like * * *