mirror of
https://github.com/reactos/reactos.git
synced 2024-10-31 20:02:55 +00:00
930 lines
23 KiB
C
930 lines
23 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
|
|
*/
|
|
|
|
#include "precomp.h"
|
|
|
|
#ifdef WITH_XSLT_DEBUG
|
|
#define WITH_XSLT_DEBUG_KEYS
|
|
#endif
|
|
|
|
static int
|
|
xsltInitDocKeyTable(xsltTransformContextPtr ctxt, const xmlChar *name,
|
|
const xmlChar *nameURI);
|
|
|
|
/************************************************************************
|
|
* *
|
|
* 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) {
|
|
xsltTransformError(NULL, NULL, NULL,
|
|
"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);
|
|
cur->nsList = NULL;
|
|
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);
|
|
if (keyd->nsList != NULL)
|
|
xmlFree(keyd->nsList);
|
|
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) {
|
|
xsltTransformError(NULL, NULL, NULL,
|
|
"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);
|
|
}
|
|
|
|
static void
|
|
xsltFreeNodeSetEntry(void *payload, const xmlChar *name ATTRIBUTE_UNUSED) {
|
|
xmlXPathFreeNodeSet((xmlNodeSetPtr) payload);
|
|
}
|
|
|
|
/**
|
|
* 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, xsltFreeNodeSetEntry);
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* skipString:
|
|
* @cur: the current pointer
|
|
* @end: the current offset
|
|
*
|
|
* skip a string delimited by " or '
|
|
*
|
|
* Returns the byte after the string or -1 in case of error
|
|
*/
|
|
static int
|
|
skipString(const xmlChar *cur, int end) {
|
|
xmlChar limit;
|
|
|
|
if ((cur == NULL) || (end < 0)) return(-1);
|
|
if ((cur[end] == '\'') || (cur[end] == '"')) limit = cur[end];
|
|
else return(end);
|
|
end++;
|
|
while (cur[end] != 0) {
|
|
if (cur[end] == limit)
|
|
return(end + 1);
|
|
end++;
|
|
}
|
|
return(-1);
|
|
}
|
|
|
|
/**
|
|
* skipPredicate:
|
|
* @cur: the current pointer
|
|
* @end: the current offset
|
|
*
|
|
* skip a predicate
|
|
*
|
|
* Returns the byte after the predicate or -1 in case of error
|
|
*/
|
|
static int
|
|
skipPredicate(const xmlChar *cur, int end) {
|
|
int level = 0;
|
|
|
|
if ((cur == NULL) || (end < 0)) return(-1);
|
|
if (cur[end] != '[') return(end);
|
|
end++;
|
|
while (cur[end] != 0) {
|
|
if ((cur[end] == '\'') || (cur[end] == '"')) {
|
|
end = skipString(cur, end);
|
|
if (end <= 0)
|
|
return(-1);
|
|
continue;
|
|
} else if (cur[end] == '[') {
|
|
level += 1;
|
|
} else if (cur[end] == ']') {
|
|
if (level == 0)
|
|
return(end + 1);
|
|
level -= 1;
|
|
}
|
|
end++;
|
|
}
|
|
return(-1);
|
|
}
|
|
|
|
/**
|
|
* 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, i = 0;
|
|
|
|
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;
|
|
key->nsList = xmlGetNsList(inst->doc, inst);
|
|
if (key->nsList != NULL) {
|
|
while (key->nsList[i] != NULL)
|
|
i++;
|
|
}
|
|
key->nsNr = i;
|
|
|
|
/*
|
|
* Split the | and register it as as many keys
|
|
*/
|
|
current = end = 0;
|
|
while (match[current] != 0) {
|
|
start = current;
|
|
while (IS_BLANK_CH(match[current]))
|
|
current++;
|
|
end = current;
|
|
while ((match[end] != 0) && (match[end] != '|')) {
|
|
if (match[end] == '[') {
|
|
end = skipPredicate(match, end);
|
|
if (end <= 0) {
|
|
xsltTransformError(NULL, style, inst,
|
|
"xsl:key : 'match' pattern is malformed: %s",
|
|
key->match);
|
|
if (style != NULL) style->errors++;
|
|
goto error;
|
|
}
|
|
} else
|
|
end++;
|
|
}
|
|
if (current == end) {
|
|
xsltTransformError(NULL, style, inst,
|
|
"xsl:key : 'match' pattern is empty\n");
|
|
if (style != NULL) style->errors++;
|
|
goto error;
|
|
}
|
|
if (match[start] != '/') {
|
|
pattern = xmlStrcat(pattern, (xmlChar *)"//");
|
|
if (pattern == NULL) {
|
|
if (style != NULL) style->errors++;
|
|
goto error;
|
|
}
|
|
}
|
|
pattern = xmlStrncat(pattern, &match[start], end - start);
|
|
if (pattern == NULL) {
|
|
if (style != NULL) style->errors++;
|
|
goto error;
|
|
}
|
|
|
|
if (match[end] == '|') {
|
|
pattern = xmlStrcat(pattern, (xmlChar *)"|");
|
|
end++;
|
|
}
|
|
current = end;
|
|
}
|
|
if (pattern == NULL) {
|
|
xsltTransformError(NULL, style, inst,
|
|
"xsl:key : 'match' pattern is empty\n");
|
|
if (style != NULL) style->errors++;
|
|
goto error;
|
|
}
|
|
#ifdef WITH_XSLT_DEBUG_KEYS
|
|
xsltGenericDebug(xsltGenericDebugContext,
|
|
" resulting pattern %s\n", pattern);
|
|
#endif
|
|
/*
|
|
* XSLT-1: "It is an error for the value of either the use
|
|
* attribute or the match attribute to contain a
|
|
* VariableReference."
|
|
* TODO: We should report a variable-reference at compile-time.
|
|
* Maybe a search for "$", if it occurs outside of quotation
|
|
* marks, could be sufficient.
|
|
*/
|
|
#ifdef XML_XPATH_NOVAR
|
|
key->comp = xsltXPathCompileFlags(style, pattern, XML_XPATH_NOVAR);
|
|
#else
|
|
key->comp = xsltXPathCompile(style, pattern);
|
|
#endif
|
|
if (key->comp == NULL) {
|
|
xsltTransformError(NULL, style, inst,
|
|
"xsl:key : 'match' pattern compilation failed '%s'\n",
|
|
pattern);
|
|
if (style != NULL) style->errors++;
|
|
}
|
|
#ifdef XML_XPATH_NOVAR
|
|
key->usecomp = xsltXPathCompileFlags(style, use, XML_XPATH_NOVAR);
|
|
#else
|
|
key->usecomp = xsltXPathCompile(style, use);
|
|
#endif
|
|
if (key->usecomp == NULL) {
|
|
xsltTransformError(NULL, style, inst,
|
|
"xsl:key : 'use' expression compilation failed '%s'\n",
|
|
use);
|
|
if (style != NULL) style->errors++;
|
|
}
|
|
|
|
/*
|
|
* Sometimes the stylesheet writer use the order to ease the
|
|
* resolution of keys when they are dependant, keep the provided
|
|
* order so add the new one at the end.
|
|
*/
|
|
if (style->keys == NULL) {
|
|
style->keys = key;
|
|
} else {
|
|
xsltKeyDefPtr prev = style->keys;
|
|
|
|
while (prev->next != NULL)
|
|
prev = prev->next;
|
|
|
|
prev->next = key;
|
|
}
|
|
key->next = NULL;
|
|
key = NULL;
|
|
|
|
error:
|
|
if (pattern != NULL)
|
|
xmlFree(pattern);
|
|
if (key != NULL)
|
|
xsltFreeKeyDef(key);
|
|
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
|
|
*
|
|
* Looks up a key of the in current source doc (the document info
|
|
* on @ctxt->document). Computes the key if not already done
|
|
* for the current source doc.
|
|
*
|
|
* 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;
|
|
int init_table = 0;
|
|
|
|
if ((ctxt == NULL) || (name == NULL) || (value == NULL) ||
|
|
(ctxt->document == NULL))
|
|
return(NULL);
|
|
|
|
#ifdef WITH_XSLT_DEBUG_KEYS
|
|
xsltGenericDebug(xsltGenericDebugContext,
|
|
"Get key %s, value %s\n", name, value);
|
|
#endif
|
|
|
|
/*
|
|
* keys are computed only on-demand on first key access for a document
|
|
*/
|
|
if ((ctxt->document->nbKeysComputed < ctxt->nbKeys) &&
|
|
(ctxt->keyInitLevel == 0)) {
|
|
/*
|
|
* If non-recursive behaviour, just try to initialize all keys
|
|
*/
|
|
if (xsltInitAllDocKeys(ctxt))
|
|
return(NULL);
|
|
}
|
|
|
|
retry:
|
|
table = (xsltKeyTablePtr) ctxt->document->keys;
|
|
while (table != NULL) {
|
|
if (((nameURI != NULL) == (table->nameURI != NULL)) &&
|
|
xmlStrEqual(table->name, name) &&
|
|
xmlStrEqual(table->nameURI, nameURI))
|
|
{
|
|
ret = (xmlNodeSetPtr)xmlHashLookup(table->keys, value);
|
|
return(ret);
|
|
}
|
|
table = table->next;
|
|
}
|
|
|
|
if ((ctxt->keyInitLevel != 0) && (init_table == 0)) {
|
|
/*
|
|
* Apparently one key is recursive and this one is needed,
|
|
* initialize just it, that time and retry
|
|
*/
|
|
xsltInitDocKeyTable(ctxt, name, nameURI);
|
|
init_table = 1;
|
|
goto retry;
|
|
}
|
|
|
|
return(NULL);
|
|
}
|
|
|
|
|
|
/**
|
|
* xsltInitDocKeyTable:
|
|
*
|
|
* INTERNAL ROUTINE ONLY
|
|
*
|
|
* Check if any keys on the current document need to be computed
|
|
*/
|
|
static int
|
|
xsltInitDocKeyTable(xsltTransformContextPtr ctxt, const xmlChar *name,
|
|
const xmlChar *nameURI)
|
|
{
|
|
xsltStylesheetPtr style;
|
|
xsltKeyDefPtr keyd = NULL;
|
|
int found = 0;
|
|
|
|
#ifdef KEY_INIT_DEBUG
|
|
fprintf(stderr, "xsltInitDocKeyTable %s\n", name);
|
|
#endif
|
|
|
|
style = ctxt->style;
|
|
while (style != NULL) {
|
|
keyd = (xsltKeyDefPtr) style->keys;
|
|
while (keyd != NULL) {
|
|
if (((keyd->nameURI != NULL) ==
|
|
(nameURI != NULL)) &&
|
|
xmlStrEqual(keyd->name, name) &&
|
|
xmlStrEqual(keyd->nameURI, nameURI))
|
|
{
|
|
xsltInitCtxtKey(ctxt, ctxt->document, keyd);
|
|
if (ctxt->document->nbKeysComputed == ctxt->nbKeys)
|
|
return(0);
|
|
found = 1;
|
|
}
|
|
keyd = keyd->next;
|
|
}
|
|
style = xsltNextImport(style);
|
|
}
|
|
if (found == 0) {
|
|
#ifdef WITH_XSLT_DEBUG_KEYS
|
|
XSLT_TRACE(ctxt,XSLT_TRACE_KEYS,xsltGenericDebug(xsltGenericDebugContext,
|
|
"xsltInitDocKeyTable: did not found %s\n", name));
|
|
#endif
|
|
xsltTransformError(ctxt, NULL, keyd? keyd->inst : NULL,
|
|
"Failed to find key definition for %s\n", name);
|
|
ctxt->state = XSLT_STATE_STOPPED;
|
|
return(-1);
|
|
}
|
|
#ifdef KEY_INIT_DEBUG
|
|
fprintf(stderr, "xsltInitDocKeyTable %s done\n", name);
|
|
#endif
|
|
return(0);
|
|
}
|
|
|
|
/**
|
|
* xsltInitAllDocKeys:
|
|
* @ctxt: transformation context
|
|
*
|
|
* INTERNAL ROUTINE ONLY
|
|
*
|
|
* Check if any keys on the current document need to be computed
|
|
*
|
|
* Returns 0 in case of success, -1 in case of failure
|
|
*/
|
|
int
|
|
xsltInitAllDocKeys(xsltTransformContextPtr ctxt)
|
|
{
|
|
xsltStylesheetPtr style;
|
|
xsltKeyDefPtr keyd;
|
|
xsltKeyTablePtr table;
|
|
|
|
if (ctxt == NULL)
|
|
return(-1);
|
|
|
|
#ifdef KEY_INIT_DEBUG
|
|
fprintf(stderr, "xsltInitAllDocKeys %d %d\n",
|
|
ctxt->document->nbKeysComputed, ctxt->nbKeys);
|
|
#endif
|
|
|
|
if (ctxt->document->nbKeysComputed == ctxt->nbKeys)
|
|
return(0);
|
|
|
|
|
|
/*
|
|
* TODO: This could be further optimized
|
|
*/
|
|
style = ctxt->style;
|
|
while (style) {
|
|
keyd = (xsltKeyDefPtr) style->keys;
|
|
while (keyd != NULL) {
|
|
#ifdef KEY_INIT_DEBUG
|
|
fprintf(stderr, "Init key %s\n", keyd->name);
|
|
#endif
|
|
/*
|
|
* Check if keys with this QName have been already
|
|
* computed.
|
|
*/
|
|
table = (xsltKeyTablePtr) ctxt->document->keys;
|
|
while (table) {
|
|
if (((keyd->nameURI != NULL) == (table->nameURI != NULL)) &&
|
|
xmlStrEqual(keyd->name, table->name) &&
|
|
xmlStrEqual(keyd->nameURI, table->nameURI))
|
|
{
|
|
break;
|
|
}
|
|
table = table->next;
|
|
}
|
|
if (table == NULL) {
|
|
/*
|
|
* Keys with this QName have not been yet computed.
|
|
*/
|
|
xsltInitDocKeyTable(ctxt, keyd->name, keyd->nameURI);
|
|
}
|
|
keyd = keyd->next;
|
|
}
|
|
style = xsltNextImport(style);
|
|
}
|
|
#ifdef KEY_INIT_DEBUG
|
|
fprintf(stderr, "xsltInitAllDocKeys: done\n");
|
|
#endif
|
|
return(0);
|
|
}
|
|
|
|
/**
|
|
* xsltInitCtxtKey:
|
|
* @ctxt: an XSLT transformation context
|
|
* @idoc: the document information (holds key values)
|
|
* @keyDef: the key definition
|
|
*
|
|
* Computes the key tables this key and for the current input document.
|
|
*
|
|
* Returns: 0 on success, -1 on error
|
|
*/
|
|
int
|
|
xsltInitCtxtKey(xsltTransformContextPtr ctxt, xsltDocumentPtr idoc,
|
|
xsltKeyDefPtr keyDef)
|
|
{
|
|
int i, len, k;
|
|
xmlNodeSetPtr matchList = NULL, keylist;
|
|
xmlXPathObjectPtr matchRes = NULL, useRes = NULL;
|
|
xmlChar *str = NULL;
|
|
xsltKeyTablePtr table;
|
|
xmlNodePtr oldInst, cur;
|
|
xmlNodePtr oldContextNode;
|
|
xsltDocumentPtr oldDocInfo;
|
|
int oldXPPos, oldXPSize;
|
|
xmlNodePtr oldXPNode;
|
|
xmlDocPtr oldXPDoc;
|
|
int oldXPNsNr;
|
|
xmlNsPtr *oldXPNamespaces;
|
|
xmlXPathContextPtr xpctxt;
|
|
|
|
#ifdef KEY_INIT_DEBUG
|
|
fprintf(stderr, "xsltInitCtxtKey %s : %d\n", keyDef->name, ctxt->keyInitLevel);
|
|
#endif
|
|
|
|
if ((keyDef->comp == NULL) || (keyDef->usecomp == NULL))
|
|
return(-1);
|
|
|
|
/*
|
|
* Detect recursive keys
|
|
*/
|
|
if (ctxt->keyInitLevel > ctxt->nbKeys) {
|
|
#ifdef WITH_XSLT_DEBUG_KEYS
|
|
XSLT_TRACE(ctxt,XSLT_TRACE_KEYS,
|
|
xsltGenericDebug(xsltGenericDebugContext,
|
|
"xsltInitCtxtKey: key definition of %s is recursive\n",
|
|
keyDef->name));
|
|
#endif
|
|
xsltTransformError(ctxt, NULL, keyDef->inst,
|
|
"Key definition for %s is recursive\n", keyDef->name);
|
|
ctxt->state = XSLT_STATE_STOPPED;
|
|
return(-1);
|
|
}
|
|
ctxt->keyInitLevel++;
|
|
|
|
xpctxt = ctxt->xpathCtxt;
|
|
idoc->nbKeysComputed++;
|
|
/*
|
|
* Save context state.
|
|
*/
|
|
oldInst = ctxt->inst;
|
|
oldDocInfo = ctxt->document;
|
|
oldContextNode = ctxt->node;
|
|
|
|
oldXPNode = xpctxt->node;
|
|
oldXPDoc = xpctxt->doc;
|
|
oldXPPos = xpctxt->proximityPosition;
|
|
oldXPSize = xpctxt->contextSize;
|
|
oldXPNsNr = xpctxt->nsNr;
|
|
oldXPNamespaces = xpctxt->namespaces;
|
|
|
|
/*
|
|
* Set up contexts.
|
|
*/
|
|
ctxt->document = idoc;
|
|
ctxt->node = (xmlNodePtr) idoc->doc;
|
|
ctxt->inst = keyDef->inst;
|
|
|
|
xpctxt->doc = idoc->doc;
|
|
xpctxt->node = (xmlNodePtr) idoc->doc;
|
|
/* TODO : clarify the use of namespaces in keys evaluation */
|
|
xpctxt->namespaces = keyDef->nsList;
|
|
xpctxt->nsNr = keyDef->nsNr;
|
|
|
|
/*
|
|
* Evaluate the 'match' expression of the xsl:key.
|
|
* TODO: The 'match' is a *pattern*.
|
|
*/
|
|
matchRes = xmlXPathCompiledEval(keyDef->comp, xpctxt);
|
|
if (matchRes == NULL) {
|
|
|
|
#ifdef WITH_XSLT_DEBUG_KEYS
|
|
XSLT_TRACE(ctxt,XSLT_TRACE_KEYS,xsltGenericDebug(xsltGenericDebugContext,
|
|
"xsltInitCtxtKey: %s evaluation failed\n", keyDef->match));
|
|
#endif
|
|
xsltTransformError(ctxt, NULL, keyDef->inst,
|
|
"Failed to evaluate the 'match' expression.\n");
|
|
ctxt->state = XSLT_STATE_STOPPED;
|
|
goto error;
|
|
} else {
|
|
if (matchRes->type == XPATH_NODESET) {
|
|
matchList = matchRes->nodesetval;
|
|
|
|
#ifdef WITH_XSLT_DEBUG_KEYS
|
|
if (matchList != NULL)
|
|
XSLT_TRACE(ctxt,XSLT_TRACE_KEYS,xsltGenericDebug(xsltGenericDebugContext,
|
|
"xsltInitCtxtKey: %s evaluates to %d nodes\n",
|
|
keyDef->match, matchList->nodeNr));
|
|
#endif
|
|
} else {
|
|
/*
|
|
* Is not a node set, but must be.
|
|
*/
|
|
#ifdef WITH_XSLT_DEBUG_KEYS
|
|
XSLT_TRACE(ctxt,XSLT_TRACE_KEYS,xsltGenericDebug(xsltGenericDebugContext,
|
|
"xsltInitCtxtKey: %s is not a node set\n", keyDef->match));
|
|
#endif
|
|
xsltTransformError(ctxt, NULL, keyDef->inst,
|
|
"The 'match' expression did not evaluate to a node set.\n");
|
|
ctxt->state = XSLT_STATE_STOPPED;
|
|
goto error;
|
|
}
|
|
}
|
|
if ((matchList == NULL) || (matchList->nodeNr <= 0))
|
|
goto exit;
|
|
|
|
/**
|
|
* Multiple key definitions for the same name are allowed, so
|
|
* we must check if the key is already present for this doc
|
|
*/
|
|
table = (xsltKeyTablePtr) idoc->keys;
|
|
while (table != NULL) {
|
|
if (xmlStrEqual(table->name, keyDef->name) &&
|
|
(((keyDef->nameURI == NULL) && (table->nameURI == NULL)) ||
|
|
((keyDef->nameURI != NULL) && (table->nameURI != NULL) &&
|
|
(xmlStrEqual(table->nameURI, keyDef->nameURI)))))
|
|
break;
|
|
table = table->next;
|
|
}
|
|
/**
|
|
* If the key was not previously defined, create it now and
|
|
* chain it to the list of keys for the doc
|
|
*/
|
|
if (table == NULL) {
|
|
table = xsltNewKeyTable(keyDef->name, keyDef->nameURI);
|
|
if (table == NULL)
|
|
goto error;
|
|
table->next = idoc->keys;
|
|
idoc->keys = table;
|
|
}
|
|
|
|
/*
|
|
* SPEC XSLT 1.0 (XSLT 2.0 does not clarify the context size!)
|
|
* "...the use attribute of the xsl:key element is evaluated with x as
|
|
" the current node and with a node list containing just x as the
|
|
* current node list"
|
|
*/
|
|
xpctxt->contextSize = 1;
|
|
xpctxt->proximityPosition = 1;
|
|
|
|
for (i = 0; i < matchList->nodeNr; i++) {
|
|
cur = matchList->nodeTab[i];
|
|
if (! IS_XSLT_REAL_NODE(cur))
|
|
continue;
|
|
ctxt->node = cur;
|
|
xpctxt->node = cur;
|
|
/*
|
|
* Process the 'use' of the xsl:key.
|
|
* SPEC XSLT 1.0:
|
|
* "The use attribute is an expression specifying the values of
|
|
* the key; the expression is evaluated once for each node that
|
|
* matches the pattern."
|
|
*/
|
|
if (useRes != NULL)
|
|
xmlXPathFreeObject(useRes);
|
|
useRes = xmlXPathCompiledEval(keyDef->usecomp, xpctxt);
|
|
if (useRes == NULL) {
|
|
xsltTransformError(ctxt, NULL, keyDef->inst,
|
|
"Failed to evaluate the 'use' expression.\n");
|
|
ctxt->state = XSLT_STATE_STOPPED;
|
|
break;
|
|
}
|
|
if (useRes->type == XPATH_NODESET) {
|
|
if ((useRes->nodesetval != NULL) &&
|
|
(useRes->nodesetval->nodeNr != 0))
|
|
{
|
|
len = useRes->nodesetval->nodeNr;
|
|
str = xmlXPathCastNodeToString(useRes->nodesetval->nodeTab[0]);
|
|
} else {
|
|
continue;
|
|
}
|
|
} else {
|
|
len = 1;
|
|
if (useRes->type == XPATH_STRING) {
|
|
/*
|
|
* Consume the string value.
|
|
*/
|
|
str = useRes->stringval;
|
|
useRes->stringval = NULL;
|
|
} else {
|
|
str = xmlXPathCastToString(useRes);
|
|
}
|
|
}
|
|
/*
|
|
* Process all strings.
|
|
*/
|
|
k = 0;
|
|
while (1) {
|
|
if (str == NULL)
|
|
goto next_string;
|
|
|
|
#ifdef WITH_XSLT_DEBUG_KEYS
|
|
XSLT_TRACE(ctxt,XSLT_TRACE_KEYS,xsltGenericDebug(xsltGenericDebugContext,
|
|
"xsl:key : node associated to ('%s', '%s')\n", keyDef->name, str));
|
|
#endif
|
|
|
|
keylist = xmlHashLookup(table->keys, str);
|
|
if (keylist == NULL) {
|
|
keylist = xmlXPathNodeSetCreate(cur);
|
|
if (keylist == NULL)
|
|
goto error;
|
|
xmlHashAddEntry(table->keys, str, keylist);
|
|
} else {
|
|
/*
|
|
* TODO: How do we know if this function failed?
|
|
*/
|
|
xmlXPathNodeSetAdd(keylist, cur);
|
|
}
|
|
switch (cur->type) {
|
|
case XML_ELEMENT_NODE:
|
|
case XML_TEXT_NODE:
|
|
case XML_CDATA_SECTION_NODE:
|
|
case XML_PI_NODE:
|
|
case XML_COMMENT_NODE:
|
|
cur->psvi = keyDef;
|
|
break;
|
|
case XML_ATTRIBUTE_NODE:
|
|
((xmlAttrPtr) cur)->psvi = keyDef;
|
|
break;
|
|
case XML_DOCUMENT_NODE:
|
|
case XML_HTML_DOCUMENT_NODE:
|
|
((xmlDocPtr) cur)->psvi = keyDef;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
xmlFree(str);
|
|
str = NULL;
|
|
|
|
next_string:
|
|
k++;
|
|
if (k >= len)
|
|
break;
|
|
str = xmlXPathCastNodeToString(useRes->nodesetval->nodeTab[k]);
|
|
}
|
|
}
|
|
|
|
exit:
|
|
error:
|
|
ctxt->keyInitLevel--;
|
|
/*
|
|
* Restore context state.
|
|
*/
|
|
xpctxt->node = oldXPNode;
|
|
xpctxt->doc = oldXPDoc;
|
|
xpctxt->nsNr = oldXPNsNr;
|
|
xpctxt->namespaces = oldXPNamespaces;
|
|
xpctxt->proximityPosition = oldXPPos;
|
|
xpctxt->contextSize = oldXPSize;
|
|
|
|
ctxt->node = oldContextNode;
|
|
ctxt->document = oldDocInfo;
|
|
ctxt->inst = oldInst;
|
|
|
|
if (str)
|
|
xmlFree(str);
|
|
if (useRes != NULL)
|
|
xmlXPathFreeObject(useRes);
|
|
if (matchRes != NULL)
|
|
xmlXPathFreeObject(matchRes);
|
|
return(0);
|
|
}
|
|
|
|
/**
|
|
* xsltInitCtxtKeys:
|
|
* @ctxt: an XSLT transformation context
|
|
* @idoc: a document info
|
|
*
|
|
* Computes all the keys tables for the current input document.
|
|
* Should be done before global varibales are initialized.
|
|
* NOTE: Not used anymore in the refactored code.
|
|
*/
|
|
void
|
|
xsltInitCtxtKeys(xsltTransformContextPtr ctxt, xsltDocumentPtr idoc) {
|
|
xsltStylesheetPtr style;
|
|
xsltKeyDefPtr keyDef;
|
|
|
|
if ((ctxt == NULL) || (idoc == NULL))
|
|
return;
|
|
|
|
#ifdef KEY_INIT_DEBUG
|
|
fprintf(stderr, "xsltInitCtxtKeys on document\n");
|
|
#endif
|
|
|
|
#ifdef WITH_XSLT_DEBUG_KEYS
|
|
if ((idoc->doc != NULL) && (idoc->doc->URL != NULL))
|
|
XSLT_TRACE(ctxt,XSLT_TRACE_KEYS,xsltGenericDebug(xsltGenericDebugContext, "Initializing keys on %s\n",
|
|
idoc->doc->URL));
|
|
#endif
|
|
style = ctxt->style;
|
|
while (style != NULL) {
|
|
keyDef = (xsltKeyDefPtr) style->keys;
|
|
while (keyDef != NULL) {
|
|
xsltInitCtxtKey(ctxt, idoc, keyDef);
|
|
|
|
keyDef = keyDef->next;
|
|
}
|
|
|
|
style = xsltNextImport(style);
|
|
}
|
|
|
|
#ifdef KEY_INIT_DEBUG
|
|
fprintf(stderr, "xsltInitCtxtKeys on document: done\n");
|
|
#endif
|
|
|
|
}
|
|
|
|
/**
|
|
* xsltFreeDocumentKeys:
|
|
* @idoc: a XSLT document
|
|
*
|
|
* Free the keys associated to a document
|
|
*/
|
|
void
|
|
xsltFreeDocumentKeys(xsltDocumentPtr idoc) {
|
|
if (idoc != NULL)
|
|
xsltFreeKeyTableList(idoc->keys);
|
|
}
|
|
|