blob: c6250dc4e1f8626cc544999e0ae427b842ae6574 [file] [log] [blame]
/*
* templates.c: Implementation of the template processing
*
* Reference:
* http://www.w3.org/TR/1999/REC-xslt-19991116
*
* See Copyright for the status of this software.
*
* daniel@veillard.com
*/
#define IN_LIBXSLT
#include "libxslt.h"
#include <string.h>
#include <libxml/xmlmemory.h>
#include <libxml/globals.h>
#include <libxml/xmlerror.h>
#include <libxml/tree.h>
#include <libxml/xpathInternals.h>
#include <libxml/parserInternals.h>
#include "xslt.h"
#include "xsltInternals.h"
#include "xsltutils.h"
#include "variables.h"
#include "functions.h"
#include "templates.h"
#include "transform.h"
#include "namespaces.h"
#include "attributes.h"
#ifdef WITH_XSLT_DEBUG
#define WITH_XSLT_DEBUG_TEMPLATES
#endif
/************************************************************************
* *
* Module interfaces *
* *
************************************************************************/
/**
* xsltEvalXPathPredicate:
* @ctxt: the XSLT transformation context
* @comp: the XPath compiled expression
* @nsList: the namespaces in scope
* @nsNr: the number of namespaces in scope
*
* Process the expression using XPath and evaluate the result as
* an XPath predicate
*
* Returns 1 is the predicate was true, 0 otherwise
*/
int
xsltEvalXPathPredicate(xsltTransformContextPtr ctxt, xmlXPathCompExprPtr comp,
xmlNsPtr *nsList, int nsNr) {
int ret;
xmlXPathObjectPtr res;
int oldNsNr;
xmlNsPtr *oldNamespaces;
xmlNodePtr oldInst;
int oldProximityPosition, oldContextSize;
oldContextSize = ctxt->xpathCtxt->contextSize;
oldProximityPosition = ctxt->xpathCtxt->proximityPosition;
oldNsNr = ctxt->xpathCtxt->nsNr;
oldNamespaces = ctxt->xpathCtxt->namespaces;
oldInst = ctxt->inst;
ctxt->xpathCtxt->node = ctxt->node;
ctxt->xpathCtxt->namespaces = nsList;
ctxt->xpathCtxt->nsNr = nsNr;
res = xmlXPathCompiledEval(comp, ctxt->xpathCtxt);
if (res != NULL) {
ret = xmlXPathEvalPredicate(ctxt->xpathCtxt, res);
xmlXPathFreeObject(res);
#ifdef WITH_XSLT_DEBUG_TEMPLATES
XSLT_TRACE(ctxt,XSLT_TRACE_TEMPLATES,xsltGenericDebug(xsltGenericDebugContext,
"xsltEvalXPathPredicate: returns %d\n", ret));
#endif
} else {
#ifdef WITH_XSLT_DEBUG_TEMPLATES
XSLT_TRACE(ctxt,XSLT_TRACE_TEMPLATES,xsltGenericDebug(xsltGenericDebugContext,
"xsltEvalXPathPredicate: failed\n"));
#endif
ctxt->state = XSLT_STATE_STOPPED;
ret = 0;
}
ctxt->xpathCtxt->nsNr = oldNsNr;
ctxt->xpathCtxt->namespaces = oldNamespaces;
ctxt->inst = oldInst;
ctxt->xpathCtxt->contextSize = oldContextSize;
ctxt->xpathCtxt->proximityPosition = oldProximityPosition;
return(ret);
}
/**
* xsltEvalXPathStringNs:
* @ctxt: the XSLT transformation context
* @comp: the compiled XPath expression
* @nsNr: the number of namespaces in the list
* @nsList: the list of in-scope namespaces to use
*
* Process the expression using XPath, allowing to pass a namespace mapping
* context and get a string
*
* Returns the computed string value or NULL, must be deallocated by the
* caller.
*/
xmlChar *
xsltEvalXPathStringNs(xsltTransformContextPtr ctxt, xmlXPathCompExprPtr comp,
int nsNr, xmlNsPtr *nsList) {
xmlChar *ret = NULL;
xmlXPathObjectPtr res;
xmlNodePtr oldInst;
xmlNodePtr oldNode;
int oldPos, oldSize;
int oldNsNr;
xmlNsPtr *oldNamespaces;
oldInst = ctxt->inst;
oldNode = ctxt->node;
oldPos = ctxt->xpathCtxt->proximityPosition;
oldSize = ctxt->xpathCtxt->contextSize;
oldNsNr = ctxt->xpathCtxt->nsNr;
oldNamespaces = ctxt->xpathCtxt->namespaces;
ctxt->xpathCtxt->node = ctxt->node;
/* TODO: do we need to propagate the namespaces here ? */
ctxt->xpathCtxt->namespaces = nsList;
ctxt->xpathCtxt->nsNr = nsNr;
res = xmlXPathCompiledEval(comp, ctxt->xpathCtxt);
if (res != NULL) {
if (res->type != XPATH_STRING)
res = xmlXPathConvertString(res);
if (res->type == XPATH_STRING) {
ret = res->stringval;
res->stringval = NULL;
} else {
xsltTransformError(ctxt, NULL, NULL,
"xpath : string() function didn't return a String\n");
}
xmlXPathFreeObject(res);
} else {
ctxt->state = XSLT_STATE_STOPPED;
}
#ifdef WITH_XSLT_DEBUG_TEMPLATES
XSLT_TRACE(ctxt,XSLT_TRACE_TEMPLATES,xsltGenericDebug(xsltGenericDebugContext,
"xsltEvalXPathString: returns %s\n", ret));
#endif
ctxt->inst = oldInst;
ctxt->node = oldNode;
ctxt->xpathCtxt->contextSize = oldSize;
ctxt->xpathCtxt->proximityPosition = oldPos;
ctxt->xpathCtxt->nsNr = oldNsNr;
ctxt->xpathCtxt->namespaces = oldNamespaces;
return(ret);
}
/**
* xsltEvalXPathString:
* @ctxt: the XSLT transformation context
* @comp: the compiled XPath expression
*
* Process the expression using XPath and get a string
*
* Returns the computed string value or NULL, must be deallocated by the
* caller.
*/
xmlChar *
xsltEvalXPathString(xsltTransformContextPtr ctxt, xmlXPathCompExprPtr comp) {
return(xsltEvalXPathStringNs(ctxt, comp, 0, NULL));
}
/**
* xsltEvalTemplateString:
* @ctxt: the XSLT transformation context
* @contextNode: the current node in the source tree
* @inst: the XSLT instruction (xsl:comment, xsl:processing-instruction)
*
* Processes the sequence constructor of the given instruction on
* @contextNode and converts the resulting tree to a string.
* This is needed by e.g. xsl:comment and xsl:processing-instruction.
*
* Returns the computed string value or NULL; it's up to the caller to
* free the result.
*/
xmlChar *
xsltEvalTemplateString(xsltTransformContextPtr ctxt,
xmlNodePtr contextNode,
xmlNodePtr inst)
{
xmlNodePtr oldInsert, insert = NULL;
xmlChar *ret;
if ((ctxt == NULL) || (contextNode == NULL) || (inst == NULL))
return(NULL);
if (inst->children == NULL)
return(NULL);
/*
* This creates a temporary element-node to add the resulting
* text content to.
* OPTIMIZE TODO: Keep such an element-node in the transformation
* context to avoid creating it every time.
*/
insert = xmlNewDocNode(ctxt->output, NULL,
(const xmlChar *)"fake", NULL);
if (insert == NULL) {
xsltTransformError(ctxt, NULL, contextNode,
"Failed to create temporary node\n");
return(NULL);
}
oldInsert = ctxt->insert;
ctxt->insert = insert;
/*
* OPTIMIZE TODO: if inst->children consists only of text-nodes.
*/
xsltApplyOneTemplate(ctxt, contextNode, inst->children, NULL, NULL);
ctxt->insert = oldInsert;
ret = xmlNodeGetContent(insert);
if (insert != NULL)
xmlFreeNode(insert);
return(ret);
}
/**
* xsltAttrTemplateValueProcessNode:
* @ctxt: the XSLT transformation context
* @str: the attribute template node value
* @inst: the instruction (or LRE) in the stylesheet holding the
* attribute with an AVT
*
* Process the given string, allowing to pass a namespace mapping
* context and return the new string value.
*
* Called by:
* - xsltAttrTemplateValueProcess() (templates.c)
* - xsltEvalAttrValueTemplate() (templates.c)
*
* QUESTION: Why is this function public? It is not used outside
* of templates.c.
*
* Returns the computed string value or NULL, must be deallocated by the
* caller.
*/
xmlChar *
xsltAttrTemplateValueProcessNode(xsltTransformContextPtr ctxt,
const xmlChar *str, xmlNodePtr inst)
{
xmlChar *ret = NULL;
const xmlChar *cur;
xmlChar *expr, *val;
xmlNsPtr *nsList = NULL;
int nsNr = 0;
if (str == NULL) return(NULL);
if (*str == 0)
return(xmlStrndup((xmlChar *)"", 0));
cur = str;
while (*cur != 0) {
if (*cur == '{') {
if (*(cur+1) == '{') { /* escaped '{' */
cur++;
ret = xmlStrncat(ret, str, cur - str);
cur++;
str = cur;
continue;
}
ret = xmlStrncat(ret, str, cur - str);
str = cur;
cur++;
while ((*cur != 0) && (*cur != '}')) cur++;
if (*cur == 0) {
xsltTransformError(ctxt, NULL, inst,
"xsltAttrTemplateValueProcessNode: unmatched '{'\n");
ret = xmlStrncat(ret, str, cur - str);
return(ret);
}
str++;
expr = xmlStrndup(str, cur - str);
if (expr == NULL)
return(ret);
else if (*expr == '{') {
ret = xmlStrcat(ret, expr);
xmlFree(expr);
} else {
xmlXPathCompExprPtr comp;
/*
* TODO: keep precompiled form around
*/
if ((nsList == NULL) && (inst != NULL)) {
int i = 0;
nsList = xmlGetNsList(inst->doc, inst);
if (nsList != NULL) {
while (nsList[i] != NULL)
i++;
nsNr = i;
}
}
comp = xmlXPathCompile(expr);
val = xsltEvalXPathStringNs(ctxt, comp, nsNr, nsList);
xmlXPathFreeCompExpr(comp);
xmlFree(expr);
if (val != NULL) {
ret = xmlStrcat(ret, val);
xmlFree(val);
}
}
cur++;
str = cur;
} else if (*cur == '}') {
cur++;
if (*cur == '}') { /* escaped '}' */
ret = xmlStrncat(ret, str, cur - str);
cur++;
str = cur;
continue;
} else {
xsltTransformError(ctxt, NULL, inst,
"xsltAttrTemplateValueProcessNode: unmatched '}'\n");
}
} else
cur++;
}
if (cur != str) {
ret = xmlStrncat(ret, str, cur - str);
}
if (nsList != NULL)
xmlFree(nsList);
return(ret);
}
/**
* xsltAttrTemplateValueProcess:
* @ctxt: the XSLT transformation context
* @str: the attribute template node value
*
* Process the given node and return the new string value.
*
* Returns the computed string value or NULL, must be deallocated by the
* caller.
*/
xmlChar *
xsltAttrTemplateValueProcess(xsltTransformContextPtr ctxt, const xmlChar *str) {
return(xsltAttrTemplateValueProcessNode(ctxt, str, NULL));
}
/**
* xsltEvalAttrValueTemplate:
* @ctxt: the XSLT transformation context
* @inst: the instruction (or LRE) in the stylesheet holding the
* attribute with an AVT
* @name: the attribute QName
* @ns: the attribute namespace URI
*
* Evaluate a attribute value template, i.e. the attribute value can
* contain expressions contained in curly braces ({}) and those are
* substituted by they computed value.
*
* Returns the computed string value or NULL, must be deallocated by the
* caller.
*/
xmlChar *
xsltEvalAttrValueTemplate(xsltTransformContextPtr ctxt, xmlNodePtr inst,
const xmlChar *name, const xmlChar *ns)
{
xmlChar *ret;
xmlChar *expr;
if ((ctxt == NULL) || (inst == NULL) || (name == NULL))
return(NULL);
expr = xsltGetNsProp(inst, name, ns);
if (expr == NULL)
return(NULL);
/*
* TODO: though now {} is detected ahead, it would still be good to
* optimize both functions to keep the splitted value if the
* attribute content and the XPath precompiled expressions around
*/
ret = xsltAttrTemplateValueProcessNode(ctxt, expr, inst);
#ifdef WITH_XSLT_DEBUG_TEMPLATES
XSLT_TRACE(ctxt,XSLT_TRACE_TEMPLATES,xsltGenericDebug(xsltGenericDebugContext,
"xsltEvalAttrValueTemplate: %s returns %s\n", expr, ret));
#endif
if (expr != NULL)
xmlFree(expr);
return(ret);
}
/**
* xsltEvalStaticAttrValueTemplate:
* @style: the XSLT stylesheet
* @inst: the instruction (or LRE) in the stylesheet holding the
* attribute with an AVT
* @name: the attribute Name
* @ns: the attribute namespace URI
* @found: indicator whether the attribute is present
*
* Check if an attribute value template has a static value, i.e. the
* attribute value does not contain expressions contained in curly braces ({})
*
* Returns the static string value or NULL, must be deallocated by the
* caller.
*/
const xmlChar *
xsltEvalStaticAttrValueTemplate(xsltStylesheetPtr style, xmlNodePtr inst,
const xmlChar *name, const xmlChar *ns, int *found) {
const xmlChar *ret;
xmlChar *expr;
if ((style == NULL) || (inst == NULL) || (name == NULL))
return(NULL);
expr = xsltGetNsProp(inst, name, ns);
if (expr == NULL) {
*found = 0;
return(NULL);
}
*found = 1;
ret = xmlStrchr(expr, '{');
if (ret != NULL) {
xmlFree(expr);
return(NULL);
}
ret = xmlDictLookup(style->dict, expr, -1);
xmlFree(expr);
return(ret);
}
/**
* xsltAttrTemplateProcess:
* @ctxt: the XSLT transformation context
* @target: the element where the attribute will be grafted
* @attr: the attribute node of a literal result element
*
* Process one attribute of a Literal Result Element (in the stylesheet).
* Evaluates Attribute Value Templates and copies the attribute over to
* the result element.
* This does *not* process attribute sets (xsl:use-attribute-set).
*
*
* Returns the generated attribute node.
*/
xmlAttrPtr
xsltAttrTemplateProcess(xsltTransformContextPtr ctxt, xmlNodePtr target,
xmlAttrPtr attr)
{
const xmlChar *value;
xmlAttrPtr ret;
if ((ctxt == NULL) || (attr == NULL) || (target == NULL))
return(NULL);
if (attr->type != XML_ATTRIBUTE_NODE)
return(NULL);
/*
* Skip all XSLT attributes.
*/
#ifdef XSLT_REFACTORED
if (attr->psvi == xsltXSLTAttrMarker)
return(NULL);
#else
if ((attr->ns != NULL) && xmlStrEqual(attr->ns->href, XSLT_NAMESPACE))
return(NULL);
#endif
/*
* Get the value.
*/
if (attr->children != NULL) {
if ((attr->children->type != XML_TEXT_NODE) ||
(attr->children->next != NULL))
{
xsltTransformError(ctxt, NULL, attr->parent,
"Internal error: The children of an attribute node of a "
"literal result element are not in the expected form.\n");
return(NULL);
}
value = attr->children->content;
if (value == NULL)
value = xmlDictLookup(ctxt->dict, BAD_CAST "", 0);
} else
value = xmlDictLookup(ctxt->dict, BAD_CAST "", 0);
/*
* Overwrite duplicates.
*/
ret = target->properties;
while (ret != NULL) {
if (((attr->ns != NULL) == (ret->ns != NULL)) &&
xmlStrEqual(ret->name, attr->name) &&
((attr->ns == NULL) || xmlStrEqual(ret->ns->href, attr->ns->href)))
{
break;
}
ret = ret->next;
}
if (ret != NULL) {
/* free the existing value */
xmlFreeNodeList(ret->children);
ret->children = ret->last = NULL;
/*
* Adjust ns-prefix if needed.
*/
if ((ret->ns != NULL) &&
(! xmlStrEqual(ret->ns->prefix, attr->ns->prefix)))
{
ret->ns = xsltGetNamespace(ctxt, attr->parent, attr->ns, target);
}
} else {
/* create a new attribute */
if (attr->ns != NULL)
ret = xmlNewNsProp(target,
xsltGetNamespace(ctxt, attr->parent, attr->ns, target),
attr->name, NULL);
else
ret = xmlNewNsProp(target, NULL, attr->name, NULL);
}
/*
* Set the value.
*/
if (ret != NULL) {
xmlNodePtr text;
text = xmlNewText(NULL);
if (text != NULL) {
ret->last = ret->children = text;
text->parent = (xmlNodePtr) ret;
text->doc = ret->doc;
if (attr->psvi != NULL) {
/*
* Evaluate the Attribute Value Template.
*/
xmlChar *val;
val = xsltEvalAVT(ctxt, attr->psvi, attr->parent);
if (val == NULL) {
/*
* TODO: Damn, we need an easy mechanism to report
* qualified names!
*/
if (attr->ns) {
xsltTransformError(ctxt, NULL, attr->parent,
"Internal error: Failed to evaluate the AVT "
"of attribute '{%s}%s'.\n",
attr->ns->href, attr->name);
} else {
xsltTransformError(ctxt, NULL, attr->parent,
"Internal error: Failed to evaluate the AVT "
"of attribute '%s'.\n",
attr->name);
}
text->content = xmlStrdup(BAD_CAST "");
} else {
text->content = val;
}
} else if ((ctxt->internalized) && (target != NULL) &&
(target->doc != NULL) &&
(target->doc->dict == ctxt->dict)) {
text->content = (xmlChar *) value;
} else {
text->content = xmlStrdup(value);
}
}
} else {
if (attr->ns) {
xsltTransformError(ctxt, NULL, attr->parent,
"Internal error: Failed to create attribute '{%s}%s'.\n",
attr->ns->href, attr->name);
} else {
xsltTransformError(ctxt, NULL, attr->parent,
"Internal error: Failed to create attribute '%s'.\n",
attr->name);
}
}
return(ret);
}
/**
* xsltAttrListTemplateProcess:
* @ctxt: the XSLT transformation context
* @target: the element where the attributes will be grafted
* @attrs: the first attribute
*
* Processes all attributes of a Literal Result Element.
* Attribute references are applied via xsl:use-attribute-set
* attributes.
* Copies all non XSLT-attributes over to the @target element
* and evaluates Attribute Value Templates.
*
* Called by xsltApplySequenceConstructor() (transform.c).
*
* Returns a new list of attribute nodes, or NULL in case of error.
* (Don't assign the result to @target->properties; if
* the result is NULL, you'll get memory leaks, since the
* attributes will be disattached.)
*/
xmlAttrPtr
xsltAttrListTemplateProcess(xsltTransformContextPtr ctxt,
xmlNodePtr target, xmlAttrPtr attrs)
{
xmlAttrPtr attr, copy, last;
xmlNodePtr oldInsert, text;
xmlNsPtr origNs = NULL, copyNs = NULL;
const xmlChar *value;
xmlChar *valueAVT;
if ((ctxt == NULL) || (target == NULL) || (attrs == NULL))
return(NULL);
oldInsert = ctxt->insert;
ctxt->insert = target;
/*
* Instantiate LRE-attributes.
*/
if (target->properties) {
last = target->properties;
while (last->next != NULL)
last = last->next;
} else {
last = NULL;
}
attr = attrs;
do {
/*
* Skip XSLT attributes.
*/
#ifdef XSLT_REFACTORED
if (attr->psvi == xsltXSLTAttrMarker) {
goto next_attribute;
}
#else
if ((attr->ns != NULL) &&
xmlStrEqual(attr->ns->href, XSLT_NAMESPACE))
{
goto next_attribute;
}
#endif
/*
* Get the value.
*/
if (attr->children != NULL) {
if ((attr->children->type != XML_TEXT_NODE) ||
(attr->children->next != NULL))
{
xsltTransformError(ctxt, NULL, attr->parent,
"Internal error: The children of an attribute node of a "
"literal result element are not in the expected form.\n");
goto error;
}
value = attr->children->content;
if (value == NULL)
value = xmlDictLookup(ctxt->dict, BAD_CAST "", 0);
} else
value = xmlDictLookup(ctxt->dict, BAD_CAST "", 0);
/*
* Create a new attribute.
*/
copy = xmlNewDocProp(target->doc, attr->name, NULL);
if (copy == NULL) {
if (attr->ns) {
xsltTransformError(ctxt, NULL, attr->parent,
"Internal error: Failed to create attribute '{%s}%s'.\n",
attr->ns->href, attr->name);
} else {
xsltTransformError(ctxt, NULL, attr->parent,
"Internal error: Failed to create attribute '%s'.\n",
attr->name);
}
goto error;
}
/*
* Attach it to the target element.
*/
copy->parent = target;
if (last == NULL) {
target->properties = copy;
last = copy;
} else {
last->next = copy;
copy->prev = last;
last = copy;
}
/*
* Set the namespace. Avoid lookups of same namespaces.
*/
if (attr->ns != origNs) {
origNs = attr->ns;
if (attr->ns != NULL) {
#ifdef XSLT_REFACTORED
copyNs = xsltGetSpecialNamespace(ctxt, attr->parent,
attr->ns->href, attr->ns->prefix, target);
#else
copyNs = xsltGetNamespace(ctxt, attr->parent,
attr->ns, target);
#endif
if (copyNs == NULL)
goto error;
} else
copyNs = NULL;
}
copy->ns = copyNs;
/*
* Set the value.
*/
text = xmlNewText(NULL);
if (text != NULL) {
copy->last = copy->children = text;
text->parent = (xmlNodePtr) copy;
text->doc = copy->doc;
if (attr->psvi != NULL) {
/*
* Evaluate the Attribute Value Template.
*/
valueAVT = xsltEvalAVT(ctxt, attr->psvi, attr->parent);
if (valueAVT == NULL) {
/*
* TODO: Damn, we need an easy mechanism to report
* qualified names!
*/
if (attr->ns) {
xsltTransformError(ctxt, NULL, attr->parent,
"Internal error: Failed to evaluate the AVT "
"of attribute '{%s}%s'.\n",
attr->ns->href, attr->name);
} else {
xsltTransformError(ctxt, NULL, attr->parent,
"Internal error: Failed to evaluate the AVT "
"of attribute '%s'.\n",
attr->name);
}
text->content = xmlStrdup(BAD_CAST "");
goto error;
} else {
text->content = valueAVT;
}
} else if ((ctxt->internalized) &&
(target->doc != NULL) &&
(target->doc->dict == ctxt->dict))
{
text->content = (xmlChar *) value;
} else {
text->content = xmlStrdup(value);
}
if ((copy != NULL) && (text != NULL) &&
(xmlIsID(copy->doc, copy->parent, copy)))
xmlAddID(NULL, copy->doc, text->content, copy);
}
next_attribute:
attr = attr->next;
} while (attr != NULL);
/*
* Apply attribute-sets.
* The creation of such attributes will not overwrite any existing
* attribute.
*/
attr = attrs;
do {
#ifdef XSLT_REFACTORED
if ((attr->psvi == xsltXSLTAttrMarker) &&
xmlStrEqual(attr->name, (const xmlChar *)"use-attribute-sets"))
{
xsltApplyAttributeSet(ctxt, ctxt->node, (xmlNodePtr) attr, NULL);
}
#else
if ((attr->ns != NULL) &&
xmlStrEqual(attr->name, (const xmlChar *)"use-attribute-sets") &&
xmlStrEqual(attr->ns->href, XSLT_NAMESPACE))
{
xsltApplyAttributeSet(ctxt, ctxt->node, (xmlNodePtr) attr, NULL);
}
#endif
attr = attr->next;
} while (attr != NULL);
ctxt->insert = oldInsert;
return(target->properties);
error:
ctxt->insert = oldInsert;
return(NULL);
}
/**
* xsltTemplateProcess:
* @ctxt: the XSLT transformation context
* @node: the attribute template node
*
* Obsolete. Don't use it.
*
* Returns NULL.
*/
xmlNodePtr *
xsltTemplateProcess(xsltTransformContextPtr ctxt ATTRIBUTE_UNUSED, xmlNodePtr node) {
if (node == NULL)
return(NULL);
return(0);
}