| /* |
| * 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 |
| |
| 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); |
| } |
| |
| /** |
| * 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); |
| } |
| |
| /** |
| * 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) { |
| 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] == '[') { |
| end = skipPredicate(cur, end); |
| if (end <= 0) |
| return(-1); |
| continue; |
| } else if (cur[end] == ']') |
| return(end + 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, |
| "key pattern is malformed: %s", |
| key->match); |
| if (style != NULL) style->errors++; |
| goto error; |
| } |
| } else |
| end++; |
| } |
| if (current == end) { |
| xsltTransformError(NULL, style, inst, |
| "key 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; |
| } |
| #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. |
| */ |
| key->comp = xsltXPathCompile(style, pattern); |
| if (key->comp == NULL) { |
| xsltTransformError(NULL, style, inst, |
| "xsl:key : XPath pattern compilation failed '%s'\n", |
| pattern); |
| if (style != NULL) style->errors++; |
| } |
| key->usecomp = xsltXPathCompile(style, use); |
| if (key->usecomp == NULL) { |
| xsltTransformError(NULL, style, inst, |
| "xsl:key : XPath pattern 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; |
| |
| 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 |
| * |
| * 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; |
| 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; |
| |
| 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; |
| 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->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); |
| } |
| |