C++程序  |  826行  |  21.55 KB

/*
 * templates.c: Implementation of the template processing
 *
 * 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/globals.h>
#include <libxml/xmlerror.h>
#include <libxml/tree.h>
#include <libxml/xpathInternals.h>
#include <libxml/parserInternals.h>
#include "xslt.h"
#include "xsltInternals.h"
#include "xsltutils.h"
#include "variables.h"
#include "functions.h"
#include "templates.h"
#include "transform.h"
#include "namespaces.h"
#include "attributes.h"

#ifdef WITH_XSLT_DEBUG
#define WITH_XSLT_DEBUG_TEMPLATES
#endif

/************************************************************************
 *									*
 *			Module interfaces				*
 *									*
 ************************************************************************/
 
/**
 * xsltEvalXPathPredicate:
 * @ctxt:  the XSLT transformation context
 * @comp:  the XPath compiled expression
 * @nsList:  the namespaces in scope
 * @nsNr:  the number of namespaces in scope
 *
 * Process the expression using XPath and evaluate the result as
 * an XPath predicate
 *
 * Returns 1 is the predicate was true, 0 otherwise
 */
int
xsltEvalXPathPredicate(xsltTransformContextPtr ctxt, xmlXPathCompExprPtr comp,
		       xmlNsPtr *nsList, int nsNr) {
    int ret;
    xmlXPathObjectPtr res;
    int oldNsNr;
    xmlNsPtr *oldNamespaces;
    xmlNodePtr oldInst;
    int oldProximityPosition, oldContextSize;

    oldContextSize = ctxt->xpathCtxt->contextSize;
    oldProximityPosition = ctxt->xpathCtxt->proximityPosition;
    oldNsNr = ctxt->xpathCtxt->nsNr;
    oldNamespaces = ctxt->xpathCtxt->namespaces;
    oldInst = ctxt->inst;

    ctxt->xpathCtxt->node = ctxt->node;
    ctxt->xpathCtxt->namespaces = nsList;
    ctxt->xpathCtxt->nsNr = nsNr;

    res = xmlXPathCompiledEval(comp, ctxt->xpathCtxt);

    if (res != NULL) {
	ret = xmlXPathEvalPredicate(ctxt->xpathCtxt, res);
	xmlXPathFreeObject(res);
#ifdef WITH_XSLT_DEBUG_TEMPLATES
	XSLT_TRACE(ctxt,XSLT_TRACE_TEMPLATES,xsltGenericDebug(xsltGenericDebugContext,
	     "xsltEvalXPathPredicate: returns %d\n", ret));
#endif
    } else {
#ifdef WITH_XSLT_DEBUG_TEMPLATES
	XSLT_TRACE(ctxt,XSLT_TRACE_TEMPLATES,xsltGenericDebug(xsltGenericDebugContext,
	     "xsltEvalXPathPredicate: failed\n"));
#endif
	ctxt->state = XSLT_STATE_STOPPED;
	ret = 0;
    }
    ctxt->xpathCtxt->nsNr = oldNsNr;

    ctxt->xpathCtxt->namespaces = oldNamespaces;
    ctxt->inst = oldInst;
    ctxt->xpathCtxt->contextSize = oldContextSize;
    ctxt->xpathCtxt->proximityPosition = oldProximityPosition;

    return(ret);
}

/**
 * xsltEvalXPathStringNs:
 * @ctxt:  the XSLT transformation context
 * @comp:  the compiled XPath expression
 * @nsNr:  the number of namespaces in the list
 * @nsList:  the list of in-scope namespaces to use
 *
 * Process the expression using XPath, allowing to pass a namespace mapping
 * context and get a string
 *
 * Returns the computed string value or NULL, must be deallocated by the
 *    caller.
 */
xmlChar *
xsltEvalXPathStringNs(xsltTransformContextPtr ctxt, xmlXPathCompExprPtr comp,
	              int nsNr, xmlNsPtr *nsList) {
    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 = nsList;
    ctxt->xpathCtxt->nsNr = nsNr;
    res = xmlXPathCompiledEval(comp, ctxt->xpathCtxt);
    if (res != NULL) {
	if (res->type != XPATH_STRING)
	    res = xmlXPathConvertString(res);
	if (res->type == XPATH_STRING) {
            ret = res->stringval;
	    res->stringval = NULL;
	} else {
	    xsltTransformError(ctxt, NULL, NULL,
		 "xpath : string() function didn't return a String\n");
	}
	xmlXPathFreeObject(res);
    } else {
	ctxt->state = XSLT_STATE_STOPPED;
    }
#ifdef WITH_XSLT_DEBUG_TEMPLATES
    XSLT_TRACE(ctxt,XSLT_TRACE_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);
}

