mirror of
https://gitlab.gnome.org/GNOME/libxslt
synced 2025-11-08 11:02:18 +03:00
- Makefile.am tests/Makefile.am tests/REC1/Makefile.am tests/REC2/Makefile.am tests/REC2/html.xml: added tests target too, added the HTML output test - libxmls/xsltutils.c: added HTML output - libxslt/xslt.c: check version on literal result used as templates - libxslt/transform.c: fixed an error in VERSION number - libxslt/templates.c: make sure generated nodes have doc and parent properly set Daniel
1180 lines
30 KiB
C
1180 lines
30 KiB
C
/*
|
|
* transform.c: Implemetation of the XSL Transformation 1.0 engine
|
|
* transform part, i.e. applying a Stylesheet to a document
|
|
*
|
|
* Reference:
|
|
* http://www.w3.org/TR/1999/REC-xslt-19991116
|
|
*
|
|
* See Copyright for the status of this software.
|
|
*
|
|
* Daniel.Veillard@imag.fr
|
|
*/
|
|
|
|
#include "xsltconfig.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include <libxml/xmlmemory.h>
|
|
#include <libxml/parser.h>
|
|
#include <libxml/tree.h>
|
|
#include <libxml/valid.h>
|
|
#include <libxml/hash.h>
|
|
#include <libxml/encoding.h>
|
|
#include <libxml/xmlerror.h>
|
|
#include <libxml/xpath.h>
|
|
#include <libxml/xpathInternals.h>
|
|
#include <libxml/HTMLtree.h>
|
|
#include "xslt.h"
|
|
#include "xsltInternals.h"
|
|
#include "xsltutils.h"
|
|
#include "pattern.h"
|
|
#include "transform.h"
|
|
#include "variables.h"
|
|
#include "templates.h"
|
|
|
|
#define DEBUG_PROCESS
|
|
|
|
/*
|
|
* Useful macros
|
|
*/
|
|
|
|
#define IS_BLANK_NODE(n) \
|
|
(((n)->type == XML_TEXT_NODE) && (xsltIsBlank((n)->content)))
|
|
|
|
|
|
/************************************************************************
|
|
* *
|
|
*
|
|
* *
|
|
************************************************************************/
|
|
|
|
/**
|
|
* xsltNewTransformContext:
|
|
*
|
|
* Create a new XSLT TransformContext
|
|
*
|
|
* Returns the newly allocated xsltTransformContextPtr or NULL in case of error
|
|
*/
|
|
xsltTransformContextPtr
|
|
xsltNewTransformContext(void) {
|
|
xsltTransformContextPtr cur;
|
|
|
|
cur = (xsltTransformContextPtr) xmlMalloc(sizeof(xsltTransformContext));
|
|
if (cur == NULL) {
|
|
xsltGenericError(xsltGenericErrorContext,
|
|
"xsltNewTransformContext : malloc failed\n");
|
|
return(NULL);
|
|
}
|
|
memset(cur, 0, sizeof(xsltTransformContext));
|
|
return(cur);
|
|
}
|
|
|
|
/**
|
|
* xsltFreeTransformContext:
|
|
* @ctxt: an XSLT parser context
|
|
*
|
|
* Free up the memory allocated by @ctxt
|
|
*/
|
|
void
|
|
xsltFreeTransformContext(xsltTransformContextPtr ctxt) {
|
|
if (ctxt == NULL)
|
|
return;
|
|
if (ctxt->xpathCtxt != NULL)
|
|
xmlXPathFreeContext(ctxt->xpathCtxt);
|
|
xsltFreeVariableHashes(ctxt);
|
|
memset(ctxt, -1, sizeof(xsltTransformContext));
|
|
xmlFree(ctxt);
|
|
}
|
|
|
|
/************************************************************************
|
|
* *
|
|
*
|
|
* *
|
|
************************************************************************/
|
|
|
|
void xsltProcessOneNode(xsltTransformContextPtr ctxt, xmlNodePtr node);
|
|
void xsltForEach(xsltTransformContextPtr ctxt, xmlNodePtr node,
|
|
xmlNodePtr inst);
|
|
void xsltIf(xsltTransformContextPtr ctxt, xmlNodePtr node, xmlNodePtr inst);
|
|
|
|
/**
|
|
* xsltSort:
|
|
* @ctxt: a XSLT process context
|
|
* @node: the node in the source tree.
|
|
* @inst: the xslt sort node
|
|
*
|
|
* Process the xslt sort node on the source node
|
|
*/
|
|
void
|
|
xsltSort(xsltTransformContextPtr ctxt, xmlNodePtr node,
|
|
xmlNodePtr inst) {
|
|
xmlXPathObjectPtr *results = NULL;
|
|
xmlNodeSetPtr list = NULL;
|
|
xmlXPathParserContextPtr xpathParserCtxt = NULL;
|
|
xmlChar *prop;
|
|
xmlXPathObjectPtr res, tmp;
|
|
const xmlChar *start;
|
|
int descending = 0;
|
|
int number = 0;
|
|
int len;
|
|
int i;
|
|
|
|
if ((ctxt == NULL) || (node == NULL) || (inst == NULL))
|
|
return;
|
|
|
|
list = ctxt->nodeList;
|
|
if ((list == NULL) || (list->nodeNr <= 1))
|
|
goto error; /* nothing to do */
|
|
|
|
len = list->nodeNr;
|
|
|
|
/* TODO: process attributes as attribute value templates */
|
|
prop = xmlGetNsProp(inst, (const xmlChar *)"data-type", XSLT_NAMESPACE);
|
|
if (prop != NULL) {
|
|
if (xmlStrEqual(prop, (const xmlChar *) "text"))
|
|
number = 0;
|
|
else if (xmlStrEqual(prop, (const xmlChar *) "number"))
|
|
number = 1;
|
|
else {
|
|
xsltGenericError(xsltGenericErrorContext,
|
|
"xsltSort: no support for data-type = %s\n", prop);
|
|
goto error;
|
|
}
|
|
xmlFree(prop);
|
|
}
|
|
prop = xmlGetNsProp(inst, (const xmlChar *)"order", XSLT_NAMESPACE);
|
|
if (prop != NULL) {
|
|
if (xmlStrEqual(prop, (const xmlChar *) "ascending"))
|
|
descending = 0;
|
|
else if (xmlStrEqual(prop, (const xmlChar *) "descending"))
|
|
descending = 1;
|
|
else {
|
|
xsltGenericError(xsltGenericErrorContext,
|
|
"xsltSort: invalid value %s for order\n", prop);
|
|
goto error;
|
|
}
|
|
xmlFree(prop);
|
|
}
|
|
/* TODO: xsl:sort lang attribute */
|
|
/* TODO: xsl:sort case-order attribute */
|
|
|
|
prop = xmlGetNsProp(inst, (const xmlChar *)"select", XSLT_NAMESPACE);
|
|
if (prop == NULL) {
|
|
xsltGenericError(xsltGenericErrorContext,
|
|
"xsltSort: select is not defined\n");
|
|
return;
|
|
}
|
|
|
|
xpathParserCtxt = xmlXPathNewParserContext(prop, ctxt->xpathCtxt);
|
|
if (xpathParserCtxt == NULL)
|
|
goto error;
|
|
results = xmlMalloc(len * sizeof(xmlXPathObjectPtr));
|
|
if (results == NULL) {
|
|
xsltGenericError(xsltGenericErrorContext,
|
|
"xsltSort: memory allocation failure\n");
|
|
goto error;
|
|
}
|
|
|
|
start = xpathParserCtxt->cur;
|
|
for (i = 0;i < len;i++) {
|
|
xpathParserCtxt->cur = start;
|
|
node = ctxt->node = list->nodeTab[i];
|
|
ctxt->xpathCtxt->proximityPosition = i + 1;
|
|
valuePush(xpathParserCtxt, xmlXPathNewNodeSet(node));
|
|
xmlXPathEvalExpr(xpathParserCtxt);
|
|
xmlXPathStringFunction(xpathParserCtxt, 1);
|
|
if (number)
|
|
xmlXPathNumberFunction(xpathParserCtxt, 1);
|
|
res = valuePop(xpathParserCtxt);
|
|
do {
|
|
tmp = valuePop(xpathParserCtxt);
|
|
if (tmp != NULL) {
|
|
xmlXPathFreeObject(tmp);
|
|
}
|
|
} while (tmp != NULL);
|
|
|
|
if (res != NULL) {
|
|
if (number) {
|
|
if (res->type == XPATH_NUMBER) {
|
|
results[i] = res;
|
|
} else {
|
|
#ifdef DEBUG_PROCESS
|
|
xsltGenericDebug(xsltGenericDebugContext,
|
|
"xsltSort: select didn't evaluate to a number\n");
|
|
#endif
|
|
results[i] = NULL;
|
|
}
|
|
} else {
|
|
if (res->type == XPATH_STRING) {
|
|
results[i] = res;
|
|
} else {
|
|
#ifdef DEBUG_PROCESS
|
|
xsltGenericDebug(xsltGenericDebugContext,
|
|
"xsltSort: select didn't evaluate to a string\n");
|
|
#endif
|
|
results[i] = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
xsltSortFunction(list, &results[0], descending, number);
|
|
|
|
error:
|
|
if (xpathParserCtxt != NULL)
|
|
xmlXPathFreeParserContext(xpathParserCtxt);
|
|
if (prop != NULL)
|
|
xmlFree(prop);
|
|
if (results != NULL) {
|
|
for (i = 0;i < len;i++)
|
|
xmlXPathFreeObject(results[i]);
|
|
xmlFree(results);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* xsltAttribute:
|
|
* @ctxt: a XSLT process context
|
|
* @node: the node in the source tree.
|
|
* @inst: the xslt attribute node
|
|
*
|
|
* Process the xslt attribute node on the source node
|
|
*/
|
|
void
|
|
xsltAttribute(xsltTransformContextPtr ctxt, xmlNodePtr node,
|
|
xmlNodePtr inst) {
|
|
xmlChar *prop = NULL;
|
|
xmlChar *ncname = NULL;
|
|
xmlChar *prefix = NULL;
|
|
xmlChar *value = NULL;
|
|
xmlNsPtr ns = NULL;
|
|
xmlAttrPtr attr;
|
|
|
|
|
|
if (ctxt->insert == NULL)
|
|
return;
|
|
if (ctxt->insert->children != NULL) {
|
|
xsltGenericError(xsltGenericErrorContext,
|
|
"xslt:attribute : node has already children\n");
|
|
return;
|
|
}
|
|
prop = xmlGetNsProp(inst, (const xmlChar *)"namespace", XSLT_NAMESPACE);
|
|
if (prop != NULL) {
|
|
/* TODO: attribute value template */
|
|
TODO
|
|
xmlFree(prop);
|
|
return;
|
|
}
|
|
prop = xmlGetNsProp(inst, (const xmlChar *)"name", XSLT_NAMESPACE);
|
|
if (prop == NULL) {
|
|
xsltGenericError(xsltGenericErrorContext,
|
|
"xslt:attribute : name is missing\n");
|
|
goto error;
|
|
}
|
|
|
|
ncname = xmlSplitQName2(prop, &prefix);
|
|
if (ncname == NULL) {
|
|
ncname = prop;
|
|
prop = NULL;
|
|
prefix = NULL;
|
|
}
|
|
if (xmlStrEqual(ncname, (const xmlChar *) "xmlns")) {
|
|
xsltGenericError(xsltGenericErrorContext,
|
|
"xslt:attribute : xmlns forbidden\n");
|
|
goto error;
|
|
}
|
|
if ((prefix != NULL) && (ns == NULL)) {
|
|
ns = xmlSearchNs(ctxt->insert->doc, ctxt->insert, prefix);
|
|
if (ns == NULL) {
|
|
xsltGenericError(xsltGenericErrorContext,
|
|
"no namespace bound to prefix %s\n", prefix);
|
|
}
|
|
}
|
|
|
|
value = xmlNodeListGetString(inst->doc, inst->children, 1);
|
|
if (value == NULL) {
|
|
if (ns) {
|
|
#if LIBXML_VERSION > 20211
|
|
attr = xmlSetNsProp(ctxt->insert, ncname, ns->href,
|
|
(const xmlChar *)"");
|
|
#else
|
|
xsltGenericError(xsltGenericErrorContext,
|
|
"xsl:attribute: recompile against newer libxml version\n");
|
|
attr = xmlSetProp(ctxt->insert, ncname, (const xmlChar *)"");
|
|
#endif
|
|
} else
|
|
attr = xmlSetProp(ctxt->insert, ncname, (const xmlChar *)"");
|
|
} else {
|
|
/* TODO: attribute value template */
|
|
if (ns) {
|
|
#if LIBXML_VERSION > 20211
|
|
attr = xmlSetNsProp(ctxt->insert, ns, ncname, value);
|
|
#else
|
|
xsltGenericError(xsltGenericErrorContext,
|
|
"xsl:attribute: recompile against newer libxml version\n");
|
|
attr = xmlSetProp(ctxt->insert, ncname, value);
|
|
#endif
|
|
} else
|
|
attr = xmlSetProp(ctxt->insert, ncname, value);
|
|
|
|
}
|
|
|
|
error:
|
|
if (prop != NULL)
|
|
xmlFree(prop);
|
|
if (ncname != NULL)
|
|
xmlFree(ncname);
|
|
if (prefix != NULL)
|
|
xmlFree(prefix);
|
|
if (value != NULL)
|
|
xmlFree(value);
|
|
}
|
|
|
|
/**
|
|
* xsltValueOf:
|
|
* @ctxt: a XSLT process context
|
|
* @node: the node in the source tree.
|
|
* @inst: the xsltValueOf node
|
|
*
|
|
* Process the xsltValueOf node on the source node
|
|
*/
|
|
void
|
|
xsltValueOf(xsltTransformContextPtr ctxt, xmlNodePtr node,
|
|
xmlNodePtr inst) {
|
|
xmlChar *prop;
|
|
int disableEscaping = 0;
|
|
xmlXPathObjectPtr res, tmp;
|
|
xmlXPathParserContextPtr xpathParserCtxt;
|
|
xmlNodePtr copy = NULL;
|
|
|
|
if ((ctxt == NULL) || (node == NULL) || (inst == NULL))
|
|
return;
|
|
|
|
prop = xmlGetNsProp(inst, (const xmlChar *)"disable-output-escaping",
|
|
XSLT_NAMESPACE);
|
|
if (prop != NULL) {
|
|
if (xmlStrEqual(prop, (const xmlChar *)"yes"))
|
|
disableEscaping = 1;
|
|
else if (xmlStrEqual(prop, (const xmlChar *)"no"))
|
|
disableEscaping = 0;
|
|
else
|
|
xsltGenericError(xsltGenericErrorContext,
|
|
"invalud value %s for disable-output-escaping\n", prop);
|
|
|
|
xmlFree(prop);
|
|
if (disableEscaping) {
|
|
TODO /* disable-output-escaping */
|
|
}
|
|
}
|
|
prop = xmlGetNsProp(inst, (const xmlChar *)"select", XSLT_NAMESPACE);
|
|
if (prop == NULL) {
|
|
xsltGenericError(xsltGenericErrorContext,
|
|
"xsltValueOf: select is not defined\n");
|
|
return;
|
|
}
|
|
#ifdef DEBUG_PROCESS
|
|
xsltGenericDebug(xsltGenericDebugContext,
|
|
"xsltValueOf: select %s\n", prop);
|
|
#endif
|
|
|
|
if (ctxt->xpathCtxt == NULL) {
|
|
xmlXPathInit();
|
|
ctxt->xpathCtxt = xmlXPathNewContext(ctxt->doc);
|
|
if (ctxt->xpathCtxt == NULL)
|
|
goto error;
|
|
XSLT_REGISTER_VARIABLE_LOOKUP(ctxt);
|
|
}
|
|
xpathParserCtxt =
|
|
xmlXPathNewParserContext(prop, ctxt->xpathCtxt);
|
|
if (xpathParserCtxt == NULL)
|
|
goto error;
|
|
ctxt->xpathCtxt->node = node;
|
|
valuePush(xpathParserCtxt, xmlXPathNewNodeSet(node));
|
|
xmlXPathEvalExpr(xpathParserCtxt);
|
|
xmlXPathStringFunction(xpathParserCtxt, 1);
|
|
res = valuePop(xpathParserCtxt);
|
|
do {
|
|
tmp = valuePop(xpathParserCtxt);
|
|
if (tmp != NULL) {
|
|
xmlXPathFreeObject(tmp);
|
|
}
|
|
} while (tmp != NULL);
|
|
if (res != NULL) {
|
|
if (res->type == XPATH_STRING) {
|
|
copy = xmlNewText(res->stringval);
|
|
if (copy != NULL) {
|
|
xmlAddChild(ctxt->insert, copy);
|
|
}
|
|
}
|
|
}
|
|
if (copy == NULL) {
|
|
xsltGenericError(xsltGenericErrorContext,
|
|
"xsltDefaultProcessOneNode: text copy failed\n");
|
|
}
|
|
#ifdef DEBUG_PROCESS
|
|
else
|
|
xsltGenericDebug(xsltGenericDebugContext,
|
|
"xsltValueOf: result %s\n", res->stringval);
|
|
#endif
|
|
error:
|
|
if (xpathParserCtxt != NULL) {
|
|
xmlXPathFreeParserContext(xpathParserCtxt);
|
|
xpathParserCtxt = NULL;
|
|
}
|
|
if (prop != NULL)
|
|
xmlFree(prop);
|
|
if (res != NULL)
|
|
xmlXPathFreeObject(res);
|
|
}
|
|
|
|
/**
|
|
* xsltCopyNode:
|
|
* @ctxt: a XSLT process context
|
|
* @node: the element node in the source tree.
|
|
* @insert: the parent in the result tree.
|
|
*
|
|
* Make a copy of the element node @node
|
|
* and insert it as last child of @insert
|
|
*
|
|
* Returns a pointer to the new node, or NULL in case of error
|
|
*/
|
|
xmlNodePtr
|
|
xsltCopyNode(xsltTransformContextPtr ctxt, xmlNodePtr node,
|
|
xmlNodePtr insert) {
|
|
xmlNodePtr copy;
|
|
|
|
copy = xmlCopyNode(node, 0);
|
|
copy->doc = ctxt->output;
|
|
if (copy != NULL) {
|
|
xmlAddChild(insert, copy);
|
|
/*
|
|
* Add namespaces as they are needed
|
|
*/
|
|
if (node->nsDef != NULL)
|
|
copy->nsDef = xmlCopyNamespaceList(node->nsDef);
|
|
if (node->ns != NULL) {
|
|
/*
|
|
* optimization, if the namespace is already the
|
|
* on on the parent node, reuse it directly
|
|
*
|
|
* TODO: check possible mess with xmlCopyNamespaceList
|
|
*/
|
|
if ((insert->type == XML_ELEMENT_NODE) &&
|
|
(insert->ns != NULL) &&
|
|
(xmlStrEqual(insert->ns->href, node->ns->href))) {
|
|
copy->ns = insert->ns;
|
|
} else {
|
|
xmlNsPtr ns;
|
|
|
|
/*
|
|
* Look in the output tree if the namespace is
|
|
* already in scope.
|
|
*/
|
|
ns = xmlSearchNsByHref(ctxt->output, copy,
|
|
node->ns->href);
|
|
if (ns != NULL)
|
|
copy->ns = ns;
|
|
else {
|
|
ns = xmlNewNs(copy, node->ns->href,
|
|
node->ns->prefix);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
xsltGenericError(xsltGenericErrorContext,
|
|
"xsltCopyNode: copy %s failed\n", node->name);
|
|
}
|
|
return(copy);
|
|
}
|
|
|
|
/**
|
|
* xsltDefaultProcessOneNode:
|
|
* @ctxt: a XSLT process context
|
|
* @node: the node in the source tree.
|
|
*
|
|
* Process the source node with the default built-in template rule:
|
|
* <xsl:template match="*|/">
|
|
* <xsl:apply-templates/>
|
|
* </xsl:template>
|
|
*
|
|
* and
|
|
*
|
|
* <xsl:template match="text()|@*">
|
|
* <xsl:value-of select="."/>
|
|
* </xsl:template>
|
|
*
|
|
* Note also that namespaces declarations are copied directly:
|
|
*
|
|
* the built-in template rule is the only template rule that is applied
|
|
* for namespace nodes.
|
|
*/
|
|
void
|
|
xsltDefaultProcessOneNode(xsltTransformContextPtr ctxt, xmlNodePtr node) {
|
|
xmlNodePtr copy;
|
|
xmlNodePtr delete = NULL;
|
|
|
|
switch (node->type) {
|
|
case XML_DOCUMENT_NODE:
|
|
case XML_HTML_DOCUMENT_NODE:
|
|
case XML_ELEMENT_NODE:
|
|
break;
|
|
case XML_TEXT_NODE:
|
|
copy = xmlCopyNode(node, 0);
|
|
if (copy != NULL) {
|
|
xmlAddChild(ctxt->insert, copy);
|
|
} else {
|
|
xsltGenericError(xsltGenericErrorContext,
|
|
"xsltDefaultProcessOneNode: text copy failed\n");
|
|
}
|
|
return;
|
|
default:
|
|
return;
|
|
}
|
|
node = node->children;
|
|
while (node != NULL) {
|
|
switch (node->type) {
|
|
case XML_DOCUMENT_NODE:
|
|
case XML_HTML_DOCUMENT_NODE:
|
|
case XML_ELEMENT_NODE:
|
|
xsltProcessOneNode(ctxt, node);
|
|
break;
|
|
case XML_TEXT_NODE:
|
|
/* TODO: check the whitespace stripping rules ! */
|
|
if ((IS_BLANK_NODE(node)) &&
|
|
(node->parent != NULL) &&
|
|
(ctxt->style->stripSpaces != NULL)) {
|
|
const xmlChar *val;
|
|
|
|
val = (const xmlChar *)
|
|
xmlHashLookup(ctxt->style->stripSpaces,
|
|
node->parent->name);
|
|
if ((val != NULL) &&
|
|
(xmlStrEqual(val, (xmlChar *) "strip"))) {
|
|
delete = node;
|
|
break;
|
|
}
|
|
}
|
|
/* no break on purpose */
|
|
case XML_CDATA_SECTION_NODE:
|
|
copy = xmlCopyNode(node, 0);
|
|
if (copy != NULL) {
|
|
xmlAddChild(ctxt->insert, copy);
|
|
} else {
|
|
xsltGenericError(xsltGenericErrorContext,
|
|
"xsltDefaultProcessOneNode: text copy failed\n");
|
|
}
|
|
break;
|
|
default:
|
|
#ifdef DEBUG_PROCESS
|
|
xsltGenericDebug(xsltGenericDebugContext,
|
|
"xsltDefaultProcessOneNode: skipping node type %d\n",
|
|
node->type);
|
|
#endif
|
|
delete = node;
|
|
}
|
|
node = node->next;
|
|
if (delete != NULL) {
|
|
#ifdef DEBUG_PROCESS
|
|
xsltGenericDebug(xsltGenericDebugContext,
|
|
"xsltDefaultProcessOneNode: removing ignorable blank node\n");
|
|
#endif
|
|
xmlUnlinkNode(delete);
|
|
xmlFreeNode(delete);
|
|
delete = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* xsltApplyTemplates:
|
|
* @ctxt: a XSLT process context
|
|
* @node: the node in the source tree.
|
|
* @inst: the apply-templates node
|
|
*
|
|
* Process the apply-templates node on the source node
|
|
*/
|
|
void
|
|
xsltApplyTemplates(xsltTransformContextPtr ctxt, xmlNodePtr node,
|
|
xmlNodePtr inst) {
|
|
xmlChar *prop = NULL;
|
|
xmlNodePtr cur, delete = NULL;
|
|
xmlXPathObjectPtr res = NULL, tmp;
|
|
xmlNodePtr replacement;
|
|
xmlNodeSetPtr list = NULL, oldlist;
|
|
xmlXPathParserContextPtr xpathParserCtxt = NULL;
|
|
int i, oldProximityPosition, oldContextSize;
|
|
|
|
if ((ctxt == NULL) || (node == NULL) || (inst == NULL))
|
|
return;
|
|
|
|
#ifdef DEBUG_PROCESS
|
|
xsltGenericDebug(xsltGenericDebugContext,
|
|
"xsltApplyTemplates: node: %s\n", node->name);
|
|
#endif
|
|
prop = xmlGetNsProp(inst, (const xmlChar *)"select", XSLT_NAMESPACE);
|
|
if (prop != NULL) {
|
|
#ifdef DEBUG_PROCESS
|
|
xsltGenericDebug(xsltGenericDebugContext,
|
|
"xsltApplyTemplates: select %s\n", prop);
|
|
#endif
|
|
|
|
if (ctxt->xpathCtxt == NULL) {
|
|
xmlXPathInit();
|
|
ctxt->xpathCtxt = xmlXPathNewContext(ctxt->doc);
|
|
if (ctxt->xpathCtxt == NULL)
|
|
goto error;
|
|
XSLT_REGISTER_VARIABLE_LOOKUP(ctxt);
|
|
}
|
|
xpathParserCtxt =
|
|
xmlXPathNewParserContext(prop, ctxt->xpathCtxt);
|
|
if (xpathParserCtxt == NULL)
|
|
goto error;
|
|
ctxt->xpathCtxt->node = node;
|
|
valuePush(xpathParserCtxt, xmlXPathNewNodeSet(node));
|
|
xmlXPathEvalExpr(xpathParserCtxt);
|
|
res = valuePop(xpathParserCtxt);
|
|
do {
|
|
tmp = valuePop(xpathParserCtxt);
|
|
if (tmp != NULL) {
|
|
xmlXPathFreeObject(tmp);
|
|
}
|
|
} while (tmp != NULL);
|
|
if (res != NULL) {
|
|
if (res->type == XPATH_NODESET) {
|
|
list = res->nodesetval;
|
|
res->nodesetval = NULL;
|
|
} else {
|
|
#ifdef DEBUG_PROCESS
|
|
xsltGenericDebug(xsltGenericDebugContext,
|
|
"xsltApplyTemplates: select didn't evaluate to a node list\n");
|
|
#endif
|
|
goto error;
|
|
}
|
|
}
|
|
} else {
|
|
/*
|
|
* Build an XPath nodelist with the children
|
|
*/
|
|
list = xmlXPathNodeSetCreate(NULL);
|
|
cur = node->children;
|
|
while (cur != NULL) {
|
|
switch (cur->type) {
|
|
case XML_TEXT_NODE:
|
|
/* TODO: check the whitespace stripping rules ! */
|
|
if ((IS_BLANK_NODE(cur)) &&
|
|
(cur->parent != NULL) &&
|
|
(ctxt->style->stripSpaces != NULL)) {
|
|
const xmlChar *val;
|
|
|
|
val = (const xmlChar *)
|
|
xmlHashLookup(ctxt->style->stripSpaces,
|
|
cur->parent->name);
|
|
if ((val != NULL) &&
|
|
(xmlStrEqual(val, (xmlChar *) "strip"))) {
|
|
delete = cur;
|
|
break;
|
|
}
|
|
}
|
|
/* no break on purpose */
|
|
case XML_DOCUMENT_NODE:
|
|
case XML_HTML_DOCUMENT_NODE:
|
|
case XML_ELEMENT_NODE:
|
|
case XML_CDATA_SECTION_NODE:
|
|
xmlXPathNodeSetAdd(list, cur);
|
|
break;
|
|
default:
|
|
#ifdef DEBUG_PROCESS
|
|
xsltGenericDebug(xsltGenericDebugContext,
|
|
"xsltApplyTemplates: skipping cur type %d\n",
|
|
cur->type);
|
|
#endif
|
|
delete = cur;
|
|
}
|
|
cur = cur->next;
|
|
if (delete != NULL) {
|
|
#ifdef DEBUG_PROCESS
|
|
xsltGenericDebug(xsltGenericDebugContext,
|
|
"xsltApplyTemplates: removing ignorable blank cur\n");
|
|
#endif
|
|
xmlUnlinkNode(delete);
|
|
xmlFreeNode(delete);
|
|
delete = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_PROCESS
|
|
xsltGenericDebug(xsltGenericDebugContext,
|
|
"xsltApplyTemplates: list of %d nodes\n", list->nodeNr);
|
|
#endif
|
|
|
|
oldlist = ctxt->nodeList;
|
|
ctxt->nodeList = list;
|
|
oldContextSize = ctxt->xpathCtxt->contextSize;
|
|
oldProximityPosition = ctxt->xpathCtxt->proximityPosition;
|
|
ctxt->xpathCtxt->contextSize = list->nodeNr;
|
|
|
|
/*
|
|
* handle and skip the xsl:sort
|
|
*/
|
|
replacement = inst->children;
|
|
while (IS_XSLT_ELEM(replacement) && (IS_XSLT_NAME(replacement, "sort"))) {
|
|
xsltSort(ctxt, node, replacement);
|
|
replacement = replacement->next;
|
|
}
|
|
|
|
for (i = 0;i < list->nodeNr;i++) {
|
|
ctxt->node = list->nodeTab[i];
|
|
ctxt->xpathCtxt->proximityPosition = i + 1;
|
|
xsltProcessOneNode(ctxt, list->nodeTab[i]);
|
|
}
|
|
ctxt->nodeList = oldlist;
|
|
ctxt->xpathCtxt->contextSize = oldContextSize;
|
|
ctxt->xpathCtxt->proximityPosition = oldProximityPosition;
|
|
|
|
error:
|
|
if (xpathParserCtxt != NULL)
|
|
xmlXPathFreeParserContext(xpathParserCtxt);
|
|
if (prop != NULL)
|
|
xmlFree(prop);
|
|
if (res != NULL)
|
|
xmlXPathFreeObject(res);
|
|
if (list != NULL)
|
|
xmlXPathFreeNodeSet(list);
|
|
}
|
|
|
|
/**
|
|
* xsltApplyOneTemplate:
|
|
* @ctxt: a XSLT process context
|
|
* @node: the node in the source tree.
|
|
* @list: the template replacement nodelist
|
|
*
|
|
* Process the apply-templates node on the source node
|
|
*/
|
|
void
|
|
xsltApplyOneTemplate(xsltTransformContextPtr ctxt, xmlNodePtr node,
|
|
xmlNodePtr list) {
|
|
xmlNodePtr cur, insert, copy;
|
|
xmlNodePtr oldInsert;
|
|
int has_variables = 0;
|
|
|
|
oldInsert = insert = ctxt->insert;
|
|
/*
|
|
* Insert all non-XSLT nodes found in the template
|
|
*/
|
|
cur = list;
|
|
while (cur != NULL) {
|
|
/*
|
|
* test, we must have a valid insertion point
|
|
*/
|
|
if (insert == NULL) {
|
|
#ifdef DEBUG_PROCESS
|
|
xsltGenericDebug(xsltGenericDebugContext,
|
|
"xsltApplyOneTemplate: insert == NULL !\n");
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
if (IS_XSLT_ELEM(cur)) {
|
|
if (IS_XSLT_NAME(cur, "apply-templates")) {
|
|
ctxt->insert = insert;
|
|
xsltApplyTemplates(ctxt, node, cur);
|
|
ctxt->insert = oldInsert;
|
|
} else if (IS_XSLT_NAME(cur, "value-of")) {
|
|
ctxt->insert = insert;
|
|
xsltValueOf(ctxt, node, cur);
|
|
ctxt->insert = oldInsert;
|
|
} else if (IS_XSLT_NAME(cur, "if")) {
|
|
ctxt->insert = insert;
|
|
xsltIf(ctxt, node, cur);
|
|
ctxt->insert = oldInsert;
|
|
} else if (IS_XSLT_NAME(cur, "for-each")) {
|
|
ctxt->insert = insert;
|
|
xsltForEach(ctxt, node, cur);
|
|
ctxt->insert = oldInsert;
|
|
} else if (IS_XSLT_NAME(cur, "attribute")) {
|
|
ctxt->insert = insert;
|
|
xsltAttribute(ctxt, node, cur);
|
|
ctxt->insert = oldInsert;
|
|
} else if (IS_XSLT_NAME(cur, "element")) {
|
|
ctxt->insert = insert;
|
|
xsltAttribute(ctxt, node, cur);
|
|
ctxt->insert = oldInsert;
|
|
} else if (IS_XSLT_NAME(cur, "variable")) {
|
|
if (has_variables == 0) {
|
|
xsltPushStack(ctxt);
|
|
}
|
|
xsltParseStylesheetVariable(ctxt, cur);
|
|
} else {
|
|
#ifdef DEBUG_PROCESS
|
|
xsltGenericError(xsltGenericDebugContext,
|
|
"xsltApplyOneTemplate: found xslt:%s\n", cur->name);
|
|
#endif
|
|
TODO
|
|
}
|
|
goto skip_children;
|
|
} else if (cur->type == XML_TEXT_NODE) {
|
|
/*
|
|
* This text comes from the stylesheet
|
|
* For stylesheets, the set of whitespace-preserving
|
|
* element names consists of just xsl:text.
|
|
*/
|
|
#ifdef DEBUG_PROCESS
|
|
xsltGenericDebug(xsltGenericDebugContext,
|
|
"xsltApplyOneTemplate: copy text %s\n", cur->content);
|
|
#endif
|
|
copy = xmlCopyNode(cur, 0);
|
|
if (copy != NULL) {
|
|
xmlAddChild(insert, copy);
|
|
} else {
|
|
xsltGenericError(xsltGenericErrorContext,
|
|
"xsltApplyOneTemplate: text copy failed\n");
|
|
}
|
|
} else if (cur->type == XML_ELEMENT_NODE) {
|
|
#ifdef DEBUG_PROCESS
|
|
xsltGenericDebug(xsltGenericDebugContext,
|
|
"xsltApplyOneTemplate: copy node %s\n", cur->name);
|
|
#endif
|
|
copy = xsltCopyNode(ctxt, cur, insert);
|
|
/*
|
|
* all the attributes are directly inherited
|
|
* TODO: Do the substitution of {} XPath expressions !!!
|
|
*/
|
|
if (cur->properties != NULL)
|
|
copy->properties = xsltAttrListTemplateProcess(ctxt,
|
|
copy, cur->properties);
|
|
}
|
|
|
|
/*
|
|
* Skip to next node, in document order.
|
|
*/
|
|
if (cur->children != NULL) {
|
|
if (cur->children->type != XML_ENTITY_DECL) {
|
|
cur = cur->children;
|
|
if (copy != NULL)
|
|
insert = copy;
|
|
continue;
|
|
}
|
|
}
|
|
skip_children:
|
|
if (cur->next != NULL) {
|
|
cur = cur->next;
|
|
continue;
|
|
}
|
|
|
|
do {
|
|
cur = cur->parent;
|
|
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);
|
|
}
|
|
if (has_variables != 0) {
|
|
xsltPopStack(ctxt);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* xsltIf:
|
|
* @ctxt: a XSLT process context
|
|
* @node: the node in the source tree.
|
|
* @inst: the xslt if node
|
|
*
|
|
* Process the xslt if node on the source node
|
|
*/
|
|
void
|
|
xsltIf(xsltTransformContextPtr ctxt, xmlNodePtr node,
|
|
xmlNodePtr inst) {
|
|
xmlChar *prop;
|
|
xmlXPathObjectPtr res, tmp;
|
|
xmlXPathParserContextPtr xpathParserCtxt;
|
|
int doit;
|
|
|
|
if ((ctxt == NULL) || (node == NULL) || (inst == NULL))
|
|
return;
|
|
|
|
prop = xmlGetNsProp(inst, (const xmlChar *)"test", XSLT_NAMESPACE);
|
|
if (prop == NULL) {
|
|
xsltGenericError(xsltGenericErrorContext,
|
|
"xsltIf: test is not defined\n");
|
|
return;
|
|
}
|
|
#ifdef DEBUG_PROCESS
|
|
xsltGenericDebug(xsltGenericDebugContext,
|
|
"xsltIf: test %s\n", prop);
|
|
#endif
|
|
|
|
if (ctxt->xpathCtxt == NULL) {
|
|
xmlXPathInit();
|
|
ctxt->xpathCtxt = xmlXPathNewContext(ctxt->doc);
|
|
if (ctxt->xpathCtxt == NULL)
|
|
goto error;
|
|
XSLT_REGISTER_VARIABLE_LOOKUP(ctxt);
|
|
}
|
|
xpathParserCtxt = xmlXPathNewParserContext(prop, ctxt->xpathCtxt);
|
|
if (xpathParserCtxt == NULL)
|
|
goto error;
|
|
ctxt->xpathCtxt->node = node;
|
|
valuePush(xpathParserCtxt, xmlXPathNewNodeSet(node));
|
|
xmlXPathEvalExpr(xpathParserCtxt);
|
|
xmlXPathBooleanFunction(xpathParserCtxt, 1);
|
|
res = valuePop(xpathParserCtxt);
|
|
do {
|
|
tmp = valuePop(xpathParserCtxt);
|
|
if (tmp != NULL) {
|
|
xmlXPathFreeObject(tmp);
|
|
}
|
|
} while (tmp != NULL);
|
|
|
|
if (res != NULL) {
|
|
if (res->type == XPATH_BOOLEAN)
|
|
doit = res->boolval;
|
|
else {
|
|
#ifdef DEBUG_PROCESS
|
|
xsltGenericDebug(xsltGenericDebugContext,
|
|
"xsltIf: test didn't evaluate to a boolean\n");
|
|
#endif
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_PROCESS
|
|
xsltGenericDebug(xsltGenericDebugContext,
|
|
"xsltIf: test evaluate to %d\n", doit);
|
|
#endif
|
|
if (doit) {
|
|
xsltApplyOneTemplate(ctxt, ctxt->node, inst->children);
|
|
}
|
|
|
|
error:
|
|
if (xpathParserCtxt != NULL)
|
|
xmlXPathFreeParserContext(xpathParserCtxt);
|
|
if (prop != NULL)
|
|
xmlFree(prop);
|
|
if (res != NULL)
|
|
xmlXPathFreeObject(res);
|
|
}
|
|
|
|
/**
|
|
* xsltForEach:
|
|
* @ctxt: a XSLT process context
|
|
* @node: the node in the source tree.
|
|
* @inst: the xslt for-each node
|
|
*
|
|
* Process the xslt for-each node on the source node
|
|
*/
|
|
void
|
|
xsltForEach(xsltTransformContextPtr ctxt, xmlNodePtr node,
|
|
xmlNodePtr inst) {
|
|
xmlChar *prop;
|
|
xmlXPathObjectPtr res, tmp;
|
|
xmlNodePtr replacement;
|
|
xmlNodeSetPtr list = NULL, oldlist;
|
|
xmlXPathParserContextPtr xpathParserCtxt;
|
|
int i, oldProximityPosition, oldContextSize;
|
|
|
|
if ((ctxt == NULL) || (node == NULL) || (inst == NULL))
|
|
return;
|
|
|
|
prop = xmlGetNsProp(inst, (const xmlChar *)"select", XSLT_NAMESPACE);
|
|
if (prop == NULL) {
|
|
xsltGenericError(xsltGenericErrorContext,
|
|
"xsltForEach: select is not defined\n");
|
|
return;
|
|
}
|
|
#ifdef DEBUG_PROCESS
|
|
xsltGenericDebug(xsltGenericDebugContext,
|
|
"xsltForEach: select %s\n", prop);
|
|
#endif
|
|
|
|
if (ctxt->xpathCtxt == NULL) {
|
|
xmlXPathInit();
|
|
ctxt->xpathCtxt = xmlXPathNewContext(ctxt->doc);
|
|
if (ctxt->xpathCtxt == NULL)
|
|
goto error;
|
|
XSLT_REGISTER_VARIABLE_LOOKUP(ctxt);
|
|
}
|
|
xpathParserCtxt = xmlXPathNewParserContext(prop, ctxt->xpathCtxt);
|
|
if (xpathParserCtxt == NULL)
|
|
goto error;
|
|
ctxt->xpathCtxt->node = node;
|
|
valuePush(xpathParserCtxt, xmlXPathNewNodeSet(node));
|
|
xmlXPathEvalExpr(xpathParserCtxt);
|
|
res = valuePop(xpathParserCtxt);
|
|
do {
|
|
tmp = valuePop(xpathParserCtxt);
|
|
if (tmp != NULL) {
|
|
xmlXPathFreeObject(tmp);
|
|
}
|
|
} while (tmp != NULL);
|
|
|
|
if (res != NULL) {
|
|
if (res->type == XPATH_NODESET)
|
|
list = res->nodesetval;
|
|
else {
|
|
#ifdef DEBUG_PROCESS
|
|
xsltGenericDebug(xsltGenericDebugContext,
|
|
"xsltForEach: select didn't evaluate to a node list\n");
|
|
#endif
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_PROCESS
|
|
xsltGenericDebug(xsltGenericDebugContext,
|
|
"xsltForEach: select evaluate to %d nodes\n", list->nodeNr);
|
|
#endif
|
|
|
|
oldlist = ctxt->nodeList;
|
|
ctxt->nodeList = list;
|
|
oldContextSize = ctxt->xpathCtxt->contextSize;
|
|
oldProximityPosition = ctxt->xpathCtxt->proximityPosition;
|
|
ctxt->xpathCtxt->contextSize = list->nodeNr;
|
|
|
|
/*
|
|
* handle and skip the xsl:sort
|
|
*/
|
|
replacement = inst->children;
|
|
while (IS_XSLT_ELEM(replacement) && (IS_XSLT_NAME(replacement, "sort"))) {
|
|
xsltSort(ctxt, node, replacement);
|
|
replacement = replacement->next;
|
|
}
|
|
|
|
for (i = 0;i < list->nodeNr;i++) {
|
|
ctxt->node = list->nodeTab[i];
|
|
ctxt->xpathCtxt->proximityPosition = i + 1;
|
|
xsltApplyOneTemplate(ctxt, list->nodeTab[i], replacement);
|
|
}
|
|
ctxt->nodeList = oldlist;
|
|
ctxt->xpathCtxt->contextSize = oldContextSize;
|
|
ctxt->xpathCtxt->proximityPosition = oldProximityPosition;
|
|
|
|
error:
|
|
if (xpathParserCtxt != NULL)
|
|
xmlXPathFreeParserContext(xpathParserCtxt);
|
|
if (prop != NULL)
|
|
xmlFree(prop);
|
|
if (res != NULL)
|
|
xmlXPathFreeObject(res);
|
|
}
|
|
|
|
/**
|
|
* xsltProcessOneNode:
|
|
* @ctxt: a XSLT process context
|
|
* @node: the node in the source tree.
|
|
*
|
|
* Process the source node.
|
|
*/
|
|
void
|
|
xsltProcessOneNode(xsltTransformContextPtr ctxt, xmlNodePtr node) {
|
|
xsltTemplatePtr template;
|
|
|
|
template = xsltGetTemplate(ctxt->style, node);
|
|
/*
|
|
* If no template is found, apply the default rule.
|
|
*/
|
|
if (template == NULL) {
|
|
#ifdef DEBUG_PROCESS
|
|
if (node->type == XML_DOCUMENT_NODE)
|
|
xsltGenericDebug(xsltGenericDebugContext,
|
|
"xsltProcessOneNode: no template found for /\n");
|
|
else
|
|
xsltGenericDebug(xsltGenericDebugContext,
|
|
"xsltProcessOneNode: no template found for %s\n", node->name);
|
|
#endif
|
|
xsltDefaultProcessOneNode(ctxt, node);
|
|
return;
|
|
}
|
|
|
|
xsltApplyOneTemplate(ctxt, node, template->content);
|
|
}
|
|
|
|
/**
|
|
* xsltApplyStylesheet:
|
|
* @style: a parsed XSLT stylesheet
|
|
* @doc: a parsed XML document
|
|
*
|
|
* 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) {
|
|
xmlDocPtr res = NULL;
|
|
xsltTransformContextPtr ctxt = NULL;
|
|
xmlNodePtr root;
|
|
|
|
if ((style == NULL) || (doc == NULL))
|
|
return(NULL);
|
|
ctxt = xsltNewTransformContext();
|
|
if (ctxt == NULL)
|
|
return(NULL);
|
|
ctxt->doc = doc;
|
|
ctxt->style = style;
|
|
if ((style->method != NULL) &&
|
|
(!xmlStrEqual(style->method, (const xmlChar *) "xml"))) {
|
|
if (xmlStrEqual(style->method, (const xmlChar *) "html")) {
|
|
ctxt->type = XSLT_OUTPUT_HTML;
|
|
res = htmlNewDoc(style->doctypePublic, style->doctypeSystem);
|
|
if (res == NULL)
|
|
goto error;
|
|
} else if (xmlStrEqual(style->method, (const xmlChar *) "text")) {
|
|
ctxt->type = XSLT_OUTPUT_TEXT;
|
|
res = xmlNewDoc(style->version);
|
|
if (res == NULL)
|
|
goto error;
|
|
} else {
|
|
xsltGenericError(xsltGenericErrorContext,
|
|
"xsltApplyStylesheet: insupported method %s\n",
|
|
style->method);
|
|
goto error;
|
|
}
|
|
} else {
|
|
ctxt->type = XSLT_OUTPUT_XML;
|
|
res = xmlNewDoc(style->version);
|
|
if (res == NULL)
|
|
goto error;
|
|
}
|
|
res->charset = XML_CHAR_ENCODING_UTF8;
|
|
if (style->encoding != NULL)
|
|
res->encoding = xmlStrdup(style->encoding);
|
|
|
|
/*
|
|
* Start.
|
|
*/
|
|
ctxt->output = res;
|
|
ctxt->insert = (xmlNodePtr) res;
|
|
ctxt->node = (xmlNodePtr) doc;
|
|
xsltProcessOneNode(ctxt, ctxt->node);
|
|
|
|
|
|
if ((ctxt->type = XSLT_OUTPUT_XML) &&
|
|
((style->doctypePublic != NULL) ||
|
|
(style->doctypeSystem != NULL))) {
|
|
root = xmlDocGetRootElement(res);
|
|
if (root != NULL)
|
|
res->intSubset = xmlCreateIntSubset(res, root->name,
|
|
style->doctypePublic, style->doctypeSystem);
|
|
}
|
|
xmlXPathFreeNodeSet(ctxt->nodeList);
|
|
xsltFreeTransformContext(ctxt);
|
|
return(res);
|
|
|
|
error:
|
|
if (res != NULL)
|
|
xmlFreeDoc(res);
|
|
if (ctxt != NULL)
|
|
xsltFreeTransformContext(ctxt);
|
|
return(NULL);
|
|
}
|
|
|