blob: 8440b1031592ab61ddbf2bda003a4e2428ae41fb [file] [log] [blame]
/*
* attributes.c: Implementation of the XSLT attributes handling
*
* 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>
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_MATH_H
#include <math.h>
#endif
#ifdef HAVE_FLOAT_H
#include <float.h>
#endif
#ifdef HAVE_IEEEFP_H
#include <ieeefp.h>
#endif
#ifdef HAVE_NAN_H
#include <nan.h>
#endif
#ifdef HAVE_CTYPE_H
#include <ctype.h>
#endif
#include <libxml/xmlmemory.h>
#include <libxml/tree.h>
#include <libxml/hash.h>
#include <libxml/xmlerror.h>
#include <libxml/uri.h>
#include <libxml/parserInternals.h>
#include "xslt.h"
#include "xsltInternals.h"
#include "xsltutils.h"
#include "attributes.h"
#include "namespaces.h"
#include "templates.h"
#include "imports.h"
#include "transform.h"
#include "preproc.h"
#define WITH_XSLT_DEBUG_ATTRIBUTES
#ifdef WITH_XSLT_DEBUG
#define WITH_XSLT_DEBUG_ATTRIBUTES
#endif
/*
* TODO: merge attribute sets from different import precedence.
* all this should be precomputed just before the transformation
* starts or at first hit with a cache in the context.
* The simple way for now would be to not allow redefinition of
* attributes once generated in the output tree, possibly costlier.
*/
/*
* Useful macros
*/
#ifdef IS_BLANK
#undef IS_BLANK
#endif
#define IS_BLANK(c) (((c) == 0x20) || ((c) == 0x09) || ((c) == 0xA) || \
((c) == 0x0D))
#define IS_BLANK_NODE(n) \
(((n)->type == XML_TEXT_NODE) && (xsltIsBlank((n)->content)))
/*
* The in-memory structure corresponding to an XSLT Attribute in
* an attribute set
*/
typedef struct _xsltAttrElem xsltAttrElem;
typedef xsltAttrElem *xsltAttrElemPtr;
struct _xsltAttrElem {
struct _xsltAttrElem *next;/* chained list */
xmlNodePtr attr; /* the xsl:attribute definition */
const xmlChar *set; /* or the attribute set */
const xmlChar *ns; /* and its namespace */
};
/************************************************************************
* *
* XSLT Attribute handling *
* *
************************************************************************/
/**
* xsltNewAttrElem:
* @attr: the new xsl:attribute node
*
* Create a new XSLT AttrElem
*
* Returns the newly allocated xsltAttrElemPtr or NULL in case of error
*/
static xsltAttrElemPtr
xsltNewAttrElem(xmlNodePtr attr) {
xsltAttrElemPtr cur;
cur = (xsltAttrElemPtr) xmlMalloc(sizeof(xsltAttrElem));
if (cur == NULL) {
xsltGenericError(xsltGenericErrorContext,
"xsltNewAttrElem : malloc failed\n");
return(NULL);
}
memset(cur, 0, sizeof(xsltAttrElem));
cur->attr = attr;
return(cur);
}
/**
* xsltFreeAttrElem:
* @attr: an XSLT AttrElem
*
* Free up the memory allocated by @attr
*/
static void
xsltFreeAttrElem(xsltAttrElemPtr attr) {
xmlFree(attr);
}
/**
* xsltFreeAttrElemList:
* @list: an XSLT AttrElem list
*
* Free up the memory allocated by @list
*/
static void
xsltFreeAttrElemList(xsltAttrElemPtr list) {
xsltAttrElemPtr next;
while (list != NULL) {
next = list->next;
xsltFreeAttrElem(list);
list = next;
}
}
#ifdef XSLT_REFACTORED
/*
* This was moved to xsltParseStylesheetAttributeSet().
*/
#else
/**
* xsltAddAttrElemList:
* @list: an XSLT AttrElem list
* @attr: the new xsl:attribute node
*
* Add the new attribute to the list.
*
* Returns the new list pointer
*/
static xsltAttrElemPtr
xsltAddAttrElemList(xsltAttrElemPtr list, xmlNodePtr attr) {
xsltAttrElemPtr next, cur;
if (attr == NULL)
return(list);
if (list == NULL)
return(xsltNewAttrElem(attr));
cur = list;
while (cur != NULL) {
next = cur->next;
if (cur->attr == attr)
return(cur);
if (cur->next == NULL) {
cur->next = xsltNewAttrElem(attr);
return(list);
}
cur = next;
}
return(list);
}
#endif /* XSLT_REFACTORED */
/**
* xsltMergeAttrElemList:
* @list: an XSLT AttrElem list
* @old: another XSLT AttrElem list
*
* Add all the attributes from list @old to list @list,
* but drop redefinition of existing values.
*
* Returns the new list pointer
*/
static xsltAttrElemPtr
xsltMergeAttrElemList(xsltStylesheetPtr style,
xsltAttrElemPtr list, xsltAttrElemPtr old) {
xsltAttrElemPtr cur;
int add;
while (old != NULL) {
if ((old->attr == NULL) && (old->set == NULL)) {
old = old->next;
continue;
}
/*
* Check that the attribute is not yet in the list
*/
cur = list;
add = 1;
while (cur != NULL) {
if ((cur->attr == NULL) && (cur->set == NULL)) {
if (cur->next == NULL)
break;
cur = cur->next;
continue;
}
if ((cur->set != NULL) && (cur->set == old->set)) {
add = 0;
break;
}
if (cur->set != NULL) {
if (cur->next == NULL)
break;
cur = cur->next;
continue;
}
if (old->set != NULL) {
if (cur->next == NULL)
break;
cur = cur->next;
continue;
}
if (cur->attr == old->attr) {
xsltGenericError(xsltGenericErrorContext,
"xsl:attribute-set : use-attribute-sets recursion detected\n");
return(list);
}
if (cur->next == NULL)
break;
cur = cur->next;
}
if (add == 1) {
/*
* Changed to use the string-dict, rather than duplicating
* @set and @ns; this fixes bug #340400.
*/
if (cur == NULL) {
list = xsltNewAttrElem(old->attr);
if (old->set != NULL) {
list->set = xmlDictLookup(style->dict, old->set, -1);
if (old->ns != NULL)
list->ns = xmlDictLookup(style->dict, old->ns, -1);
}
} else if (add) {
cur->next = xsltNewAttrElem(old->attr);
if (old->set != NULL) {
cur->next->set = xmlDictLookup(style->dict, old->set, -1);
if (old->ns != NULL)
cur->next->ns = xmlDictLookup(style->dict, old->ns, -1);
}
}
}
old = old->next;
}
return(list);
}
/************************************************************************
* *
* Module interfaces *
* *
************************************************************************/
/**
* xsltParseStylesheetAttributeSet:
* @style: the XSLT stylesheet
* @cur: the "attribute-set" element
*
* parse an XSLT stylesheet attribute-set element
*/
void
xsltParseStylesheetAttributeSet(xsltStylesheetPtr style, xmlNodePtr cur) {
const xmlChar *ncname;
const xmlChar *prefix;
xmlChar *value;
xmlNodePtr child;
xsltAttrElemPtr attrItems;
if ((cur == NULL) || (style == NULL) || (cur->type != XML_ELEMENT_NODE))
return;
value = xmlGetNsProp(cur, (const xmlChar *)"name", NULL);
if ((value == NULL) || (*value == 0)) {
xsltGenericError(xsltGenericErrorContext,
"xsl:attribute-set : name is missing\n");
if (value)
xmlFree(value);
return;
}
ncname = xsltSplitQName(style->dict, value, &prefix);
xmlFree(value);
value = NULL;
if (style->attributeSets == NULL) {
#ifdef WITH_XSLT_DEBUG_ATTRIBUTES
xsltGenericDebug(xsltGenericDebugContext,
"creating attribute set table\n");
#endif
style->attributeSets = xmlHashCreate(10);
}
if (style->attributeSets == NULL)
return;
attrItems = xmlHashLookup2(style->attributeSets, ncname, prefix);
/*
* Parse the content. Only xsl:attribute elements are allowed.
*/
child = cur->children;
while (child != NULL) {
/*
* Report invalid nodes.
*/
if ((child->type != XML_ELEMENT_NODE) ||
(child->ns == NULL) ||
(! IS_XSLT_ELEM(child)))
{
if (child->type == XML_ELEMENT_NODE)
xsltTransformError(NULL, style, child,
"xsl:attribute-set : unexpected child %s\n",
child->name);
else
xsltTransformError(NULL, style, child,
"xsl:attribute-set : child of unexpected type\n");
} else if (!IS_XSLT_NAME(child, "attribute")) {
xsltTransformError(NULL, style, child,
"xsl:attribute-set : unexpected child xsl:%s\n",
child->name);
} else {
#ifdef XSLT_REFACTORED
xsltAttrElemPtr nextAttr, curAttr;
/*
* Process xsl:attribute
* ---------------------
*/
#ifdef WITH_XSLT_DEBUG_ATTRIBUTES
xsltGenericDebug(xsltGenericDebugContext,
"add attribute to list %s\n", ncname);
#endif
/*
* The following was taken over from
* xsltAddAttrElemList().
*/
if (attrItems == NULL) {
attrItems = xsltNewAttrElem(child);
} else {
curAttr = attrItems;
while (curAttr != NULL) {
nextAttr = curAttr->next;
if (curAttr->attr == child) {
/*
* URGENT TODO: Can somebody explain
* why attrItems is set to curAttr
* here? Is this somehow related to
* avoidance of recursions?
*/
attrItems = curAttr;
goto next_child;
}
if (curAttr->next == NULL)
curAttr->next = xsltNewAttrElem(child);
curAttr = nextAttr;
}
}
/*
* Parse the xsl:attribute and its content.
*/
xsltParseAnyXSLTElem(XSLT_CCTXT(style), child);
#else
#ifdef WITH_XSLT_DEBUG_ATTRIBUTES
xsltGenericDebug(xsltGenericDebugContext,
"add attribute to list %s\n", ncname);
#endif
/*
* OLD behaviour:
*/
attrItems = xsltAddAttrElemList(attrItems, child);
#endif
}
#ifdef XSLT_REFACTORED
next_child:
#endif
child = child->next;
}
/*
* Process attribue "use-attribute-sets".
*/
/* TODO check recursion */
value = xmlGetNsProp(cur, (const xmlChar *)"use-attribute-sets",
NULL);
if (value != NULL) {
const xmlChar *curval, *endval;
curval = value;
while (*curval != 0) {
while (IS_BLANK(*curval)) curval++;
if (*curval == 0)
break;
endval = curval;
while ((*endval != 0) && (!IS_BLANK(*endval))) endval++;
curval = xmlDictLookup(style->dict, curval, endval - curval);
if (curval) {
const xmlChar *ncname2 = NULL;
const xmlChar *prefix2 = NULL;
xsltAttrElemPtr refAttrItems;
#ifdef WITH_XSLT_DEBUG_ATTRIBUTES
xsltGenericDebug(xsltGenericDebugContext,
"xsl:attribute-set : %s adds use %s\n", ncname, curval);
#endif
ncname2 = xsltSplitQName(style->dict, curval, &prefix2);
refAttrItems = xsltNewAttrElem(NULL);
if (refAttrItems != NULL) {
refAttrItems->set = ncname2;
refAttrItems->ns = prefix2;
attrItems = xsltMergeAttrElemList(style,
attrItems, refAttrItems);
xsltFreeAttrElem(refAttrItems);
}
}
curval = endval;
}
xmlFree(value);
value = NULL;
}
/*
* Update the value
*/
/*
* TODO: Why is this dummy entry needed.?
*/
if (attrItems == NULL)
attrItems = xsltNewAttrElem(NULL);
xmlHashUpdateEntry2(style->attributeSets, ncname, prefix, attrItems, NULL);
#ifdef WITH_XSLT_DEBUG_ATTRIBUTES
xsltGenericDebug(xsltGenericDebugContext,
"updated attribute list %s\n", ncname);
#endif
}
/**
* xsltGetSAS:
* @style: the XSLT stylesheet
* @name: the attribute list name
* @ns: the attribute list namespace
*
* lookup an attribute set based on the style cascade
*
* Returns the attribute set or NULL
*/
static xsltAttrElemPtr
xsltGetSAS(xsltStylesheetPtr style, const xmlChar *name, const xmlChar *ns) {
xsltAttrElemPtr values;
while (style != NULL) {
values = xmlHashLookup2(style->attributeSets, name, ns);
if (values != NULL)
return(values);
style = xsltNextImport(style);
}
return(NULL);
}
/**
* xsltResolveSASCallbackInt:
* @style: the XSLT stylesheet
*
* resolve the references in an attribute set.
*/
static void
xsltResolveSASCallbackInt(xsltAttrElemPtr values, xsltStylesheetPtr style,
const xmlChar *name, const xmlChar *ns,
int depth) {
xsltAttrElemPtr tmp;
xsltAttrElemPtr refs;
tmp = values;
if ((name == NULL) || (name[0] == 0))
return;
if (depth > 100) {
xsltGenericError(xsltGenericErrorContext,
"xsl:attribute-set : use-attribute-sets recursion detected on %s\n",
name);
return;
}
while (tmp != NULL) {
if (tmp->set != NULL) {
/*
* Check against cycles !
*/
if ((xmlStrEqual(name, tmp->set)) && (xmlStrEqual(ns, tmp->ns))) {
xsltGenericError(xsltGenericErrorContext,
"xsl:attribute-set : use-attribute-sets recursion detected on %s\n",
name);
} else {
#ifdef WITH_XSLT_DEBUG_ATTRIBUTES
xsltGenericDebug(xsltGenericDebugContext,
"Importing attribute list %s\n", tmp->set);
#endif
refs = xsltGetSAS(style, tmp->set, tmp->ns);
if (refs == NULL) {
xsltGenericError(xsltGenericErrorContext,
"xsl:attribute-set : use-attribute-sets %s reference missing %s\n",
name, tmp->set);
} else {
/*
* recurse first for cleanup
*/
xsltResolveSASCallbackInt(refs, style, name, ns, depth + 1);
/*
* Then merge
*/
xsltMergeAttrElemList(style, values, refs);
/*
* Then suppress the reference
*/
tmp->set = NULL;
tmp->ns = NULL;
}
}
}
tmp = tmp->next;
}
}
/**
* xsltResolveSASCallback,:
* @style: the XSLT stylesheet
*
* resolve the references in an attribute set.
*/
static void
xsltResolveSASCallback(xsltAttrElemPtr values, xsltStylesheetPtr style,
const xmlChar *name, const xmlChar *ns,
ATTRIBUTE_UNUSED const xmlChar *ignored) {
xsltResolveSASCallbackInt(values, style, name, ns, 1);
}
/**
* xsltMergeSASCallback,:
* @style: the XSLT stylesheet
*
* Merge an attribute set from an imported stylesheet.
*/
static void
xsltMergeSASCallback(xsltAttrElemPtr values, xsltStylesheetPtr style,
const xmlChar *name, const xmlChar *ns,
ATTRIBUTE_UNUSED const xmlChar *ignored) {
int ret;
xsltAttrElemPtr topSet;
ret = xmlHashAddEntry2(style->attributeSets, name, ns, values);
if (ret < 0) {
/*
* Add failed, this attribute set can be removed.
*/
#ifdef WITH_XSLT_DEBUG_ATTRIBUTES
xsltGenericDebug(xsltGenericDebugContext,
"attribute set %s present already in top stylesheet"
" - merging\n", name);
#endif
topSet = xmlHashLookup2(style->attributeSets, name, ns);
if (topSet==NULL) {
xsltGenericError(xsltGenericErrorContext,
"xsl:attribute-set : logic error merging from imports for"
" attribute-set %s\n", name);
} else {
topSet = xsltMergeAttrElemList(style, topSet, values);
xmlHashUpdateEntry2(style->attributeSets, name, ns, topSet, NULL);
}
xsltFreeAttrElemList(values);
#ifdef WITH_XSLT_DEBUG_ATTRIBUTES
} else {
xsltGenericDebug(xsltGenericDebugContext,
"attribute set %s moved to top stylesheet\n",
name);
#endif
}
}
/**
* xsltResolveStylesheetAttributeSet:
* @style: the XSLT stylesheet
*
* resolve the references between attribute sets.
*/
void
xsltResolveStylesheetAttributeSet(xsltStylesheetPtr style) {
xsltStylesheetPtr cur;
#ifdef WITH_XSLT_DEBUG_ATTRIBUTES
xsltGenericDebug(xsltGenericDebugContext,
"Resolving attribute sets references\n");
#endif
/*
* First aggregate all the attribute sets definitions from the imports
*/
cur = xsltNextImport(style);
while (cur != NULL) {
if (cur->attributeSets != NULL) {
if (style->attributeSets == NULL) {
#ifdef WITH_XSLT_DEBUG_ATTRIBUTES
xsltGenericDebug(xsltGenericDebugContext,
"creating attribute set table\n");
#endif
style->attributeSets = xmlHashCreate(10);
}
xmlHashScanFull(cur->attributeSets,
(xmlHashScannerFull) xsltMergeSASCallback, style);
/*
* the attribute lists have either been migrated to style
* or freed directly in xsltMergeSASCallback()
*/
xmlHashFree(cur->attributeSets, NULL);
cur->attributeSets = NULL;
}
cur = xsltNextImport(cur);
}
/*
* Then resolve all the references and computes the resulting sets
*/
if (style->attributeSets != NULL) {
xmlHashScanFull(style->attributeSets,
(xmlHashScannerFull) xsltResolveSASCallback, style);
}
}
/**
* xsltAttributeInternal:
* @ctxt: a XSLT process context
* @node: the current node in the source tree
* @inst: the xsl:attribute element
* @comp: precomputed information
* @fromAttributeSet: the attribute comes from an attribute-set
*
* Process the xslt attribute node on the source node
*/
static void
xsltAttributeInternal(xsltTransformContextPtr ctxt,
xmlNodePtr contextNode,
xmlNodePtr inst,
xsltStylePreCompPtr castedComp,
int fromAttributeSet)
{
#ifdef XSLT_REFACTORED
xsltStyleItemAttributePtr comp =
(xsltStyleItemAttributePtr) castedComp;
#else
xsltStylePreCompPtr comp = castedComp;
#endif
xmlNodePtr targetElem;
xmlChar *prop = NULL;
const xmlChar *name = NULL, *prefix = NULL, *nsName = NULL;
xmlChar *value = NULL;
xmlNsPtr ns = NULL;
xmlAttrPtr attr;
if ((ctxt == NULL) || (contextNode == NULL) || (inst == NULL) ||
(inst->type != XML_ELEMENT_NODE) )
return;
/*
* A comp->has_name == 0 indicates that we need to skip this instruction,
* since it was evaluated to be invalid already during compilation.
*/
if (!comp->has_name)
return;
/*
* BIG NOTE: This previously used xsltGetSpecialNamespace() and
* xsltGetNamespace(), but since both are not appropriate, we
* will process namespace lookup here to avoid adding yet another
* ns-lookup function to namespaces.c.
*/
/*
* SPEC XSLT 1.0: Error cases:
* - Creating nodes other than text nodes during the instantiation of
* the content of the xsl:attribute element; implementations may
* either signal the error or ignore the offending nodes."
*/
if (comp == NULL) {
xsltTransformError(ctxt, NULL, inst,
"Internal error in xsltAttributeInternal(): "
"The XSLT 'attribute' instruction was not compiled.\n");
return;
}
/*
* TODO: Shouldn't ctxt->insert == NULL be treated as an internal error?
* So report an internal error?
*/
if (ctxt->insert == NULL)
return;
/*
* SPEC XSLT 1.0:
* "Adding an attribute to a node that is not an element;
* implementations may either signal the error or ignore the attribute."
*
* TODO: I think we should signal such errors in the future, and maybe
* provide an option to ignore such errors.
*/
targetElem = ctxt->insert;
if (targetElem->type != XML_ELEMENT_NODE)
return;
/*
* SPEC XSLT 1.0:
* "Adding an attribute to an element after children have been added
* to it; implementations may either signal the error or ignore the
* attribute."
*
* TODO: We should decide whether not to report such errors or
* to ignore them; note that we *ignore* if the parent is not an
* element, but here we report an error.
*/
if (targetElem->children != NULL) {
/*
* NOTE: Ah! This seems to be intended to support streamed
* result generation!.
*/
xsltTransformError(ctxt, NULL, inst,
"xsl:attribute: Cannot add attributes to an "
"element if children have been already added "
"to the element.\n");
return;
}
/*
* Process the name
* ----------------
*/
#ifdef WITH_DEBUGGER
if (ctxt->debugStatus != XSLT_DEBUG_NONE)
xslHandleDebugger(inst, contextNode, NULL, ctxt);
#endif
if (comp->name == NULL) {
/* TODO: fix attr acquisition wrt to the XSLT namespace */
prop = xsltEvalAttrValueTemplate(ctxt, inst,
(const xmlChar *) "name", XSLT_NAMESPACE);
if (prop == NULL) {
xsltTransformError(ctxt, NULL, inst,
"xsl:attribute: The attribute 'name' is missing.\n");
goto error;
}
if (xmlValidateQName(prop, 0)) {
xsltTransformError(ctxt, NULL, inst,
"xsl:attribute: The effective name '%s' is not a "
"valid QName.\n", prop);
/* we fall through to catch any further errors, if possible */
}
/*
* Reject a name of "xmlns".
*/
if (xmlStrEqual(prop, BAD_CAST "xmlns")) {
xsltTransformError(ctxt, NULL, inst,
"xsl:attribute: The effective name 'xmlns' is not allowed.\n");
xmlFree(prop);
goto error;
}
name = xsltSplitQName(ctxt->dict, prop, &prefix);
xmlFree(prop);
} else {
/*
* The "name" value was static.
*/
#ifdef XSLT_REFACTORED
prefix = comp->nsPrefix;
name = comp->name;
#else
name = xsltSplitQName(ctxt->dict, comp->name, &prefix);
#endif
}
/*
* Process namespace semantics
* ---------------------------
*
* Evaluate the namespace name.
*/
if (comp->has_ns) {
/*
* The "namespace" attribute was existent.
*/
if (comp->ns != NULL) {
/*
* No AVT; just plain text for the namespace name.
*/
if (comp->ns[0] != 0)
nsName = comp->ns;
} else {
xmlChar *tmpNsName;
/*
* Eval the AVT.
*/
/* TODO: check attr acquisition wrt to the XSLT namespace */
tmpNsName = xsltEvalAttrValueTemplate(ctxt, inst,
(const xmlChar *) "namespace", XSLT_NAMESPACE);
/*
* This fixes bug #302020: The AVT might also evaluate to the
* empty string; this means that the empty string also indicates
* "no namespace".
* SPEC XSLT 1.0:
* "If the string is empty, then the expanded-name of the
* attribute has a null namespace URI."
*/
if ((tmpNsName != NULL) && (tmpNsName[0] != 0))
nsName = xmlDictLookup(ctxt->dict, BAD_CAST tmpNsName, -1);
xmlFree(tmpNsName);
}
if (xmlStrEqual(nsName, BAD_CAST "http://www.w3.org/2000/xmlns/")) {
xsltTransformError(ctxt, NULL, inst,
"xsl:attribute: Namespace http://www.w3.org/2000/xmlns/ "
"forbidden.\n");
goto error;
}
if (xmlStrEqual(nsName, XML_XML_NAMESPACE)) {
prefix = BAD_CAST "xml";
} else if (xmlStrEqual(prefix, BAD_CAST "xml")) {
prefix = NULL;
}
} else if (prefix != NULL) {
/*
* SPEC XSLT 1.0:
* "If the namespace attribute is not present, then the QName is
* expanded into an expanded-name using the namespace declarations
* in effect for the xsl:attribute element, *not* including any
* default namespace declaration."
*/
ns = xmlSearchNs(inst->doc, inst, prefix);
if (ns == NULL) {
/*
* Note that this is treated as an error now (checked with
* Saxon, Xalan-J and MSXML).
*/
xsltTransformError(ctxt, NULL, inst,
"xsl:attribute: The QName '%s:%s' has no "
"namespace binding in scope in the stylesheet; "
"this is an error, since the namespace was not "
"specified by the instruction itself.\n", prefix, name);
} else
nsName = ns->href;
}
if (fromAttributeSet) {
/*
* This tries to ensure that xsl:attribute(s) coming
* from an xsl:attribute-set won't override attribute of
* literal result elements or of explicit xsl:attribute(s).
* URGENT TODO: This might be buggy, since it will miss to
* overwrite two equal attributes both from attribute sets.
*/
attr = xmlHasNsProp(targetElem, name, nsName);
if (attr != NULL)
return;
}
/*
* Find/create a matching ns-decl in the result tree.
*/
ns = NULL;
#if 0
if (0) {
/*
* OPTIMIZE TODO: How do we know if we are adding to a
* fragment or to the result tree?
*
* If we are adding to a result tree fragment (i.e., not to the
* actual result tree), we'll don't bother searching for the
* ns-decl, but just store it in the dummy-doc of the result
* tree fragment.
*/
if (nsName != NULL) {
/*
* TODO: Get the doc of @targetElem.
*/
ns = xsltTreeAcquireStoredNs(some doc, nsName, prefix);
}
}
#endif
if (nsName != NULL) {
/*
* Something about ns-prefixes:
* SPEC XSLT 1.0:
* "XSLT processors may make use of the prefix of the QName specified
* in the name attribute when selecting the prefix used for outputting
* the created attribute as XML; however, they are not required to do
* so and, if the prefix is xmlns, they must not do so"
*/
/*
* xsl:attribute can produce a scenario where the prefix is NULL,
* so generate a prefix.
*/
if ((prefix == NULL) || xmlStrEqual(prefix, BAD_CAST "xmlns")) {
xmlChar *pref = xmlStrdup(BAD_CAST "ns_1");
ns = xsltGetSpecialNamespace(ctxt, inst, nsName, pref, targetElem);
xmlFree(pref);
} else {
ns = xsltGetSpecialNamespace(ctxt, inst, nsName, prefix,
targetElem);
}
if (ns == NULL) {
xsltTransformError(ctxt, NULL, inst,
"Namespace fixup error: Failed to acquire an in-scope "
"namespace binding for the generated attribute '{%s}%s'.\n",
nsName, name);
goto error;
}
}
/*
* Construction of the value
* -------------------------
*/
if (inst->children == NULL) {
/*
* No content.
* TODO: Do we need to put the empty string in ?
*/
attr = xmlSetNsProp(ctxt->insert, ns, name, (const xmlChar *) "");
} else if ((inst->children->next == NULL) &&
((inst->children->type == XML_TEXT_NODE) ||
(inst->children->type == XML_CDATA_SECTION_NODE)))
{
xmlNodePtr copyTxt;
/*
* xmlSetNsProp() will take care of duplicates.
*/
attr = xmlSetNsProp(ctxt->insert, ns, name, NULL);
if (attr == NULL) /* TODO: report error ? */
goto error;
/*
* This was taken over from xsltCopyText() (transform.c).
*/
if (ctxt->internalized &&
(ctxt->insert->doc != NULL) &&
(ctxt->insert->doc->dict == ctxt->dict))
{
copyTxt = xmlNewText(NULL);
if (copyTxt == NULL) /* TODO: report error */
goto error;
/*
* This is a safe scenario where we don't need to lookup
* the dict.
*/
copyTxt->content = inst->children->content;
/*
* Copy "disable-output-escaping" information.
* TODO: Does this have any effect for attribute values
* anyway?
*/
if (inst->children->name == xmlStringTextNoenc)
copyTxt->name = xmlStringTextNoenc;
} else {
/*
* Copy the value.
*/
copyTxt = xmlNewText(inst->children->content);
if (copyTxt == NULL) /* TODO: report error */
goto error;
}
attr->children = attr->last = copyTxt;
copyTxt->parent = (xmlNodePtr) attr;
copyTxt->doc = attr->doc;
/*
* Copy "disable-output-escaping" information.
* TODO: Does this have any effect for attribute values
* anyway?
*/
if (inst->children->name == xmlStringTextNoenc)
copyTxt->name = xmlStringTextNoenc;
/*
* since we create the attribute without content IDness must be
* asserted as a second step
*/
if ((copyTxt->content != NULL) &&
(xmlIsID(attr->doc, attr->parent, attr)))
xmlAddID(NULL, attr->doc, copyTxt->content, attr);
} else {
/*
* The sequence constructor might be complex, so instantiate it.
*/
value = xsltEvalTemplateString(ctxt, contextNode, inst);
if (value != NULL) {
attr = xmlSetNsProp(ctxt->insert, ns, name, value);
xmlFree(value);
} else {
/*
* TODO: Do we have to add the empty string to the attr?
* TODO: Does a value of NULL indicate an
* error in xsltEvalTemplateString() ?
*/
attr = xmlSetNsProp(ctxt->insert, ns, name,
(const xmlChar *) "");
}
}
error:
return;
}
/**
* xsltAttribute:
* @ctxt: a XSLT process context
* @node: the node in the source tree.
* @inst: the xslt attribute node
* @comp: precomputed information
*
* Process the xslt attribute node on the source node
*/
void
xsltAttribute(xsltTransformContextPtr ctxt, xmlNodePtr node,
xmlNodePtr inst, xsltStylePreCompPtr comp) {
xsltAttributeInternal(ctxt, node, inst, comp, 0);
}
/**
* xsltApplyAttributeSet:
* @ctxt: the XSLT stylesheet
* @node: the node in the source tree.
* @inst: the attribute node "xsl:use-attribute-sets"
* @attrSets: the list of QNames of the attribute-sets to be applied
*
* Apply the xsl:use-attribute-sets.
* If @attrSets is NULL, then @inst will be used to exctract this
* value.
* If both, @attrSets and @inst, are NULL, then this will do nothing.
*/
void
xsltApplyAttributeSet(xsltTransformContextPtr ctxt, xmlNodePtr node,
xmlNodePtr inst,
const xmlChar *attrSets)
{
const xmlChar *ncname = NULL;
const xmlChar *prefix = NULL;
const xmlChar *curstr, *endstr;
xsltAttrElemPtr attrs;
xsltStylesheetPtr style;
if (attrSets == NULL) {
if (inst == NULL)
return;
else {
/*
* Extract the value from @inst.
*/
if (inst->type == XML_ATTRIBUTE_NODE) {
if ( ((xmlAttrPtr) inst)->children != NULL)
attrSets = ((xmlAttrPtr) inst)->children->content;
}
if (attrSets == NULL) {
/*
* TODO: Return an error?
*/
return;
}
}
}
/*
* Parse/apply the list of QNames.
*/
curstr = attrSets;
while (*curstr != 0) {
while (IS_BLANK(*curstr))
curstr++;
if (*curstr == 0)
break;
endstr = curstr;
while ((*endstr != 0) && (!IS_BLANK(*endstr)))
endstr++;
curstr = xmlDictLookup(ctxt->dict, curstr, endstr - curstr);
if (curstr) {
/*
* TODO: Validate the QName.
*/
#ifdef WITH_XSLT_DEBUG_curstrUTES
xsltGenericDebug(xsltGenericDebugContext,
"apply curstrute set %s\n", curstr);
#endif
ncname = xsltSplitQName(ctxt->dict, curstr, &prefix);
style = ctxt->style;
#ifdef WITH_DEBUGGER
if ((style != NULL) &&
(style->attributeSets != NULL) &&
(ctxt->debugStatus != XSLT_DEBUG_NONE))
{
attrs =
xmlHashLookup2(style->attributeSets, ncname, prefix);
if ((attrs != NULL) && (attrs->attr != NULL))
xslHandleDebugger(attrs->attr->parent, node, NULL,
ctxt);
}
#endif
/*
* Lookup the referenced curstrute-set.
*/
while (style != NULL) {
attrs =
xmlHashLookup2(style->attributeSets, ncname, prefix);
while (attrs != NULL) {
if (attrs->attr != NULL) {
xsltAttributeInternal(ctxt, node, attrs->attr,
attrs->attr->psvi, 1);
}
attrs = attrs->next;
}
style = xsltNextImport(style);
}
}
curstr = endstr;
}
}
/**
* xsltFreeAttributeSetsHashes:
* @style: an XSLT stylesheet
*
* Free up the memory used by attribute sets
*/
void
xsltFreeAttributeSetsHashes(xsltStylesheetPtr style) {
if (style->attributeSets != NULL)
xmlHashFree((xmlHashTablePtr) style->attributeSets,
(xmlHashDeallocator) xsltFreeAttrElemList);
style->attributeSets = NULL;
}