/**
 * xsltEvalXPathString:
 * @ctxt:  the XSLT transformation context
 * @comp:  the compiled XPath expression
 *
 * Process the expression using XPath and get a string
 *
 * Returns the computed string value or NULL, must be deallocated by the
 *    caller.
 */
xmlChar *
xsltEvalXPathString(xsltTransformContextPtr ctxt, xmlXPathCompExprPtr comp) {
    return(xsltEvalXPathStringNs(ctxt, comp, 0, NULL));
}

/**
 * xsltEvalTemplateString:
 * @ctxt:  the XSLT transformation context
 * @contextNode:  the current node in the source tree
 * @inst:  the XSLT instruction (xsl:comment, xsl:processing-instruction)
 *
 * Processes the sequence constructor of the given instruction on
 * @contextNode and converts the resulting tree to a string.
 * This is needed by e.g. xsl:comment and xsl:processing-instruction.
 *
 * Returns the computed string value or NULL; it's up to the caller to
 *         free the result.
 */
xmlChar *
xsltEvalTemplateString(xsltTransformContextPtr ctxt,
		       xmlNodePtr contextNode,
	               xmlNodePtr inst)
{
    xmlNodePtr oldInsert, insert = NULL;
    xmlChar *ret;

    if ((ctxt == NULL) || (contextNode == NULL) || (inst == NULL))
	return(NULL);

    if (inst->children == NULL)
	return(NULL);

    /*
    * This creates a temporary element-node to add the resulting
    * text content to.
    * OPTIMIZE TODO: Keep such an element-node in the transformation
    *  context to avoid creating it every time.
    */
    insert = xmlNewDocNode(ctxt->output, NULL,
	                   (const xmlChar *)"fake", NULL);
    if (insert == NULL) {
	xsltTransformError(ctxt, NULL, contextNode,
		"Failed to create temporary node\n");
	return(NULL);
    }
    oldInsert = ctxt->insert;
    ctxt->insert = insert;
    /*
    * OPTIMIZE TODO: if inst->children consists only of text-nodes.
    */
    xsltApplyOneTemplate(ctxt, contextNode, inst->children, NULL, NULL);

    ctxt->insert = oldInsert;

    ret = xmlNodeGetContent(insert);
    if (insert != NULL)
	xmlFreeNode(insert);
    return(ret);
}

/**
 * xsltAttrTemplateValueProcessNode:
 * @ctxt:  the XSLT transformation context
 * @str:  the attribute template node value
 * @inst:  the instruction (or LRE) in the stylesheet holding the
 *         attribute with an AVT
 *
 * Process the given string, allowing to pass a namespace mapping
 * context and return the new string value.
 *
 * Called by:
 *  - xsltAttrTemplateValueProcess() (templates.c)
 *  - xsltEvalAttrValueTemplate() (templates.c)
 *
 * QUESTION: Why is this function public? It is not used outside
 *  of templates.c.
 *
 * Returns the computed string value or NULL, must be deallocated by the
 *    caller.
 */
