/* * transform.c: Implementation of the XSL Transformation 1.0 engine * transform part, i.e. applying a Stylesheet to a document * * References: * http://www.w3.org/TR/1999/REC-xslt-19991116 * * Michael Kay "XSLT Programmer's Reference" pp 637-643 * Writing Multiple Output Files * * XSLT-1.1 Working Draft * http://www.w3.org/TR/xslt11#multiple-output * * See Copyright for the status of this software. * * daniel@veillard.com */ #define IN_LIBXSLT #include "libxslt.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "xslt.h" #include "xsltInternals.h" #include "xsltutils.h" #include "pattern.h" #include "transform.h" #include "variables.h" #include "numbersInternals.h" #include "namespaces.h" #include "attributes.h" #include "templates.h" #include "imports.h" #include "keys.h" #include "documents.h" #include "extensions.h" #include "extra.h" #include "preproc.h" #include "security.h" #ifdef WITH_XSLT_DEBUG #define WITH_XSLT_DEBUG_EXTRA #define WITH_XSLT_DEBUG_PROCESS #endif #define XSLT_GENERATE_HTML_DOCTYPE #ifdef XSLT_GENERATE_HTML_DOCTYPE static int xsltGetHTMLIDs(const xmlChar *version, const xmlChar **publicID, const xmlChar **systemID); #endif int xsltMaxDepth = 3000; int xsltMaxVars = 15000; /* * Useful macros */ #ifndef FALSE # define FALSE (0 == 1) # define TRUE (!FALSE) #endif #define IS_BLANK_NODE(n) \ (((n)->type == XML_TEXT_NODE) && (xsltIsBlank((n)->content))) /* * Forward declarations */ static xmlNsPtr xsltCopyNamespaceListInternal(xmlNodePtr node, xmlNsPtr cur); static xmlNodePtr xsltCopyTreeInternal(xsltTransformContextPtr ctxt, xmlNodePtr invocNode, xmlNodePtr node, xmlNodePtr insert, int isLRE, int topElemVisited); static void xsltApplySequenceConstructor(xsltTransformContextPtr ctxt, xmlNodePtr contextNode, xmlNodePtr list, xsltTemplatePtr templ); static void xsltApplyXSLTTemplate(xsltTransformContextPtr ctxt, xmlNodePtr contextNode, xmlNodePtr list, xsltTemplatePtr templ, xsltStackElemPtr withParams); /** * templPush: * @ctxt: the transformation context * @value: the template to push on the stack * * Push a template on the stack * * Returns the new index in the stack or 0 in case of error */ static int templPush(xsltTransformContextPtr ctxt, xsltTemplatePtr value) { if (ctxt->templMax == 0) { ctxt->templMax = 4; ctxt->templTab = (xsltTemplatePtr *) xmlMalloc(ctxt->templMax * sizeof(ctxt->templTab[0])); if (ctxt->templTab == NULL) { xmlGenericError(xmlGenericErrorContext, "malloc failed !\n"); return (0); } } else if (ctxt->templNr >= ctxt->templMax) { ctxt->templMax *= 2; ctxt->templTab = (xsltTemplatePtr *) xmlRealloc(ctxt->templTab, ctxt->templMax * sizeof(ctxt->templTab[0])); if (ctxt->templTab == NULL) { xmlGenericError(xmlGenericErrorContext, "realloc failed !\n"); return (0); } } ctxt->templTab[ctxt->templNr] = value; ctxt->templ = value; return (ctxt->templNr++); } /** * templPop: * @ctxt: the transformation context * * Pop a template value from the stack * * Returns the stored template value */ static xsltTemplatePtr templPop(xsltTransformContextPtr ctxt) { xsltTemplatePtr ret; if (ctxt->templNr <= 0) return (0); ctxt->templNr--; if (ctxt->templNr > 0) ctxt->templ = ctxt->templTab[ctxt->templNr - 1]; else ctxt->templ = (xsltTemplatePtr) 0; ret = ctxt->templTab[ctxt->templNr]; ctxt->templTab[ctxt->templNr] = 0; return (ret); } /** * xsltLocalVariablePop: * @ctxt: the transformation context * @limitNr: number of variables which should remain * @level: the depth in the xsl:template's tree * * Pops all variable values at the given @depth from the stack. * * Returns the stored variable value * **NOTE:** * This is an internal routine and should not be called by users! */ void xsltLocalVariablePop(xsltTransformContextPtr ctxt, int limitNr, int level) { xsltStackElemPtr variable; if (ctxt->varsNr <= 0) return; do { if (ctxt->varsNr <= limitNr) break; variable = ctxt->varsTab[ctxt->varsNr - 1]; if (variable->level <= level) break; if (variable->level >= 0) xsltFreeStackElemList(variable); ctxt->varsNr--; } while (ctxt->varsNr != 0); if (ctxt->varsNr > 0) ctxt->vars = ctxt->varsTab[ctxt->varsNr - 1]; else ctxt->vars = NULL; } /** * xsltTemplateParamsCleanup: * * Removes xsl:param and xsl:with-param items from the * variable-stack. Only xsl:with-param items are not freed. */ static void xsltTemplateParamsCleanup(xsltTransformContextPtr ctxt) { xsltStackElemPtr param; for (; ctxt->varsNr > ctxt->varsBase; ctxt->varsNr--) { param = ctxt->varsTab[ctxt->varsNr -1]; /* * Free xsl:param items. * xsl:with-param items will have a level of -1 or -2. */ if (param->level >= 0) { xsltFreeStackElemList(param); } } if (ctxt->varsNr > 0) ctxt->vars = ctxt->varsTab[ctxt->varsNr - 1]; else ctxt->vars = NULL; } /** * profPush: * @ctxt: the transformation context * @value: the profiling value to push on the stack * * Push a profiling value on the stack * * Returns the new index in the stack or 0 in case of error */ static int profPush(xsltTransformContextPtr ctxt, long value) { if (ctxt->profMax == 0) { ctxt->profMax = 4; ctxt->profTab = (long *) xmlMalloc(ctxt->profMax * sizeof(ctxt->profTab[0])); if (ctxt->profTab == NULL) { xmlGenericError(xmlGenericErrorContext, "malloc failed !\n"); return (0); } } else if (ctxt->profNr >= ctxt->profMax) { ctxt->profMax *= 2; ctxt->profTab = (long *) xmlRealloc(ctxt->profTab, ctxt->profMax * sizeof(ctxt->profTab[0])); if (ctxt->profTab == NULL) { xmlGenericError(xmlGenericErrorContext, "realloc failed !\n"); return (0); } } ctxt->profTab[ctxt->profNr] = value; ctxt->prof = value; return (ctxt->profNr++); } /** * profPop: * @ctxt: the transformation context * * Pop a profiling value from the stack * * Returns the stored profiling value */ static long profPop(xsltTransformContextPtr ctxt) { long ret; if (ctxt->profNr <= 0) return (0); ctxt->profNr--; if (ctxt->profNr > 0) ctxt->prof = ctxt->profTab[ctxt->profNr - 1]; else ctxt->prof = (long) 0; ret = ctxt->profTab[ctxt->profNr]; ctxt->profTab[ctxt->profNr] = 0; return (ret); } static void profCallgraphAdd(xsltTemplatePtr templ, xsltTemplatePtr parent) { int i; if (templ->templMax == 0) { templ->templMax = 4; templ->templCalledTab = (xsltTemplatePtr *) xmlMalloc(templ->templMax * sizeof(templ->templCalledTab[0])); templ->templCountTab = (int *) xmlMalloc(templ->templMax * sizeof(templ->templCountTab[0])); if (templ->templCalledTab == NULL || templ->templCountTab == NULL) { xmlGenericError(xmlGenericErrorContext, "malloc failed !\n"); return; } } else if (templ->templNr >= templ->templMax) { templ->templMax *= 2; templ->templCalledTab = (xsltTemplatePtr *) xmlRealloc(templ->templCalledTab, templ->templMax * sizeof(templ->templCalledTab[0])); templ->templCountTab = (int *) xmlRealloc(templ->templCountTab, templ->templMax * sizeof(templ->templCountTab[0])); if (templ->templCalledTab == NULL || templ->templCountTab == NULL) { xmlGenericError(xmlGenericErrorContext, "realloc failed !\n"); return; } } for (i = 0; i < templ->templNr; i++) { if (templ->templCalledTab[i] == parent) { templ->templCountTab[i]++; break; } } if (i == templ->templNr) { /* not found, add new one */ templ->templCalledTab[templ->templNr] = parent; templ->templCountTab[templ->templNr] = 1; templ->templNr++; } } /************************************************************************ * * * XInclude default settings * * * ************************************************************************/ static int xsltDoXIncludeDefault = 0; /** * xsltSetXIncludeDefault: * @xinclude: whether to do XInclude processing * * Set whether XInclude should be processed on document being loaded by default */ void xsltSetXIncludeDefault(int xinclude) { xsltDoXIncludeDefault = (xinclude != 0); } /** * xsltGetXIncludeDefault: * * Provides the default state for XInclude processing * * Returns 0 if there is no processing 1 otherwise */ int xsltGetXIncludeDefault(void) { return(xsltDoXIncludeDefault); } unsigned long xsltDefaultTrace = (unsigned long) XSLT_TRACE_ALL; /** * xsltDebugSetDefaultTrace: * @val: tracing level mask * * Set the default debug tracing level mask */ void xsltDebugSetDefaultTrace(xsltDebugTraceCodes val) { xsltDefaultTrace = val; } /** * xsltDebugGetDefaultTrace: * * Get the current default debug tracing level mask * * Returns the current default debug tracing level mask */ xsltDebugTraceCodes xsltDebugGetDefaultTrace() { return xsltDefaultTrace; } /************************************************************************ * * * Handling of Transformation Contexts * * * ************************************************************************/ static xsltTransformCachePtr xsltTransformCacheCreate(void) { xsltTransformCachePtr ret; ret = (xsltTransformCachePtr) xmlMalloc(sizeof(xsltTransformCache)); if (ret == NULL) { xsltTransformError(NULL, NULL, NULL, "xsltTransformCacheCreate : malloc failed\n"); return(NULL); } memset(ret, 0, sizeof(xsltTransformCache)); return(ret); } static void xsltTransformCacheFree(xsltTransformCachePtr cache) { if (cache == NULL) return; /* * Free tree fragments. */ if (cache->RVT) { xmlDocPtr tmp, cur = cache->RVT; while (cur) { tmp = cur; cur = (xmlDocPtr) cur->next; if (tmp->_private != NULL) { /* * Tree the document info. */ xsltFreeDocumentKeys((xsltDocumentPtr) tmp->_private); xmlFree(tmp->_private); } xmlFreeDoc(tmp); } } /* * Free vars/params. */ if (cache->stackItems) { xsltStackElemPtr tmp, cur = cache->stackItems; while (cur) { tmp = cur; cur = cur->next; /* * REVISIT TODO: Should be call a destruction-function * instead? */ xmlFree(tmp); } } xmlFree(cache); } /** * xsltNewTransformContext: * @style: a parsed XSLT stylesheet * @doc: the input document * * Create a new XSLT TransformContext * * Returns the newly allocated xsltTransformContextPtr or NULL in case of error */ xsltTransformContextPtr xsltNewTransformContext(xsltStylesheetPtr style, xmlDocPtr doc) { xsltTransformContextPtr cur; xsltDocumentPtr docu; int i; xsltInitGlobals(); cur = (xsltTransformContextPtr) xmlMalloc(sizeof(xsltTransformContext)); if (cur == NULL) { xsltTransformError(NULL, NULL, (xmlNodePtr)doc, "xsltNewTransformContext : malloc failed\n"); return(NULL); } memset(cur, 0, sizeof(xsltTransformContext)); cur->cache = xsltTransformCacheCreate(); if (cur->cache == NULL) goto internal_err; /* * setup of the dictionary must be done early as some of the * processing later like key handling may need it. */ cur->dict = xmlDictCreateSub(style->dict); cur->internalized = ((style->internalized) && (cur->dict != NULL)); #ifdef WITH_XSLT_DEBUG xsltGenericDebug(xsltGenericDebugContext, "Creating sub-dictionary from stylesheet for transformation\n"); #endif /* * initialize the template stack */ cur->templTab = (xsltTemplatePtr *) xmlMalloc(10 * sizeof(xsltTemplatePtr)); if (cur->templTab == NULL) { xsltTransformError(NULL, NULL, (xmlNodePtr) doc, "xsltNewTransformContext: out of memory\n"); goto internal_err; } cur->templNr = 0; cur->templMax = 5; cur->templ = NULL; cur->maxTemplateDepth = xsltMaxDepth; /* * initialize the variables stack */ cur->varsTab = (xsltStackElemPtr *) xmlMalloc(10 * sizeof(xsltStackElemPtr)); if (cur->varsTab == NULL) { xmlGenericError(xmlGenericErrorContext, "xsltNewTransformContext: out of memory\n"); goto internal_err; } cur->varsNr = 0; cur->varsMax = 10; cur->vars = NULL; cur->varsBase = 0; cur->maxTemplateVars = xsltMaxVars; /* * the profiling stack is not initialized by default */ cur->profTab = NULL; cur->profNr = 0; cur->profMax = 0; cur->prof = 0; cur->style = style; xmlXPathInit(); cur->xpathCtxt = xmlXPathNewContext(doc); if (cur->xpathCtxt == NULL) { xsltTransformError(NULL, NULL, (xmlNodePtr) doc, "xsltNewTransformContext : xmlXPathNewContext failed\n"); goto internal_err; } /* * Create an XPath cache. */ if (xmlXPathContextSetCache(cur->xpathCtxt, 1, -1, 0) == -1) goto internal_err; /* * Initialize the extras array */ if (style->extrasNr != 0) { cur->extrasMax = style->extrasNr + 20; cur->extras = (xsltRuntimeExtraPtr) xmlMalloc(cur->extrasMax * sizeof(xsltRuntimeExtra)); if (cur->extras == NULL) { xmlGenericError(xmlGenericErrorContext, "xsltNewTransformContext: out of memory\n"); goto internal_err; } cur->extrasNr = style->extrasNr; for (i = 0;i < cur->extrasMax;i++) { cur->extras[i].info = NULL; cur->extras[i].deallocate = NULL; cur->extras[i].val.ptr = NULL; } } else { cur->extras = NULL; cur->extrasNr = 0; cur->extrasMax = 0; } XSLT_REGISTER_VARIABLE_LOOKUP(cur); XSLT_REGISTER_FUNCTION_LOOKUP(cur); cur->xpathCtxt->nsHash = style->nsHash; /* * Initialize the registered external modules */ xsltInitCtxtExts(cur); /* * Setup document element ordering for later efficiencies * (bug 133289) */ if (xslDebugStatus == XSLT_DEBUG_NONE) xmlXPathOrderDocElems(doc); /* * Must set parserOptions before calling xsltNewDocument * (bug 164530) */ cur->parserOptions = XSLT_PARSE_OPTIONS; docu = xsltNewDocument(cur, doc); if (docu == NULL) { xsltTransformError(cur, NULL, (xmlNodePtr)doc, "xsltNewTransformContext : xsltNewDocument failed\n"); goto internal_err; } docu->main = 1; cur->document = docu; cur->inst = NULL; cur->outputFile = NULL; cur->sec = xsltGetDefaultSecurityPrefs(); cur->debugStatus = xslDebugStatus; cur->traceCode = (unsigned long*) &xsltDefaultTrace; cur->xinclude = xsltGetXIncludeDefault(); cur->keyInitLevel = 0; return(cur); internal_err: if (cur != NULL) xsltFreeTransformContext(cur); return(NULL); } /** * xsltFreeTransformContext: * @ctxt: an XSLT parser context * * Free up the memory allocated by @ctxt */ void xsltFreeTransformContext(xsltTransformContextPtr ctxt) { if (ctxt == NULL) return; /* * Shutdown the extension modules associated to the stylesheet * used if needed. */ xsltShutdownCtxtExts(ctxt); if (ctxt->xpathCtxt != NULL) { ctxt->xpathCtxt->nsHash = NULL; xmlXPathFreeContext(ctxt->xpathCtxt); } if (ctxt->templTab != NULL) xmlFree(ctxt->templTab); if (ctxt->varsTab != NULL) xmlFree(ctxt->varsTab); if (ctxt->profTab != NULL) xmlFree(ctxt->profTab); if ((ctxt->extrasNr > 0) && (ctxt->extras != NULL)) { int i; for (i = 0;i < ctxt->extrasNr;i++) { if ((ctxt->extras[i].deallocate != NULL) && (ctxt->extras[i].info != NULL)) ctxt->extras[i].deallocate(ctxt->extras[i].info); } xmlFree(ctxt->extras); } xsltFreeGlobalVariables(ctxt); xsltFreeDocuments(ctxt); xsltFreeCtxtExts(ctxt); xsltFreeRVTs(ctxt); xsltTransformCacheFree(ctxt->cache); xmlDictFree(ctxt->dict); #ifdef WITH_XSLT_DEBUG xsltGenericDebug(xsltGenericDebugContext, "freeing transformation dictionary\n"); #endif memset(ctxt, -1, sizeof(xsltTransformContext)); xmlFree(ctxt); } /************************************************************************ * * * Copy of Nodes in an XSLT fashion * * * ************************************************************************/ xmlNodePtr xsltCopyTree(xsltTransformContextPtr ctxt, xmlNodePtr node, xmlNodePtr insert, int literal); /** * xsltAddChild: * @parent: the parent node * @cur: the child node * * Wrapper version of xmlAddChild with a more consistent behaviour on * error. One expect the use to be child = xsltAddChild(parent, child); * and the routine will take care of not leaking on errors or node merge * * Returns the child is successfully attached or NULL if merged or freed */ static xmlNodePtr xsltAddChild(xmlNodePtr parent, xmlNodePtr cur) { xmlNodePtr ret; if ((cur == NULL) || (parent == NULL)) return(NULL); if (parent == NULL) { xmlFreeNode(cur); return(NULL); } ret = xmlAddChild(parent, cur); return(ret); } /** * xsltAddTextString: * @ctxt: a XSLT process context * @target: the text node where the text will be attached * @string: the text string * @len: the string length in byte * * Extend the current text node with the new string, it handles coalescing * * Returns: the text node */ static xmlNodePtr xsltAddTextString(xsltTransformContextPtr ctxt, xmlNodePtr target, const xmlChar *string, int len) { /* * optimization */ if ((len <= 0) || (string == NULL) || (target == NULL)) return(target); if (ctxt->lasttext == target->content) { if (ctxt->lasttuse + len >= ctxt->lasttsize) { xmlChar *newbuf; int size; size = ctxt->lasttsize + len + 100; size *= 2; newbuf = (xmlChar *) xmlRealloc(target->content,size); if (newbuf == NULL) { xsltTransformError(ctxt, NULL, target, "xsltCopyText: text allocation failed\n"); return(NULL); } ctxt->lasttsize = size; ctxt->lasttext = newbuf; target->content = newbuf; } memcpy(&(target->content[ctxt->lasttuse]), string, len); ctxt->lasttuse += len; target->content[ctxt->lasttuse] = 0; } else { xmlNodeAddContent(target, string); ctxt->lasttext = target->content; len = xmlStrlen(target->content); ctxt->lasttsize = len; ctxt->lasttuse = len; } return(target); } /** * xsltCopyTextString: * @ctxt: a XSLT process context * @target: the element where the text will be attached * @string: the text string * @noescape: should disable-escaping be activated for this text node. * * Adds @string to a newly created or an existent text node child of * @target. * * Returns: the text node, where the text content of @cur is copied to. * NULL in case of API or internal errors. */ xmlNodePtr xsltCopyTextString(xsltTransformContextPtr ctxt, xmlNodePtr target, const xmlChar *string, int noescape) { xmlNodePtr copy; int len; if (string == NULL) return(NULL); #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_COPY_TEXT,xsltGenericDebug(xsltGenericDebugContext, "xsltCopyTextString: copy text %s\n", string)); #endif /* * Play safe and reset the merging mechanism for every new * target node. */ if ((target == NULL) || (target->children == NULL)) { ctxt->lasttext = NULL; } /* handle coalescing of text nodes here */ len = xmlStrlen(string); if ((ctxt->type == XSLT_OUTPUT_XML) && (ctxt->style->cdataSection != NULL) && (target != NULL) && (target->type == XML_ELEMENT_NODE) && (((target->ns == NULL) && (xmlHashLookup2(ctxt->style->cdataSection, target->name, NULL) != NULL)) || ((target->ns != NULL) && (xmlHashLookup2(ctxt->style->cdataSection, target->name, target->ns->href) != NULL)))) { /* * Process "cdata-section-elements". */ if ((target->last != NULL) && (target->last->type == XML_CDATA_SECTION_NODE)) { return(xsltAddTextString(ctxt, target->last, string, len)); } copy = xmlNewCDataBlock(ctxt->output, string, len); } else if (noescape) { /* * Process "disable-output-escaping". */ if ((target != NULL) && (target->last != NULL) && (target->last->type == XML_TEXT_NODE) && (target->last->name == xmlStringTextNoenc)) { return(xsltAddTextString(ctxt, target->last, string, len)); } copy = xmlNewTextLen(string, len); if (copy != NULL) copy->name = xmlStringTextNoenc; } else { /* * Default processing. */ if ((target != NULL) && (target->last != NULL) && (target->last->type == XML_TEXT_NODE) && (target->last->name == xmlStringText)) { return(xsltAddTextString(ctxt, target->last, string, len)); } copy = xmlNewTextLen(string, len); } if (copy != NULL) { if (target != NULL) copy = xsltAddChild(target, copy); ctxt->lasttext = copy->content; ctxt->lasttsize = len; ctxt->lasttuse = len; } else { xsltTransformError(ctxt, NULL, target, "xsltCopyTextString: text copy failed\n"); ctxt->lasttext = NULL; } return(copy); } /** * xsltCopyText: * @ctxt: a XSLT process context * @target: the element where the text will be attached * @cur: the text or CDATA node * @interned: the string is in the target doc dictionary * * Copy the text content of @cur and append it to @target's children. * * Returns: the text node, where the text content of @cur is copied to. * NULL in case of API or internal errors. */ static xmlNodePtr xsltCopyText(xsltTransformContextPtr ctxt, xmlNodePtr target, xmlNodePtr cur, int interned) { xmlNodePtr copy; if ((cur->type != XML_TEXT_NODE) && (cur->type != XML_CDATA_SECTION_NODE)) return(NULL); if (cur->content == NULL) return(NULL); #ifdef WITH_XSLT_DEBUG_PROCESS if (cur->type == XML_CDATA_SECTION_NODE) { XSLT_TRACE(ctxt,XSLT_TRACE_COPY_TEXT,xsltGenericDebug(xsltGenericDebugContext, "xsltCopyText: copy CDATA text %s\n", cur->content)); } else if (cur->name == xmlStringTextNoenc) { XSLT_TRACE(ctxt,XSLT_TRACE_COPY_TEXT,xsltGenericDebug(xsltGenericDebugContext, "xsltCopyText: copy unescaped text %s\n", cur->content)); } else { XSLT_TRACE(ctxt,XSLT_TRACE_COPY_TEXT,xsltGenericDebug(xsltGenericDebugContext, "xsltCopyText: copy text %s\n", cur->content)); } #endif /* * Play save and reset the merging mechanism for every new * target node. */ if ((target == NULL) || (target->children == NULL)) { ctxt->lasttext = NULL; } if ((ctxt->style->cdataSection != NULL) && (ctxt->type == XSLT_OUTPUT_XML) && (target != NULL) && (target->type == XML_ELEMENT_NODE) && (((target->ns == NULL) && (xmlHashLookup2(ctxt->style->cdataSection, target->name, NULL) != NULL)) || ((target->ns != NULL) && (xmlHashLookup2(ctxt->style->cdataSection, target->name, target->ns->href) != NULL)))) { /* * Process "cdata-section-elements". */ /* * OPTIMIZE TODO: xsltCopyText() is also used for attribute content. */ /* * TODO: Since this doesn't merge adjacent CDATA-section nodes, * we'll get: . * TODO: Reported in #321505. */ if ((target->last != NULL) && (target->last->type == XML_CDATA_SECTION_NODE)) { /* * Append to existing CDATA-section node. */ copy = xsltAddTextString(ctxt, target->last, cur->content, xmlStrlen(cur->content)); goto exit; } else { unsigned int len; len = xmlStrlen(cur->content); copy = xmlNewCDataBlock(ctxt->output, cur->content, len); if (copy == NULL) goto exit; ctxt->lasttext = copy->content; ctxt->lasttsize = len; ctxt->lasttuse = len; } } else if ((target != NULL) && (target->last != NULL) && /* both escaped or both non-escaped text-nodes */ (((target->last->type == XML_TEXT_NODE) && (target->last->name == cur->name)) || /* non-escaped text nodes and CDATA-section nodes */ (((target->last->type == XML_CDATA_SECTION_NODE) && (cur->name == xmlStringTextNoenc))))) { /* * we are appending to an existing text node */ copy = xsltAddTextString(ctxt, target->last, cur->content, xmlStrlen(cur->content)); goto exit; } else if ((interned) && (target != NULL) && (target->doc != NULL) && (target->doc->dict == ctxt->dict)) { /* * TODO: DO we want to use this also for "text" output? */ copy = xmlNewTextLen(NULL, 0); if (copy == NULL) goto exit; if (cur->name == xmlStringTextNoenc) copy->name = xmlStringTextNoenc; /* * Must confirm that content is in dict (bug 302821) * TODO: This check should be not needed for text coming * from the stylesheets */ if (xmlDictOwns(ctxt->dict, cur->content)) copy->content = cur->content; else { if ((copy->content = xmlStrdup(cur->content)) == NULL) return NULL; } } else { /* * normal processing. keep counters to extend the text node * in xsltAddTextString if needed. */ unsigned int len; len = xmlStrlen(cur->content); copy = xmlNewTextLen(cur->content, len); if (copy == NULL) goto exit; if (cur->name == xmlStringTextNoenc) copy->name = xmlStringTextNoenc; ctxt->lasttext = copy->content; ctxt->lasttsize = len; ctxt->lasttuse = len; } if (copy != NULL) { if (target != NULL) { copy->doc = target->doc; /* * MAYBE TODO: Maybe we should reset the ctxt->lasttext here * to ensure that the optimized text-merging mechanism * won't interfere with normal node-merging in any case. */ copy = xsltAddChild(target, copy); } } else { xsltTransformError(ctxt, NULL, target, "xsltCopyText: text copy failed\n"); } exit: if ((copy == NULL) || (copy->content == NULL)) { xsltTransformError(ctxt, NULL, target, "Internal error in xsltCopyText(): " "Failed to copy the string.\n"); ctxt->state = XSLT_STATE_STOPPED; } return(copy); } /** * xsltShallowCopyAttr: * @ctxt: a XSLT process context * @invocNode: responsible node in the stylesheet; used for error reports * @target: the element where the attribute will be grafted * @attr: the attribute to be copied * * Do a copy of an attribute. * Called by: * - xsltCopyTreeInternal() * - xsltCopyOf() * - xsltCopy() * * Returns: a new xmlAttrPtr, or NULL in case of error. */ static xmlAttrPtr xsltShallowCopyAttr(xsltTransformContextPtr ctxt, xmlNodePtr invocNode, xmlNodePtr target, xmlAttrPtr attr) { xmlAttrPtr copy; xmlChar *value; if (attr == NULL) return(NULL); if (target->type != XML_ELEMENT_NODE) { xsltTransformError(ctxt, NULL, invocNode, "Cannot add an attribute node to a non-element node.\n"); return(NULL); } if (target->children != NULL) { xsltTransformError(ctxt, NULL, invocNode, "Attribute nodes must be added before " "any child nodes to an element.\n"); return(NULL); } value = xmlNodeListGetString(attr->doc, attr->children, 1); if (attr->ns != NULL) { xmlNsPtr ns; ns = xsltGetSpecialNamespace(ctxt, invocNode, attr->ns->href, attr->ns->prefix, target); if (ns == NULL) { xsltTransformError(ctxt, NULL, invocNode, "Namespace fixup error: Failed to acquire an in-scope " "namespace binding of the copied attribute '{%s}%s'.\n", attr->ns->href, attr->name); /* * TODO: Should we just stop here? */ } /* * Note that xmlSetNsProp() will take care of duplicates * and assigns the new namespace even to a duplicate. */ copy = xmlSetNsProp(target, ns, attr->name, value); } else { copy = xmlSetNsProp(target, NULL, attr->name, value); } if (value != NULL) xmlFree(value); if (copy == NULL) return(NULL); #if 0 /* * NOTE: This was optimized according to bug #342695. * TODO: Can this further be optimized, if source and target * share the same dict and attr->children is just 1 text node * which is in the dict? How probable is such a case? */ /* * TODO: Do we need to create an empty text node if the value * is the empty string? */ value = xmlNodeListGetString(attr->doc, attr->children, 1); if (value != NULL) { txtNode = xmlNewDocText(target->doc, NULL); if (txtNode == NULL) return(NULL); if ((target->doc != NULL) && (target->doc->dict != NULL)) { txtNode->content = (xmlChar *) xmlDictLookup(target->doc->dict, BAD_CAST value, -1); xmlFree(value); } else txtNode->content = value; copy->children = txtNode; } #endif return(copy); } /** * xsltCopyAttrListNoOverwrite: * @ctxt: a XSLT process context * @invocNode: responsible node in the stylesheet; used for error reports * @target: the element where the new attributes will be grafted * @attr: the first attribute in the list to be copied * * Copies a list of attribute nodes, starting with @attr, over to the * @target element node. * * Called by: * - xsltCopyTreeInternal() * * Returns 0 on success and -1 on errors and internal errors. */ static int xsltCopyAttrListNoOverwrite(xsltTransformContextPtr ctxt, xmlNodePtr invocNode, xmlNodePtr target, xmlAttrPtr attr) { xmlAttrPtr copy; xmlNsPtr origNs = NULL, copyNs = NULL; xmlChar *value; /* * Don't use xmlCopyProp() here, since it will try to * reconciliate namespaces. */ while (attr != NULL) { /* * Find a namespace node in the tree of @target. * Avoid searching for the same ns. */ if (attr->ns != origNs) { origNs = attr->ns; if (attr->ns != NULL) { copyNs = xsltGetSpecialNamespace(ctxt, invocNode, attr->ns->href, attr->ns->prefix, target); if (copyNs == NULL) return(-1); } else copyNs = NULL; } /* * If attribute has a value, we need to copy it (watching out * for possible entities) */ if ((attr->children) && (attr->children->type == XML_TEXT_NODE) && (attr->children->next == NULL)) { copy = xmlNewNsProp(target, copyNs, attr->name, attr->children->content); } else if (attr->children != NULL) { value = xmlNodeListGetString(attr->doc, attr->children, 1); copy = xmlNewNsProp(target, copyNs, attr->name, BAD_CAST value); xmlFree(value); } else { copy = xmlNewNsProp(target, copyNs, attr->name, NULL); } if (copy == NULL) return(-1); attr = attr->next; } return(0); } /** * xsltShallowCopyElem: * @ctxt: the XSLT process context * @node: the element node in the source tree * or the Literal Result Element * @insert: the parent in the result tree * @isLRE: if @node is a Literal Result Element * * Make a copy of the element node @node * and insert it as last child of @insert. * * URGENT TODO: The problem with this one (for the non-refactored code) * is that it is used for both, Literal Result Elements *and* * copying input nodes. * * BIG NOTE: This is only called for XML_ELEMENT_NODEs. * * Called from: * xsltApplySequenceConstructor() * (for Literal Result Elements - which is a problem) * xsltCopy() (for shallow-copying elements via xsl:copy) * * Returns a pointer to the new node, or NULL in case of error */ static xmlNodePtr xsltShallowCopyElem(xsltTransformContextPtr ctxt, xmlNodePtr node, xmlNodePtr insert, int isLRE) { xmlNodePtr copy; if ((node->type == XML_DTD_NODE) || (insert == NULL)) return(NULL); if ((node->type == XML_TEXT_NODE) || (node->type == XML_CDATA_SECTION_NODE)) return(xsltCopyText(ctxt, insert, node, 0)); copy = xmlDocCopyNode(node, insert->doc, 0); if (copy != NULL) { copy->doc = ctxt->output; copy = xsltAddChild(insert, copy); if (node->type == XML_ELEMENT_NODE) { /* * Add namespaces as they are needed */ if (node->nsDef != NULL) { /* * TODO: Remove the LRE case in the refactored code * gets enabled. */ if (isLRE) xsltCopyNamespaceList(ctxt, copy, node->nsDef); else xsltCopyNamespaceListInternal(copy, node->nsDef); } /* * URGENT TODO: The problem with this is that it does not * copy over all namespace nodes in scope. * The damn thing about this is, that we would need to * use the xmlGetNsList(), for every single node; this is * also done in xsltCopyTreeInternal(), but only for the top node. */ if (node->ns != NULL) { if (isLRE) { /* * REVISIT TODO: Since the non-refactored code still does * ns-aliasing, we need to call xsltGetNamespace() here. * Remove this when ready. */ copy->ns = xsltGetNamespace(ctxt, node, node->ns, copy); } else { copy->ns = xsltGetSpecialNamespace(ctxt, node, node->ns->href, node->ns->prefix, copy); } } else if ((insert->type == XML_ELEMENT_NODE) && (insert->ns != NULL)) { /* * "Undeclare" the default namespace. */ xsltGetSpecialNamespace(ctxt, node, NULL, NULL, copy); } } } else { xsltTransformError(ctxt, NULL, node, "xsltShallowCopyElem: copy %s failed\n", node->name); } return(copy); } /** * xsltCopyTreeList: * @ctxt: a XSLT process context * @invocNode: responsible node in the stylesheet; used for error reports * @list: the list of element nodes in the source tree. * @insert: the parent in the result tree. * @isLRE: is this a literal result element list * @topElemVisited: indicates if a top-most element was already processed * * Make a copy of the full list of tree @list * and insert it as last children of @insert * * NOTE: Not to be used for Literal Result Elements. * * Used by: * - xsltCopyOf() * * Returns a pointer to the new list, or NULL in case of error */ static xmlNodePtr xsltCopyTreeList(xsltTransformContextPtr ctxt, xmlNodePtr invocNode, xmlNodePtr list, xmlNodePtr insert, int isLRE, int topElemVisited) { xmlNodePtr copy, ret = NULL; while (list != NULL) { copy = xsltCopyTreeInternal(ctxt, invocNode, list, insert, isLRE, topElemVisited); if (copy != NULL) { if (ret == NULL) { ret = copy; } } list = list->next; } return(ret); } /** * xsltCopyNamespaceListInternal: * @node: the target node * @cur: the first namespace * * Do a copy of a namespace list. If @node is non-NULL the * new namespaces are added automatically. * Called by: * xsltCopyTreeInternal() * * QUESTION: What is the exact difference between this function * and xsltCopyNamespaceList() in "namespaces.c"? * ANSWER: xsltCopyNamespaceList() tries to apply ns-aliases. * * Returns: a new xmlNsPtr, or NULL in case of error. */ static xmlNsPtr xsltCopyNamespaceListInternal(xmlNodePtr elem, xmlNsPtr ns) { xmlNsPtr ret = NULL; xmlNsPtr p = NULL, q, luNs; if (ns == NULL) return(NULL); /* * One can add namespaces only on element nodes */ if ((elem != NULL) && (elem->type != XML_ELEMENT_NODE)) elem = NULL; do { if (ns->type != XML_NAMESPACE_DECL) break; /* * Avoid duplicating namespace declarations on the tree. */ if (elem != NULL) { if ((elem->ns != NULL) && xmlStrEqual(elem->ns->prefix, ns->prefix) && xmlStrEqual(elem->ns->href, ns->href)) { ns = ns->next; continue; } luNs = xmlSearchNs(elem->doc, elem, ns->prefix); if ((luNs != NULL) && (xmlStrEqual(luNs->href, ns->href))) { ns = ns->next; continue; } } q = xmlNewNs(elem, ns->href, ns->prefix); if (p == NULL) { ret = p = q; } else if (q != NULL) { p->next = q; p = q; } ns = ns->next; } while (ns != NULL); return(ret); } /** * xsltShallowCopyNsNode: * @ctxt: the XSLT transformation context * @invocNode: responsible node in the stylesheet; used for error reports * @insert: the target element node in the result tree * @ns: the namespace node * * This is used for copying ns-nodes with xsl:copy-of and xsl:copy. * * Returns a new/existing ns-node, or NULL. */ static xmlNsPtr xsltShallowCopyNsNode(xsltTransformContextPtr ctxt, xmlNodePtr invocNode, xmlNodePtr insert, xmlNsPtr ns) { /* * TODO: Contrary to header comments, this is declared as int. * be modified to return a node pointer, or NULL if any error */ xmlNsPtr tmpns; if ((insert == NULL) || (insert->type != XML_ELEMENT_NODE)) return(NULL); if (insert->children != NULL) { xsltTransformError(ctxt, NULL, invocNode, "Namespace nodes must be added before " "any child nodes are added to an element.\n"); return(NULL); } /* * BIG NOTE: Xalan-J simply overwrites any ns-decls with * an equal prefix. We definitively won't do that. * * MSXML 4.0 and the .NET ignores ns-decls for which an * equal prefix is already in use. * * Saxon raises an error like: * "net.sf.saxon.xpath.DynamicError: Cannot create two namespace * nodes with the same name". * * NOTE: We'll currently follow MSXML here. * REVISIT TODO: Check if it's better to follow Saxon here. */ if (ns->prefix == NULL) { /* * If we are adding ns-nodes to an element using e.g. * , then we need * to ensure that we don't incorrectly declare a default * namespace on an element in no namespace, which otherwise * would move the element incorrectly into a namespace, if * the node tree is serialized. */ if (insert->ns == NULL) goto occupied; } else if ((ns->prefix[0] == 'x') && xmlStrEqual(ns->prefix, BAD_CAST "xml")) { /* * The XML namespace is built in. */ return(NULL); } if (insert->nsDef != NULL) { tmpns = insert->nsDef; do { if ((tmpns->prefix == NULL) == (ns->prefix == NULL)) { if ((tmpns->prefix == ns->prefix) || xmlStrEqual(tmpns->prefix, ns->prefix)) { /* * Same prefix. */ if (xmlStrEqual(tmpns->href, ns->href)) return(NULL); goto occupied; } } tmpns = tmpns->next; } while (tmpns != NULL); } tmpns = xmlSearchNs(insert->doc, insert, ns->prefix); if ((tmpns != NULL) && xmlStrEqual(tmpns->href, ns->href)) return(NULL); /* * Declare a new namespace. * TODO: The problem (wrt efficiency) with this xmlNewNs() is * that it will again search the already declared namespaces * for a duplicate :-/ */ return(xmlNewNs(insert, ns->href, ns->prefix)); occupied: /* * TODO: We could as well raise an error here (like Saxon does), * or at least generate a warning. */ return(NULL); } /** * xsltCopyTreeInternal: * @ctxt: the XSLT transformation context * @invocNode: responsible node in the stylesheet; used for error reports * @node: the element node in the source tree * @insert: the parent in the result tree * @isLRE: indicates if @node is a Literal Result Element * @topElemVisited: indicates if a top-most element was already processed * * Make a copy of the full tree under the element node @node * and insert it as last child of @insert * * NOTE: Not to be used for Literal Result Elements. * * Used by: * - xsltCopyOf() * * Returns a pointer to the new tree, or NULL in case of error */ static xmlNodePtr xsltCopyTreeInternal(xsltTransformContextPtr ctxt, xmlNodePtr invocNode, xmlNodePtr node, xmlNodePtr insert, int isLRE, int topElemVisited) { xmlNodePtr copy; if (node == NULL) return(NULL); switch (node->type) { case XML_ELEMENT_NODE: case XML_ENTITY_REF_NODE: case XML_ENTITY_NODE: case XML_PI_NODE: case XML_COMMENT_NODE: case XML_DOCUMENT_NODE: case XML_HTML_DOCUMENT_NODE: #ifdef LIBXML_DOCB_ENABLED case XML_DOCB_DOCUMENT_NODE: #endif break; case XML_TEXT_NODE: { int noenc = (node->name == xmlStringTextNoenc); return(xsltCopyTextString(ctxt, insert, node->content, noenc)); } case XML_CDATA_SECTION_NODE: return(xsltCopyTextString(ctxt, insert, node->content, 0)); case XML_ATTRIBUTE_NODE: return((xmlNodePtr) xsltShallowCopyAttr(ctxt, invocNode, insert, (xmlAttrPtr) node)); case XML_NAMESPACE_DECL: return((xmlNodePtr) xsltShallowCopyNsNode(ctxt, invocNode, insert, (xmlNsPtr) node)); case XML_DOCUMENT_TYPE_NODE: case XML_DOCUMENT_FRAG_NODE: case XML_NOTATION_NODE: case XML_DTD_NODE: case XML_ELEMENT_DECL: case XML_ATTRIBUTE_DECL: case XML_ENTITY_DECL: case XML_XINCLUDE_START: case XML_XINCLUDE_END: return(NULL); } if (XSLT_IS_RES_TREE_FRAG(node)) { if (node->children != NULL) copy = xsltCopyTreeList(ctxt, invocNode, node->children, insert, 0, 0); else copy = NULL; return(copy); } copy = xmlDocCopyNode(node, insert->doc, 0); if (copy != NULL) { copy->doc = ctxt->output; copy = xsltAddChild(insert, copy); /* * The node may have been coalesced into another text node. */ if (insert->last != copy) return(insert->last); copy->next = NULL; if (node->type == XML_ELEMENT_NODE) { /* * Copy in-scope namespace nodes. * * REVISIT: Since we try to reuse existing in-scope ns-decls by * using xmlSearchNsByHref(), this will eventually change * the prefix of an original ns-binding; thus it might * break QNames in element/attribute content. * OPTIMIZE TODO: If we had a xmlNsPtr * on the transformation * context, plus a ns-lookup function, which writes directly * to a given list, then we wouldn't need to create/free the * nsList every time. */ if ((topElemVisited == 0) && (node->parent != NULL) && (node->parent->type != XML_DOCUMENT_NODE) && (node->parent->type != XML_HTML_DOCUMENT_NODE)) { xmlNsPtr *nsList, *curns, ns; /* * If this is a top-most element in a tree to be * copied, then we need to ensure that all in-scope * namespaces are copied over. For nodes deeper in the * tree, it is sufficient to reconcile only the ns-decls * (node->nsDef entries). */ nsList = xmlGetNsList(node->doc, node); if (nsList != NULL) { curns = nsList; do { /* * Search by prefix first in order to break as less * QNames in element/attribute content as possible. */ ns = xmlSearchNs(insert->doc, insert, (*curns)->prefix); if ((ns == NULL) || (! xmlStrEqual(ns->href, (*curns)->href))) { ns = NULL; /* * Search by namespace name. * REVISIT TODO: Currently disabled. */ #if 0 ns = xmlSearchNsByHref(insert->doc, insert, (*curns)->href); #endif } if (ns == NULL) { /* * Declare a new namespace on the copied element. */ ns = xmlNewNs(copy, (*curns)->href, (*curns)->prefix); /* TODO: Handle errors */ } if (node->ns == *curns) { /* * If this was the original's namespace then set * the generated counterpart on the copy. */ copy->ns = ns; } curns++; } while (*curns != NULL); xmlFree(nsList); } } else if (node->nsDef != NULL) { /* * Copy over all namespace declaration attributes. */ if (node->nsDef != NULL) { if (isLRE) xsltCopyNamespaceList(ctxt, copy, node->nsDef); else xsltCopyNamespaceListInternal(copy, node->nsDef); } } /* * Set the namespace. */ if (node->ns != NULL) { if (copy->ns == NULL) { /* * This will map copy->ns to one of the newly created * in-scope ns-decls, OR create a new ns-decl on @copy. */ copy->ns = xsltGetSpecialNamespace(ctxt, invocNode, node->ns->href, node->ns->prefix, copy); } } else if ((insert->type == XML_ELEMENT_NODE) && (insert->ns != NULL)) { /* * "Undeclare" the default namespace on @copy with xmlns="". */ xsltGetSpecialNamespace(ctxt, invocNode, NULL, NULL, copy); } /* * Copy attribute nodes. */ if (node->properties != NULL) { xsltCopyAttrListNoOverwrite(ctxt, invocNode, copy, node->properties); } if (topElemVisited == 0) topElemVisited = 1; } /* * Copy the subtree. */ if (node->children != NULL) { xsltCopyTreeList(ctxt, invocNode, node->children, copy, isLRE, topElemVisited); } } else { xsltTransformError(ctxt, NULL, invocNode, "xsltCopyTreeInternal: Copying of '%s' failed.\n", node->name); } return(copy); } /** * xsltCopyTree: * @ctxt: the XSLT transformation context * @node: the element node in the source tree * @insert: the parent in the result tree * @literal: indicates if @node is a Literal Result Element * * Make a copy of the full tree under the element node @node * and insert it as last child of @insert * For literal result element, some of the namespaces may not be copied * over according to section 7.1. * TODO: Why is this a public function? * * Returns a pointer to the new tree, or NULL in case of error */ xmlNodePtr xsltCopyTree(xsltTransformContextPtr ctxt, xmlNodePtr node, xmlNodePtr insert, int literal) { return(xsltCopyTreeInternal(ctxt, node, node, insert, literal, 0)); } /************************************************************************ * * * Error/fallback processing * * * ************************************************************************/ /** * xsltApplyFallbacks: * @ctxt: a XSLT process context * @node: the node in the source tree. * @inst: the node generating the error * * Process possible xsl:fallback nodes present under @inst * * Returns the number of xsl:fallback element found and processed */ static int xsltApplyFallbacks(xsltTransformContextPtr ctxt, xmlNodePtr node, xmlNodePtr inst) { xmlNodePtr child; int ret = 0; if ((ctxt == NULL) || (node == NULL) || (inst == NULL) || (inst->children == NULL)) return(0); child = inst->children; while (child != NULL) { if ((IS_XSLT_ELEM(child)) && (xmlStrEqual(child->name, BAD_CAST "fallback"))) { #ifdef WITH_XSLT_DEBUG_PARSING xsltGenericDebug(xsltGenericDebugContext, "applying xsl:fallback\n"); #endif ret++; xsltApplySequenceConstructor(ctxt, node, child->children, NULL); } child = child->next; } return(ret); } /************************************************************************ * * * Default processing * * * ************************************************************************/ /** * xsltDefaultProcessOneNode: * @ctxt: a XSLT process context * @node: the node in the source tree. * @params: extra parameters passed to the template if any * * Process the source node with the default built-in template rule: * * * * * and * * * * * * Note also that namespace declarations are copied directly: * * the built-in template rule is the only template rule that is applied * for namespace nodes. */ static void xsltDefaultProcessOneNode(xsltTransformContextPtr ctxt, xmlNodePtr node, xsltStackElemPtr params) { xmlNodePtr copy; xmlNodePtr delete = NULL, cur; int nbchild = 0, oldSize; int childno = 0, oldPos; xsltTemplatePtr template; CHECK_STOPPED; /* * Handling of leaves */ switch (node->type) { case XML_DOCUMENT_NODE: case XML_HTML_DOCUMENT_NODE: case XML_ELEMENT_NODE: break; case XML_CDATA_SECTION_NODE: #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_PROCESS_NODE,xsltGenericDebug(xsltGenericDebugContext, "xsltDefaultProcessOneNode: copy CDATA %s\n", node->content)); #endif copy = xsltCopyText(ctxt, ctxt->insert, node, 0); if (copy == NULL) { xsltTransformError(ctxt, NULL, node, "xsltDefaultProcessOneNode: cdata copy failed\n"); } return; case XML_TEXT_NODE: #ifdef WITH_XSLT_DEBUG_PROCESS if (node->content == NULL) { XSLT_TRACE(ctxt,XSLT_TRACE_PROCESS_NODE,xsltGenericDebug(xsltGenericDebugContext, "xsltDefaultProcessOneNode: copy empty text\n")); return; } else { XSLT_TRACE(ctxt,XSLT_TRACE_PROCESS_NODE,xsltGenericDebug(xsltGenericDebugContext, "xsltDefaultProcessOneNode: copy text %s\n", node->content)); } #endif copy = xsltCopyText(ctxt, ctxt->insert, node, 0); if (copy == NULL) { xsltTransformError(ctxt, NULL, node, "xsltDefaultProcessOneNode: text copy failed\n"); } return; case XML_ATTRIBUTE_NODE: cur = node->children; while ((cur != NULL) && (cur->type != XML_TEXT_NODE)) cur = cur->next; if (cur == NULL) { xsltTransformError(ctxt, NULL, node, "xsltDefaultProcessOneNode: no text for attribute\n"); } else { #ifdef WITH_XSLT_DEBUG_PROCESS if (cur->content == NULL) { XSLT_TRACE(ctxt,XSLT_TRACE_PROCESS_NODE,xsltGenericDebug(xsltGenericDebugContext, "xsltDefaultProcessOneNode: copy empty text\n")); } else { XSLT_TRACE(ctxt,XSLT_TRACE_PROCESS_NODE,xsltGenericDebug(xsltGenericDebugContext, "xsltDefaultProcessOneNode: copy text %s\n", cur->content)); } #endif copy = xsltCopyText(ctxt, ctxt->insert, cur, 0); if (copy == NULL) { xsltTransformError(ctxt, NULL, node, "xsltDefaultProcessOneNode: text copy failed\n"); } } return; default: return; } /* * Handling of Elements: first pass, cleanup and counting */ cur = node->children; while (cur != NULL) { switch (cur->type) { case XML_TEXT_NODE: case XML_CDATA_SECTION_NODE: case XML_DOCUMENT_NODE: case XML_HTML_DOCUMENT_NODE: case XML_ELEMENT_NODE: case XML_PI_NODE: case XML_COMMENT_NODE: nbchild++; break; case XML_DTD_NODE: /* Unlink the DTD, it's still reachable using doc->intSubset */ if (cur->next != NULL) cur->next->prev = cur->prev; if (cur->prev != NULL) cur->prev->next = cur->next; break; default: #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_PROCESS_NODE,xsltGenericDebug(xsltGenericDebugContext, "xsltDefaultProcessOneNode: skipping node type %d\n", cur->type)); #endif delete = cur; } cur = cur->next; if (delete != NULL) { #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_PROCESS_NODE,xsltGenericDebug(xsltGenericDebugContext, "xsltDefaultProcessOneNode: removing ignorable blank node\n")); #endif xmlUnlinkNode(delete); xmlFreeNode(delete); delete = NULL; } } if (delete != NULL) { #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_PROCESS_NODE,xsltGenericDebug(xsltGenericDebugContext, "xsltDefaultProcessOneNode: removing ignorable blank node\n")); #endif xmlUnlinkNode(delete); xmlFreeNode(delete); delete = NULL; } /* * Handling of Elements: second pass, actual processing */ oldSize = ctxt->xpathCtxt->contextSize; oldPos = ctxt->xpathCtxt->proximityPosition; cur = node->children; while (cur != NULL) { childno++; switch (cur->type) { case XML_DOCUMENT_NODE: case XML_HTML_DOCUMENT_NODE: case XML_ELEMENT_NODE: ctxt->xpathCtxt->contextSize = nbchild; ctxt->xpathCtxt->proximityPosition = childno; xsltProcessOneNode(ctxt, cur, params); break; case XML_CDATA_SECTION_NODE: template = xsltGetTemplate(ctxt, cur, NULL); if (template) { #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_PROCESS_NODE,xsltGenericDebug(xsltGenericDebugContext, "xsltDefaultProcessOneNode: applying template for CDATA %s\n", cur->content)); #endif /* * Instantiate the xsl:template. */ xsltApplyXSLTTemplate(ctxt, cur, template->content, template, params); } else /* if (ctxt->mode == NULL) */ { #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_PROCESS_NODE,xsltGenericDebug(xsltGenericDebugContext, "xsltDefaultProcessOneNode: copy CDATA %s\n", cur->content)); #endif copy = xsltCopyText(ctxt, ctxt->insert, cur, 0); if (copy == NULL) { xsltTransformError(ctxt, NULL, cur, "xsltDefaultProcessOneNode: cdata copy failed\n"); } } break; case XML_TEXT_NODE: template = xsltGetTemplate(ctxt, cur, NULL); if (template) { #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_PROCESS_NODE,xsltGenericDebug(xsltGenericDebugContext, "xsltDefaultProcessOneNode: applying template for text %s\n", cur->content)); #endif ctxt->xpathCtxt->contextSize = nbchild; ctxt->xpathCtxt->proximityPosition = childno; /* * Instantiate the xsl:template. */ xsltApplyXSLTTemplate(ctxt, cur, template->content, template, params); } else /* if (ctxt->mode == NULL) */ { #ifdef WITH_XSLT_DEBUG_PROCESS if (cur->content == NULL) { XSLT_TRACE(ctxt,XSLT_TRACE_PROCESS_NODE,xsltGenericDebug(xsltGenericDebugContext, "xsltDefaultProcessOneNode: copy empty text\n")); } else { XSLT_TRACE(ctxt,XSLT_TRACE_PROCESS_NODE,xsltGenericDebug(xsltGenericDebugContext, "xsltDefaultProcessOneNode: copy text %s\n", cur->content)); } #endif copy = xsltCopyText(ctxt, ctxt->insert, cur, 0); if (copy == NULL) { xsltTransformError(ctxt, NULL, cur, "xsltDefaultProcessOneNode: text copy failed\n"); } } break; case XML_PI_NODE: case XML_COMMENT_NODE: template = xsltGetTemplate(ctxt, cur, NULL); if (template) { #ifdef WITH_XSLT_DEBUG_PROCESS if (cur->type == XML_PI_NODE) { XSLT_TRACE(ctxt,XSLT_TRACE_PROCESS_NODE,xsltGenericDebug(xsltGenericDebugContext, "xsltDefaultProcessOneNode: template found for PI %s\n", cur->name)); } else if (cur->type == XML_COMMENT_NODE) { XSLT_TRACE(ctxt,XSLT_TRACE_PROCESS_NODE,xsltGenericDebug(xsltGenericDebugContext, "xsltDefaultProcessOneNode: template found for comment\n")); } #endif ctxt->xpathCtxt->contextSize = nbchild; ctxt->xpathCtxt->proximityPosition = childno; /* * Instantiate the xsl:template. */ xsltApplyXSLTTemplate(ctxt, cur, template->content, template, params); } break; default: break; } cur = cur->next; } ctxt->xpathCtxt->contextSize = oldSize; ctxt->xpathCtxt->proximityPosition = oldPos; } /** * xsltProcessOneNode: * @ctxt: a XSLT process context * @contextNode: the "current node" in the source tree * @withParams: extra parameters (e.g. xsl:with-param) passed to the * template if any * * Process the source node. */ void xsltProcessOneNode(xsltTransformContextPtr ctxt, xmlNodePtr contextNode, xsltStackElemPtr withParams) { xsltTemplatePtr templ; xmlNodePtr oldNode; templ = xsltGetTemplate(ctxt, contextNode, NULL); /* * If no template is found, apply the default rule. */ if (templ == NULL) { #ifdef WITH_XSLT_DEBUG_PROCESS if (contextNode->type == XML_DOCUMENT_NODE) { XSLT_TRACE(ctxt,XSLT_TRACE_PROCESS_NODE,xsltGenericDebug(xsltGenericDebugContext, "xsltProcessOneNode: no template found for /\n")); } else if (contextNode->type == XML_CDATA_SECTION_NODE) { XSLT_TRACE(ctxt,XSLT_TRACE_PROCESS_NODE,xsltGenericDebug(xsltGenericDebugContext, "xsltProcessOneNode: no template found for CDATA\n")); } else if (contextNode->type == XML_ATTRIBUTE_NODE) { XSLT_TRACE(ctxt,XSLT_TRACE_PROCESS_NODE,xsltGenericDebug(xsltGenericDebugContext, "xsltProcessOneNode: no template found for attribute %s\n", ((xmlAttrPtr) contextNode)->name)); } else { XSLT_TRACE(ctxt,XSLT_TRACE_PROCESS_NODE,xsltGenericDebug(xsltGenericDebugContext, "xsltProcessOneNode: no template found for %s\n", contextNode->name)); } #endif oldNode = ctxt->node; ctxt->node = contextNode; xsltDefaultProcessOneNode(ctxt, contextNode, withParams); ctxt->node = oldNode; return; } if (contextNode->type == XML_ATTRIBUTE_NODE) { xsltTemplatePtr oldCurTempRule = ctxt->currentTemplateRule; /* * Set the "current template rule". */ ctxt->currentTemplateRule = templ; #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_PROCESS_NODE,xsltGenericDebug(xsltGenericDebugContext, "xsltProcessOneNode: applying template '%s' for attribute %s\n", templ->match, contextNode->name)); #endif xsltApplyXSLTTemplate(ctxt, contextNode, templ->content, templ, withParams); ctxt->currentTemplateRule = oldCurTempRule; } else { xsltTemplatePtr oldCurTempRule = ctxt->currentTemplateRule; /* * Set the "current template rule". */ ctxt->currentTemplateRule = templ; #ifdef WITH_XSLT_DEBUG_PROCESS if (contextNode->type == XML_DOCUMENT_NODE) { XSLT_TRACE(ctxt,XSLT_TRACE_PROCESS_NODE,xsltGenericDebug(xsltGenericDebugContext, "xsltProcessOneNode: applying template '%s' for /\n", templ->match)); } else { XSLT_TRACE(ctxt,XSLT_TRACE_PROCESS_NODE,xsltGenericDebug(xsltGenericDebugContext, "xsltProcessOneNode: applying template '%s' for %s\n", templ->match, contextNode->name)); } #endif xsltApplyXSLTTemplate(ctxt, contextNode, templ->content, templ, withParams); ctxt->currentTemplateRule = oldCurTempRule; } } static xmlNodePtr xsltDebuggerStartSequenceConstructor(xsltTransformContextPtr ctxt, xmlNodePtr contextNode, xmlNodePtr list, xsltTemplatePtr templ, int *addCallResult) { xmlNodePtr debugedNode = NULL; if (ctxt->debugStatus != XSLT_DEBUG_NONE) { if (templ) { *addCallResult = xslAddCall(templ, templ->elem); } else { *addCallResult = xslAddCall(NULL, list); } switch (ctxt->debugStatus) { case XSLT_DEBUG_RUN_RESTART: case XSLT_DEBUG_QUIT: if (*addCallResult) xslDropCall(); return(NULL); } if (templ) { xslHandleDebugger(templ->elem, contextNode, templ, ctxt); debugedNode = templ->elem; } else if (list) { xslHandleDebugger(list, contextNode, templ, ctxt); debugedNode = list; } else if (ctxt->inst) { xslHandleDebugger(ctxt->inst, contextNode, templ, ctxt); debugedNode = ctxt->inst; } } return(debugedNode); } /** * xsltLocalVariablePush: * @ctxt: the transformation context * @variable: variable to be pushed to the variable stack * @level: new value for variable's level * * Places the variable onto the local variable stack * * Returns: 0 for success, -1 for any error * **NOTE:** * This is an internal routine and should not be called by users! */ int xsltLocalVariablePush(xsltTransformContextPtr ctxt, xsltStackElemPtr variable, int level) { if (ctxt->varsMax == 0) { ctxt->varsMax = 10; ctxt->varsTab = (xsltStackElemPtr *) xmlMalloc(ctxt->varsMax * sizeof(ctxt->varsTab[0])); if (ctxt->varsTab == NULL) { xmlGenericError(xmlGenericErrorContext, "malloc failed !\n"); return (-1); } } if (ctxt->varsNr >= ctxt->varsMax) { ctxt->varsMax *= 2; ctxt->varsTab = (xsltStackElemPtr *) xmlRealloc(ctxt->varsTab, ctxt->varsMax * sizeof(ctxt->varsTab[0])); if (ctxt->varsTab == NULL) { xmlGenericError(xmlGenericErrorContext, "realloc failed !\n"); return (-1); } } ctxt->varsTab[ctxt->varsNr++] = variable; ctxt->vars = variable; variable->level = level; return(0); } /** * xsltReleaseLocalRVTs: * * Fragments which are results of extension instructions * are preserved; all other fragments are freed/cached. */ static void xsltReleaseLocalRVTs(xsltTransformContextPtr ctxt, xmlDocPtr base) { xmlDocPtr cur = ctxt->localRVT, tmp; while ((cur != NULL) && (cur != base)) { if (cur->psvi == (void *) ((long) 1)) { cur = (xmlDocPtr) cur->next; } else { tmp = cur; cur = (xmlDocPtr) cur->next; if (tmp == ctxt->localRVT) ctxt->localRVT = cur; /* * We need ctxt->localRVTBase for extension instructions * which return values (like EXSLT's function). */ if (tmp == ctxt->localRVTBase) ctxt->localRVTBase = cur; if (tmp->prev) tmp->prev->next = (xmlNodePtr) cur; if (cur) cur->prev = tmp->prev; xsltReleaseRVT(ctxt, tmp); } } } /** * xsltApplySequenceConstructor: * @ctxt: a XSLT process context * @contextNode: the "current node" in the source tree * @list: the nodes of a sequence constructor; * (plus leading xsl:param elements) * @templ: the compiled xsl:template (optional) * * Processes a sequence constructor. * * NOTE: ctxt->currentTemplateRule was introduced to reflect the * semantics of "current template rule". I.e. the field ctxt->templ * is not intended to reflect this, thus always pushed onto the * template stack. */ static void xsltApplySequenceConstructor(xsltTransformContextPtr ctxt, xmlNodePtr contextNode, xmlNodePtr list, xsltTemplatePtr templ) { xmlNodePtr oldInsert, oldInst, oldCurInst, oldContextNode; xmlNodePtr cur, insert, copy = NULL; int level = 0, oldVarsNr; xmlDocPtr oldLocalFragmentTop, oldLocalFragmentBase; #ifdef XSLT_REFACTORED xsltStylePreCompPtr info; #endif #ifdef WITH_DEBUGGER int addCallResult = 0; xmlNodePtr debuggedNode = NULL; #endif if (ctxt == NULL) return; #ifdef WITH_DEBUGGER if (ctxt->debugStatus != XSLT_DEBUG_NONE) { debuggedNode = xsltDebuggerStartSequenceConstructor(ctxt, contextNode, list, templ, &addCallResult); if (debuggedNode == NULL) return; } #endif if (list == NULL) return; CHECK_STOPPED; oldLocalFragmentTop = ctxt->localRVT; oldInsert = insert = ctxt->insert; oldInst = oldCurInst = ctxt->inst; oldContextNode = ctxt->node; /* * Save current number of variables on the stack; new vars are popped when * exiting. */ oldVarsNr = ctxt->varsNr; /* * Process the sequence constructor. */ cur = list; while (cur != NULL) { ctxt->inst = cur; #ifdef WITH_DEBUGGER switch (ctxt->debugStatus) { case XSLT_DEBUG_RUN_RESTART: case XSLT_DEBUG_QUIT: break; } #endif /* * Test; we must have a valid insertion point. */ if (insert == NULL) { #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_APPLY_TEMPLATE,xsltGenericDebug(xsltGenericDebugContext, "xsltApplySequenceConstructor: insert == NULL !\n")); #endif goto error; } #ifdef WITH_DEBUGGER if ((ctxt->debugStatus != XSLT_DEBUG_NONE) && (debuggedNode != cur)) xslHandleDebugger(cur, contextNode, templ, ctxt); #endif #ifdef XSLT_REFACTORED if (cur->type == XML_ELEMENT_NODE) { info = (xsltStylePreCompPtr) cur->psvi; /* * We expect a compiled representation on: * 1) XSLT instructions of this XSLT version (1.0) * (with a few exceptions) * 2) Literal result elements * 3) Extension instructions * 4) XSLT instructions of future XSLT versions * (forwards-compatible mode). */ if (info == NULL) { /* * Handle the rare cases where we don't expect a compiled * representation on an XSLT element. */ if (IS_XSLT_ELEM_FAST(cur) && IS_XSLT_NAME(cur, "message")) { xsltMessage(ctxt, contextNode, cur); goto skip_children; } /* * Something really went wrong: */ xsltTransformError(ctxt, NULL, cur, "Internal error in xsltApplySequenceConstructor(): " "The element '%s' in the stylesheet has no compiled " "representation.\n", cur->name); goto skip_children; } if (info->type == XSLT_FUNC_LITERAL_RESULT_ELEMENT) { xsltStyleItemLRElementInfoPtr lrInfo = (xsltStyleItemLRElementInfoPtr) info; /* * Literal result elements * -------------------------------------------------------- */ #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt, XSLT_TRACE_APPLY_TEMPLATE, xsltGenericDebug(xsltGenericDebugContext, "xsltApplySequenceConstructor: copy literal result " "element '%s'\n", cur->name)); #endif /* * Copy the raw element-node. * OLD: if ((copy = xsltShallowCopyElem(ctxt, cur, insert)) * == NULL) * goto error; */ copy = xmlDocCopyNode(cur, insert->doc, 0); if (copy == NULL) { xsltTransformError(ctxt, NULL, cur, "Internal error in xsltApplySequenceConstructor(): " "Failed to copy literal result element '%s'.\n", cur->name); goto error; } else { /* * Add the element-node to the result tree. */ copy->doc = ctxt->output; copy = xsltAddChild(insert, copy); /* * Create effective namespaces declarations. * OLD: xsltCopyNamespaceList(ctxt, copy, cur->nsDef); */ if (lrInfo->effectiveNs != NULL) { xsltEffectiveNsPtr effNs = lrInfo->effectiveNs; xmlNsPtr ns, lastns = NULL; while (effNs != NULL) { /* * Avoid generating redundant namespace * declarations; thus lookup if there is already * such a ns-decl in the result. */ ns = xmlSearchNs(copy->doc, copy, effNs->prefix); if ((ns != NULL) && (xmlStrEqual(ns->href, effNs->nsName))) { effNs = effNs->next; continue; } ns = xmlNewNs(copy, effNs->nsName, effNs->prefix); if (ns == NULL) { xsltTransformError(ctxt, NULL, cur, "Internal error in " "xsltApplySequenceConstructor(): " "Failed to copy a namespace " "declaration.\n"); goto error; } if (lastns == NULL) copy->nsDef = ns; else lastns->next =ns; lastns = ns; effNs = effNs->next; } } /* * NOTE that we don't need to apply ns-alising: this was * already done at compile-time. */ if (cur->ns != NULL) { /* * If there's no such ns-decl in the result tree, * then xsltGetSpecialNamespace() will * create a ns-decl on the copied node. */ copy->ns = xsltGetSpecialNamespace(ctxt, cur, cur->ns->href, cur->ns->prefix, copy); } else { /* * Undeclare the default namespace if needed. * This can be skipped, if the result element has * no ns-decls, in which case the result element * obviously does not declare a default namespace; * AND there's either no parent, or the parent * element is in no namespace; this means there's no * default namespace is scope to care about. * * REVISIT: This might result in massive * generation of ns-decls if nodes in a default * namespaces are mixed with nodes in no namespace. * */ if (copy->nsDef || ((insert != NULL) && (insert->type == XML_ELEMENT_NODE) && (insert->ns != NULL))) { xsltGetSpecialNamespace(ctxt, cur, NULL, NULL, copy); } } } /* * SPEC XSLT 2.0 "Each attribute of the literal result * element, other than an attribute in the XSLT namespace, * is processed to produce an attribute for the element in * the result tree." * NOTE: See bug #341325. */ if (cur->properties != NULL) { xsltAttrListTemplateProcess(ctxt, copy, cur->properties); } } else if (IS_XSLT_ELEM_FAST(cur)) { /* * XSLT instructions * -------------------------------------------------------- */ if (info->type == XSLT_FUNC_UNKOWN_FORWARDS_COMPAT) { /* * We hit an unknown XSLT element. * Try to apply one of the fallback cases. */ ctxt->insert = insert; if (!xsltApplyFallbacks(ctxt, contextNode, cur)) { xsltTransformError(ctxt, NULL, cur, "The is no fallback behaviour defined for " "the unknown XSLT element '%s'.\n", cur->name); } ctxt->insert = oldInsert; } else if (info->func != NULL) { /* * Execute the XSLT instruction. */ ctxt->insert = insert; info->func(ctxt, contextNode, cur, (xsltElemPreCompPtr) info); /* * Cleanup temporary tree fragments. */ if (oldLocalFragmentTop != ctxt->localRVT) xsltReleaseLocalRVTs(ctxt, oldLocalFragmentTop); ctxt->insert = oldInsert; } else if (info->type == XSLT_FUNC_VARIABLE) { xsltStackElemPtr tmpvar = ctxt->vars; xsltParseStylesheetVariable(ctxt, cur); if (tmpvar != ctxt->vars) { /* * TODO: Using a @tmpvar is an annoying workaround, but * the current mechanisms do not provide any other way * of knowing if the var was really pushed onto the * stack. */ ctxt->vars->level = level; } } else if (info->type == XSLT_FUNC_MESSAGE) { /* * TODO: Won't be hit, since we don't compile xsl:message. */ xsltMessage(ctxt, contextNode, cur); } else { xsltTransformError(ctxt, NULL, cur, "Unexpected XSLT element '%s'.\n", cur->name); } goto skip_children; } else { xsltTransformFunction func; /* * Extension intructions (elements) * -------------------------------------------------------- */ if (cur->psvi == xsltExtMarker) { /* * The xsltExtMarker was set during the compilation * of extension instructions if there was no registered * handler for this specific extension function at * compile-time. * Libxslt will now lookup if a handler is * registered in the context of this transformation. */ func = (xsltTransformFunction) xsltExtElementLookup(ctxt, cur->name, cur->ns->href); } else func = ((xsltElemPreCompPtr) cur->psvi)->func; if (func == NULL) { /* * No handler available. * Try to execute fallback behaviour via xsl:fallback. */ #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt, XSLT_TRACE_APPLY_TEMPLATE, xsltGenericDebug(xsltGenericDebugContext, "xsltApplySequenceConstructor: unknown extension %s\n", cur->name)); #endif ctxt->insert = insert; if (!xsltApplyFallbacks(ctxt, contextNode, cur)) { xsltTransformError(ctxt, NULL, cur, "Unknown extension instruction '{%s}%s'.\n", cur->ns->href, cur->name); } ctxt->insert = oldInsert; } else { /* * Execute the handler-callback. */ #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_APPLY_TEMPLATE,xsltGenericDebug(xsltGenericDebugContext, "xsltApplySequenceConstructor: extension construct %s\n", cur->name)); #endif ctxt->insert = insert; /* * We need the fragment base for extension instructions * which return values (like EXSLT's function). */ oldLocalFragmentBase = ctxt->localRVTBase; ctxt->localRVTBase = NULL; func(ctxt, contextNode, cur, cur->psvi); ctxt->localRVTBase = oldLocalFragmentBase; /* * Cleanup temporary tree fragments. */ if (oldLocalFragmentTop != ctxt->localRVT) xsltReleaseLocalRVTs(ctxt, oldLocalFragmentTop); ctxt->insert = oldInsert; } goto skip_children; } } else if (XSLT_IS_TEXT_NODE(cur)) { /* * Text * ------------------------------------------------------------ */ #ifdef WITH_XSLT_DEBUG_PROCESS if (cur->name == xmlStringTextNoenc) { XSLT_TRACE(ctxt, XSLT_TRACE_APPLY_TEMPLATE, xsltGenericDebug(xsltGenericDebugContext, "xsltApplySequenceConstructor: copy unescaped text '%s'\n", cur->content)); } else { XSLT_TRACE(ctxt, XSLT_TRACE_APPLY_TEMPLATE, xsltGenericDebug(xsltGenericDebugContext, "xsltApplySequenceConstructor: copy text '%s'\n", cur->content)); } #endif if (xsltCopyText(ctxt, insert, cur, ctxt->internalized) == NULL) goto error; } #else /* XSLT_REFACTORED */ if (IS_XSLT_ELEM(cur)) { /* * This is an XSLT node */ xsltStylePreCompPtr info = (xsltStylePreCompPtr) cur->psvi; if (info == NULL) { if (IS_XSLT_NAME(cur, "message")) { xsltMessage(ctxt, contextNode, cur); } else { /* * That's an error try to apply one of the fallback cases */ ctxt->insert = insert; if (!xsltApplyFallbacks(ctxt, contextNode, cur)) { xsltGenericError(xsltGenericErrorContext, "xsltApplySequenceConstructor: %s was not compiled\n", cur->name); } ctxt->insert = oldInsert; } goto skip_children; } if (info->func != NULL) { oldCurInst = ctxt->inst; ctxt->inst = cur; ctxt->insert = insert; oldLocalFragmentBase = ctxt->localRVTBase; ctxt->localRVTBase = NULL; info->func(ctxt, contextNode, cur, (xsltElemPreCompPtr) info); ctxt->localRVTBase = oldLocalFragmentBase; /* * Cleanup temporary tree fragments. */ if (oldLocalFragmentTop != ctxt->localRVT) xsltReleaseLocalRVTs(ctxt, oldLocalFragmentTop); ctxt->insert = oldInsert; ctxt->inst = oldCurInst; goto skip_children; } if (IS_XSLT_NAME(cur, "variable")) { xsltStackElemPtr tmpvar = ctxt->vars; oldCurInst = ctxt->inst; ctxt->inst = cur; xsltParseStylesheetVariable(ctxt, cur); ctxt->inst = oldCurInst; if (tmpvar != ctxt->vars) { /* * TODO: Using a @tmpvar is an annoying workaround, but * the current mechanisms do not provide any other way * of knowing if the var was really pushed onto the * stack. */ ctxt->vars->level = level; } } else if (IS_XSLT_NAME(cur, "message")) { xsltMessage(ctxt, contextNode, cur); } else { xsltTransformError(ctxt, NULL, cur, "Unexpected XSLT element '%s'.\n", cur->name); } goto skip_children; } else if ((cur->type == XML_TEXT_NODE) || (cur->type == XML_CDATA_SECTION_NODE)) { /* * This text comes from the stylesheet * For stylesheets, the set of whitespace-preserving * element names consists of just xsl:text. */ #ifdef WITH_XSLT_DEBUG_PROCESS if (cur->type == XML_CDATA_SECTION_NODE) { XSLT_TRACE(ctxt,XSLT_TRACE_APPLY_TEMPLATE,xsltGenericDebug(xsltGenericDebugContext, "xsltApplySequenceConstructor: copy CDATA text %s\n", cur->content)); } else if (cur->name == xmlStringTextNoenc) { XSLT_TRACE(ctxt,XSLT_TRACE_APPLY_TEMPLATE,xsltGenericDebug(xsltGenericDebugContext, "xsltApplySequenceConstructor: copy unescaped text %s\n", cur->content)); } else { XSLT_TRACE(ctxt,XSLT_TRACE_APPLY_TEMPLATE,xsltGenericDebug(xsltGenericDebugContext, "xsltApplySequenceConstructor: copy text %s\n", cur->content)); } #endif if (xsltCopyText(ctxt, insert, cur, ctxt->internalized) == NULL) goto error; } else if ((cur->type == XML_ELEMENT_NODE) && (cur->ns != NULL) && (cur->psvi != NULL)) { xsltTransformFunction function; oldCurInst = ctxt->inst; ctxt->inst = cur; /* * Flagged as an extension element */ if (cur->psvi == xsltExtMarker) function = (xsltTransformFunction) xsltExtElementLookup(ctxt, cur->name, cur->ns->href); else function = ((xsltElemPreCompPtr) cur->psvi)->func; if (function == NULL) { xmlNodePtr child; int found = 0; #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_APPLY_TEMPLATE,xsltGenericDebug(xsltGenericDebugContext, "xsltApplySequenceConstructor: unknown extension %s\n", cur->name)); #endif /* * Search if there are fallbacks */ child = cur->children; while (child != NULL) { if ((IS_XSLT_ELEM(child)) && (IS_XSLT_NAME(child, "fallback"))) { found = 1; xsltApplySequenceConstructor(ctxt, contextNode, child->children, NULL); } child = child->next; } if (!found) { xsltTransformError(ctxt, NULL, cur, "xsltApplySequenceConstructor: failed to find extension %s\n", cur->name); } } else { #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_APPLY_TEMPLATE,xsltGenericDebug(xsltGenericDebugContext, "xsltApplySequenceConstructor: extension construct %s\n", cur->name)); #endif ctxt->insert = insert; /* * We need the fragment base for extension instructions * which return values (like EXSLT's function). */ oldLocalFragmentBase = ctxt->localRVTBase; ctxt->localRVTBase = NULL; function(ctxt, contextNode, cur, cur->psvi); /* * Cleanup temporary tree fragments. */ if (oldLocalFragmentTop != ctxt->localRVT) xsltReleaseLocalRVTs(ctxt, oldLocalFragmentTop); ctxt->localRVTBase = oldLocalFragmentBase; ctxt->insert = oldInsert; } ctxt->inst = oldCurInst; goto skip_children; } else if (cur->type == XML_ELEMENT_NODE) { #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_APPLY_TEMPLATE,xsltGenericDebug(xsltGenericDebugContext, "xsltApplySequenceConstructor: copy node %s\n", cur->name)); #endif oldCurInst = ctxt->inst; ctxt->inst = cur; if ((copy = xsltShallowCopyElem(ctxt, cur, insert, 1)) == NULL) goto error; /* * Add extra namespaces inherited from the current template * if we are in the first level children and this is a * "real" template. */ if ((templ != NULL) && (oldInsert == insert) && (ctxt->templ != NULL) && (ctxt->templ->inheritedNs != NULL)) { int i; xmlNsPtr ns, ret; for (i = 0; i < ctxt->templ->inheritedNsNr; i++) { const xmlChar *URI = NULL; xsltStylesheetPtr style; ns = ctxt->templ->inheritedNs[i]; /* Note that the XSLT namespace was already excluded * in xsltGetInheritedNsList(). */ #if 0 if (xmlStrEqual(ns->href, XSLT_NAMESPACE)) continue; #endif style = ctxt->style; while (style != NULL) { if (style->nsAliases != NULL) URI = (const xmlChar *) xmlHashLookup(style->nsAliases, ns->href); if (URI != NULL) break; style = xsltNextImport(style); } if (URI == UNDEFINED_DEFAULT_NS) continue; if (URI == NULL) URI = ns->href; /* * TODO: The following will still be buggy for the * non-refactored code. */ ret = xmlSearchNs(copy->doc, copy, ns->prefix); if ((ret == NULL) || (!xmlStrEqual(ret->href, URI))) { xmlNewNs(copy, URI, ns->prefix); } } if (copy->ns != NULL) { /* * Fix the node namespace if needed */ copy->ns = xsltGetNamespace(ctxt, cur, copy->ns, copy); } } /* * all the attributes are directly inherited */ if (cur->properties != NULL) { xsltAttrListTemplateProcess(ctxt, copy, cur->properties); } ctxt->inst = oldCurInst; } #endif /* else of XSLT_REFACTORED */ /* * Descend into content in document order. */ if (cur->children != NULL) { if (cur->children->type != XML_ENTITY_DECL) { cur = cur->children; level++; if (copy != NULL) insert = copy; continue; } } skip_children: /* * If xslt:message was just processed, we might have hit a * terminate='yes'; if so, then break the loop and clean up. * TODO: Do we need to check this also before trying to descend * into the content? */ if (ctxt->state == XSLT_STATE_STOPPED) break; if (cur->next != NULL) { cur = cur->next; continue; } do { cur = cur->parent; level--; /* * Pop variables/params (xsl:variable and xsl:param). */ if ((ctxt->varsNr > oldVarsNr) && (ctxt->vars->level > level)) { xsltLocalVariablePop(ctxt, oldVarsNr, level); } insert = insert->parent; if (cur == NULL) break; if (cur == list->parent) { cur = NULL; break; } if (cur->next != NULL) { cur = cur->next; break; } } while (cur != NULL); } error: /* * In case of errors: pop remaining variables. */ if (ctxt->varsNr > oldVarsNr) xsltLocalVariablePop(ctxt, oldVarsNr, -1); ctxt->node = oldContextNode; ctxt->inst = oldInst; ctxt->insert = oldInsert; #ifdef WITH_DEBUGGER if ((ctxt->debugStatus != XSLT_DEBUG_NONE) && (addCallResult)) { xslDropCall(); } #endif } /* * xsltApplyXSLTTemplate: * @ctxt: a XSLT transformation context * @contextNode: the node in the source tree. * @list: the nodes of a sequence constructor; * (plus leading xsl:param elements) * @templ: the compiled xsl:template declaration; * NULL if a sequence constructor * @withParams: a set of caller-parameters (xsl:with-param) or NULL * * Called by: * - xsltApplyImports() * - xsltCallTemplate() * - xsltDefaultProcessOneNode() * - xsltProcessOneNode() */ static void xsltApplyXSLTTemplate(xsltTransformContextPtr ctxt, xmlNodePtr contextNode, xmlNodePtr list, xsltTemplatePtr templ, xsltStackElemPtr withParams) { int oldVarsBase = 0; long start = 0; xmlNodePtr cur; xsltStackElemPtr tmpParam = NULL; xmlDocPtr oldUserFragmentTop, oldLocalFragmentTop; #ifdef XSLT_REFACTORED xsltStyleItemParamPtr iparam; #else xsltStylePreCompPtr iparam; #endif #ifdef WITH_DEBUGGER int addCallResult = 0; #endif if (ctxt == NULL) return; if (templ == NULL) { xsltTransformError(ctxt, NULL, list, "xsltApplyXSLTTemplate: Bad arguments; @templ is mandatory.\n"); return; } #ifdef WITH_DEBUGGER if (ctxt->debugStatus != XSLT_DEBUG_NONE) { if (xsltDebuggerStartSequenceConstructor(ctxt, contextNode, list, templ, &addCallResult) == NULL) return; } #endif if (list == NULL) return; CHECK_STOPPED; /* * Check for infinite recursion: stop if the maximum of nested templates * is excceeded. Adjust xsltMaxDepth if you need more. */ if (ctxt->templNr >= ctxt->maxTemplateDepth) { xsltTransformError(ctxt, NULL, list, "xsltApplyXSLTTemplate: A potential infinite template recursion " "was detected.\n" "You can adjust xsltMaxDepth (--maxdepth) in order to " "raise the maximum number of nested template calls and " "variables/params (currently set to %d).\n", ctxt->maxTemplateDepth); xsltDebug(ctxt, contextNode, list, NULL); return; } if (ctxt->varsNr >= ctxt->maxTemplateVars) { xsltTransformError(ctxt, NULL, list, "xsltApplyXSLTTemplate: A potential infinite template recursion " "was detected.\n" "You can adjust maxTemplateVars (--maxvars) in order to " "raise the maximum number of variables/params (currently set to %d).\n", ctxt->maxTemplateVars); xsltDebug(ctxt, contextNode, list, NULL); return; } oldUserFragmentTop = ctxt->tmpRVT; ctxt->tmpRVT = NULL; oldLocalFragmentTop = ctxt->localRVT; /* * Initiate a distinct scope of local params/variables. */ oldVarsBase = ctxt->varsBase; ctxt->varsBase = ctxt->varsNr; ctxt->node = contextNode; if (ctxt->profile) { templ->nbCalls++; start = xsltTimestamp(); profPush(ctxt, 0); profCallgraphAdd(templ, ctxt->templ); } /* * Push the xsl:template declaration onto the stack. */ templPush(ctxt, templ); #ifdef WITH_XSLT_DEBUG_PROCESS if (templ->name != NULL) XSLT_TRACE(ctxt,XSLT_TRACE_APPLY_TEMPLATE,xsltGenericDebug(xsltGenericDebugContext, "applying xsl:template '%s'\n", templ->name)); #endif /* * Process xsl:param instructions and skip those elements for * further processing. */ cur = list; do { if (cur->type == XML_TEXT_NODE) { cur = cur->next; continue; } if ((cur->type != XML_ELEMENT_NODE) || (cur->name[0] != 'p') || (cur->psvi == NULL) || (! xmlStrEqual(cur->name, BAD_CAST "param")) || (! IS_XSLT_ELEM(cur))) { break; } list = cur->next; #ifdef XSLT_REFACTORED iparam = (xsltStyleItemParamPtr) cur->psvi; #else iparam = (xsltStylePreCompPtr) cur->psvi; #endif /* * Substitute xsl:param for a given xsl:with-param. * Since the XPath expression will reference the params/vars * by index, we need to slot the xsl:with-params in the * order of encountered xsl:params to keep the sequence of * params/variables in the stack exactly as it was at * compile time, */ tmpParam = NULL; if (withParams) { tmpParam = withParams; do { if ((tmpParam->name == (iparam->name)) && (tmpParam->nameURI == (iparam->ns))) { /* * Push the caller-parameter. */ xsltLocalVariablePush(ctxt, tmpParam, -1); break; } tmpParam = tmpParam->next; } while (tmpParam != NULL); } /* * Push the xsl:param. */ if (tmpParam == NULL) { /* * Note that we must assume that the added parameter * has a @depth of 0. */ xsltParseStylesheetParam(ctxt, cur); } cur = cur->next; } while (cur != NULL); /* * Process the sequence constructor. */ xsltApplySequenceConstructor(ctxt, contextNode, list, templ); /* * Remove remaining xsl:param and xsl:with-param items from * the stack. Don't free xsl:with-param items. */ if (ctxt->varsNr > ctxt->varsBase) xsltTemplateParamsCleanup(ctxt); ctxt->varsBase = oldVarsBase; /* * Clean up remaining local tree fragments. * This also frees fragments which are the result of * extension instructions. Should normally not be hit; but * just for the case xsltExtensionInstructionResultFinalize() * was not called by the extension author. */ if (oldLocalFragmentTop != ctxt->localRVT) { xmlDocPtr curdoc = ctxt->localRVT, tmp; do { tmp = curdoc; curdoc = (xmlDocPtr) curdoc->next; /* Need to housekeep localRVTBase */ if (tmp == ctxt->localRVTBase) ctxt->localRVTBase = curdoc; if (tmp->prev) tmp->prev->next = (xmlNodePtr) curdoc; if (curdoc) curdoc->prev = tmp->prev; xsltReleaseRVT(ctxt, tmp); } while (curdoc != oldLocalFragmentTop); } ctxt->localRVT = oldLocalFragmentTop; /* * Release user-created fragments stored in the scope * of xsl:template. Note that this mechanism is deprecated: * user code should now use xsltRegisterLocalRVT() instead * of the obsolete xsltRegisterTmpRVT(). */ if (ctxt->tmpRVT) { xmlDocPtr curdoc = ctxt->tmpRVT, tmp; while (curdoc != NULL) { tmp = curdoc; curdoc = (xmlDocPtr) curdoc->next; xsltReleaseRVT(ctxt, tmp); } } ctxt->tmpRVT = oldUserFragmentTop; /* * Pop the xsl:template declaration from the stack. */ templPop(ctxt); if (ctxt->profile) { long spent, child, total, end; end = xsltTimestamp(); child = profPop(ctxt); total = end - start; spent = total - child; if (spent <= 0) { /* * Not possible unless the original calibration failed * we can try to correct it on the fly. */ xsltCalibrateAdjust(spent); spent = 0; } templ->time += spent; if (ctxt->profNr > 0) ctxt->profTab[ctxt->profNr - 1] += total; } #ifdef WITH_DEBUGGER if ((ctxt->debugStatus != XSLT_DEBUG_NONE) && (addCallResult)) { xslDropCall(); } #endif } /** * xsltApplyOneTemplate: * @ctxt: a XSLT process context * @contextNode: the node in the source tree. * @list: the nodes of a sequence constructor * @templ: not used * @params: a set of parameters (xsl:param) or NULL * * Processes a sequence constructor on the current node in the source tree. * * @params are the already computed variable stack items; this function * pushes them on the variable stack, and pops them before exiting; it's * left to the caller to free or reuse @params afterwards. The initial * states of the variable stack will always be restored before this * function exits. * NOTE that this does *not* initiate a new distinct variable scope; i.e. * variables already on the stack are visible to the process. The caller's * side needs to start a new variable scope if needed (e.g. in exsl:function). * * @templ is obsolete and not used anymore (e.g. does not * provide a @templ); a non-NULL @templ might raise an error in the future. * * BIG NOTE: This function is not intended to process the content of an * xsl:template; it does not expect xsl:param instructions in @list and * will report errors if found. * * Called by: * - xsltEvalVariable() (variables.c) * - exsltFuncFunctionFunction() (libexsl/functions.c) */ void xsltApplyOneTemplate(xsltTransformContextPtr ctxt, xmlNodePtr contextNode, xmlNodePtr list, xsltTemplatePtr templ ATTRIBUTE_UNUSED, xsltStackElemPtr params) { if ((ctxt == NULL) || (list == NULL)) return; CHECK_STOPPED; if (params) { /* * This code should be obsolete - was previously used * by libexslt/functions.c, but due to bug 381319 the * logic there was changed. */ int oldVarsNr = ctxt->varsNr; /* * Push the given xsl:param(s) onto the variable stack. */ while (params != NULL) { xsltLocalVariablePush(ctxt, params, -1); params = params->next; } xsltApplySequenceConstructor(ctxt, contextNode, list, templ); /* * Pop the given xsl:param(s) from the stack but don't free them. */ xsltLocalVariablePop(ctxt, oldVarsNr, -2); } else xsltApplySequenceConstructor(ctxt, contextNode, list, templ); } /************************************************************************ * * * XSLT-1.1 extensions * * * ************************************************************************/ /** * xsltDocumentElem: * @ctxt: an XSLT processing context * @node: The current node * @inst: the instruction in the stylesheet * @castedComp: precomputed information * * Process an EXSLT/XSLT-1.1 document element */ void xsltDocumentElem(xsltTransformContextPtr ctxt, xmlNodePtr node, xmlNodePtr inst, xsltStylePreCompPtr castedComp) { #ifdef XSLT_REFACTORED xsltStyleItemDocumentPtr comp = (xsltStyleItemDocumentPtr) castedComp; #else xsltStylePreCompPtr comp = castedComp; #endif xsltStylesheetPtr style = NULL; int ret; xmlChar *filename = NULL, *prop, *elements; xmlChar *element, *end; xmlDocPtr res = NULL; xmlDocPtr oldOutput; xmlNodePtr oldInsert, root; const char *oldOutputFile; xsltOutputType oldType; xmlChar *URL = NULL; const xmlChar *method; const xmlChar *doctypePublic; const xmlChar *doctypeSystem; const xmlChar *version; const xmlChar *encoding; int redirect_write_append = 0; if ((ctxt == NULL) || (node == NULL) || (inst == NULL) || (comp == NULL)) return; if (comp->filename == NULL) { if (xmlStrEqual(inst->name, (const xmlChar *) "output")) { /* * The element "output" is in the namespace XSLT_SAXON_NAMESPACE * (http://icl.com/saxon) * The @file is in no namespace. */ #ifdef WITH_XSLT_DEBUG_EXTRA xsltGenericDebug(xsltGenericDebugContext, "Found saxon:output extension\n"); #endif URL = xsltEvalAttrValueTemplate(ctxt, inst, (const xmlChar *) "file", XSLT_SAXON_NAMESPACE); if (URL == NULL) URL = xsltEvalAttrValueTemplate(ctxt, inst, (const xmlChar *) "href", XSLT_SAXON_NAMESPACE); } else if (xmlStrEqual(inst->name, (const xmlChar *) "write")) { #ifdef WITH_XSLT_DEBUG_EXTRA xsltGenericDebug(xsltGenericDebugContext, "Found xalan:write extension\n"); #endif URL = xsltEvalAttrValueTemplate(ctxt, inst, (const xmlChar *) "select", XSLT_XALAN_NAMESPACE); if (URL != NULL) { xmlXPathCompExprPtr cmp; xmlChar *val; /* * Trying to handle bug #59212 * The value of the "select" attribute is an * XPath expression. * (see http://xml.apache.org/xalan-j/extensionslib.html#redirect) */ cmp = xmlXPathCompile(URL); val = xsltEvalXPathString(ctxt, cmp); xmlXPathFreeCompExpr(cmp); xmlFree(URL); URL = val; } if (URL == NULL) URL = xsltEvalAttrValueTemplate(ctxt, inst, (const xmlChar *) "file", XSLT_XALAN_NAMESPACE); if (URL == NULL) URL = xsltEvalAttrValueTemplate(ctxt, inst, (const xmlChar *) "href", XSLT_XALAN_NAMESPACE); } else if (xmlStrEqual(inst->name, (const xmlChar *) "document")) { URL = xsltEvalAttrValueTemplate(ctxt, inst, (const xmlChar *) "href", NULL); } } else { URL = xmlStrdup(comp->filename); } if (URL == NULL) { xsltTransformError(ctxt, NULL, inst, "xsltDocumentElem: href/URI-Reference not found\n"); return; } /* * If the computation failed, it's likely that the URL wasn't escaped */ filename = xmlBuildURI(URL, (const xmlChar *) ctxt->outputFile); if (filename == NULL) { xmlChar *escURL; escURL=xmlURIEscapeStr(URL, BAD_CAST ":/.?,"); if (escURL != NULL) { filename = xmlBuildURI(escURL, (const xmlChar *) ctxt->outputFile); xmlFree(escURL); } } if (filename == NULL) { xsltTransformError(ctxt, NULL, inst, "xsltDocumentElem: URL computation failed for %s\n", URL); xmlFree(URL); return; } /* * Security checking: can we write to this resource */ if (ctxt->sec != NULL) { ret = xsltCheckWrite(ctxt->sec, ctxt, filename); if (ret == 0) { xsltTransformError(ctxt, NULL, inst, "xsltDocumentElem: write rights for %s denied\n", filename); xmlFree(URL); xmlFree(filename); return; } } oldOutputFile = ctxt->outputFile; oldOutput = ctxt->output; oldInsert = ctxt->insert; oldType = ctxt->type; ctxt->outputFile = (const char *) filename; style = xsltNewStylesheet(); if (style == NULL) { xsltTransformError(ctxt, NULL, inst, "xsltDocumentElem: out of memory\n"); goto error; } /* * Version described in 1.1 draft allows full parameterization * of the output. */ prop = xsltEvalAttrValueTemplate(ctxt, inst, (const xmlChar *) "version", NULL); if (prop != NULL) { if (style->version != NULL) xmlFree(style->version); style->version = prop; } prop = xsltEvalAttrValueTemplate(ctxt, inst, (const xmlChar *) "encoding", NULL); if (prop != NULL) { if (style->encoding != NULL) xmlFree(style->encoding); style->encoding = prop; } prop = xsltEvalAttrValueTemplate(ctxt, inst, (const xmlChar *) "method", NULL); if (prop != NULL) { const xmlChar *URI; if (style->method != NULL) xmlFree(style->method); style->method = NULL; if (style->methodURI != NULL) xmlFree(style->methodURI); style->methodURI = NULL; URI = xsltGetQNameURI(inst, &prop); if (prop == NULL) { if (style != NULL) style->errors++; } else if (URI == NULL) { if ((xmlStrEqual(prop, (const xmlChar *) "xml")) || (xmlStrEqual(prop, (const xmlChar *) "html")) || (xmlStrEqual(prop, (const xmlChar *) "text"))) { style->method = prop; } else { xsltTransformError(ctxt, NULL, inst, "invalid value for method: %s\n", prop); if (style != NULL) style->warnings++; } } else { style->method = prop; style->methodURI = xmlStrdup(URI); } } prop = xsltEvalAttrValueTemplate(ctxt, inst, (const xmlChar *) "doctype-system", NULL); if (prop != NULL) { if (style->doctypeSystem != NULL) xmlFree(style->doctypeSystem); style->doctypeSystem = prop; } prop = xsltEvalAttrValueTemplate(ctxt, inst, (const xmlChar *) "doctype-public", NULL); if (prop != NULL) { if (style->doctypePublic != NULL) xmlFree(style->doctypePublic); style->doctypePublic = prop; } prop = xsltEvalAttrValueTemplate(ctxt, inst, (const xmlChar *) "standalone", NULL); if (prop != NULL) { if (xmlStrEqual(prop, (const xmlChar *) "yes")) { style->standalone = 1; } else if (xmlStrEqual(prop, (const xmlChar *) "no")) { style->standalone = 0; } else { xsltTransformError(ctxt, NULL, inst, "invalid value for standalone: %s\n", prop); if (style != NULL) style->warnings++; } xmlFree(prop); } prop = xsltEvalAttrValueTemplate(ctxt, inst, (const xmlChar *) "indent", NULL); if (prop != NULL) { if (xmlStrEqual(prop, (const xmlChar *) "yes")) { style->indent = 1; } else if (xmlStrEqual(prop, (const xmlChar *) "no")) { style->indent = 0; } else { xsltTransformError(ctxt, NULL, inst, "invalid value for indent: %s\n", prop); if (style != NULL) style->warnings++; } xmlFree(prop); } prop = xsltEvalAttrValueTemplate(ctxt, inst, (const xmlChar *) "omit-xml-declaration", NULL); if (prop != NULL) { if (xmlStrEqual(prop, (const xmlChar *) "yes")) { style->omitXmlDeclaration = 1; } else if (xmlStrEqual(prop, (const xmlChar *) "no")) { style->omitXmlDeclaration = 0; } else { xsltTransformError(ctxt, NULL, inst, "invalid value for omit-xml-declaration: %s\n", prop); if (style != NULL) style->warnings++; } xmlFree(prop); } elements = xsltEvalAttrValueTemplate(ctxt, inst, (const xmlChar *) "cdata-section-elements", NULL); if (elements != NULL) { if (style->stripSpaces == NULL) style->stripSpaces = xmlHashCreate(10); if (style->stripSpaces == NULL) return; element = elements; while (*element != 0) { while (IS_BLANK_CH(*element)) element++; if (*element == 0) break; end = element; while ((*end != 0) && (!IS_BLANK_CH(*end))) end++; element = xmlStrndup(element, end - element); if (element) { const xmlChar *URI; #ifdef WITH_XSLT_DEBUG_PARSING xsltGenericDebug(xsltGenericDebugContext, "add cdata section output element %s\n", element); #endif URI = xsltGetQNameURI(inst, &element); xmlHashAddEntry2(style->stripSpaces, element, URI, (xmlChar *) "cdata"); xmlFree(element); } element = end; } xmlFree(elements); } /* * Create a new document tree and process the element template */ XSLT_GET_IMPORT_PTR(method, style, method) XSLT_GET_IMPORT_PTR(doctypePublic, style, doctypePublic) XSLT_GET_IMPORT_PTR(doctypeSystem, style, doctypeSystem) XSLT_GET_IMPORT_PTR(version, style, version) XSLT_GET_IMPORT_PTR(encoding, style, encoding) if ((method != NULL) && (!xmlStrEqual(method, (const xmlChar *) "xml"))) { if (xmlStrEqual(method, (const xmlChar *) "html")) { ctxt->type = XSLT_OUTPUT_HTML; if (((doctypePublic != NULL) || (doctypeSystem != NULL))) res = htmlNewDoc(doctypeSystem, doctypePublic); else { if (version != NULL) { #ifdef XSLT_GENERATE_HTML_DOCTYPE xsltGetHTMLIDs(version, &doctypePublic, &doctypeSystem); #endif } res = htmlNewDocNoDtD(doctypeSystem, doctypePublic); } if (res == NULL) goto error; res->dict = ctxt->dict; xmlDictReference(res->dict); } else if (xmlStrEqual(method, (const xmlChar *) "xhtml")) { xsltTransformError(ctxt, NULL, inst, "xsltDocumentElem: unsupported method xhtml\n", style->method); ctxt->type = XSLT_OUTPUT_HTML; res = htmlNewDocNoDtD(doctypeSystem, doctypePublic); if (res == NULL) goto error; res->dict = ctxt->dict; xmlDictReference(res->dict); } else if (xmlStrEqual(method, (const xmlChar *) "text")) { ctxt->type = XSLT_OUTPUT_TEXT; res = xmlNewDoc(style->version); if (res == NULL) goto error; res->dict = ctxt->dict; xmlDictReference(res->dict); #ifdef WITH_XSLT_DEBUG xsltGenericDebug(xsltGenericDebugContext, "reusing transformation dict for output\n"); #endif } else { xsltTransformError(ctxt, NULL, inst, "xsltDocumentElem: unsupported method %s\n", style->method); goto error; } } else { ctxt->type = XSLT_OUTPUT_XML; res = xmlNewDoc(style->version); if (res == NULL) goto error; res->dict = ctxt->dict; xmlDictReference(res->dict); #ifdef WITH_XSLT_DEBUG xsltGenericDebug(xsltGenericDebugContext, "reusing transformation dict for output\n"); #endif } res->charset = XML_CHAR_ENCODING_UTF8; if (encoding != NULL) res->encoding = xmlStrdup(encoding); ctxt->output = res; ctxt->insert = (xmlNodePtr) res; xsltApplySequenceConstructor(ctxt, node, inst->children, NULL); /* * Do some post processing work depending on the generated output */ root = xmlDocGetRootElement(res); if (root != NULL) { const xmlChar *doctype = NULL; if ((root->ns != NULL) && (root->ns->prefix != NULL)) doctype = xmlDictQLookup(ctxt->dict, root->ns->prefix, root->name); if (doctype == NULL) doctype = root->name; /* * Apply the default selection of the method */ if ((method == NULL) && (root->ns == NULL) && (!xmlStrcasecmp(root->name, (const xmlChar *) "html"))) { xmlNodePtr tmp; tmp = res->children; while ((tmp != NULL) && (tmp != root)) { if (tmp->type == XML_ELEMENT_NODE) break; if ((tmp->type == XML_TEXT_NODE) && (!xmlIsBlankNode(tmp))) break; tmp = tmp->next; } if (tmp == root) { ctxt->type = XSLT_OUTPUT_HTML; res->type = XML_HTML_DOCUMENT_NODE; if (((doctypePublic != NULL) || (doctypeSystem != NULL))) { res->intSubset = xmlCreateIntSubset(res, doctype, doctypePublic, doctypeSystem); #ifdef XSLT_GENERATE_HTML_DOCTYPE } else if (version != NULL) { xsltGetHTMLIDs(version, &doctypePublic, &doctypeSystem); if (((doctypePublic != NULL) || (doctypeSystem != NULL))) res->intSubset = xmlCreateIntSubset(res, doctype, doctypePublic, doctypeSystem); #endif } } } if (ctxt->type == XSLT_OUTPUT_XML) { XSLT_GET_IMPORT_PTR(doctypePublic, style, doctypePublic) XSLT_GET_IMPORT_PTR(doctypeSystem, style, doctypeSystem) if (((doctypePublic != NULL) || (doctypeSystem != NULL))) res->intSubset = xmlCreateIntSubset(res, doctype, doctypePublic, doctypeSystem); } } /* * Calls to redirect:write also take an optional attribute append. * Attribute append="true|yes" which will attempt to simply append * to an existing file instead of always opening a new file. The * default behavior of always overwriting the file still happens * if we do not specify append. * Note that append use will forbid use of remote URI target. */ prop = xsltEvalAttrValueTemplate(ctxt, inst, (const xmlChar *)"append", NULL); if (prop != NULL) { if (xmlStrEqual(prop, (const xmlChar *) "true") || xmlStrEqual(prop, (const xmlChar *) "yes")) { style->omitXmlDeclaration = 1; redirect_write_append = 1; } else style->omitXmlDeclaration = 0; xmlFree(prop); } if (redirect_write_append) { FILE *f; f = fopen((const char *) filename, "ab"); if (f == NULL) { ret = -1; } else { ret = xsltSaveResultToFile(f, res, style); fclose(f); } } else { ret = xsltSaveResultToFilename((const char *) filename, res, style, 0); } if (ret < 0) { xsltTransformError(ctxt, NULL, inst, "xsltDocumentElem: unable to save to %s\n", filename); ctxt->state = XSLT_STATE_ERROR; #ifdef WITH_XSLT_DEBUG_EXTRA } else { xsltGenericDebug(xsltGenericDebugContext, "Wrote %d bytes to %s\n", ret, filename); #endif } error: ctxt->output = oldOutput; ctxt->insert = oldInsert; ctxt->type = oldType; ctxt->outputFile = oldOutputFile; if (URL != NULL) xmlFree(URL); if (filename != NULL) xmlFree(filename); if (style != NULL) xsltFreeStylesheet(style); if (res != NULL) xmlFreeDoc(res); } /************************************************************************ * * * Most of the XSLT-1.0 transformations * * * ************************************************************************/ /** * xsltSort: * @ctxt: a XSLT process context * @node: the node in the source tree. * @inst: the xslt sort node * @comp: precomputed information * * function attached to xsl:sort nodes, but this should not be * called directly */ void xsltSort(xsltTransformContextPtr ctxt, xmlNodePtr node ATTRIBUTE_UNUSED, xmlNodePtr inst, xsltStylePreCompPtr comp) { if (comp == NULL) { xsltTransformError(ctxt, NULL, inst, "xsl:sort : compilation failed\n"); return; } xsltTransformError(ctxt, NULL, inst, "xsl:sort : improper use this should not be reached\n"); } /** * xsltCopy: * @ctxt: an XSLT process context * @node: the node in the source tree * @inst: the element node of the XSLT-copy instruction * @castedComp: computed information of the XSLT-copy instruction * * Execute the XSLT-copy instruction on the source node. */ void xsltCopy(xsltTransformContextPtr ctxt, xmlNodePtr node, xmlNodePtr inst, xsltStylePreCompPtr castedComp) { #ifdef XSLT_REFACTORED xsltStyleItemCopyPtr comp = (xsltStyleItemCopyPtr) castedComp; #else xsltStylePreCompPtr comp = castedComp; #endif xmlNodePtr copy, oldInsert; oldInsert = ctxt->insert; if (ctxt->insert != NULL) { switch (node->type) { case XML_TEXT_NODE: case XML_CDATA_SECTION_NODE: /* * This text comes from the stylesheet * For stylesheets, the set of whitespace-preserving * element names consists of just xsl:text. */ #ifdef WITH_XSLT_DEBUG_PROCESS if (node->type == XML_CDATA_SECTION_NODE) { XSLT_TRACE(ctxt,XSLT_TRACE_COPY,xsltGenericDebug(xsltGenericDebugContext, "xsltCopy: CDATA text %s\n", node->content)); } else { XSLT_TRACE(ctxt,XSLT_TRACE_COPY,xsltGenericDebug(xsltGenericDebugContext, "xsltCopy: text %s\n", node->content)); } #endif xsltCopyText(ctxt, ctxt->insert, node, 0); break; case XML_DOCUMENT_NODE: case XML_HTML_DOCUMENT_NODE: break; case XML_ELEMENT_NODE: /* * REVISIT NOTE: The "fake" is a doc-node, not an element node. * REMOVED: * if (xmlStrEqual(node->name, BAD_CAST " fake node libxslt")) * return; */ #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_COPY,xsltGenericDebug(xsltGenericDebugContext, "xsltCopy: node %s\n", node->name)); #endif copy = xsltShallowCopyElem(ctxt, node, ctxt->insert, 0); ctxt->insert = copy; if (comp->use != NULL) { xsltApplyAttributeSet(ctxt, node, inst, comp->use); } break; case XML_ATTRIBUTE_NODE: { #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_COPY,xsltGenericDebug(xsltGenericDebugContext, "xsltCopy: attribute %s\n", node->name)); #endif /* * REVISIT: We could also raise an error if the parent is not * an element node. * OPTIMIZE TODO: Can we set the value/children of the * attribute without an intermediate copy of the string value? */ xsltShallowCopyAttr(ctxt, inst, ctxt->insert, (xmlAttrPtr) node); break; } case XML_PI_NODE: #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_COPY,xsltGenericDebug(xsltGenericDebugContext, "xsltCopy: PI %s\n", node->name)); #endif copy = xmlNewDocPI(ctxt->insert->doc, node->name, node->content); copy = xsltAddChild(ctxt->insert, copy); break; case XML_COMMENT_NODE: #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_COPY,xsltGenericDebug(xsltGenericDebugContext, "xsltCopy: comment\n")); #endif copy = xmlNewComment(node->content); copy = xsltAddChild(ctxt->insert, copy); break; case XML_NAMESPACE_DECL: #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_COPY,xsltGenericDebug(xsltGenericDebugContext, "xsltCopy: namespace declaration\n")); #endif xsltShallowCopyNsNode(ctxt, inst, ctxt->insert, (xmlNsPtr)node); break; default: break; } } switch (node->type) { case XML_DOCUMENT_NODE: case XML_HTML_DOCUMENT_NODE: case XML_ELEMENT_NODE: xsltApplySequenceConstructor(ctxt, ctxt->node, inst->children, NULL); break; default: break; } ctxt->insert = oldInsert; } /** * xsltText: * @ctxt: a XSLT process context * @node: the node in the source tree. * @inst: the xslt text node * @comp: precomputed information * * Process the xslt text node on the source node */ void xsltText(xsltTransformContextPtr ctxt, xmlNodePtr node ATTRIBUTE_UNUSED, xmlNodePtr inst, xsltStylePreCompPtr comp ATTRIBUTE_UNUSED) { if ((inst->children != NULL) && (comp != NULL)) { xmlNodePtr text = inst->children; xmlNodePtr copy; while (text != NULL) { if ((text->type != XML_TEXT_NODE) && (text->type != XML_CDATA_SECTION_NODE)) { xsltTransformError(ctxt, NULL, inst, "xsl:text content problem\n"); break; } copy = xmlNewDocText(ctxt->output, text->content); if (text->type != XML_CDATA_SECTION_NODE) { #ifdef WITH_XSLT_DEBUG_PARSING xsltGenericDebug(xsltGenericDebugContext, "Disable escaping: %s\n", text->content); #endif copy->name = xmlStringTextNoenc; } copy = xsltAddChild(ctxt->insert, copy); text = text->next; } } } /** * xsltElement: * @ctxt: a XSLT process context * @node: the node in the source tree. * @inst: the xslt element node * @castedComp: precomputed information * * Process the xslt element node on the source node */ void xsltElement(xsltTransformContextPtr ctxt, xmlNodePtr node, xmlNodePtr inst, xsltStylePreCompPtr castedComp) { #ifdef XSLT_REFACTORED xsltStyleItemElementPtr comp = (xsltStyleItemElementPtr) castedComp; #else xsltStylePreCompPtr comp = castedComp; #endif xmlChar *prop = NULL; const xmlChar *name, *prefix = NULL, *nsName = NULL; xmlNodePtr copy; xmlNodePtr oldInsert; if (ctxt->insert == NULL) return; /* * A comp->has_name == 0 indicates that we need to skip this instruction, * since it was evaluated to be invalid already during compilation. */ if (!comp->has_name) return; /* * stack and saves */ oldInsert = ctxt->insert; if (comp->name == NULL) { /* TODO: fix attr acquisition wrt to the XSLT namespace */ prop = xsltEvalAttrValueTemplate(ctxt, inst, (const xmlChar *) "name", XSLT_NAMESPACE); if (prop == NULL) { xsltTransformError(ctxt, NULL, inst, "xsl:element: The attribute 'name' is missing.\n"); goto error; } if (xmlValidateQName(prop, 0)) { xsltTransformError(ctxt, NULL, inst, "xsl:element: The effective name '%s' is not a " "valid QName.\n", prop); /* we fall through to catch any further errors, if possible */ } name = xsltSplitQName(ctxt->dict, prop, &prefix); xmlFree(prop); } else { /* * The "name" value was static. */ #ifdef XSLT_REFACTORED prefix = comp->nsPrefix; name = comp->name; #else name = xsltSplitQName(ctxt->dict, comp->name, &prefix); #endif } /* * Create the new element */ if (ctxt->output->dict == ctxt->dict) { copy = xmlNewDocNodeEatName(ctxt->output, NULL, (xmlChar *)name, NULL); } else { copy = xmlNewDocNode(ctxt->output, NULL, (xmlChar *)name, NULL); } if (copy == NULL) { xsltTransformError(ctxt, NULL, inst, "xsl:element : creation of %s failed\n", name); return; } copy = xsltAddChild(ctxt->insert, copy); /* * Namespace * --------- */ if (comp->has_ns) { if (comp->ns != NULL) { /* * No AVT; just plain text for the namespace name. */ if (comp->ns[0] != 0) nsName = comp->ns; } else { xmlChar *tmpNsName; /* * Eval the AVT. */ /* TODO: check attr acquisition wrt to the XSLT namespace */ tmpNsName = xsltEvalAttrValueTemplate(ctxt, inst, (const xmlChar *) "namespace", XSLT_NAMESPACE); /* * SPEC XSLT 1.0: * "If the string is empty, then the expanded-name of the * attribute has a null namespace URI." */ if ((tmpNsName != NULL) && (tmpNsName[0] != 0)) nsName = xmlDictLookup(ctxt->dict, BAD_CAST tmpNsName, -1); xmlFree(tmpNsName); } if (xmlStrEqual(nsName, BAD_CAST "http://www.w3.org/2000/xmlns/")) { xsltTransformError(ctxt, NULL, inst, "xsl:attribute: Namespace http://www.w3.org/2000/xmlns/ " "forbidden.\n"); goto error; } if (xmlStrEqual(nsName, XML_XML_NAMESPACE)) { prefix = BAD_CAST "xml"; } else if (xmlStrEqual(prefix, BAD_CAST "xml")) { prefix = NULL; } } else { xmlNsPtr ns; /* * SPEC XSLT 1.0: * "If the namespace attribute is not present, then the QName is * expanded into an expanded-name using the namespace declarations * in effect for the xsl:element element, including any default * namespace declaration. */ ns = xmlSearchNs(inst->doc, inst, prefix); if (ns == NULL) { /* * TODO: Check this in the compilation layer in case it's a * static value. */ if (prefix != NULL) { xsltTransformError(ctxt, NULL, inst, "xsl:element: The QName '%s:%s' has no " "namespace binding in scope in the stylesheet; " "this is an error, since the namespace was not " "specified by the instruction itself.\n", prefix, name); } } else nsName = ns->href; } /* * Find/create a matching ns-decl in the result tree. */ if (nsName != NULL) { if (xmlStrEqual(prefix, BAD_CAST "xmlns")) { /* Don't use a prefix of "xmlns" */ xmlChar *pref = xmlStrdup(BAD_CAST "ns_1"); copy->ns = xsltGetSpecialNamespace(ctxt, inst, nsName, pref, copy); xmlFree(pref); } else { copy->ns = xsltGetSpecialNamespace(ctxt, inst, nsName, prefix, copy); } } else if ((copy->parent != NULL) && (copy->parent->type == XML_ELEMENT_NODE) && (copy->parent->ns != NULL)) { /* * "Undeclare" the default namespace. */ xsltGetSpecialNamespace(ctxt, inst, NULL, NULL, copy); } ctxt->insert = copy; if (comp->has_use) { if (comp->use != NULL) { xsltApplyAttributeSet(ctxt, node, inst, comp->use); } else { xmlChar *attrSets = NULL; /* * BUG TODO: use-attribute-sets is not a value template. * use-attribute-sets = qnames */ attrSets = xsltEvalAttrValueTemplate(ctxt, inst, (const xmlChar *)"use-attribute-sets", NULL); if (attrSets != NULL) { xsltApplyAttributeSet(ctxt, node, inst, attrSets); xmlFree(attrSets); } } } /* * Instantiate the sequence constructor. */ if (inst->children != NULL) xsltApplySequenceConstructor(ctxt, ctxt->node, inst->children, NULL); error: ctxt->insert = oldInsert; return; } /** * xsltComment: * @ctxt: a XSLT process context * @node: the node in the source tree. * @inst: the xslt comment node * @comp: precomputed information * * Process the xslt comment node on the source node */ void xsltComment(xsltTransformContextPtr ctxt, xmlNodePtr node, xmlNodePtr inst, xsltStylePreCompPtr comp ATTRIBUTE_UNUSED) { xmlChar *value = NULL; xmlNodePtr commentNode; int len; value = xsltEvalTemplateString(ctxt, node, inst); /* TODO: use or generate the compiled form */ len = xmlStrlen(value); if (len > 0) { if ((value[len-1] == '-') || (xmlStrstr(value, BAD_CAST "--"))) { xsltTransformError(ctxt, NULL, inst, "xsl:comment : '--' or ending '-' not allowed in comment\n"); /* fall through to try to catch further errors */ } } #ifdef WITH_XSLT_DEBUG_PROCESS if (value == NULL) { XSLT_TRACE(ctxt,XSLT_TRACE_COMMENT,xsltGenericDebug(xsltGenericDebugContext, "xsltComment: empty\n")); } else { XSLT_TRACE(ctxt,XSLT_TRACE_COMMENT,xsltGenericDebug(xsltGenericDebugContext, "xsltComment: content %s\n", value)); } #endif commentNode = xmlNewComment(value); commentNode = xsltAddChild(ctxt->insert, commentNode); if (value != NULL) xmlFree(value); } /** * xsltProcessingInstruction: * @ctxt: a XSLT process context * @node: the node in the source tree. * @inst: the xslt processing-instruction node * @castedComp: precomputed information * * Process the xslt processing-instruction node on the source node */ void xsltProcessingInstruction(xsltTransformContextPtr ctxt, xmlNodePtr node, xmlNodePtr inst, xsltStylePreCompPtr castedComp) { #ifdef XSLT_REFACTORED xsltStyleItemPIPtr comp = (xsltStyleItemPIPtr) castedComp; #else xsltStylePreCompPtr comp = castedComp; #endif const xmlChar *name; xmlChar *value = NULL; xmlNodePtr pi; if (ctxt->insert == NULL) return; if (comp->has_name == 0) return; if (comp->name == NULL) { name = xsltEvalAttrValueTemplate(ctxt, inst, (const xmlChar *)"name", NULL); if (name == NULL) { xsltTransformError(ctxt, NULL, inst, "xsl:processing-instruction : name is missing\n"); goto error; } } else { name = comp->name; } /* TODO: check that it's both an an NCName and a PITarget. */ value = xsltEvalTemplateString(ctxt, node, inst); if (xmlStrstr(value, BAD_CAST "?>") != NULL) { xsltTransformError(ctxt, NULL, inst, "xsl:processing-instruction: '?>' not allowed within PI content\n"); goto error; } #ifdef WITH_XSLT_DEBUG_PROCESS if (value == NULL) { XSLT_TRACE(ctxt,XSLT_TRACE_PI,xsltGenericDebug(xsltGenericDebugContext, "xsltProcessingInstruction: %s empty\n", name)); } else { XSLT_TRACE(ctxt,XSLT_TRACE_PI,xsltGenericDebug(xsltGenericDebugContext, "xsltProcessingInstruction: %s content %s\n", name, value)); } #endif pi = xmlNewDocPI(ctxt->insert->doc, name, value); pi = xsltAddChild(ctxt->insert, pi); error: if ((name != NULL) && (name != comp->name)) xmlFree((xmlChar *) name); if (value != NULL) xmlFree(value); } /** * xsltCopyOf: * @ctxt: an XSLT transformation context * @node: the current node in the source tree * @inst: the element node of the XSLT copy-of instruction * @castedComp: precomputed information of the XSLT copy-of instruction * * Process the XSLT copy-of instruction. */ void xsltCopyOf(xsltTransformContextPtr ctxt, xmlNodePtr node, xmlNodePtr inst, xsltStylePreCompPtr castedComp) { #ifdef XSLT_REFACTORED xsltStyleItemCopyOfPtr comp = (xsltStyleItemCopyOfPtr) castedComp; #else xsltStylePreCompPtr comp = castedComp; #endif xmlXPathObjectPtr res = NULL; xmlNodeSetPtr list = NULL; int i; xmlDocPtr oldXPContextDoc; xmlNsPtr *oldXPNamespaces; xmlNodePtr oldXPContextNode; int oldXPProximityPosition, oldXPContextSize, oldXPNsNr; xmlXPathContextPtr xpctxt; if ((ctxt == NULL) || (node == NULL) || (inst == NULL)) return; if ((comp == NULL) || (comp->select == NULL) || (comp->comp == NULL)) { xsltTransformError(ctxt, NULL, inst, "xsl:copy-of : compilation failed\n"); return; } /* * SPEC XSLT 1.0: * "The xsl:copy-of element can be used to insert a result tree * fragment into the result tree, without first converting it to * a string as xsl:value-of does (see [7.6.1 Generating Text with * xsl:value-of]). The required select attribute contains an * expression. When the result of evaluating the expression is a * result tree fragment, the complete fragment is copied into the * result tree. When the result is a node-set, all the nodes in the * set are copied in document order into the result tree; copying * an element node copies the attribute nodes, namespace nodes and * children of the element node as well as the element node itself; * a root node is copied by copying its children. When the result * is neither a node-set nor a result tree fragment, the result is * converted to a string and then inserted into the result tree, * as with xsl:value-of. */ #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_COPY_OF,xsltGenericDebug(xsltGenericDebugContext, "xsltCopyOf: select %s\n", comp->select)); #endif /* * Evaluate the "select" expression. */ xpctxt = ctxt->xpathCtxt; oldXPContextDoc = xpctxt->doc; oldXPContextNode = xpctxt->node; oldXPProximityPosition = xpctxt->proximityPosition; oldXPContextSize = xpctxt->contextSize; oldXPNsNr = xpctxt->nsNr; oldXPNamespaces = xpctxt->namespaces; xpctxt->node = node; if (comp != NULL) { #ifdef XSLT_REFACTORED if (comp->inScopeNs != NULL) { xpctxt->namespaces = comp->inScopeNs->list; xpctxt->nsNr = comp->inScopeNs->xpathNumber; } else { xpctxt->namespaces = NULL; xpctxt->nsNr = 0; } #else xpctxt->namespaces = comp->nsList; xpctxt->nsNr = comp->nsNr; #endif } else { xpctxt->namespaces = NULL; xpctxt->nsNr = 0; } res = xmlXPathCompiledEval(comp->comp, xpctxt); xpctxt->doc = oldXPContextDoc; xpctxt->node = oldXPContextNode; xpctxt->contextSize = oldXPContextSize; xpctxt->proximityPosition = oldXPProximityPosition; xpctxt->nsNr = oldXPNsNr; xpctxt->namespaces = oldXPNamespaces; if (res != NULL) { if (res->type == XPATH_NODESET) { /* * Node-set * -------- */ #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_COPY_OF,xsltGenericDebug(xsltGenericDebugContext, "xsltCopyOf: result is a node set\n")); #endif list = res->nodesetval; if (list != NULL) { xmlNodePtr cur; /* * The list is already sorted in document order by XPath. * Append everything in this order under ctxt->insert. */ for (i = 0;i < list->nodeNr;i++) { cur = list->nodeTab[i]; if (cur == NULL) continue; if ((cur->type == XML_DOCUMENT_NODE) || (cur->type == XML_HTML_DOCUMENT_NODE)) { xsltCopyTreeList(ctxt, inst, cur->children, ctxt->insert, 0, 0); } else if (cur->type == XML_ATTRIBUTE_NODE) { xsltShallowCopyAttr(ctxt, inst, ctxt->insert, (xmlAttrPtr) cur); } else { xsltCopyTreeInternal(ctxt, inst, cur, ctxt->insert, 0, 0); } } } } else if (res->type == XPATH_XSLT_TREE) { /* * Result tree fragment * -------------------- * E.g. via * Note that the root node of such trees is an xmlDocPtr in Libxslt. */ #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_COPY_OF,xsltGenericDebug(xsltGenericDebugContext, "xsltCopyOf: result is a result tree fragment\n")); #endif list = res->nodesetval; if ((list != NULL) && (list->nodeTab != NULL) && (list->nodeTab[0] != NULL) && (IS_XSLT_REAL_NODE(list->nodeTab[0]))) { xsltCopyTreeList(ctxt, inst, list->nodeTab[0]->children, ctxt->insert, 0, 0); } } else { xmlChar *value = NULL; /* * Convert to a string. */ value = xmlXPathCastToString(res); if (value == NULL) { xsltTransformError(ctxt, NULL, inst, "Internal error in xsltCopyOf(): " "failed to cast an XPath object to string.\n"); ctxt->state = XSLT_STATE_STOPPED; } else { if (value[0] != 0) { /* * Append content as text node. */ xsltCopyTextString(ctxt, ctxt->insert, value, 0); } xmlFree(value); #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_COPY_OF,xsltGenericDebug(xsltGenericDebugContext, "xsltCopyOf: result %s\n", res->stringval)); #endif } } } else { ctxt->state = XSLT_STATE_STOPPED; } if (res != NULL) xmlXPathFreeObject(res); } /** * xsltValueOf: * @ctxt: a XSLT process context * @node: the node in the source tree. * @inst: the xslt value-of node * @castedComp: precomputed information * * Process the xslt value-of node on the source node */ void xsltValueOf(xsltTransformContextPtr ctxt, xmlNodePtr node, xmlNodePtr inst, xsltStylePreCompPtr castedComp) { #ifdef XSLT_REFACTORED xsltStyleItemValueOfPtr comp = (xsltStyleItemValueOfPtr) castedComp; #else xsltStylePreCompPtr comp = castedComp; #endif xmlXPathObjectPtr res = NULL; xmlChar *value = NULL; xmlDocPtr oldXPContextDoc; xmlNsPtr *oldXPNamespaces; xmlNodePtr oldXPContextNode; int oldXPProximityPosition, oldXPContextSize, oldXPNsNr; xmlXPathContextPtr xpctxt; if ((ctxt == NULL) || (node == NULL) || (inst == NULL)) return; if ((comp == NULL) || (comp->select == NULL) || (comp->comp == NULL)) { xsltTransformError(ctxt, NULL, inst, "Internal error in xsltValueOf(): " "The XSLT 'value-of' instruction was not compiled.\n"); return; } #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_VALUE_OF,xsltGenericDebug(xsltGenericDebugContext, "xsltValueOf: select %s\n", comp->select)); #endif xpctxt = ctxt->xpathCtxt; oldXPContextDoc = xpctxt->doc; oldXPContextNode = xpctxt->node; oldXPProximityPosition = xpctxt->proximityPosition; oldXPContextSize = xpctxt->contextSize; oldXPNsNr = xpctxt->nsNr; oldXPNamespaces = xpctxt->namespaces; xpctxt->node = node; if (comp != NULL) { #ifdef XSLT_REFACTORED if (comp->inScopeNs != NULL) { xpctxt->namespaces = comp->inScopeNs->list; xpctxt->nsNr = comp->inScopeNs->xpathNumber; } else { xpctxt->namespaces = NULL; xpctxt->nsNr = 0; } #else xpctxt->namespaces = comp->nsList; xpctxt->nsNr = comp->nsNr; #endif } else { xpctxt->namespaces = NULL; xpctxt->nsNr = 0; } res = xmlXPathCompiledEval(comp->comp, xpctxt); xpctxt->doc = oldXPContextDoc; xpctxt->node = oldXPContextNode; xpctxt->contextSize = oldXPContextSize; xpctxt->proximityPosition = oldXPProximityPosition; xpctxt->nsNr = oldXPNsNr; xpctxt->namespaces = oldXPNamespaces; /* * Cast the XPath object to string. */ if (res != NULL) { value = xmlXPathCastToString(res); if (value == NULL) { xsltTransformError(ctxt, NULL, inst, "Internal error in xsltValueOf(): " "failed to cast an XPath object to string.\n"); ctxt->state = XSLT_STATE_STOPPED; goto error; } if (value[0] != 0) { xsltCopyTextString(ctxt, ctxt->insert, value, comp->noescape); } } else { xsltTransformError(ctxt, NULL, inst, "XPath evaluation returned no result.\n"); ctxt->state = XSLT_STATE_STOPPED; goto error; } #ifdef WITH_XSLT_DEBUG_PROCESS if (value) { XSLT_TRACE(ctxt,XSLT_TRACE_VALUE_OF,xsltGenericDebug(xsltGenericDebugContext, "xsltValueOf: result '%s'\n", value)); } #endif error: if (value != NULL) xmlFree(value); if (res != NULL) xmlXPathFreeObject(res); } /** * xsltNumber: * @ctxt: a XSLT process context * @node: the node in the source tree. * @inst: the xslt number node * @castedComp: precomputed information * * Process the xslt number node on the source node */ void xsltNumber(xsltTransformContextPtr ctxt, xmlNodePtr node, xmlNodePtr inst, xsltStylePreCompPtr castedComp) { #ifdef XSLT_REFACTORED xsltStyleItemNumberPtr comp = (xsltStyleItemNumberPtr) castedComp; #else xsltStylePreCompPtr comp = castedComp; #endif if (comp == NULL) { xsltTransformError(ctxt, NULL, inst, "xsl:number : compilation failed\n"); return; } if ((ctxt == NULL) || (node == NULL) || (inst == NULL) || (comp == NULL)) return; comp->numdata.doc = inst->doc; comp->numdata.node = inst; xsltNumberFormat(ctxt, &comp->numdata, node); } /** * xsltApplyImports: * @ctxt: an XSLT transformation context * @contextNode: the current node in the source tree. * @inst: the element node of the XSLT 'apply-imports' instruction * @comp: the compiled instruction * * Process the XSLT apply-imports element. */ void xsltApplyImports(xsltTransformContextPtr ctxt, xmlNodePtr contextNode, xmlNodePtr inst, xsltStylePreCompPtr comp ATTRIBUTE_UNUSED) { xsltTemplatePtr templ; if ((ctxt == NULL) || (inst == NULL)) return; if (comp == NULL) { xsltTransformError(ctxt, NULL, inst, "Internal error in xsltApplyImports(): " "The XSLT 'apply-imports' instruction was not compiled.\n"); return; } /* * NOTE that ctxt->currentTemplateRule and ctxt->templ is not the * same; the former is the "Current Template Rule" as defined by the * XSLT spec, the latter is simply the template struct being * currently processed. */ if (ctxt->currentTemplateRule == NULL) { /* * SPEC XSLT 2.0: * "[ERR XTDE0560] It is a non-recoverable dynamic error if * xsl:apply-imports or xsl:next-match is evaluated when the * current template rule is null." */ xsltTransformError(ctxt, NULL, inst, "It is an error to call 'apply-imports' " "when there's no current template rule.\n"); return; } /* * TODO: Check if this is correct. */ templ = xsltGetTemplate(ctxt, contextNode, ctxt->currentTemplateRule->style); if (templ != NULL) { xsltTemplatePtr oldCurTemplRule = ctxt->currentTemplateRule; /* * Set the current template rule. */ ctxt->currentTemplateRule = templ; /* * URGENT TODO: Need xsl:with-param be handled somehow here? */ xsltApplyXSLTTemplate(ctxt, contextNode, templ->content, templ, NULL); ctxt->currentTemplateRule = oldCurTemplRule; } } /** * xsltCallTemplate: * @ctxt: a XSLT transformation context * @node: the "current node" in the source tree * @inst: the XSLT 'call-template' instruction * @castedComp: the compiled information of the instruction * * Processes the XSLT call-template instruction on the source node. */ void xsltCallTemplate(xsltTransformContextPtr ctxt, xmlNodePtr node, xmlNodePtr inst, xsltStylePreCompPtr castedComp) { #ifdef XSLT_REFACTORED xsltStyleItemCallTemplatePtr comp = (xsltStyleItemCallTemplatePtr) castedComp; #else xsltStylePreCompPtr comp = castedComp; #endif xsltStackElemPtr withParams = NULL; if (ctxt->insert == NULL) return; if (comp == NULL) { xsltTransformError(ctxt, NULL, inst, "The XSLT 'call-template' instruction was not compiled.\n"); return; } /* * The template must have been precomputed */ if (comp->templ == NULL) { comp->templ = xsltFindTemplate(ctxt, comp->name, comp->ns); if (comp->templ == NULL) { if (comp->ns != NULL) { xsltTransformError(ctxt, NULL, inst, "The called template '{%s}%s' was not found.\n", comp->ns, comp->name); } else { xsltTransformError(ctxt, NULL, inst, "The called template '%s' was not found.\n", comp->name); } return; } } #ifdef WITH_XSLT_DEBUG_PROCESS if ((comp != NULL) && (comp->name != NULL)) XSLT_TRACE(ctxt,XSLT_TRACE_CALL_TEMPLATE,xsltGenericDebug(xsltGenericDebugContext, "call-template: name %s\n", comp->name)); #endif if (inst->children) { xmlNodePtr cur; xsltStackElemPtr param; cur = inst->children; while (cur != NULL) { #ifdef WITH_DEBUGGER if (ctxt->debugStatus != XSLT_DEBUG_NONE) xslHandleDebugger(cur, node, comp->templ, ctxt); #endif if (ctxt->state == XSLT_STATE_STOPPED) break; /* * TODO: The "with-param"s could be part of the "call-template" * structure. Avoid to "search" for params dynamically * in the XML tree every time. */ if (IS_XSLT_ELEM(cur)) { if (IS_XSLT_NAME(cur, "with-param")) { param = xsltParseStylesheetCallerParam(ctxt, cur); if (param != NULL) { param->next = withParams; withParams = param; } } else { xsltGenericError(xsltGenericErrorContext, "xsl:call-template: misplaced xsl:%s\n", cur->name); } } else { xsltGenericError(xsltGenericErrorContext, "xsl:call-template: misplaced %s element\n", cur->name); } cur = cur->next; } } /* * Create a new frame using the params first */ xsltApplyXSLTTemplate(ctxt, node, comp->templ->content, comp->templ, withParams); if (withParams != NULL) xsltFreeStackElemList(withParams); #ifdef WITH_XSLT_DEBUG_PROCESS if ((comp != NULL) && (comp->name != NULL)) XSLT_TRACE(ctxt,XSLT_TRACE_CALL_TEMPLATE,xsltGenericDebug(xsltGenericDebugContext, "call-template returned: name %s\n", comp->name)); #endif } /** * xsltApplyTemplates: * @ctxt: a XSLT transformation context * @node: the 'current node' in the source tree * @inst: the element node of an XSLT 'apply-templates' instruction * @castedComp: the compiled instruction * * Processes the XSLT 'apply-templates' instruction on the current node. */ void xsltApplyTemplates(xsltTransformContextPtr ctxt, xmlNodePtr node, xmlNodePtr inst, xsltStylePreCompPtr castedComp) { #ifdef XSLT_REFACTORED xsltStyleItemApplyTemplatesPtr comp = (xsltStyleItemApplyTemplatesPtr) castedComp; #else xsltStylePreCompPtr comp = castedComp; #endif int i; xmlNodePtr cur, delNode = NULL, oldContextNode; xmlNodeSetPtr list = NULL, oldList; xsltStackElemPtr withParams = NULL; int oldXPProximityPosition, oldXPContextSize, oldXPNsNr; const xmlChar *oldMode, *oldModeURI; xmlDocPtr oldXPDoc; xsltDocumentPtr oldDocInfo; xmlXPathContextPtr xpctxt; xmlNsPtr *oldXPNamespaces; if (comp == NULL) { xsltTransformError(ctxt, NULL, inst, "xsl:apply-templates : compilation failed\n"); return; } if ((ctxt == NULL) || (node == NULL) || (inst == NULL) || (comp == NULL)) return; #ifdef WITH_XSLT_DEBUG_PROCESS if ((node != NULL) && (node->name != NULL)) XSLT_TRACE(ctxt,XSLT_TRACE_APPLY_TEMPLATES,xsltGenericDebug(xsltGenericDebugContext, "xsltApplyTemplates: node: '%s'\n", node->name)); #endif xpctxt = ctxt->xpathCtxt; /* * Save context states. */ oldContextNode = ctxt->node; oldMode = ctxt->mode; oldModeURI = ctxt->modeURI; oldDocInfo = ctxt->document; oldList = ctxt->nodeList; /* * The xpath context size and proximity position, as * well as the xpath and context documents, may be changed * so we save their initial state and will restore on exit */ oldXPContextSize = xpctxt->contextSize; oldXPProximityPosition = xpctxt->proximityPosition; oldXPDoc = xpctxt->doc; oldXPNsNr = xpctxt->nsNr; oldXPNamespaces = xpctxt->namespaces; /* * Set up contexts. */ ctxt->mode = comp->mode; ctxt->modeURI = comp->modeURI; if (comp->select != NULL) { xmlXPathObjectPtr res = NULL; if (comp->comp == NULL) { xsltTransformError(ctxt, NULL, inst, "xsl:apply-templates : compilation failed\n"); goto error; } #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_APPLY_TEMPLATES,xsltGenericDebug(xsltGenericDebugContext, "xsltApplyTemplates: select %s\n", comp->select)); #endif /* * Set up XPath. */ xpctxt->node = node; /* Set the "context node" */ #ifdef XSLT_REFACTORED if (comp->inScopeNs != NULL) { xpctxt->namespaces = comp->inScopeNs->list; xpctxt->nsNr = comp->inScopeNs->xpathNumber; } else { xpctxt->namespaces = NULL; xpctxt->nsNr = 0; } #else xpctxt->namespaces = comp->nsList; xpctxt->nsNr = comp->nsNr; #endif res = xmlXPathCompiledEval(comp->comp, xpctxt); xpctxt->contextSize = oldXPContextSize; xpctxt->proximityPosition = oldXPProximityPosition; if (res != NULL) { if (res->type == XPATH_NODESET) { list = res->nodesetval; /* consume the node set */ res->nodesetval = NULL; } else { xsltTransformError(ctxt, NULL, inst, "The 'select' expression did not evaluate to a " "node set.\n"); ctxt->state = XSLT_STATE_STOPPED; xmlXPathFreeObject(res); goto error; } xmlXPathFreeObject(res); /* * Note: An xsl:apply-templates with a 'select' attribute, * can change the current source doc. */ } else { xsltTransformError(ctxt, NULL, inst, "Failed to evaluate the 'select' expression.\n"); ctxt->state = XSLT_STATE_STOPPED; goto error; } if (list == NULL) { #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_APPLY_TEMPLATES,xsltGenericDebug(xsltGenericDebugContext, "xsltApplyTemplates: select didn't evaluate to a node list\n")); #endif goto exit; } /* * * NOTE: Previously a document info (xsltDocument) was * created and attached to the Result Tree Fragment. * But such a document info is created on demand in * xsltKeyFunction() (functions.c), so we need to create * it here beforehand. * In order to take care of potential keys we need to * do some extra work for the case when a Result Tree Fragment * is converted into a nodeset (e.g. exslt:node-set()) : * We attach a "pseudo-doc" (xsltDocument) to _private. * This xsltDocument, together with the keyset, will be freed * when the Result Tree Fragment is freed. * */ #if 0 if ((ctxt->nbKeys > 0) && (list->nodeNr != 0) && (list->nodeTab[0]->doc != NULL) && XSLT_IS_RES_TREE_FRAG(list->nodeTab[0]->doc)) { /* * NOTE that it's also OK if @effectiveDocInfo will be * set to NULL. */ isRTF = 1; effectiveDocInfo = list->nodeTab[0]->doc->_private; } #endif } else { /* * Build an XPath node set with the children */ list = xmlXPathNodeSetCreate(NULL); if (list == NULL) goto error; if (node->type != XML_NAMESPACE_DECL) cur = node->children; else cur = NULL; while (cur != NULL) { switch (cur->type) { case XML_TEXT_NODE: if ((IS_BLANK_NODE(cur)) && (cur->parent != NULL) && (cur->parent->type == XML_ELEMENT_NODE) && (ctxt->style->stripSpaces != NULL)) { const xmlChar *val; if (cur->parent->ns != NULL) { val = (const xmlChar *) xmlHashLookup2(ctxt->style->stripSpaces, cur->parent->name, cur->parent->ns->href); if (val == NULL) { val = (const xmlChar *) xmlHashLookup2(ctxt->style->stripSpaces, BAD_CAST "*", cur->parent->ns->href); } } else { val = (const xmlChar *) xmlHashLookup2(ctxt->style->stripSpaces, cur->parent->name, NULL); } if ((val != NULL) && (xmlStrEqual(val, (xmlChar *) "strip"))) { delNode = cur; break; } } /* no break on purpose */ case XML_ELEMENT_NODE: case XML_DOCUMENT_NODE: case XML_HTML_DOCUMENT_NODE: case XML_CDATA_SECTION_NODE: case XML_PI_NODE: case XML_COMMENT_NODE: xmlXPathNodeSetAddUnique(list, cur); break; case XML_DTD_NODE: /* Unlink the DTD, it's still reachable * using doc->intSubset */ if (cur->next != NULL) cur->next->prev = cur->prev; if (cur->prev != NULL) cur->prev->next = cur->next; break; case XML_NAMESPACE_DECL: break; default: #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_APPLY_TEMPLATES,xsltGenericDebug(xsltGenericDebugContext, "xsltApplyTemplates: skipping cur type %d\n", cur->type)); #endif delNode = cur; } cur = cur->next; if (delNode != NULL) { #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_APPLY_TEMPLATES,xsltGenericDebug(xsltGenericDebugContext, "xsltApplyTemplates: removing ignorable blank cur\n")); #endif xmlUnlinkNode(delNode); xmlFreeNode(delNode); delNode = NULL; } } } #ifdef WITH_XSLT_DEBUG_PROCESS if (list != NULL) XSLT_TRACE(ctxt,XSLT_TRACE_APPLY_TEMPLATES,xsltGenericDebug(xsltGenericDebugContext, "xsltApplyTemplates: list of %d nodes\n", list->nodeNr)); #endif if ((list == NULL) || (list->nodeNr == 0)) goto exit; /* * Set the context's node set and size; this is also needed for * for xsltDoSortFunction(). */ ctxt->nodeList = list; /* * Process xsl:with-param and xsl:sort instructions. * (The code became so verbose just to avoid the * xmlNodePtr sorts[XSLT_MAX_SORT] if there's no xsl:sort) * BUG TODO: We are not using namespaced potentially defined on the * xsl:sort or xsl:with-param elements; XPath expression might fail. */ if (inst->children) { xsltStackElemPtr param; cur = inst->children; while (cur) { #ifdef WITH_DEBUGGER if (ctxt->debugStatus != XSLT_DEBUG_NONE) xslHandleDebugger(cur, node, NULL, ctxt); #endif if (ctxt->state == XSLT_STATE_STOPPED) break; if (cur->type == XML_TEXT_NODE) { cur = cur->next; continue; } if (! IS_XSLT_ELEM(cur)) break; if (IS_XSLT_NAME(cur, "with-param")) { param = xsltParseStylesheetCallerParam(ctxt, cur); if (param != NULL) { param->next = withParams; withParams = param; } } if (IS_XSLT_NAME(cur, "sort")) { xsltTemplatePtr oldCurTempRule = ctxt->currentTemplateRule; int nbsorts = 0; xmlNodePtr sorts[XSLT_MAX_SORT]; sorts[nbsorts++] = cur; while (cur) { #ifdef WITH_DEBUGGER if (ctxt->debugStatus != XSLT_DEBUG_NONE) xslHandleDebugger(cur, node, NULL, ctxt); #endif if (ctxt->state == XSLT_STATE_STOPPED) break; if (cur->type == XML_TEXT_NODE) { cur = cur->next; continue; } if (! IS_XSLT_ELEM(cur)) break; if (IS_XSLT_NAME(cur, "with-param")) { param = xsltParseStylesheetCallerParam(ctxt, cur); if (param != NULL) { param->next = withParams; withParams = param; } } if (IS_XSLT_NAME(cur, "sort")) { if (nbsorts >= XSLT_MAX_SORT) { xsltTransformError(ctxt, NULL, cur, "The number (%d) of xsl:sort instructions exceeds the " "maximum allowed by this processor's settings.\n", nbsorts); ctxt->state = XSLT_STATE_STOPPED; break; } else { sorts[nbsorts++] = cur; } } cur = cur->next; } /* * The "current template rule" is cleared for xsl:sort. */ ctxt->currentTemplateRule = NULL; /* * Sort. */ xsltDoSortFunction(ctxt, sorts, nbsorts); ctxt->currentTemplateRule = oldCurTempRule; break; } cur = cur->next; } } xpctxt->contextSize = list->nodeNr; /* * Apply templates for all selected source nodes. */ for (i = 0; i < list->nodeNr; i++) { cur = list->nodeTab[i]; /* * The node becomes the "current node". */ ctxt->node = cur; /* * An xsl:apply-templates can change the current context doc. * OPTIMIZE TODO: Get rid of the need to set the context doc. */ if ((cur->type != XML_NAMESPACE_DECL) && (cur->doc != NULL)) xpctxt->doc = cur->doc; xpctxt->proximityPosition = i + 1; /* * Find and apply a template for this node. */ xsltProcessOneNode(ctxt, cur, withParams); } exit: error: /* * Free the parameter list. */ if (withParams != NULL) xsltFreeStackElemList(withParams); if (list != NULL) xmlXPathFreeNodeSet(list); /* * Restore context states. */ xpctxt->nsNr = oldXPNsNr; xpctxt->namespaces = oldXPNamespaces; xpctxt->doc = oldXPDoc; xpctxt->contextSize = oldXPContextSize; xpctxt->proximityPosition = oldXPProximityPosition; ctxt->document = oldDocInfo; ctxt->nodeList = oldList; ctxt->node = oldContextNode; ctxt->mode = oldMode; ctxt->modeURI = oldModeURI; } /** * xsltChoose: * @ctxt: a XSLT process context * @contextNode: the current node in the source tree * @inst: the xsl:choose instruction * @comp: compiled information of the instruction * * Processes the xsl:choose instruction on the source node. */ void xsltChoose(xsltTransformContextPtr ctxt, xmlNodePtr contextNode, xmlNodePtr inst, xsltStylePreCompPtr comp ATTRIBUTE_UNUSED) { xmlNodePtr cur; if ((ctxt == NULL) || (contextNode == NULL) || (inst == NULL)) return; /* * TODO: Content model checks should be done only at compilation * time. */ cur = inst->children; if (cur == NULL) { xsltTransformError(ctxt, NULL, inst, "xsl:choose: The instruction has no content.\n"); return; } #ifdef XSLT_REFACTORED /* * We don't check the content model during transformation. */ #else if ((! IS_XSLT_ELEM(cur)) || (! IS_XSLT_NAME(cur, "when"))) { xsltTransformError(ctxt, NULL, inst, "xsl:choose: xsl:when expected first\n"); return; } #endif { int testRes = 0, res = 0; xmlXPathContextPtr xpctxt = ctxt->xpathCtxt; xmlDocPtr oldXPContextDoc = xpctxt->doc; int oldXPProximityPosition = xpctxt->proximityPosition; int oldXPContextSize = xpctxt->contextSize; xmlNsPtr *oldXPNamespaces = xpctxt->namespaces; int oldXPNsNr = xpctxt->nsNr; #ifdef XSLT_REFACTORED xsltStyleItemWhenPtr wcomp = NULL; #else xsltStylePreCompPtr wcomp = NULL; #endif /* * Process xsl:when --------------------------------------------------- */ while (IS_XSLT_ELEM(cur) && IS_XSLT_NAME(cur, "when")) { wcomp = cur->psvi; if ((wcomp == NULL) || (wcomp->test == NULL) || (wcomp->comp == NULL)) { xsltTransformError(ctxt, NULL, cur, "Internal error in xsltChoose(): " "The XSLT 'when' instruction was not compiled.\n"); goto error; } #ifdef WITH_DEBUGGER if (xslDebugStatus != XSLT_DEBUG_NONE) { /* * TODO: Isn't comp->templ always NULL for xsl:choose? */ xslHandleDebugger(cur, contextNode, NULL, ctxt); } #endif #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_CHOOSE,xsltGenericDebug(xsltGenericDebugContext, "xsltChoose: test %s\n", wcomp->test)); #endif xpctxt->node = contextNode; xpctxt->doc = oldXPContextDoc; xpctxt->proximityPosition = oldXPProximityPosition; xpctxt->contextSize = oldXPContextSize; #ifdef XSLT_REFACTORED if (wcomp->inScopeNs != NULL) { xpctxt->namespaces = wcomp->inScopeNs->list; xpctxt->nsNr = wcomp->inScopeNs->xpathNumber; } else { xpctxt->namespaces = NULL; xpctxt->nsNr = 0; } #else xpctxt->namespaces = wcomp->nsList; xpctxt->nsNr = wcomp->nsNr; #endif #ifdef XSLT_FAST_IF res = xmlXPathCompiledEvalToBoolean(wcomp->comp, xpctxt); if (res == -1) { ctxt->state = XSLT_STATE_STOPPED; goto error; } testRes = (res == 1) ? 1 : 0; #else /* XSLT_FAST_IF */ res = xmlXPathCompiledEval(wcomp->comp, xpctxt); if (res != NULL) { if (res->type != XPATH_BOOLEAN) res = xmlXPathConvertBoolean(res); if (res->type == XPATH_BOOLEAN) testRes = res->boolval; else { #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_CHOOSE,xsltGenericDebug(xsltGenericDebugContext, "xsltChoose: test didn't evaluate to a boolean\n")); #endif goto error; } xmlXPathFreeObject(res); res = NULL; } else { ctxt->state = XSLT_STATE_STOPPED; goto error; } #endif /* else of XSLT_FAST_IF */ #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_CHOOSE,xsltGenericDebug(xsltGenericDebugContext, "xsltChoose: test evaluate to %d\n", testRes)); #endif if (testRes) goto test_is_true; cur = cur->next; } /* * Process xsl:otherwise ---------------------------------------------- */ if (IS_XSLT_ELEM(cur) && IS_XSLT_NAME(cur, "otherwise")) { #ifdef WITH_DEBUGGER if (xslDebugStatus != XSLT_DEBUG_NONE) xslHandleDebugger(cur, contextNode, NULL, ctxt); #endif #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_CHOOSE,xsltGenericDebug(xsltGenericDebugContext, "evaluating xsl:otherwise\n")); #endif goto test_is_true; } xpctxt->node = contextNode; xpctxt->doc = oldXPContextDoc; xpctxt->proximityPosition = oldXPProximityPosition; xpctxt->contextSize = oldXPContextSize; xpctxt->namespaces = oldXPNamespaces; xpctxt->nsNr = oldXPNsNr; goto exit; test_is_true: xpctxt->node = contextNode; xpctxt->doc = oldXPContextDoc; xpctxt->proximityPosition = oldXPProximityPosition; xpctxt->contextSize = oldXPContextSize; xpctxt->namespaces = oldXPNamespaces; xpctxt->nsNr = oldXPNsNr; goto process_sequence; } process_sequence: /* * Instantiate the sequence constructor. */ xsltApplySequenceConstructor(ctxt, ctxt->node, cur->children, NULL); exit: error: return; } /** * xsltIf: * @ctxt: a XSLT process context * @contextNode: the current node in the source tree * @inst: the xsl:if instruction * @castedComp: compiled information of the instruction * * Processes the xsl:if instruction on the source node. */ void xsltIf(xsltTransformContextPtr ctxt, xmlNodePtr contextNode, xmlNodePtr inst, xsltStylePreCompPtr castedComp) { int res = 0; #ifdef XSLT_REFACTORED xsltStyleItemIfPtr comp = (xsltStyleItemIfPtr) castedComp; #else xsltStylePreCompPtr comp = castedComp; #endif if ((ctxt == NULL) || (contextNode == NULL) || (inst == NULL)) return; if ((comp == NULL) || (comp->test == NULL) || (comp->comp == NULL)) { xsltTransformError(ctxt, NULL, inst, "Internal error in xsltIf(): " "The XSLT 'if' instruction was not compiled.\n"); return; } #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_IF,xsltGenericDebug(xsltGenericDebugContext, "xsltIf: test %s\n", comp->test)); #endif #ifdef XSLT_FAST_IF { xmlXPathContextPtr xpctxt = ctxt->xpathCtxt; xmlDocPtr oldXPContextDoc = xpctxt->doc; xmlNsPtr *oldXPNamespaces = xpctxt->namespaces; xmlNodePtr oldXPContextNode = xpctxt->node; int oldXPProximityPosition = xpctxt->proximityPosition; int oldXPContextSize = xpctxt->contextSize; int oldXPNsNr = xpctxt->nsNr; xmlDocPtr oldLocalFragmentTop = ctxt->localRVT; xpctxt->node = contextNode; if (comp != NULL) { #ifdef XSLT_REFACTORED if (comp->inScopeNs != NULL) { xpctxt->namespaces = comp->inScopeNs->list; xpctxt->nsNr = comp->inScopeNs->xpathNumber; } else { xpctxt->namespaces = NULL; xpctxt->nsNr = 0; } #else xpctxt->namespaces = comp->nsList; xpctxt->nsNr = comp->nsNr; #endif } else { xpctxt->namespaces = NULL; xpctxt->nsNr = 0; } /* * This XPath function is optimized for boolean results. */ res = xmlXPathCompiledEvalToBoolean(comp->comp, xpctxt); /* * Cleanup fragments created during evaluation of the * "select" expression. */ if (oldLocalFragmentTop != ctxt->localRVT) xsltReleaseLocalRVTs(ctxt, oldLocalFragmentTop); xpctxt->doc = oldXPContextDoc; xpctxt->node = oldXPContextNode; xpctxt->contextSize = oldXPContextSize; xpctxt->proximityPosition = oldXPProximityPosition; xpctxt->nsNr = oldXPNsNr; xpctxt->namespaces = oldXPNamespaces; } #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_IF,xsltGenericDebug(xsltGenericDebugContext, "xsltIf: test evaluate to %d\n", res)); #endif if (res == -1) { ctxt->state = XSLT_STATE_STOPPED; goto error; } if (res == 1) { /* * Instantiate the sequence constructor of xsl:if. */ xsltApplySequenceConstructor(ctxt, contextNode, inst->children, NULL); } #else /* XSLT_FAST_IF */ { xmlXPathObjectPtr xpobj = NULL; /* * OLD CODE: */ { xmlXPathContextPtr xpctxt = ctxt->xpathCtxt; xmlDocPtr oldXPContextDoc = xpctxt->doc; xmlNsPtr *oldXPNamespaces = xpctxt->namespaces; xmlNodePtr oldXPContextNode = xpctxt->node; int oldXPProximityPosition = xpctxt->proximityPosition; int oldXPContextSize = xpctxt->contextSize; int oldXPNsNr = xpctxt->nsNr; xpctxt->node = contextNode; if (comp != NULL) { #ifdef XSLT_REFACTORED if (comp->inScopeNs != NULL) { xpctxt->namespaces = comp->inScopeNs->list; xpctxt->nsNr = comp->inScopeNs->xpathNumber; } else { xpctxt->namespaces = NULL; xpctxt->nsNr = 0; } #else xpctxt->namespaces = comp->nsList; xpctxt->nsNr = comp->nsNr; #endif } else { xpctxt->namespaces = NULL; xpctxt->nsNr = 0; } /* * This XPath function is optimized for boolean results. */ xpobj = xmlXPathCompiledEval(comp->comp, xpctxt); xpctxt->doc = oldXPContextDoc; xpctxt->node = oldXPContextNode; xpctxt->contextSize = oldXPContextSize; xpctxt->proximityPosition = oldXPProximityPosition; xpctxt->nsNr = oldXPNsNr; xpctxt->namespaces = oldXPNamespaces; } if (xpobj != NULL) { if (xpobj->type != XPATH_BOOLEAN) xpobj = xmlXPathConvertBoolean(xpobj); if (xpobj->type == XPATH_BOOLEAN) { res = xpobj->boolval; #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_IF,xsltGenericDebug(xsltGenericDebugContext, "xsltIf: test evaluate to %d\n", res)); #endif if (res) { xsltApplySequenceConstructor(ctxt, contextNode, inst->children, NULL); } } else { #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt, XSLT_TRACE_IF, xsltGenericDebug(xsltGenericDebugContext, "xsltIf: test didn't evaluate to a boolean\n")); #endif ctxt->state = XSLT_STATE_STOPPED; } xmlXPathFreeObject(xpobj); } else { ctxt->state = XSLT_STATE_STOPPED; } } #endif /* else of XSLT_FAST_IF */ error: return; } /** * xsltForEach: * @ctxt: an XSLT transformation context * @contextNode: the "current node" in the source tree * @inst: the element node of the xsl:for-each instruction * @castedComp: the compiled information of the instruction * * Process the xslt for-each node on the source node */ void xsltForEach(xsltTransformContextPtr ctxt, xmlNodePtr contextNode, xmlNodePtr inst, xsltStylePreCompPtr castedComp) { #ifdef XSLT_REFACTORED xsltStyleItemForEachPtr comp = (xsltStyleItemForEachPtr) castedComp; #else xsltStylePreCompPtr comp = castedComp; #endif int i; xmlXPathObjectPtr res = NULL; xmlNodePtr cur, curInst; xmlNodeSetPtr list = NULL; xmlNodeSetPtr oldList; int oldXPProximityPosition, oldXPContextSize; xmlNodePtr oldContextNode; xsltTemplatePtr oldCurTemplRule; xmlDocPtr oldXPDoc; xsltDocumentPtr oldDocInfo; xmlXPathContextPtr xpctxt; if ((ctxt == NULL) || (contextNode == NULL) || (inst == NULL)) { xsltGenericError(xsltGenericErrorContext, "xsltForEach(): Bad arguments.\n"); return; } if (comp == NULL) { xsltTransformError(ctxt, NULL, inst, "Internal error in xsltForEach(): " "The XSLT 'for-each' instruction was not compiled.\n"); return; } if ((comp->select == NULL) || (comp->comp == NULL)) { xsltTransformError(ctxt, NULL, inst, "Internal error in xsltForEach(): " "The selecting expression of the XSLT 'for-each' " "instruction was not compiled correctly.\n"); return; } xpctxt = ctxt->xpathCtxt; #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_FOR_EACH,xsltGenericDebug(xsltGenericDebugContext, "xsltForEach: select %s\n", comp->select)); #endif /* * Save context states. */ oldDocInfo = ctxt->document; oldList = ctxt->nodeList; oldContextNode = ctxt->node; /* * The "current template rule" is cleared for the instantiation of * xsl:for-each. */ oldCurTemplRule = ctxt->currentTemplateRule; ctxt->currentTemplateRule = NULL; oldXPDoc = xpctxt->doc; oldXPProximityPosition = xpctxt->proximityPosition; oldXPContextSize = xpctxt->contextSize; /* * Set up XPath. */ xpctxt->node = contextNode; #ifdef XSLT_REFACTORED if (comp->inScopeNs != NULL) { xpctxt->namespaces = comp->inScopeNs->list; xpctxt->nsNr = comp->inScopeNs->xpathNumber; } else { xpctxt->namespaces = NULL; xpctxt->nsNr = 0; } #else xpctxt->namespaces = comp->nsList; xpctxt->nsNr = comp->nsNr; #endif /* * Evaluate the 'select' expression. */ res = xmlXPathCompiledEval(comp->comp, ctxt->xpathCtxt); if (res != NULL) { if (res->type == XPATH_NODESET) list = res->nodesetval; else { xsltTransformError(ctxt, NULL, inst, "The 'select' expression does not evaluate to a node set.\n"); #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_FOR_EACH,xsltGenericDebug(xsltGenericDebugContext, "xsltForEach: select didn't evaluate to a node list\n")); #endif goto error; } } else { xsltTransformError(ctxt, NULL, inst, "Failed to evaluate the 'select' expression.\n"); ctxt->state = XSLT_STATE_STOPPED; goto error; } if ((list == NULL) || (list->nodeNr <= 0)) goto exit; #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_FOR_EACH,xsltGenericDebug(xsltGenericDebugContext, "xsltForEach: select evaluates to %d nodes\n", list->nodeNr)); #endif /* * Restore XPath states for the "current node". */ xpctxt->contextSize = oldXPContextSize; xpctxt->proximityPosition = oldXPProximityPosition; xpctxt->node = contextNode; /* * Set the list; this has to be done already here for xsltDoSortFunction(). */ ctxt->nodeList = list; /* * Handle xsl:sort instructions and skip them for further processing. * BUG TODO: We are not using namespaced potentially defined on the * xsl:sort element; XPath expression might fail. */ curInst = inst->children; if (IS_XSLT_ELEM(curInst) && IS_XSLT_NAME(curInst, "sort")) { int nbsorts = 0; xmlNodePtr sorts[XSLT_MAX_SORT]; sorts[nbsorts++] = curInst; #ifdef WITH_DEBUGGER if (xslDebugStatus != XSLT_DEBUG_NONE) xslHandleDebugger(curInst, contextNode, NULL, ctxt); #endif curInst = curInst->next; while (IS_XSLT_ELEM(curInst) && IS_XSLT_NAME(curInst, "sort")) { if (nbsorts >= XSLT_MAX_SORT) { xsltTransformError(ctxt, NULL, curInst, "The number of xsl:sort instructions exceeds the " "maximum (%d) allowed by this processor.\n", XSLT_MAX_SORT); goto error; } else { sorts[nbsorts++] = curInst; } #ifdef WITH_DEBUGGER if (xslDebugStatus != XSLT_DEBUG_NONE) xslHandleDebugger(curInst, contextNode, NULL, ctxt); #endif curInst = curInst->next; } xsltDoSortFunction(ctxt, sorts, nbsorts); } xpctxt->contextSize = list->nodeNr; /* * Instantiate the sequence constructor for each selected node. */ for (i = 0; i < list->nodeNr; i++) { cur = list->nodeTab[i]; /* * The selected node becomes the "current node". */ ctxt->node = cur; /* * An xsl:for-each can change the current context doc. * OPTIMIZE TODO: Get rid of the need to set the context doc. */ if ((cur->type != XML_NAMESPACE_DECL) && (cur->doc != NULL)) xpctxt->doc = cur->doc; xpctxt->proximityPosition = i + 1; xsltApplySequenceConstructor(ctxt, cur, curInst, NULL); } exit: error: if (res != NULL) xmlXPathFreeObject(res); /* * Restore old states. */ ctxt->document = oldDocInfo; ctxt->nodeList = oldList; ctxt->node = oldContextNode; ctxt->currentTemplateRule = oldCurTemplRule; xpctxt->doc = oldXPDoc; xpctxt->contextSize = oldXPContextSize; xpctxt->proximityPosition = oldXPProximityPosition; } /************************************************************************ * * * Generic interface * * * ************************************************************************/ #ifdef XSLT_GENERATE_HTML_DOCTYPE typedef struct xsltHTMLVersion { const char *version; const char *public; const char *system; } xsltHTMLVersion; static xsltHTMLVersion xsltHTMLVersions[] = { { "4.01frame", "-//W3C//DTD HTML 4.01 Frameset//EN", "http://www.w3.org/TR/1999/REC-html401-19991224/frameset.dtd"}, { "4.01strict", "-//W3C//DTD HTML 4.01//EN", "http://www.w3.org/TR/1999/REC-html401-19991224/strict.dtd"}, { "4.01trans", "-//W3C//DTD HTML 4.01 Transitional//EN", "http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd"}, { "4.01", "-//W3C//DTD HTML 4.01 Transitional//EN", "http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd"}, { "4.0strict", "-//W3C//DTD HTML 4.01//EN", "http://www.w3.org/TR/html4/strict.dtd"}, { "4.0trans", "-//W3C//DTD HTML 4.01 Transitional//EN", "http://www.w3.org/TR/html4/loose.dtd"}, { "4.0frame", "-//W3C//DTD HTML 4.01 Frameset//EN", "http://www.w3.org/TR/html4/frameset.dtd"}, { "4.0", "-//W3C//DTD HTML 4.01 Transitional//EN", "http://www.w3.org/TR/html4/loose.dtd"}, { "3.2", "-//W3C//DTD HTML 3.2//EN", NULL } }; /** * xsltGetHTMLIDs: * @version: the version string * @publicID: used to return the public ID * @systemID: used to return the system ID * * Returns -1 if not found, 0 otherwise and the system and public * Identifier for this given verion of HTML */ static int xsltGetHTMLIDs(const xmlChar *version, const xmlChar **publicID, const xmlChar **systemID) { unsigned int i; if (version == NULL) return(-1); for (i = 0;i < (sizeof(xsltHTMLVersions)/sizeof(xsltHTMLVersions[1])); i++) { if (!xmlStrcasecmp(version, (const xmlChar *) xsltHTMLVersions[i].version)) { if (publicID != NULL) *publicID = (const xmlChar *) xsltHTMLVersions[i].public; if (systemID != NULL) *systemID = (const xmlChar *) xsltHTMLVersions[i].system; return(0); } } return(-1); } #endif /** * xsltApplyStripSpaces: * @ctxt: a XSLT process context * @node: the root of the XML tree * * Strip the unwanted ignorable spaces from the input tree */ void xsltApplyStripSpaces(xsltTransformContextPtr ctxt, xmlNodePtr node) { xmlNodePtr current; #ifdef WITH_XSLT_DEBUG_PROCESS int nb = 0; #endif current = node; while (current != NULL) { /* * Cleanup children empty nodes if asked for */ if ((IS_XSLT_REAL_NODE(current)) && (current->children != NULL) && (xsltFindElemSpaceHandling(ctxt, current))) { xmlNodePtr delete = NULL, cur = current->children; while (cur != NULL) { if (IS_BLANK_NODE(cur)) delete = cur; cur = cur->next; if (delete != NULL) { xmlUnlinkNode(delete); xmlFreeNode(delete); delete = NULL; #ifdef WITH_XSLT_DEBUG_PROCESS nb++; #endif } } } /* * Skip to next node in document order. */ if (node->type == XML_ENTITY_REF_NODE) { /* process deep in entities */ xsltApplyStripSpaces(ctxt, node->children); } if ((current->children != NULL) && (current->type != XML_ENTITY_REF_NODE)) { current = current->children; } else if (current->next != NULL) { current = current->next; } else { do { current = current->parent; if (current == NULL) break; if (current == node) goto done; if (current->next != NULL) { current = current->next; break; } } while (current != NULL); } } done: #ifdef WITH_XSLT_DEBUG_PROCESS XSLT_TRACE(ctxt,XSLT_TRACE_STRIP_SPACES,xsltGenericDebug(xsltGenericDebugContext, "xsltApplyStripSpaces: removed %d ignorable blank node\n", nb)); #endif return; } static int xsltCountKeys(xsltTransformContextPtr ctxt) { xsltStylesheetPtr style; xsltKeyDefPtr keyd; if (ctxt == NULL) return(-1); /* * Do we have those nastly templates with a key() in the match pattern? */ ctxt->hasTemplKeyPatterns = 0; style = ctxt->style; while (style != NULL) { if (style->keyMatch != NULL) { ctxt->hasTemplKeyPatterns = 1; break; } style = xsltNextImport(style); } /* * Count number of key declarations. */ ctxt->nbKeys = 0; style = ctxt->style; while (style != NULL) { keyd = style->keys; while (keyd) { ctxt->nbKeys++; keyd = keyd->next; } style = xsltNextImport(style); } return(ctxt->nbKeys); } /** * xsltApplyStylesheetInternal: * @style: a parsed XSLT stylesheet * @doc: a parsed XML document * @params: a NULL terminated array of parameters names/values tuples * @output: the targetted output * @profile: profile FILE * output or NULL * @user: user provided parameter * * Apply the stylesheet to the document * NOTE: This may lead to a non-wellformed output XML wise ! * * Returns the result document or NULL in case of error */ static xmlDocPtr xsltApplyStylesheetInternal(xsltStylesheetPtr style, xmlDocPtr doc, const char **params, const char *output, FILE * profile, xsltTransformContextPtr userCtxt) { xmlDocPtr res = NULL; xsltTransformContextPtr ctxt = NULL; xmlNodePtr root, node; const xmlChar *method; const xmlChar *doctypePublic; const xmlChar *doctypeSystem; const xmlChar *version; const xmlChar *encoding; xsltStackElemPtr variables; xsltStackElemPtr vptr; xsltInitGlobals(); if ((style == NULL) || (doc == NULL)) return (NULL); if (style->internalized == 0) { #ifdef WITH_XSLT_DEBUG xsltGenericDebug(xsltGenericDebugContext, "Stylesheet was not fully internalized !\n"); #endif } if (doc->intSubset != NULL) { /* * Avoid hitting the DTD when scanning nodes * but keep it linked as doc->intSubset */ xmlNodePtr cur = (xmlNodePtr) doc->intSubset; if (cur->next != NULL) cur->next->prev = cur->prev; if (cur->prev != NULL) cur->prev->next = cur->next; if (doc->children == cur) doc->children = cur->next; if (doc->last == cur) doc->last = cur->prev; cur->prev = cur->next = NULL; } /* * Check for XPath document order availability */ root = xmlDocGetRootElement(doc); if (root != NULL) { if (((long) root->content) >= 0 && (xslDebugStatus == XSLT_DEBUG_NONE)) xmlXPathOrderDocElems(doc); } if (userCtxt != NULL) ctxt = userCtxt; else ctxt = xsltNewTransformContext(style, doc); if (ctxt == NULL) return (NULL); ctxt->initialContextDoc = doc; ctxt->initialContextNode = (xmlNodePtr) doc; if (profile != NULL) ctxt->profile = 1; if (output != NULL) ctxt->outputFile = output; else ctxt->outputFile = NULL; /* * internalize the modes if needed */ if (ctxt->dict != NULL) { if (ctxt->mode != NULL) ctxt->mode = xmlDictLookup(ctxt->dict, ctxt->mode, -1); if (ctxt->modeURI != NULL) ctxt->modeURI = xmlDictLookup(ctxt->dict, ctxt->modeURI, -1); } XSLT_GET_IMPORT_PTR(method, style, method) XSLT_GET_IMPORT_PTR(doctypePublic, style, doctypePublic) XSLT_GET_IMPORT_PTR(doctypeSystem, style, doctypeSystem) XSLT_GET_IMPORT_PTR(version, style, version) XSLT_GET_IMPORT_PTR(encoding, style, encoding) if ((method != NULL) && (!xmlStrEqual(method, (const xmlChar *) "xml"))) { if (xmlStrEqual(method, (const xmlChar *) "html")) { ctxt->type = XSLT_OUTPUT_HTML; if (((doctypePublic != NULL) || (doctypeSystem != NULL))) { res = htmlNewDoc(doctypeSystem, doctypePublic); } else { if (version == NULL) { xmlDtdPtr dtd; res = htmlNewDoc(NULL, NULL); /* * Make sure no DTD node is generated in this case */ if (res != NULL) { dtd = xmlGetIntSubset(res); if (dtd != NULL) { xmlUnlinkNode((xmlNodePtr) dtd); xmlFreeDtd(dtd); } res->intSubset = NULL; res->extSubset = NULL; } } else { #ifdef XSLT_GENERATE_HTML_DOCTYPE xsltGetHTMLIDs(version, &doctypePublic, &doctypeSystem); #endif res = htmlNewDoc(doctypeSystem, doctypePublic); } } if (res == NULL) goto error; res->dict = ctxt->dict; xmlDictReference(res->dict); #ifdef WITH_XSLT_DEBUG xsltGenericDebug(xsltGenericDebugContext, "reusing transformation dict for output\n"); #endif } else if (xmlStrEqual(method, (const xmlChar *) "xhtml")) { xsltTransformError(ctxt, NULL, (xmlNodePtr) doc, "xsltApplyStylesheetInternal: unsupported method xhtml, using html\n", style->method); ctxt->type = XSLT_OUTPUT_HTML; res = htmlNewDoc(doctypeSystem, doctypePublic); if (res == NULL) goto error; res->dict = ctxt->dict; xmlDictReference(res->dict); #ifdef WITH_XSLT_DEBUG xsltGenericDebug(xsltGenericDebugContext, "reusing transformation dict for output\n"); #endif } else if (xmlStrEqual(method, (const xmlChar *) "text")) { ctxt->type = XSLT_OUTPUT_TEXT; res = xmlNewDoc(style->version); if (res == NULL) goto error; res->dict = ctxt->dict; xmlDictReference(res->dict); #ifdef WITH_XSLT_DEBUG xsltGenericDebug(xsltGenericDebugContext, "reusing transformation dict for output\n"); #endif } else { xsltTransformError(ctxt, NULL, (xmlNodePtr) doc, "xsltApplyStylesheetInternal: unsupported method %s\n", style->method); goto error; } } else { ctxt->type = XSLT_OUTPUT_XML; res = xmlNewDoc(style->version); if (res == NULL) goto error; res->dict = ctxt->dict; xmlDictReference(ctxt->dict); #ifdef WITH_XSLT_DEBUG xsltGenericDebug(xsltGenericDebugContext, "reusing transformation dict for output\n"); #endif } res->charset = XML_CHAR_ENCODING_UTF8; if (encoding != NULL) res->encoding = xmlStrdup(encoding); variables = style->variables; /* * Start the evaluation, evaluate the params, the stylesheets globals * and start by processing the top node. */ if (xsltNeedElemSpaceHandling(ctxt)) xsltApplyStripSpaces(ctxt, xmlDocGetRootElement(doc)); /* * Evaluate global params and user-provided params. */ ctxt->node = (xmlNodePtr) doc; if (ctxt->globalVars == NULL) ctxt->globalVars = xmlHashCreate(20); if (params != NULL) { xsltEvalUserParams(ctxt, params); } /* need to be called before evaluating global variables */ xsltCountKeys(ctxt); xsltEvalGlobalVariables(ctxt); ctxt->node = (xmlNodePtr) doc; ctxt->output = res; ctxt->insert = (xmlNodePtr) res; ctxt->varsBase = ctxt->varsNr - 1; ctxt->xpathCtxt->contextSize = 1; ctxt->xpathCtxt->proximityPosition = 1; ctxt->xpathCtxt->node = NULL; /* TODO: Set the context node here? */ /* * Start processing the source tree ----------------------------------- */ xsltProcessOneNode(ctxt, ctxt->node, NULL); /* * Remove all remaining vars from the stack. */ xsltLocalVariablePop(ctxt, 0, -2); xsltShutdownCtxtExts(ctxt); xsltCleanupTemplates(style); /* TODO: <- style should be read only */ /* * Now cleanup our variables so stylesheet can be re-used * * TODO: this is not needed anymore global variables are copied * and not evaluated directly anymore, keep this as a check */ if (style->variables != variables) { vptr = style->variables; while (vptr->next != variables) vptr = vptr->next; vptr->next = NULL; xsltFreeStackElemList(style->variables); style->variables = variables; } vptr = style->variables; while (vptr != NULL) { if (vptr->computed) { if (vptr->value != NULL) { xmlXPathFreeObject(vptr->value); vptr->value = NULL; vptr->computed = 0; } } vptr = vptr->next; } #if 0 /* * code disabled by wmb; awaiting kb's review * problem is that global variable(s) may contain xpath objects * from doc associated with RVT, so can't be freed at this point. * xsltFreeTransformContext includes a call to xsltFreeRVTs, so * I assume this shouldn't be required at this point. */ /* * Free all remaining tree fragments. */ xsltFreeRVTs(ctxt); #endif /* * Do some post processing work depending on the generated output */ root = xmlDocGetRootElement(res); if (root != NULL) { const xmlChar *doctype = NULL; if ((root->ns != NULL) && (root->ns->prefix != NULL)) doctype = xmlDictQLookup(ctxt->dict, root->ns->prefix, root->name); if (doctype == NULL) doctype = root->name; /* * Apply the default selection of the method */ if ((method == NULL) && (root->ns == NULL) && (!xmlStrcasecmp(root->name, (const xmlChar *) "html"))) { xmlNodePtr tmp; tmp = res->children; while ((tmp != NULL) && (tmp != root)) { if (tmp->type == XML_ELEMENT_NODE) break; if ((tmp->type == XML_TEXT_NODE) && (!xmlIsBlankNode(tmp))) break; tmp = tmp->next; } if (tmp == root) { ctxt->type = XSLT_OUTPUT_HTML; /* * REVISIT TODO: XML_HTML_DOCUMENT_NODE is set after the * transformation on the doc, but functions like */ res->type = XML_HTML_DOCUMENT_NODE; if (((doctypePublic != NULL) || (doctypeSystem != NULL))) { res->intSubset = xmlCreateIntSubset(res, doctype, doctypePublic, doctypeSystem); #ifdef XSLT_GENERATE_HTML_DOCTYPE } else if (version != NULL) { xsltGetHTMLIDs(version, &doctypePublic, &doctypeSystem); if (((doctypePublic != NULL) || (doctypeSystem != NULL))) res->intSubset = xmlCreateIntSubset(res, doctype, doctypePublic, doctypeSystem); #endif } } } if (ctxt->type == XSLT_OUTPUT_XML) { XSLT_GET_IMPORT_PTR(doctypePublic, style, doctypePublic) XSLT_GET_IMPORT_PTR(doctypeSystem, style, doctypeSystem) if (((doctypePublic != NULL) || (doctypeSystem != NULL))) { xmlNodePtr last; /* Need a small "hack" here to assure DTD comes before possible comment nodes */ node = res->children; last = res->last; res->children = NULL; res->last = NULL; res->intSubset = xmlCreateIntSubset(res, doctype, doctypePublic, doctypeSystem); if (res->children != NULL) { res->children->next = node; node->prev = res->children; res->last = last; } else { res->children = node; res->last = last; } } } } xmlXPathFreeNodeSet(ctxt->nodeList); if (profile != NULL) { xsltSaveProfiling(ctxt, profile); } /* * Be pedantic. */ if ((ctxt != NULL) && (ctxt->state == XSLT_STATE_ERROR)) { xmlFreeDoc(res); res = NULL; } if ((res != NULL) && (ctxt != NULL) && (output != NULL)) { int ret; ret = xsltCheckWrite(ctxt->sec, ctxt, (const xmlChar *) output); if (ret == 0) { xsltTransformError(ctxt, NULL, NULL, "xsltApplyStylesheet: forbidden to save to %s\n", output); } else if (ret < 0) { xsltTransformError(ctxt, NULL, NULL, "xsltApplyStylesheet: saving to %s may not be possible\n", output); } } #ifdef XSLT_DEBUG_PROFILE_CACHE printf("# Cache:\n"); printf("# Reused tree fragments: %d\n", ctxt->cache->dbgReusedRVTs); printf("# Reused variables : %d\n", ctxt->cache->dbgReusedVars); #endif if ((ctxt != NULL) && (userCtxt == NULL)) xsltFreeTransformContext(ctxt); return (res); error: if (res != NULL) xmlFreeDoc(res); #ifdef XSLT_DEBUG_PROFILE_CACHE printf("# Cache:\n"); printf("# Reused tree fragments: %d\n", ctxt->cache->dbgReusedRVTs); printf("# Reused variables : %d\n", ctxt->cache->dbgReusedVars); #endif if ((ctxt != NULL) && (userCtxt == NULL)) xsltFreeTransformContext(ctxt); return (NULL); } /** * xsltApplyStylesheet: * @style: a parsed XSLT stylesheet * @doc: a parsed XML document * @params: a NULL terminated arry of parameters names/values tuples * * Apply the stylesheet to the document * NOTE: This may lead to a non-wellformed output XML wise ! * * Returns the result document or NULL in case of error */ xmlDocPtr xsltApplyStylesheet(xsltStylesheetPtr style, xmlDocPtr doc, const char **params) { return (xsltApplyStylesheetInternal(style, doc, params, NULL, NULL, NULL)); } /** * xsltProfileStylesheet: * @style: a parsed XSLT stylesheet * @doc: a parsed XML document * @params: a NULL terminated arry of parameters names/values tuples * @output: a FILE * for the profiling output * * Apply the stylesheet to the document and dump the profiling to * the given output. * * Returns the result document or NULL in case of error */ xmlDocPtr xsltProfileStylesheet(xsltStylesheetPtr style, xmlDocPtr doc, const char **params, FILE * output) { xmlDocPtr res; res = xsltApplyStylesheetInternal(style, doc, params, NULL, output, NULL); return (res); } /** * xsltApplyStylesheetUser: * @style: a parsed XSLT stylesheet * @doc: a parsed XML document * @params: a NULL terminated array of parameters names/values tuples * @output: the targetted output * @profile: profile FILE * output or NULL * @userCtxt: user provided transform context * * Apply the stylesheet to the document and allow the user to provide * its own transformation context. * * Returns the result document or NULL in case of error */ xmlDocPtr xsltApplyStylesheetUser(xsltStylesheetPtr style, xmlDocPtr doc, const char **params, const char *output, FILE * profile, xsltTransformContextPtr userCtxt) { xmlDocPtr res; res = xsltApplyStylesheetInternal(style, doc, params, output, profile, userCtxt); return (res); } /** * xsltRunStylesheetUser: * @style: a parsed XSLT stylesheet * @doc: a parsed XML document * @params: a NULL terminated array of parameters names/values tuples * @output: the URL/filename ot the generated resource if available * @SAX: a SAX handler for progressive callback output (not implemented yet) * @IObuf: an output buffer for progressive output (not implemented yet) * @profile: profile FILE * output or NULL * @userCtxt: user provided transform context * * Apply the stylesheet to the document and generate the output according * to @output @SAX and @IObuf. It's an error to specify both @SAX and @IObuf. * * NOTE: This may lead to a non-wellformed output XML wise ! * NOTE: This may also result in multiple files being generated * NOTE: using IObuf, the result encoding used will be the one used for * creating the output buffer, use the following macro to read it * from the stylesheet * XSLT_GET_IMPORT_PTR(encoding, style, encoding) * NOTE: using SAX, any encoding specified in the stylesheet will be lost * since the interface uses only UTF8 * * Returns the number of by written to the main resource or -1 in case of * error. */ int xsltRunStylesheetUser(xsltStylesheetPtr style, xmlDocPtr doc, const char **params, const char *output, xmlSAXHandlerPtr SAX, xmlOutputBufferPtr IObuf, FILE * profile, xsltTransformContextPtr userCtxt) { xmlDocPtr tmp; int ret; if ((output == NULL) && (SAX == NULL) && (IObuf == NULL)) return (-1); if ((SAX != NULL) && (IObuf != NULL)) return (-1); /* unsupported yet */ if (SAX != NULL) { XSLT_TODO /* xsltRunStylesheet xmlSAXHandlerPtr SAX */ return (-1); } tmp = xsltApplyStylesheetInternal(style, doc, params, output, profile, userCtxt); if (tmp == NULL) { xsltTransformError(NULL, NULL, (xmlNodePtr) doc, "xsltRunStylesheet : run failed\n"); return (-1); } if (IObuf != NULL) { /* TODO: incomplete, IObuf output not progressive */ ret = xsltSaveResultTo(IObuf, tmp, style); } else { ret = xsltSaveResultToFilename(output, tmp, style, 0); } xmlFreeDoc(tmp); return (ret); } /** * xsltRunStylesheet: * @style: a parsed XSLT stylesheet * @doc: a parsed XML document * @params: a NULL terminated array of parameters names/values tuples * @output: the URL/filename ot the generated resource if available * @SAX: a SAX handler for progressive callback output (not implemented yet) * @IObuf: an output buffer for progressive output (not implemented yet) * * Apply the stylesheet to the document and generate the output according * to @output @SAX and @IObuf. It's an error to specify both @SAX and @IObuf. * * NOTE: This may lead to a non-wellformed output XML wise ! * NOTE: This may also result in multiple files being generated * NOTE: using IObuf, the result encoding used will be the one used for * creating the output buffer, use the following macro to read it * from the stylesheet * XSLT_GET_IMPORT_PTR(encoding, style, encoding) * NOTE: using SAX, any encoding specified in the stylesheet will be lost * since the interface uses only UTF8 * * Returns the number of bytes written to the main resource or -1 in case of * error. */ int xsltRunStylesheet(xsltStylesheetPtr style, xmlDocPtr doc, const char **params, const char *output, xmlSAXHandlerPtr SAX, xmlOutputBufferPtr IObuf) { return(xsltRunStylesheetUser(style, doc, params, output, SAX, IObuf, NULL, NULL)); } /** * xsltRegisterAllElement: * @ctxt: the XPath context * * Registers all default XSLT elements in this context */ void xsltRegisterAllElement(xsltTransformContextPtr ctxt) { xsltRegisterExtElement(ctxt, (const xmlChar *) "apply-templates", XSLT_NAMESPACE, (xsltTransformFunction) xsltApplyTemplates); xsltRegisterExtElement(ctxt, (const xmlChar *) "apply-imports", XSLT_NAMESPACE, (xsltTransformFunction) xsltApplyImports); xsltRegisterExtElement(ctxt, (const xmlChar *) "call-template", XSLT_NAMESPACE, (xsltTransformFunction) xsltCallTemplate); xsltRegisterExtElement(ctxt, (const xmlChar *) "element", XSLT_NAMESPACE, (xsltTransformFunction) xsltElement); xsltRegisterExtElement(ctxt, (const xmlChar *) "attribute", XSLT_NAMESPACE, (xsltTransformFunction) xsltAttribute); xsltRegisterExtElement(ctxt, (const xmlChar *) "text", XSLT_NAMESPACE, (xsltTransformFunction) xsltText); xsltRegisterExtElement(ctxt, (const xmlChar *) "processing-instruction", XSLT_NAMESPACE, (xsltTransformFunction) xsltProcessingInstruction); xsltRegisterExtElement(ctxt, (const xmlChar *) "comment", XSLT_NAMESPACE, (xsltTransformFunction) xsltComment); xsltRegisterExtElement(ctxt, (const xmlChar *) "copy", XSLT_NAMESPACE, (xsltTransformFunction) xsltCopy); xsltRegisterExtElement(ctxt, (const xmlChar *) "value-of", XSLT_NAMESPACE, (xsltTransformFunction) xsltValueOf); xsltRegisterExtElement(ctxt, (const xmlChar *) "number", XSLT_NAMESPACE, (xsltTransformFunction) xsltNumber); xsltRegisterExtElement(ctxt, (const xmlChar *) "for-each", XSLT_NAMESPACE, (xsltTransformFunction) xsltForEach); xsltRegisterExtElement(ctxt, (const xmlChar *) "if", XSLT_NAMESPACE, (xsltTransformFunction) xsltIf); xsltRegisterExtElement(ctxt, (const xmlChar *) "choose", XSLT_NAMESPACE, (xsltTransformFunction) xsltChoose); xsltRegisterExtElement(ctxt, (const xmlChar *) "sort", XSLT_NAMESPACE, (xsltTransformFunction) xsltSort); xsltRegisterExtElement(ctxt, (const xmlChar *) "copy-of", XSLT_NAMESPACE, (xsltTransformFunction) xsltCopyOf); xsltRegisterExtElement(ctxt, (const xmlChar *) "message", XSLT_NAMESPACE, (xsltTransformFunction) xsltMessage); /* * Those don't have callable entry points but are registered anyway */ xsltRegisterExtElement(ctxt, (const xmlChar *) "variable", XSLT_NAMESPACE, (xsltTransformFunction) xsltDebug); xsltRegisterExtElement(ctxt, (const xmlChar *) "param", XSLT_NAMESPACE, (xsltTransformFunction) xsltDebug); xsltRegisterExtElement(ctxt, (const xmlChar *) "with-param", XSLT_NAMESPACE, (xsltTransformFunction) xsltDebug); xsltRegisterExtElement(ctxt, (const xmlChar *) "decimal-format", XSLT_NAMESPACE, (xsltTransformFunction) xsltDebug); xsltRegisterExtElement(ctxt, (const xmlChar *) "when", XSLT_NAMESPACE, (xsltTransformFunction) xsltDebug); xsltRegisterExtElement(ctxt, (const xmlChar *) "otherwise", XSLT_NAMESPACE, (xsltTransformFunction) xsltDebug); xsltRegisterExtElement(ctxt, (const xmlChar *) "fallback", XSLT_NAMESPACE, (xsltTransformFunction) xsltDebug); }