1
0
mirror of https://gitlab.gnome.org/GNOME/libxslt synced 2025-11-05 12:10:38 +03:00
Files
libxslt/libxslt/keys.c
Daniel Veillard 42681a3efe fixed bug #78735 added the tests in a separate directory Daniel
* libxslt/keys.c: fixed bug #78735
* configure.in tests/Makefile.am tests/keys/*:
  added the tests in a separate directory
Daniel
2002-04-15 12:02:03 +00:00

623 lines
15 KiB
C

/*
* keys.c: Implemetation of the keys support
*
* 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/tree.h>
#include <libxml/valid.h>
#include <libxml/hash.h>
#include <libxml/xmlerror.h>
#include <libxml/parserInternals.h>
#include <libxml/xpathInternals.h>
#include "xslt.h"
#include "xsltInternals.h"
#include "xsltutils.h"
#include "imports.h"
#include "templates.h"
#include "keys.h"
#ifdef WITH_XSLT_DEBUG
#define WITH_XSLT_DEBUG_KEYS
#endif
typedef struct _xsltKeyDef xsltKeyDef;
typedef xsltKeyDef *xsltKeyDefPtr;
struct _xsltKeyDef {
struct _xsltKeyDef *next;
xmlNodePtr inst;
xmlChar *name;
xmlChar *nameURI;
xmlChar *match;
xmlChar *use;
xmlXPathCompExprPtr comp;
xmlXPathCompExprPtr usecomp;
};
typedef struct _xsltKeyTable xsltKeyTable;
typedef xsltKeyTable *xsltKeyTablePtr;
struct _xsltKeyTable {
struct _xsltKeyTable *next;
xmlChar *name;
xmlChar *nameURI;
xmlHashTablePtr keys;
};
/************************************************************************
* *
* Type functions *
* *
************************************************************************/
/**
* xsltNewKeyDef:
* @name: the key name or NULL
* @nameURI: the name URI or NULL
*
* Create a new XSLT KeyDef
*
* Returns the newly allocated xsltKeyDefPtr or NULL in case of error
*/
static xsltKeyDefPtr
xsltNewKeyDef(const xmlChar *name, const xmlChar *nameURI) {
xsltKeyDefPtr cur;
cur = (xsltKeyDefPtr) xmlMalloc(sizeof(xsltKeyDef));
if (cur == NULL) {
xsltPrintErrorContext(NULL, NULL, NULL);
xsltGenericError(xsltGenericErrorContext,
"xsltNewKeyDef : malloc failed\n");
return(NULL);
}
memset(cur, 0, sizeof(xsltKeyDef));
if (name != NULL)
cur->name = xmlStrdup(name);
if (nameURI != NULL)
cur->nameURI = xmlStrdup(nameURI);
return(cur);
}
/**
* xsltFreeKeyDef:
* @keyd: an XSLT key definition
*
* Free up the memory allocated by @keyd
*/
static void
xsltFreeKeyDef(xsltKeyDefPtr keyd) {
if (keyd == NULL)
return;
if (keyd->comp != NULL)
xmlXPathFreeCompExpr(keyd->comp);
if (keyd->usecomp != NULL)
xmlXPathFreeCompExpr(keyd->usecomp);
if (keyd->name != NULL)
xmlFree(keyd->name);
if (keyd->nameURI != NULL)
xmlFree(keyd->nameURI);
if (keyd->match != NULL)
xmlFree(keyd->match);
if (keyd->use != NULL)
xmlFree(keyd->use);
memset(keyd, -1, sizeof(xsltKeyDef));
xmlFree(keyd);
}
/**
* xsltFreeKeyDefList:
* @keyd: an XSLT key definition list
*
* Free up the memory allocated by all the elements of @keyd
*/
static void
xsltFreeKeyDefList(xsltKeyDefPtr keyd) {
xsltKeyDefPtr cur;
while (keyd != NULL) {
cur = keyd;
keyd = keyd->next;
xsltFreeKeyDef(cur);
}
}
/**
* xsltNewKeyTable:
* @name: the key name or NULL
* @nameURI: the name URI or NULL
*
* Create a new XSLT KeyTable
*
* Returns the newly allocated xsltKeyTablePtr or NULL in case of error
*/
static xsltKeyTablePtr
xsltNewKeyTable(const xmlChar *name, const xmlChar *nameURI) {
xsltKeyTablePtr cur;
cur = (xsltKeyTablePtr) xmlMalloc(sizeof(xsltKeyTable));
if (cur == NULL) {
xsltPrintErrorContext(NULL, NULL, NULL);
xsltGenericError(xsltGenericErrorContext,
"xsltNewKeyTable : malloc failed\n");
return(NULL);
}
memset(cur, 0, sizeof(xsltKeyTable));
if (name != NULL)
cur->name = xmlStrdup(name);
if (nameURI != NULL)
cur->nameURI = xmlStrdup(nameURI);
cur->keys = xmlHashCreate(0);
return(cur);
}
/**
* xsltFreeKeyTable:
* @keyt: an XSLT key table
*
* Free up the memory allocated by @keyt
*/
static void
xsltFreeKeyTable(xsltKeyTablePtr keyt) {
if (keyt == NULL)
return;
if (keyt->name != NULL)
xmlFree(keyt->name);
if (keyt->nameURI != NULL)
xmlFree(keyt->nameURI);
if (keyt->keys != NULL)
xmlHashFree(keyt->keys,
(xmlHashDeallocator) xmlXPathFreeNodeSet);
memset(keyt, -1, sizeof(xsltKeyTable));
xmlFree(keyt);
}
/**
* xsltFreeKeyTableList:
* @keyt: an XSLT key table list
*
* Free up the memory allocated by all the elements of @keyt
*/
static void
xsltFreeKeyTableList(xsltKeyTablePtr keyt) {
xsltKeyTablePtr cur;
while (keyt != NULL) {
cur = keyt;
keyt = keyt->next;
xsltFreeKeyTable(cur);
}
}
/************************************************************************
* *
* The interpreter for the precompiled patterns *
* *
************************************************************************/
/**
* xsltFreeKeys:
* @style: an XSLT stylesheet
*
* Free up the memory used by XSLT keys in a stylesheet
*/
void
xsltFreeKeys(xsltStylesheetPtr style) {
if (style->keys)
xsltFreeKeyDefList((xsltKeyDefPtr) style->keys);
}
/**
* xsltAddKey:
* @style: an XSLT stylesheet
* @name: the key name or NULL
* @nameURI: the name URI or NULL
* @match: the match value
* @use: the use value
* @inst: the key instruction
*
* add a key definition to a stylesheet
*
* Returns 0 in case of success, and -1 in case of failure.
*/
int
xsltAddKey(xsltStylesheetPtr style, const xmlChar *name,
const xmlChar *nameURI, const xmlChar *match,
const xmlChar *use, xmlNodePtr inst) {
xsltKeyDefPtr key;
xmlChar *pattern = NULL;
int current, end, start;
if ((style == NULL) || (name == NULL) || (match == NULL) || (use == NULL))
return(-1);
#ifdef WITH_XSLT_DEBUG_KEYS
xsltGenericDebug(xsltGenericDebugContext,
"Add key %s, match %s, use %s\n", name, match, use);
#endif
key = xsltNewKeyDef(name, nameURI);
key->match = xmlStrdup(match);
key->use = xmlStrdup(use);
key->inst = inst;
/*
* Split the | and register it as as many keys
*/
current = end = 0;
while (match[current] != 0) {
start = current;
while (IS_BLANK(match[current]))
current++;
end = current;
while ((match[end] != 0) && (match[end] != '|')) {
end++;
}
if (current == end) {
xsltPrintErrorContext(NULL, style, inst);
xsltGenericError(xsltGenericErrorContext,
"key pattern is empty\n");
style->errors++;
goto error;
}
if (match[start] != '/') {
pattern = xmlStrcat(pattern, (xmlChar *)"//");
if (pattern == NULL) {
style->errors++;
goto error;
}
}
pattern = xmlStrncat(pattern, &match[start], end - start);
if (pattern == NULL) {
style->errors++;
goto error;
}
if (match[end] == '|') {
pattern = xmlStrcat(pattern, (xmlChar *)"|");
end++;
}
current = end;
}
#ifdef WITH_XSLT_DEBUG_KEYS
xsltGenericDebug(xsltGenericDebugContext,
" resulting pattern %s\n", pattern);
#endif
key->comp = xmlXPathCompile(pattern);
if (key->comp == NULL) {
xsltPrintErrorContext(NULL, style, inst);
xsltGenericError(xsltGenericErrorContext,
"xsl:key : XPath pattern compilation failed '%s'\n",
pattern);
style->errors++;
}
key->usecomp = xmlXPathCompile(use);
if (key->usecomp == NULL) {
xsltPrintErrorContext(NULL, style, inst);
xsltGenericError(xsltGenericErrorContext,
"xsl:key : XPath pattern compilation failed '%s'\n",
use);
style->errors++;
}
key->next = style->keys;
style->keys = key;
error:
if (pattern != NULL)
xmlFree(pattern);
return(0);
}
/**
* xsltGetKey:
* @ctxt: an XSLT transformation context
* @name: the key name or NULL
* @nameURI: the name URI or NULL
* @value: the key value to look for
*
* Lookup a key
*
* Returns the nodeset resulting from the query or NULL
*/
xmlNodeSetPtr
xsltGetKey(xsltTransformContextPtr ctxt, const xmlChar *name,
const xmlChar *nameURI, const xmlChar *value) {
xmlNodeSetPtr ret;
xsltKeyTablePtr table;
if ((ctxt == NULL) || (name == NULL) || (value == NULL))
return(NULL);
#ifdef WITH_XSLT_DEBUG_KEYS
xsltGenericDebug(xsltGenericDebugContext,
"Get key %s, value %s\n", name, value);
#endif
table = (xsltKeyTablePtr) ctxt->document->keys;
while (table != NULL) {
if (xmlStrEqual(table->name, name) &&
(((nameURI == NULL) && (table->nameURI == NULL)) ||
((nameURI != NULL) && (table->nameURI != NULL) &&
(xmlStrEqual(table->nameURI, nameURI))))) {
ret = (xmlNodeSetPtr)xmlHashLookup(table->keys, value);
return(ret);
}
table = table->next;
}
return(NULL);
}
/**
* xsltEvalXPathKeys:
* @ctxt: the XSLT transformation context
* @comp: the compiled XPath expression
*
* Process the expression using XPath to get the list of keys
*
* Returns the array of computed string value or NULL, must be deallocated
* by the caller.
*/
static xmlChar **
xsltEvalXPathKeys(xsltTransformContextPtr ctxt, xmlXPathCompExprPtr comp) {
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 = NULL;
ctxt->xpathCtxt->nsNr = 0;
res = xmlXPathCompiledEval(comp, ctxt->xpathCtxt);
if (res != NULL) {
if (res->type == XPATH_NODESET) {
int len, i, j;
if (res->nodesetval != NULL)
len = res->nodesetval->nodeNr;
if (len != 0) {
ret = (xmlChar **) xmlMalloc((len + 1) * sizeof(xmlChar *));
if (ret != NULL) {
for (i = 0,j = 0;i < len;i++) {
ret[j] = xmlXPathCastNodeToString(
res->nodesetval->nodeTab[i]);
if (ret[j] != NULL)
j++;
}
ret[j] = NULL;
}
}
} else {
if (res->type != XPATH_STRING)
res = xmlXPathConvertString(res);
if (res->type == XPATH_STRING) {
ret = (xmlChar **) xmlMalloc(2 * sizeof(xmlChar *));
if (ret != NULL) {
ret[0] = res->stringval;
ret[1] = NULL;
res->stringval = NULL;
}
} else {
xsltPrintErrorContext(ctxt, NULL, NULL);
xsltGenericError(xsltGenericErrorContext,
"xpath : string() function didn't return a String\n");
}
}
xmlXPathFreeObject(res);
} else {
ctxt->state = XSLT_STATE_STOPPED;
}
#ifdef WITH_XSLT_DEBUG_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);
}
/**
* xsltInitCtxtKey:
* @ctxt: an XSLT transformation context
* @doc: an XSLT document
* @keyd: the key definition
*
* Computes the key tables this key and for the current input document.
*/
static void
xsltInitCtxtKey(xsltTransformContextPtr ctxt, xsltDocumentPtr doc,
xsltKeyDefPtr keyd) {
int i;
xmlNodeSetPtr nodelist = NULL, keylist;
xmlXPathObjectPtr res = NULL;
xmlChar *str, **list;
xsltKeyTablePtr table;
int oldPos, oldSize;
xmlNodePtr oldInst;
xsltDocumentPtr oldDoc;
xmlDocPtr oldXDoc;
int oldNsNr;
xmlNsPtr *oldNamespaces;
/*
* Evaluate the nodelist
*/
oldXDoc= ctxt->xpathCtxt->doc;
oldPos = ctxt->xpathCtxt->proximityPosition;
oldSize = ctxt->xpathCtxt->contextSize;
oldInst = ctxt->inst;
oldDoc = ctxt->document;
oldNsNr = ctxt->xpathCtxt->nsNr;
oldNamespaces = ctxt->xpathCtxt->namespaces;
if (keyd->comp == NULL)
goto error;
if (keyd->usecomp == NULL)
goto error;
ctxt->document = doc;
ctxt->xpathCtxt->doc = doc->doc;
ctxt->xpathCtxt->node = (xmlNodePtr) doc->doc;
ctxt->node = (xmlNodePtr) doc->doc;
/* TODO : clarify the use of namespaces in keys evaluation */
ctxt->xpathCtxt->namespaces = NULL;
ctxt->xpathCtxt->nsNr = 0;
ctxt->inst = keyd->inst;
res = xmlXPathCompiledEval(keyd->comp, ctxt->xpathCtxt);
ctxt->xpathCtxt->contextSize = oldSize;
ctxt->xpathCtxt->proximityPosition = oldPos;
ctxt->inst = oldInst;
if (res != NULL) {
if (res->type == XPATH_NODESET) {
nodelist = res->nodesetval;
#ifdef WITH_XSLT_DEBUG_KEYS
if (nodelist != NULL)
xsltGenericDebug(xsltGenericDebugContext,
"xsltInitCtxtKey: %s evaluates to %d nodes\n",
keyd->match, nodelist->nodeNr);
#endif
} else {
#ifdef WITH_XSLT_DEBUG_KEYS
xsltGenericDebug(xsltGenericDebugContext,
"xsltInitCtxtKey: %s is not a node set\n", keyd->match);
#endif
goto error;
}
} else {
#ifdef WITH_XSLT_DEBUG_KEYS
xsltGenericDebug(xsltGenericDebugContext,
"xsltInitCtxtKey: %s evaluation failed\n", keyd->match);
#endif
ctxt->state = XSLT_STATE_STOPPED;
goto error;
}
/*
* for each node in the list evaluate the key and insert the node
*/
if ((nodelist == NULL) || (nodelist->nodeNr <= 0))
goto error;
table = xsltNewKeyTable(keyd->name, keyd->nameURI);
if (table == NULL)
goto error;
for (i = 0;i < nodelist->nodeNr;i++) {
if (IS_XSLT_REAL_NODE(nodelist->nodeTab[i])) {
ctxt->node = nodelist->nodeTab[i];
list = xsltEvalXPathKeys(ctxt, keyd->usecomp);
if (list != NULL) {
int index = 0;
str = list[index++];
while (str != NULL) {
#ifdef WITH_XSLT_DEBUG_KEYS
xsltGenericDebug(xsltGenericDebugContext,
"xsl:key : node associated to(%s,%s)\n",
keyd->name, str);
#endif
keylist = xmlHashLookup(table->keys, str);
if (keylist == NULL) {
keylist = xmlXPathNodeSetCreate(nodelist->nodeTab[i]);
xmlHashAddEntry(table->keys, str, keylist);
} else {
xmlXPathNodeSetAdd(keylist, nodelist->nodeTab[i]);
}
nodelist->nodeTab[i]->_private = keyd;
xmlFree(str);
str = list[index++];
}
xmlFree(list);
#ifdef WITH_XSLT_DEBUG_KEYS
} else {
xsltGenericDebug(xsltGenericDebugContext,
"xsl:key : use %s failed to return strings\n",
keyd->use);
#endif
}
}
}
table->next = doc->keys;
doc->keys = table;
error:
ctxt->document = oldDoc;
ctxt->xpathCtxt->doc = oldXDoc;
ctxt->xpathCtxt->nsNr = oldNsNr;
ctxt->xpathCtxt->namespaces = oldNamespaces;
if (res != NULL)
xmlXPathFreeObject(res);
}
/**
* xsltInitCtxtKeys:
* @ctxt: an XSLT transformation context
* @doc: an XSLT document
*
* Computes all the keys tables for the current input document.
* Should be done before global varibales are initialized.
*/
void
xsltInitCtxtKeys(xsltTransformContextPtr ctxt, xsltDocumentPtr doc) {
xsltStylesheetPtr style;
xsltKeyDefPtr keyd;
if ((ctxt == NULL) || (doc == NULL))
return;
#ifdef WITH_XSLT_DEBUG_KEYS
if ((doc->doc != NULL) && (doc->doc->URL != NULL))
xsltGenericDebug(xsltGenericDebugContext, "Initializing keys on %s\n",
doc->doc->URL);
#endif
style = ctxt->style;
while (style != NULL) {
keyd = (xsltKeyDefPtr) style->keys;
while (keyd != NULL) {
xsltInitCtxtKey(ctxt, doc, keyd);
keyd = keyd->next;
}
style = xsltNextImport(style);
}
}
/**
* xsltFreeDocumentKeys:
* @doc: a XSLT document
*
* Free the keys associated to a document
*/
void
xsltFreeDocumentKeys(xsltDocumentPtr doc) {
if (doc != NULL)
xsltFreeKeyTableList(doc->keys);
}