xmlChar *
xsltAttrTemplateValueProcessNode(xsltTransformContextPtr ctxt,
	  const xmlChar *str, xmlNodePtr inst)
{
    xmlChar *ret = NULL;
    const xmlChar *cur;
    xmlChar *expr, *val;
    xmlNsPtr *nsList = NULL;
    int nsNr = 0;

    if (str == NULL) return(NULL);
    if (*str == 0)
	return(xmlStrndup((xmlChar *)"", 0));

    cur = str;
    while (*cur != 0) {
	if (*cur == '{') {
	    if (*(cur+1) == '{') {	/* escaped '{' */
	        cur++;
		ret = xmlStrncat(ret, str, cur - str);
		cur++;
		str = cur;
		continue;
	    }
	    ret = xmlStrncat(ret, str, cur - str);
	    str = cur;
	    cur++;
	    while ((*cur != 0) && (*cur != '}')) cur++;
	    if (*cur == 0) {
	        xsltTransformError(ctxt, NULL, inst,
			"xsltAttrTemplateValueProcessNode: unmatched '{'\n");
		ret = xmlStrncat(ret, str, cur - str);
		return(ret);
	    }
	    str++;
	    expr = xmlStrndup(str, cur - str);
	    if (expr == NULL)
		return(ret);
	    else if (*expr == '{') {
		ret = xmlStrcat(ret, expr);
		xmlFree(expr);
	    } else {
		xmlXPathCompExprPtr comp;
		/*
		 * TODO: keep precompiled form around
		 */
		if ((nsList == NULL) && (inst != NULL)) {
		    int i = 0;

		    nsList = xmlGetNsList(inst->doc, inst);
		    if (nsList != NULL) {
			while (nsList[i] != NULL)
			    i++;
			nsNr = i;
		    }
		}
		comp = xmlXPathCompile(expr);
                val = xsltEvalXPathStringNs(ctxt, comp, nsNr, nsList);
		xmlXPathFreeCompExpr(comp);
		xmlFree(expr);
		if (val != NULL) {
		    ret = xmlStrcat(ret, val);
		    xmlFree(val);
		}
	    }
	    cur++;
	    str = cur;
	} else if (*cur == '}') {
	    cur++;
	    if (*cur == '}') {	/* escaped '}' */
		ret = xmlStrncat(ret, str, cur - str);
		cur++;
		str = cur;
		continue;
	    } else {
	        xsltTransformError(ctxt, NULL, inst,
		     "xsltAttrTemplateValueProcessNode: unmatched '}'\n");
	    }
	} else
	    cur++;
    }
    if (cur != str) {
	ret = xmlStrncat(ret, str, cur - str);
    }

    if (nsList != NULL)
	xmlFree(nsList);

    return(ret);
}

/**
 * xsltAttrTemplateValueProcess:
 * @ctxt:  the XSLT transformation context
 * @str:  the attribute template node value
 *
 * Process the given node and return the new string value.
 *
 * Returns the computed string value or NULL, must be deallocated by the
 *    caller.
 */
xmlChar *
xsltAttrTemplateValueProcess(xsltTransformContextPtr ctxt, const xmlChar *str) {
    return(xsltAttrTemplateValueProcessNode(ctxt, str, NULL));
}

/**
 * xsltEvalAttrValueTemplate:
 * @ctxt:  the XSLT transformation context
 * @inst:  the instruction (or LRE) in the stylesheet holding the
 *         attribute with an AVT
 * @name:  the attribute QName
 * @ns:  the attribute namespace URI
 *
 * Evaluate a attribute value template, i.e. the attribute value can
 * contain expressions contained in curly braces ({}) and those are
 * substituted by they computed value.
 *
 * Returns the computed string value or NULL, must be deallocated by the
 *    caller.
 */
xmlChar *
xsltEvalAttrValueTemplate(xsltTransformContextPtr ctxt, xmlNodePtr inst,
	                  const xmlChar *name, const xmlChar *ns)
{
    xmlChar *ret;
    xmlChar *expr;

    if ((ctxt == NULL) || (inst == NULL) || (name == NULL))
	return(NULL);

    expr = xsltGetNsProp(inst, name, ns);
    if (expr == NULL)
	return(NULL);

    /*
     * TODO: though now {} is detected ahead, it would still be good to
     *       optimize both functions to keep the splitted value if the
     *       attribute content and the XPath precompiled expressions around
     */

    ret = xsltAttrTemplateValueProcessNode(ctxt, expr, inst);
#ifdef WITH_XSLT_DEBUG_TEMPLATES
    XSLT_TRACE(ctxt,XSLT_TRACE_TEMPLATES,xsltGenericDebug(xsltGenericDebugContext,
	 "xsltEvalAttrValueTemplate: %s returns %s\n", expr, ret));
#endif
    if (expr != NULL)
	xmlFree(expr);
    return(ret);
}

/**
 * xsltEvalStaticAttrValueTemplate:
 * @style:  the XSLT stylesheet
 * @inst:  the instruction (or LRE) in the stylesheet holding the
 *         attribute with an AVT
 * @name:  the attribute Name
 * @ns:  the attribute namespace URI
 * @found:  indicator whether the attribute is present
 *
 * Check if an attribute value template has a static value, i.e. the
 * attribute value does not contain expressions contained in curly braces ({})
 *
 * Returns the static string value or NULL, must be deallocated by the
 *    caller.
 */
