Home | History | Annotate | Download | only in libxslt
      1 /*
      2  * templates.c: Implementation of the template processing
      3  *
      4  * Reference:
      5  *   http://www.w3.org/TR/1999/REC-xslt-19991116
      6  *
      7  * See Copyright for the status of this software.
      8  *
      9  * daniel (at) veillard.com
     10  */
     11 
     12 #define IN_LIBXSLT
     13 #include "libxslt.h"
     14 
     15 #include <string.h>
     16 
     17 #include <libxml/xmlmemory.h>
     18 #include <libxml/globals.h>
     19 #include <libxml/xmlerror.h>
     20 #include <libxml/tree.h>
     21 #include <libxml/xpathInternals.h>
     22 #include <libxml/parserInternals.h>
     23 #include "xslt.h"
     24 #include "xsltInternals.h"
     25 #include "xsltutils.h"
     26 #include "variables.h"
     27 #include "functions.h"
     28 #include "templates.h"
     29 #include "transform.h"
     30 #include "namespaces.h"
     31 #include "attributes.h"
     32 
     33 #ifdef WITH_XSLT_DEBUG
     34 #define WITH_XSLT_DEBUG_TEMPLATES
     35 #endif
     36 
     37 /************************************************************************
     38  *									*
     39  *			Module interfaces				*
     40  *									*
     41  ************************************************************************/
     42 
     43 /**
     44  * xsltEvalXPathPredicate:
     45  * @ctxt:  the XSLT transformation context
     46  * @comp:  the XPath compiled expression
     47  * @nsList:  the namespaces in scope
     48  * @nsNr:  the number of namespaces in scope
     49  *
     50  * Process the expression using XPath and evaluate the result as
     51  * an XPath predicate
     52  *
     53  * Returns 1 is the predicate was true, 0 otherwise
     54  */
     55 int
     56 xsltEvalXPathPredicate(xsltTransformContextPtr ctxt, xmlXPathCompExprPtr comp,
     57 		       xmlNsPtr *nsList, int nsNr) {
     58     int ret;
     59     xmlXPathObjectPtr res;
     60     int oldNsNr;
     61     xmlNsPtr *oldNamespaces;
     62     xmlNodePtr oldInst;
     63     int oldProximityPosition, oldContextSize;
     64 
     65     oldContextSize = ctxt->xpathCtxt->contextSize;
     66     oldProximityPosition = ctxt->xpathCtxt->proximityPosition;
     67     oldNsNr = ctxt->xpathCtxt->nsNr;
     68     oldNamespaces = ctxt->xpathCtxt->namespaces;
     69     oldInst = ctxt->inst;
     70 
     71     ctxt->xpathCtxt->node = ctxt->node;
     72     ctxt->xpathCtxt->namespaces = nsList;
     73     ctxt->xpathCtxt->nsNr = nsNr;
     74 
     75     res = xmlXPathCompiledEval(comp, ctxt->xpathCtxt);
     76 
     77     if (res != NULL) {
     78 	ret = xmlXPathEvalPredicate(ctxt->xpathCtxt, res);
     79 	xmlXPathFreeObject(res);
     80 #ifdef WITH_XSLT_DEBUG_TEMPLATES
     81 	XSLT_TRACE(ctxt,XSLT_TRACE_TEMPLATES,xsltGenericDebug(xsltGenericDebugContext,
     82 	     "xsltEvalXPathPredicate: returns %d\n", ret));
     83 #endif
     84     } else {
     85 #ifdef WITH_XSLT_DEBUG_TEMPLATES
     86 	XSLT_TRACE(ctxt,XSLT_TRACE_TEMPLATES,xsltGenericDebug(xsltGenericDebugContext,
     87 	     "xsltEvalXPathPredicate: failed\n"));
     88 #endif
     89 	ctxt->state = XSLT_STATE_STOPPED;
     90 	ret = 0;
     91     }
     92     ctxt->xpathCtxt->nsNr = oldNsNr;
     93 
     94     ctxt->xpathCtxt->namespaces = oldNamespaces;
     95     ctxt->inst = oldInst;
     96     ctxt->xpathCtxt->contextSize = oldContextSize;
     97     ctxt->xpathCtxt->proximityPosition = oldProximityPosition;
     98 
     99     return(ret);
    100 }
    101 
    102 /**
    103  * xsltEvalXPathStringNs:
    104  * @ctxt:  the XSLT transformation context
    105  * @comp:  the compiled XPath expression
    106  * @nsNr:  the number of namespaces in the list
    107  * @nsList:  the list of in-scope namespaces to use
    108  *
    109  * Process the expression using XPath, allowing to pass a namespace mapping
    110  * context and get a string
    111  *
    112  * Returns the computed string value or NULL, must be deallocated by the
    113  *    caller.
    114  */
    115 xmlChar *
    116 xsltEvalXPathStringNs(xsltTransformContextPtr ctxt, xmlXPathCompExprPtr comp,
    117 	              int nsNr, xmlNsPtr *nsList) {
    118     xmlChar *ret = NULL;
    119     xmlXPathObjectPtr res;
    120     xmlNodePtr oldInst;
    121     xmlNodePtr oldNode;
    122     int	oldPos, oldSize;
    123     int oldNsNr;
    124     xmlNsPtr *oldNamespaces;
    125 
    126     oldInst = ctxt->inst;
    127     oldNode = ctxt->node;
    128     oldPos = ctxt->xpathCtxt->proximityPosition;
    129     oldSize = ctxt->xpathCtxt->contextSize;
    130     oldNsNr = ctxt->xpathCtxt->nsNr;
    131     oldNamespaces = ctxt->xpathCtxt->namespaces;
    132 
    133     ctxt->xpathCtxt->node = ctxt->node;
    134     /* TODO: do we need to propagate the namespaces here ? */
    135     ctxt->xpathCtxt->namespaces = nsList;
    136     ctxt->xpathCtxt->nsNr = nsNr;
    137     res = xmlXPathCompiledEval(comp, ctxt->xpathCtxt);
    138     if (res != NULL) {
    139 	if (res->type != XPATH_STRING)
    140 	    res = xmlXPathConvertString(res);
    141 	if (res->type == XPATH_STRING) {
    142             ret = res->stringval;
    143 	    res->stringval = NULL;
    144 	} else {
    145 	    xsltTransformError(ctxt, NULL, NULL,
    146 		 "xpath : string() function didn't return a String\n");
    147 	}
    148 	xmlXPathFreeObject(res);
    149     } else {
    150 	ctxt->state = XSLT_STATE_STOPPED;
    151     }
    152 #ifdef WITH_XSLT_DEBUG_TEMPLATES
    153     XSLT_TRACE(ctxt,XSLT_TRACE_TEMPLATES,xsltGenericDebug(xsltGenericDebugContext,
    154 	 "xsltEvalXPathString: returns %s\n", ret));
    155 #endif
    156     ctxt->inst = oldInst;
    157     ctxt->node = oldNode;
    158     ctxt->xpathCtxt->contextSize = oldSize;
    159     ctxt->xpathCtxt->proximityPosition = oldPos;
    160     ctxt->xpathCtxt->nsNr = oldNsNr;
    161     ctxt->xpathCtxt->namespaces = oldNamespaces;
    162     return(ret);
    163 }
    164 
    165 /**
    166  * xsltEvalXPathString:
    167  * @ctxt:  the XSLT transformation context
    168  * @comp:  the compiled XPath expression
    169  *
    170  * Process the expression using XPath and get a string
    171  *
    172  * Returns the computed string value or NULL, must be deallocated by the
    173  *    caller.
    174  */
    175 xmlChar *
    176 xsltEvalXPathString(xsltTransformContextPtr ctxt, xmlXPathCompExprPtr comp) {
    177     return(xsltEvalXPathStringNs(ctxt, comp, 0, NULL));
    178 }
    179 
    180 /**
    181  * xsltEvalTemplateString:
    182  * @ctxt:  the XSLT transformation context
    183  * @contextNode:  the current node in the source tree
    184  * @inst:  the XSLT instruction (xsl:comment, xsl:processing-instruction)
    185  *
    186  * Processes the sequence constructor of the given instruction on
    187  * @contextNode and converts the resulting tree to a string.
    188  * This is needed by e.g. xsl:comment and xsl:processing-instruction.
    189  *
    190  * Returns the computed string value or NULL; it's up to the caller to
    191  *         free the result.
    192  */
    193 xmlChar *
    194 xsltEvalTemplateString(xsltTransformContextPtr ctxt,
    195 		       xmlNodePtr contextNode,
    196 	               xmlNodePtr inst)
    197 {
    198     xmlNodePtr oldInsert, insert = NULL;
    199     xmlChar *ret;
    200 
    201     if ((ctxt == NULL) || (contextNode == NULL) || (inst == NULL))
    202 	return(NULL);
    203 
    204     if (inst->children == NULL)
    205 	return(NULL);
    206 
    207     /*
    208     * This creates a temporary element-node to add the resulting
    209     * text content to.
    210     * OPTIMIZE TODO: Keep such an element-node in the transformation
    211     *  context to avoid creating it every time.
    212     */
    213     insert = xmlNewDocNode(ctxt->output, NULL,
    214 	                   (const xmlChar *)"fake", NULL);
    215     if (insert == NULL) {
    216 	xsltTransformError(ctxt, NULL, contextNode,
    217 		"Failed to create temporary node\n");
    218 	return(NULL);
    219     }
    220     oldInsert = ctxt->insert;
    221     ctxt->insert = insert;
    222     /*
    223     * OPTIMIZE TODO: if inst->children consists only of text-nodes.
    224     */
    225     xsltApplyOneTemplate(ctxt, contextNode, inst->children, NULL, NULL);
    226 
    227     ctxt->insert = oldInsert;
    228 
    229     ret = xmlNodeGetContent(insert);
    230     if (insert != NULL)
    231 	xmlFreeNode(insert);
    232     return(ret);
    233 }
    234 
    235 /**
    236  * xsltAttrTemplateValueProcessNode:
    237  * @ctxt:  the XSLT transformation context
    238  * @str:  the attribute template node value
    239  * @inst:  the instruction (or LRE) in the stylesheet holding the
    240  *         attribute with an AVT
    241  *
    242  * Process the given string, allowing to pass a namespace mapping
    243  * context and return the new string value.
    244  *
    245  * Called by:
    246  *  - xsltAttrTemplateValueProcess() (templates.c)
    247  *  - xsltEvalAttrValueTemplate() (templates.c)
    248  *
    249  * QUESTION: Why is this function public? It is not used outside
    250  *  of templates.c.
    251  *
    252  * Returns the computed string value or NULL, must be deallocated by the
    253  *    caller.
    254  */
    255 xmlChar *
    256 xsltAttrTemplateValueProcessNode(xsltTransformContextPtr ctxt,
    257 	  const xmlChar *str, xmlNodePtr inst)
    258 {
    259     xmlChar *ret = NULL;
    260     const xmlChar *cur;
    261     xmlChar *expr, *val;
    262     xmlNsPtr *nsList = NULL;
    263     int nsNr = 0;
    264 
    265     if (str == NULL) return(NULL);
    266     if (*str == 0)
    267 	return(xmlStrndup((xmlChar *)"", 0));
    268 
    269     cur = str;
    270     while (*cur != 0) {
    271 	if (*cur == '{') {
    272 	    if (*(cur+1) == '{') {	/* escaped '{' */
    273 	        cur++;
    274 		ret = xmlStrncat(ret, str, cur - str);
    275 		cur++;
    276 		str = cur;
    277 		continue;
    278 	    }
    279 	    ret = xmlStrncat(ret, str, cur - str);
    280 	    str = cur;
    281 	    cur++;
    282 	    while ((*cur != 0) && (*cur != '}')) cur++;
    283 	    if (*cur == 0) {
    284 	        xsltTransformError(ctxt, NULL, inst,
    285 			"xsltAttrTemplateValueProcessNode: unmatched '{'\n");
    286 		ret = xmlStrncat(ret, str, cur - str);
    287 		return(ret);
    288 	    }
    289 	    str++;
    290 	    expr = xmlStrndup(str, cur - str);
    291 	    if (expr == NULL)
    292 		return(ret);
    293 	    else if (*expr == '{') {
    294 		ret = xmlStrcat(ret, expr);
    295 		xmlFree(expr);
    296 	    } else {
    297 		xmlXPathCompExprPtr comp;
    298 		/*
    299 		 * TODO: keep precompiled form around
    300 		 */
    301 		if ((nsList == NULL) && (inst != NULL)) {
    302 		    int i = 0;
    303 
    304 		    nsList = xmlGetNsList(inst->doc, inst);
    305 		    if (nsList != NULL) {
    306 			while (nsList[i] != NULL)
    307 			    i++;
    308 			nsNr = i;
    309 		    }
    310 		}
    311 		comp = xmlXPathCompile(expr);
    312                 val = xsltEvalXPathStringNs(ctxt, comp, nsNr, nsList);
    313 		xmlXPathFreeCompExpr(comp);
    314 		xmlFree(expr);
    315 		if (val != NULL) {
    316 		    ret = xmlStrcat(ret, val);
    317 		    xmlFree(val);
    318 		}
    319 	    }
    320 	    cur++;
    321 	    str = cur;
    322 	} else if (*cur == '}') {
    323 	    cur++;
    324 	    if (*cur == '}') {	/* escaped '}' */
    325 		ret = xmlStrncat(ret, str, cur - str);
    326 		cur++;
    327 		str = cur;
    328 		continue;
    329 	    } else {
    330 	        xsltTransformError(ctxt, NULL, inst,
    331 		     "xsltAttrTemplateValueProcessNode: unmatched '}'\n");
    332 	    }
    333 	} else
    334 	    cur++;
    335     }
    336     if (cur != str) {
    337 	ret = xmlStrncat(ret, str, cur - str);
    338     }
    339 
    340     if (nsList != NULL)
    341 	xmlFree(nsList);
    342 
    343     return(ret);
    344 }
    345 
    346 /**
    347  * xsltAttrTemplateValueProcess:
    348  * @ctxt:  the XSLT transformation context
    349  * @str:  the attribute template node value
    350  *
    351  * Process the given node and return the new string value.
    352  *
    353  * Returns the computed string value or NULL, must be deallocated by the
    354  *    caller.
    355  */
    356 xmlChar *
    357 xsltAttrTemplateValueProcess(xsltTransformContextPtr ctxt, const xmlChar *str) {
    358     return(xsltAttrTemplateValueProcessNode(ctxt, str, NULL));
    359 }
    360 
    361 /**
    362  * xsltEvalAttrValueTemplate:
    363  * @ctxt:  the XSLT transformation context
    364  * @inst:  the instruction (or LRE) in the stylesheet holding the
    365  *         attribute with an AVT
    366  * @name:  the attribute QName
    367  * @ns:  the attribute namespace URI
    368  *
    369  * Evaluate a attribute value template, i.e. the attribute value can
    370  * contain expressions contained in curly braces ({}) and those are
    371  * substituted by they computed value.
    372  *
    373  * Returns the computed string value or NULL, must be deallocated by the
    374  *    caller.
    375  */
    376 xmlChar *
    377 xsltEvalAttrValueTemplate(xsltTransformContextPtr ctxt, xmlNodePtr inst,
    378 	                  const xmlChar *name, const xmlChar *ns)
    379 {
    380     xmlChar *ret;
    381     xmlChar *expr;
    382 
    383     if ((ctxt == NULL) || (inst == NULL) || (name == NULL))
    384 	return(NULL);
    385 
    386     expr = xsltGetNsProp(inst, name, ns);
    387     if (expr == NULL)
    388 	return(NULL);
    389 
    390     /*
    391      * TODO: though now {} is detected ahead, it would still be good to
    392      *       optimize both functions to keep the splitted value if the
    393      *       attribute content and the XPath precompiled expressions around
    394      */
    395 
    396     ret = xsltAttrTemplateValueProcessNode(ctxt, expr, inst);
    397 #ifdef WITH_XSLT_DEBUG_TEMPLATES
    398     XSLT_TRACE(ctxt,XSLT_TRACE_TEMPLATES,xsltGenericDebug(xsltGenericDebugContext,
    399 	 "xsltEvalAttrValueTemplate: %s returns %s\n", expr, ret));
    400 #endif
    401     if (expr != NULL)
    402 	xmlFree(expr);
    403     return(ret);
    404 }
    405 
    406 /**
    407  * xsltEvalStaticAttrValueTemplate:
    408  * @style:  the XSLT stylesheet
    409  * @inst:  the instruction (or LRE) in the stylesheet holding the
    410  *         attribute with an AVT
    411  * @name:  the attribute Name
    412  * @ns:  the attribute namespace URI
    413  * @found:  indicator whether the attribute is present
    414  *
    415  * Check if an attribute value template has a static value, i.e. the
    416  * attribute value does not contain expressions contained in curly braces ({})
    417  *
    418  * Returns the static string value or NULL, must be deallocated by the
    419  *    caller.
    420  */
    421 const xmlChar *
    422 xsltEvalStaticAttrValueTemplate(xsltStylesheetPtr style, xmlNodePtr inst,
    423 			const xmlChar *name, const xmlChar *ns, int *found) {
    424     const xmlChar *ret;
    425     xmlChar *expr;
    426 
    427     if ((style == NULL) || (inst == NULL) || (name == NULL))
    428 	return(NULL);
    429 
    430     expr = xsltGetNsProp(inst, name, ns);
    431     if (expr == NULL) {
    432 	*found = 0;
    433 	return(NULL);
    434     }
    435     *found = 1;
    436 
    437     ret = xmlStrchr(expr, '{');
    438     if (ret != NULL) {
    439 	xmlFree(expr);
    440 	return(NULL);
    441     }
    442     ret = xmlDictLookup(style->dict, expr, -1);
    443     xmlFree(expr);
    444     return(ret);
    445 }
    446 
    447 /**
    448  * xsltAttrTemplateProcess:
    449  * @ctxt:  the XSLT transformation context
    450  * @target:  the element where the attribute will be grafted
    451  * @attr:  the attribute node of a literal result element
    452  *
    453  * Process one attribute of a Literal Result Element (in the stylesheet).
    454  * Evaluates Attribute Value Templates and copies the attribute over to
    455  * the result element.
    456  * This does *not* process attribute sets (xsl:use-attribute-set).
    457  *
    458  *
    459  * Returns the generated attribute node.
    460  */
    461 xmlAttrPtr
    462 xsltAttrTemplateProcess(xsltTransformContextPtr ctxt, xmlNodePtr target,
    463 	                xmlAttrPtr attr)
    464 {
    465     const xmlChar *value;
    466     xmlAttrPtr ret;
    467 
    468     if ((ctxt == NULL) || (attr == NULL) || (target == NULL))
    469 	return(NULL);
    470 
    471     if (attr->type != XML_ATTRIBUTE_NODE)
    472 	return(NULL);
    473 
    474     /*
    475     * Skip all XSLT attributes.
    476     */
    477 #ifdef XSLT_REFACTORED
    478     if (attr->psvi == xsltXSLTAttrMarker)
    479 	return(NULL);
    480 #else
    481     if ((attr->ns != NULL) && xmlStrEqual(attr->ns->href, XSLT_NAMESPACE))
    482 	return(NULL);
    483 #endif
    484     /*
    485     * Get the value.
    486     */
    487     if (attr->children != NULL) {
    488 	if ((attr->children->type != XML_TEXT_NODE) ||
    489 	    (attr->children->next != NULL))
    490 	{
    491 	    xsltTransformError(ctxt, NULL, attr->parent,
    492 		"Internal error: The children of an attribute node of a "
    493 		"literal result element are not in the expected form.\n");
    494 	    return(NULL);
    495 	}
    496 	value = attr->children->content;
    497 	if (value == NULL)
    498 	    value = xmlDictLookup(ctxt->dict, BAD_CAST "", 0);
    499     } else
    500 	value = xmlDictLookup(ctxt->dict, BAD_CAST "", 0);
    501     /*
    502     * Overwrite duplicates.
    503     */
    504     ret = target->properties;
    505     while (ret != NULL) {
    506         if (((attr->ns != NULL) == (ret->ns != NULL)) &&
    507 	    xmlStrEqual(ret->name, attr->name) &&
    508 	    ((attr->ns == NULL) || xmlStrEqual(ret->ns->href, attr->ns->href)))
    509 	{
    510 	    break;
    511 	}
    512         ret = ret->next;
    513     }
    514     if (ret != NULL) {
    515         /* free the existing value */
    516 	xmlFreeNodeList(ret->children);
    517 	ret->children = ret->last = NULL;
    518 	/*
    519 	* Adjust ns-prefix if needed.
    520 	*/
    521 	if ((ret->ns != NULL) &&
    522 	    (! xmlStrEqual(ret->ns->prefix, attr->ns->prefix)))
    523 	{
    524 	    ret->ns = xsltGetNamespace(ctxt, attr->parent, attr->ns, target);
    525 	}
    526     } else {
    527         /* create a new attribute */
    528 	if (attr->ns != NULL)
    529 	    ret = xmlNewNsProp(target,
    530 		xsltGetNamespace(ctxt, attr->parent, attr->ns, target),
    531 		    attr->name, NULL);
    532 	else
    533 	    ret = xmlNewNsProp(target, NULL, attr->name, NULL);
    534     }
    535     /*
    536     * Set the value.
    537     */
    538     if (ret != NULL) {
    539         xmlNodePtr text;
    540 
    541         text = xmlNewText(NULL);
    542 	if (text != NULL) {
    543 	    ret->last = ret->children = text;
    544 	    text->parent = (xmlNodePtr) ret;
    545 	    text->doc = ret->doc;
    546 
    547 	    if (attr->psvi != NULL) {
    548 		/*
    549 		* Evaluate the Attribute Value Template.
    550 		*/
    551 		xmlChar *val;
    552 		val = xsltEvalAVT(ctxt, attr->psvi, attr->parent);
    553 		if (val == NULL) {
    554 		    /*
    555 		    * TODO: Damn, we need an easy mechanism to report
    556 		    * qualified names!
    557 		    */
    558 		    if (attr->ns) {
    559 			xsltTransformError(ctxt, NULL, attr->parent,
    560 			    "Internal error: Failed to evaluate the AVT "
    561 			    "of attribute '{%s}%s'.\n",
    562 			    attr->ns->href, attr->name);
    563 		    } else {
    564 			xsltTransformError(ctxt, NULL, attr->parent,
    565 			    "Internal error: Failed to evaluate the AVT "
    566 			    "of attribute '%s'.\n",
    567 			    attr->name);
    568 		    }
    569 		    text->content = xmlStrdup(BAD_CAST "");
    570 		} else {
    571 		    text->content = val;
    572 		}
    573 	    } else if ((ctxt->internalized) && (target != NULL) &&
    574 	               (target->doc != NULL) &&
    575 		       (target->doc->dict == ctxt->dict)) {
    576 		text->content = (xmlChar *) value;
    577 	    } else {
    578 		text->content = xmlStrdup(value);
    579 	    }
    580 	}
    581     } else {
    582 	if (attr->ns) {
    583 	    xsltTransformError(ctxt, NULL, attr->parent,
    584 	    	"Internal error: Failed to create attribute '{%s}%s'.\n",
    585 		attr->ns->href, attr->name);
    586 	} else {
    587 	    xsltTransformError(ctxt, NULL, attr->parent,
    588 	    	"Internal error: Failed to create attribute '%s'.\n",
    589 		attr->name);
    590 	}
    591     }
    592     return(ret);
    593 }
    594 
    595 
    596 /**
    597  * xsltAttrListTemplateProcess:
    598  * @ctxt:  the XSLT transformation context
    599  * @target:  the element where the attributes will be grafted
    600  * @attrs:  the first attribute
    601  *
    602  * Processes all attributes of a Literal Result Element.
    603  * Attribute references are applied via xsl:use-attribute-set
    604  * attributes.
    605  * Copies all non XSLT-attributes over to the @target element
    606  * and evaluates Attribute Value Templates.
    607  *
    608  * Called by xsltApplySequenceConstructor() (transform.c).
    609  *
    610  * Returns a new list of attribute nodes, or NULL in case of error.
    611  *         (Don't assign the result to @target->properties; if
    612  *         the result is NULL, you'll get memory leaks, since the
    613  *         attributes will be disattached.)
    614  */
    615 xmlAttrPtr
    616 xsltAttrListTemplateProcess(xsltTransformContextPtr ctxt,
    617 	                    xmlNodePtr target, xmlAttrPtr attrs)
    618 {
    619     xmlAttrPtr attr, copy, last;
    620     xmlNodePtr oldInsert, text;
    621     xmlNsPtr origNs = NULL, copyNs = NULL;
    622     const xmlChar *value;
    623     xmlChar *valueAVT;
    624 
    625     if ((ctxt == NULL) || (target == NULL) || (attrs == NULL))
    626 	return(NULL);
    627 
    628     oldInsert = ctxt->insert;
    629     ctxt->insert = target;
    630 
    631     /*
    632     * Instantiate LRE-attributes.
    633     */
    634     if (target->properties) {
    635 	last = target->properties;
    636 	while (last->next != NULL)
    637 	    last = last->next;
    638     } else {
    639 	last = NULL;
    640     }
    641     attr = attrs;
    642     do {
    643 	/*
    644 	* Skip XSLT attributes.
    645 	*/
    646 #ifdef XSLT_REFACTORED
    647 	if (attr->psvi == xsltXSLTAttrMarker) {
    648 	    goto next_attribute;
    649 	}
    650 #else
    651 	if ((attr->ns != NULL) &&
    652 	    xmlStrEqual(attr->ns->href, XSLT_NAMESPACE))
    653 	{
    654 	    goto next_attribute;
    655 	}
    656 #endif
    657 	/*
    658 	* Get the value.
    659 	*/
    660 	if (attr->children != NULL) {
    661 	    if ((attr->children->type != XML_TEXT_NODE) ||
    662 		(attr->children->next != NULL))
    663 	    {
    664 		xsltTransformError(ctxt, NULL, attr->parent,
    665 		    "Internal error: The children of an attribute node of a "
    666 		    "literal result element are not in the expected form.\n");
    667 		goto error;
    668 	    }
    669 	    value = attr->children->content;
    670 	    if (value == NULL)
    671 		value = xmlDictLookup(ctxt->dict, BAD_CAST "", 0);
    672 	} else
    673 	    value = xmlDictLookup(ctxt->dict, BAD_CAST "", 0);
    674 
    675 	/*
    676 	* Create a new attribute.
    677 	*/
    678 	copy = xmlNewDocProp(target->doc, attr->name, NULL);
    679 	if (copy == NULL) {
    680 	    if (attr->ns) {
    681 		xsltTransformError(ctxt, NULL, attr->parent,
    682 		    "Internal error: Failed to create attribute '{%s}%s'.\n",
    683 		    attr->ns->href, attr->name);
    684 	    } else {
    685 		xsltTransformError(ctxt, NULL, attr->parent,
    686 		    "Internal error: Failed to create attribute '%s'.\n",
    687 		    attr->name);
    688 	    }
    689 	    goto error;
    690 	}
    691 	/*
    692 	* Attach it to the target element.
    693 	*/
    694 	copy->parent = target;
    695 	if (last == NULL) {
    696 	    target->properties = copy;
    697 	    last = copy;
    698 	} else {
    699 	    last->next = copy;
    700 	    copy->prev = last;
    701 	    last = copy;
    702 	}
    703 	/*
    704 	* Set the namespace. Avoid lookups of same namespaces.
    705 	*/
    706 	if (attr->ns != origNs) {
    707 	    origNs = attr->ns;
    708 	    if (attr->ns != NULL) {
    709 #ifdef XSLT_REFACTORED
    710 		copyNs = xsltGetSpecialNamespace(ctxt, attr->parent,
    711 		    attr->ns->href, attr->ns->prefix, target);
    712 #else
    713 		copyNs = xsltGetNamespace(ctxt, attr->parent,
    714 		    attr->ns, target);
    715 #endif
    716 		if (copyNs == NULL)
    717 		    goto error;
    718 	    } else
    719 		copyNs = NULL;
    720 	}
    721 	copy->ns = copyNs;
    722 
    723 	/*
    724 	* Set the value.
    725 	*/
    726 	text = xmlNewText(NULL);
    727 	if (text != NULL) {
    728 	    copy->last = copy->children = text;
    729 	    text->parent = (xmlNodePtr) copy;
    730 	    text->doc = copy->doc;
    731 
    732 	    if (attr->psvi != NULL) {
    733 		/*
    734 		* Evaluate the Attribute Value Template.
    735 		*/
    736 		valueAVT = xsltEvalAVT(ctxt, attr->psvi, attr->parent);
    737 		if (valueAVT == NULL) {
    738 		    /*
    739 		    * TODO: Damn, we need an easy mechanism to report
    740 		    * qualified names!
    741 		    */
    742 		    if (attr->ns) {
    743 			xsltTransformError(ctxt, NULL, attr->parent,
    744 			    "Internal error: Failed to evaluate the AVT "
    745 			    "of attribute '{%s}%s'.\n",
    746 			    attr->ns->href, attr->name);
    747 		    } else {
    748 			xsltTransformError(ctxt, NULL, attr->parent,
    749 			    "Internal error: Failed to evaluate the AVT "
    750 			    "of attribute '%s'.\n",
    751 			    attr->name);
    752 		    }
    753 		    text->content = xmlStrdup(BAD_CAST "");
    754 		    goto error;
    755 		} else {
    756 		    text->content = valueAVT;
    757 		}
    758 	    } else if ((ctxt->internalized) &&
    759 		(target->doc != NULL) &&
    760 		(target->doc->dict == ctxt->dict))
    761 	    {
    762 		text->content = (xmlChar *) value;
    763 	    } else {
    764 		text->content = xmlStrdup(value);
    765 	    }
    766             if ((copy != NULL) && (text != NULL) &&
    767                 (xmlIsID(copy->doc, copy->parent, copy)))
    768                 xmlAddID(NULL, copy->doc, text->content, copy);
    769 	}
    770 
    771 next_attribute:
    772 	attr = attr->next;
    773     } while (attr != NULL);
    774 
    775     /*
    776     * Apply attribute-sets.
    777     * The creation of such attributes will not overwrite any existing
    778     * attribute.
    779     */
    780     attr = attrs;
    781     do {
    782 #ifdef XSLT_REFACTORED
    783 	if ((attr->psvi == xsltXSLTAttrMarker) &&
    784 	    xmlStrEqual(attr->name, (const xmlChar *)"use-attribute-sets"))
    785 	{
    786 	    xsltApplyAttributeSet(ctxt, ctxt->node, (xmlNodePtr) attr, NULL);
    787 	}
    788 #else
    789 	if ((attr->ns != NULL) &&
    790 	    xmlStrEqual(attr->name, (const xmlChar *)"use-attribute-sets") &&
    791 	    xmlStrEqual(attr->ns->href, XSLT_NAMESPACE))
    792 	{
    793 	    xsltApplyAttributeSet(ctxt, ctxt->node, (xmlNodePtr) attr, NULL);
    794 	}
    795 #endif
    796 	attr = attr->next;
    797     } while (attr != NULL);
    798 
    799     ctxt->insert = oldInsert;
    800     return(target->properties);
    801 
    802 error:
    803     ctxt->insert = oldInsert;
    804     return(NULL);
    805 }
    806 
    807 
    808 /**
    809  * xsltTemplateProcess:
    810  * @ctxt:  the XSLT transformation context
    811  * @node:  the attribute template node
    812  *
    813  * Obsolete. Don't use it.
    814  *
    815  * Returns NULL.
    816  */
    817 xmlNodePtr *
    818 xsltTemplateProcess(xsltTransformContextPtr ctxt ATTRIBUTE_UNUSED, xmlNodePtr node) {
    819     if (node == NULL)
    820 	return(NULL);
    821 
    822     return(0);
    823 }
    824 
    825 
    826