const xmlChar *
xsltEvalStaticAttrValueTemplate(xsltStylesheetPtr style, xmlNodePtr inst,
			const xmlChar *name, const xmlChar *ns, int *found) {
    const xmlChar *ret;
    xmlChar *expr;

    if ((style == NULL) || (inst == NULL) || (name == NULL))
	return(NULL);

    expr = xsltGetNsProp(inst, name, ns);
    if (expr == NULL) {
	*found = 0;
	return(NULL);
    }
    *found = 1;

    ret = xmlStrchr(expr, '{');
    if (ret != NULL) {
	xmlFree(expr);
	return(NULL);
    }
    ret = xmlDictLookup(style->dict, expr, -1);
    xmlFree(expr);
    return(ret);
}

/**
 * xsltAttrTemplateProcess:
 * @ctxt:  the XSLT transformation context
 * @target:  the element where the attribute will be grafted
 * @attr:  the attribute node of a literal result element
 *
 * Process one attribute of a Literal Result Element (in the stylesheet).
 * Evaluates Attribute Value Templates and copies the attribute over to
 * the result element.
 * This does *not* process attribute sets (xsl:use-attribute-set).
 * 
 *
 * Returns the generated attribute node.
 */
xmlAttrPtr
xsltAttrTemplateProcess(xsltTransformContextPtr ctxt, xmlNodePtr target,
	                xmlAttrPtr attr)
{
    const xmlChar *value;
    xmlAttrPtr ret;

    if ((ctxt == NULL) || (attr == NULL) || (target == NULL))
	return(NULL);
    
    if (attr->type != XML_ATTRIBUTE_NODE)
	return(NULL);

    /*
    * Skip all XSLT attributes.
    */
#ifdef XSLT_REFACTORED    
    if (attr->psvi == xsltXSLTAttrMarker)
	return(NULL);
#else
    if ((attr->ns != NULL) && xmlStrEqual(attr->ns->href, XSLT_NAMESPACE))
	return(NULL);
#endif
    /*
    * Get the value.
    */
    if (attr->children != NULL) {
	if ((attr->children->type != XML_TEXT_NODE) ||
	    (attr->children->next != NULL))
	{
	    xsltTransformError(ctxt, NULL, attr->parent,
		"Internal error: The children of an attribute node of a "
		"literal result element are not in the expected form.\n");
	    return(NULL);
	}
	value = attr->children->content;
	if (value == NULL)
	    value = xmlDictLookup(ctxt->dict, BAD_CAST "", 0);
    } else
	value = xmlDictLookup(ctxt->dict, BAD_CAST "", 0);
    /*
    * Overwrite duplicates.
    */
    ret = target->properties;
    while (ret != NULL) {
        if (((attr->ns != NULL) == (ret->ns != NULL)) &&
	    xmlStrEqual(ret->name, attr->name) &&
	    ((attr->ns == NULL) || xmlStrEqual(ret->ns->href, attr->ns->href)))
	{
	    break;
	}
        ret = ret->next;
    }
    if (ret != NULL) {	
        /* free the existing value */
	xmlFreeNodeList(ret->children);
	ret->children = ret->last = NULL;
	/*
	* Adjust ns-prefix if needed.
	*/
	if ((ret->ns != NULL) &&
	    (! xmlStrEqual(ret->ns->prefix, attr->ns->prefix)))
	{
	    ret->ns = xsltGetNamespace(ctxt, attr->parent, attr->ns, target);
	}
    } else {
        /* create a new attribute */
	if (attr->ns != NULL)
	    ret = xmlNewNsProp(target,
		xsltGetNamespace(ctxt, attr->parent, attr->ns, target),
		    attr->name, NULL);
	else
	    ret = xmlNewNsProp(target, NULL, attr->name, NULL);	
    }
    /*
    * Set the value.
    */
    if (ret != NULL) {
        xmlNodePtr text;

        text = xmlNewText(NULL);
	if (text != NULL) {
	    ret->last = ret->children = text;
	    text->parent = (xmlNodePtr) ret;
	    text->doc = ret->doc;

	    if (attr->psvi != NULL) {
		/*
		* Evaluate the Attribute Value Template.
		*/
		xmlChar *val;
		val = xsltEvalAVT(ctxt, attr->psvi, attr->parent);
		if (val == NULL) {
		    /*
		    * TODO: Damn, we need an easy mechanism to report
		    * qualified names!
		    */
		    if (attr->ns) {
			xsltTransformError(ctxt, NULL, attr->parent,
			    "Internal error: Failed to evaluate the AVT "
			    "of attribute '{%s}%s'.\n",
			    attr->ns->href, attr->name);
		    } else {
			xsltTransformError(ctxt, NULL, attr->parent,
			    "Internal error: Failed to evaluate the AVT "
			    "of attribute '%s'.\n",
			    attr->name);
		    }
		    text->content = xmlStrdup(BAD_CAST "");
		} else {
		    text->content = val;
		}
	    } else if ((ctxt->internalized) && (target != NULL) &&
	               (target->doc != NULL) &&
		       (target->doc->dict == ctxt->dict)) {
		text->content = (xmlChar *) value;
	    } else {
		text->content = xmlStrdup(value);
	    }
	}
    } else {
	if (attr->ns) {
	    xsltTransformError(ctxt, NULL, attr->parent,
	    	"Internal error: Failed to create attribute '{%s}%s'.\n",
		attr->ns->href, attr->name);
	} else {
	    xsltTransformError(ctxt, NULL, attr->parent,
	    	"Internal error: Failed to create attribute '%s'.\n",
		attr->name);
	}
    }
    return(ret);
}


/**
 * xsltAttrListTemplateProcess:
 * @ctxt:  the XSLT transformation context
 * @target:  the element where the attributes will be grafted
 * @attrs:  the first attribute
 *
 * Processes all attributes of a Literal Result Element.
 * Attribute references are applied via xsl:use-attribute-set
 * attributes.
 * Copies all non XSLT-attributes over to the @target element
 * and evaluates Attribute Value Templates.
 *
 * Called by xsltApplySequenceConstructor() (transform.c).
 *
 * Returns a new list of attribute nodes, or NULL in case of error.
 *         (Don't assign the result to @target->properties; if
 *         the result is NULL, you'll get memory leaks, since the
 *         attributes will be disattached.)
 */
xmlAttrPtr
xsltAttrListTemplateProcess(xsltTransformContextPtr ctxt, 
	                    xmlNodePtr target, xmlAttrPtr attrs)
{
    xmlAttrPtr attr, copy, last;
    xmlNodePtr oldInsert, text;
    xmlNsPtr origNs = NULL, copyNs = NULL;
    const xmlChar *value;
    xmlChar *valueAVT;

    if ((ctxt == NULL) || (target == NULL) || (attrs == NULL))
	return(NULL);

    oldInsert = ctxt->insert;
    ctxt->insert = target;        

    /*
    * Instantiate LRE-attributes.
    */
    if (target->properties) {
	last = target->properties;
	while (last->next != NULL)
	    last = last->next;
    } else {
	last = NULL;
    }
    attr = attrs;
    do {
	/*
	* Skip XSLT attributes.
	*/
#ifdef XSLT_REFACTORED
	if (attr->psvi == xsltXSLTAttrMarker) {
	    goto next_attribute;
	}
#else
	if ((attr->ns != NULL) &&
	    xmlStrEqual(attr->ns->href, XSLT_NAMESPACE))
	{
	    goto next_attribute;
	}
#endif
	/*
	* Get the value.
	*/
	if (attr->children != NULL) {
	    if ((attr->children->type != XML_TEXT_NODE) ||
		(attr->children->next != NULL))
	    {
		xsltTransformError(ctxt, NULL, attr->parent,
		    "Internal error: The children of an attribute node of a "
		    "literal result element are not in the expected form.\n");
		goto error;
	    }
	    value = attr->children->content;
	    if (value == NULL)
		value = xmlDictLookup(ctxt->dict, BAD_CAST "", 0);
	} else
	    value = xmlDictLookup(ctxt->dict, BAD_CAST "", 0);

	/*
	* Create a new attribute.
	*/
	copy = xmlNewDocProp(target->doc, attr->name, NULL);
	if (copy == NULL) {
	    if (attr->ns) {
		xsltTransformError(ctxt, NULL, attr->parent,
		    "Internal error: Failed to create attribute '{%s}%s'.\n",
		    attr->ns->href, attr->name);
	    } else {
		xsltTransformError(ctxt, NULL, attr->parent,
		    "Internal error: Failed to create attribute '%s'.\n",
		    attr->name);
	    }
	    goto error;
	}
	/*
	* Attach it to the target element.
	*/
	copy->parent = target;
	if (last == NULL) {
	    target->properties = copy;
	    last = copy;
	} else {
	    last->next = copy;
	    copy->prev = last;
	    last = copy;
	}
	/*
	* Set the namespace. Avoid lookups of same namespaces.
	*/
	if (attr->ns != origNs) {
	    origNs = attr->ns;
	    if (attr->ns != NULL) {
#ifdef XSLT_REFACTORED
		copyNs = xsltGetSpecialNamespace(ctxt, attr->parent,
		    attr->ns->href, attr->ns->prefix, target);
#else
		copyNs = xsltGetNamespace(ctxt, attr->parent,
		    attr->ns, target);
#endif
		if (copyNs == NULL)
		    goto error;
	    } else
		copyNs = NULL;
	}
	copy->ns = copyNs;

	/*
	* Set the value.
	*/
	text = xmlNewText(NULL);
	if (text != NULL) {
	    copy->last = copy->children = text;
	    text->parent = (xmlNodePtr) copy;
	    text->doc = copy->doc;

	    if (attr->psvi != NULL) {
		/*
		* Evaluate the Attribute Value Template.
		*/
		valueAVT = xsltEvalAVT(ctxt, attr->psvi, attr->parent);
		if (valueAVT == NULL) {
		    /*
		    * TODO: Damn, we need an easy mechanism to report
		    * qualified names!
		    */
		    if (attr->ns) {
			xsltTransformError(ctxt, NULL, attr->parent,
			    "Internal error: Failed to evaluate the AVT "
			    "of attribute '{%s}%s'.\n",
			    attr->ns->href, attr->name);
		    } else {
			xsltTransformError(ctxt, NULL, attr->parent,
			    "Internal error: Failed to evaluate the AVT "
			    "of attribute '%s'.\n",
			    attr->name);
		    }
		    text->content = xmlStrdup(BAD_CAST "");
		    goto error;
		} else {
		    text->content = valueAVT;
		}
	    } else if ((ctxt->internalized) &&
		(target->doc != NULL) &&
		(target->doc->dict == ctxt->dict))
	    {
		text->content = (xmlChar *) value;
	    } else {
		text->content = xmlStrdup(value);
	    }
            if ((copy != NULL) && (text != NULL) &&
                (xmlIsID(copy->doc, copy->parent, copy)))
                xmlAddID(NULL, copy->doc, text->content, copy);
	}

next_attribute:
	attr = attr->next;
    } while (attr != NULL);

    /*
    * Apply attribute-sets.
    * The creation of such attributes will not overwrite any existing
    * attribute.
    */
    attr = attrs;
    do {
#ifdef XSLT_REFACTORED
	if ((attr->psvi == xsltXSLTAttrMarker) &&
	    xmlStrEqual(attr->name, (const xmlChar *)"use-attribute-sets"))
	{
	    xsltApplyAttributeSet(ctxt, ctxt->node, (xmlNodePtr) attr, NULL);
	}
#else
	if ((attr->ns != NULL) &&
	    xmlStrEqual(attr->name, (const xmlChar *)"use-attribute-sets") &&
	    xmlStrEqual(attr->ns->href, XSLT_NAMESPACE))
	{
	    xsltApplyAttributeSet(ctxt, ctxt->node, (xmlNodePtr) attr, NULL);
	}
#endif
	attr = attr->next;
    } while (attr != NULL);

    ctxt->insert = oldInsert;
    return(target->properties);

error:
    ctxt->insert = oldInsert;
    return(NULL);
}


/**
 * xsltTemplateProcess:
 * @ctxt:  the XSLT transformation context
 * @node:  the attribute template node
 *
 * Obsolete. Don't use it.
 *
 * Returns NULL.
 */
xmlNodePtr *
xsltTemplateProcess(xsltTransformContextPtr ctxt ATTRIBUTE_UNUSED, xmlNodePtr node) {
    if (node == NULL)
	return(NULL);
    
    return(0);
}