/* * 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 */ #include "precomp.h" #define WITH_XSLT_DEBUG_ATTRIBUTES #ifdef WITH_XSLT_DEBUG #define WITH_XSLT_DEBUG_ATTRIBUTES #endif /* * 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))) #define ATTRSET_UNRESOLVED 0 #define ATTRSET_RESOLVING 1 #define ATTRSET_RESOLVED 2 /* * 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 */ }; typedef struct _xsltUseAttrSet xsltUseAttrSet; typedef xsltUseAttrSet *xsltUseAttrSetPtr; struct _xsltUseAttrSet { struct _xsltUseAttrSet *next; /* chained list */ const xmlChar *ncname; const xmlChar *ns; }; typedef struct _xsltAttrSet xsltAttrSet; typedef xsltAttrSet *xsltAttrSetPtr; struct _xsltAttrSet { int state; xsltAttrElemPtr attrs; /* list head */ xsltUseAttrSetPtr useAttrSets; /* list head */ }; typedef struct _xsltAttrSetContext xsltAttrSetContext; typedef xsltAttrSetContext *xsltAttrSetContextPtr; struct _xsltAttrSetContext { xsltStylesheetPtr topStyle; xsltStylesheetPtr style; }; static void xsltResolveAttrSet(xsltAttrSetPtr set, xsltStylesheetPtr topStyle, xsltStylesheetPtr style, const xmlChar *name, const xmlChar *ns, int depth); /************************************************************************ * * * 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; } } /** * 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 (next == NULL) { cur->next = xsltNewAttrElem(attr); return(list); } cur = next; } return(list); } /** * xsltNewUseAttrSet: * @ncname: local name * @ns: namespace URI * * Create a new XSLT UseAttrSet * * Returns the newly allocated xsltUseAttrSetPtr or NULL in case of error. */ static xsltUseAttrSetPtr xsltNewUseAttrSet(const xmlChar *ncname, const xmlChar *ns) { xsltUseAttrSetPtr cur; cur = (xsltUseAttrSetPtr) xmlMalloc(sizeof(xsltUseAttrSet)); if (cur == NULL) { xsltGenericError(xsltGenericErrorContext, "xsltNewUseAttrSet : malloc failed\n"); return(NULL); } memset(cur, 0, sizeof(xsltUseAttrSet)); cur->ncname = ncname; cur->ns = ns; return(cur); } /** * xsltFreeUseAttrSet: * @use: an XSLT UseAttrSet * * Free up the memory allocated by @use */ static void xsltFreeUseAttrSet(xsltUseAttrSetPtr use) { xmlFree(use); } /** * xsltFreeUseAttrSetList: * @list: an XSLT UseAttrSet list * * Free up the memory allocated by @list */ static void xsltFreeUseAttrSetList(xsltUseAttrSetPtr list) { xsltUseAttrSetPtr next; while (list != NULL) { next = list->next; xsltFreeUseAttrSet(list); list = next; } } /** * xsltAddUseAttrSetList: * @list: a xsltUseAttrSet list * @ncname: local name * @ns: namespace URI * * Add the use-attribute-set name to the list. * * Returns the new list pointer. */ static xsltUseAttrSetPtr xsltAddUseAttrSetList(xsltUseAttrSetPtr list, const xmlChar *ncname, const xmlChar *ns) { xsltUseAttrSetPtr next, cur; if (ncname == NULL) return(list); if (list == NULL) return(xsltNewUseAttrSet(ncname, ns)); cur = list; while (cur != NULL) { if ((cur->ncname == ncname) && (cur->ns == ns)) return(list); next = cur->next; if (next == NULL) { cur->next = xsltNewUseAttrSet(ncname, ns); return(list); } cur = next; } return(list); } /** * xsltNewAttrSet: * * Create a new attribute set. * * Returns the newly allocated xsltAttrSetPtr or NULL in case of error. */ static xsltAttrSetPtr xsltNewAttrSet() { xsltAttrSetPtr cur; cur = (xsltAttrSetPtr) xmlMalloc(sizeof(xsltAttrSet)); if (cur == NULL) { xsltGenericError(xsltGenericErrorContext, "xsltNewAttrSet : malloc failed\n"); return(NULL); } memset(cur, 0, sizeof(xsltAttrSet)); return(cur); } /** * xsltFreeAttrSet: * @set: an attribute set * * Free memory allocated by @set */ static void xsltFreeAttrSet(xsltAttrSetPtr set) { if (set == NULL) return; xsltFreeAttrElemList(set->attrs); xsltFreeUseAttrSetList(set->useAttrSets); xmlFree(set); } /** * xsltMergeAttrSets: * @set: an attribute set * @other: another attribute set * * Add all the attributes from @other to @set, * but drop redefinition of existing values. */ static void xsltMergeAttrSets(xsltAttrSetPtr set, xsltAttrSetPtr other) { xsltAttrElemPtr cur; xsltAttrElemPtr old = other->attrs; int add; while (old != NULL) { /* * Check that the attribute is not yet in the list */ cur = set->attrs; add = 1; while (cur != NULL) { xsltStylePreCompPtr curComp = cur->attr->psvi; xsltStylePreCompPtr oldComp = old->attr->psvi; if ((curComp->name == oldComp->name) && (curComp->ns == oldComp->ns)) { add = 0; break; } if (cur->next == NULL) break; cur = cur->next; } if (add == 1) { if (cur == NULL) { set->attrs = xsltNewAttrElem(old->attr); } else if (add) { cur->next = xsltNewAttrElem(old->attr); } } old = old->next; } } /************************************************************************ * * * 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; const xmlChar *nsUri = NULL; xmlChar *value; xmlNodePtr child; xsltAttrSetPtr set; 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; } if (xmlValidateQName(value, 0)) { xsltTransformError(NULL, style, cur, "xsl:attribute-set : The name '%s' is not a valid QName.\n", value); style->errors++; xmlFree(value); return; } ncname = xsltSplitQName(style->dict, value, &prefix); xmlFree(value); value = NULL; if (prefix != NULL) { xmlNsPtr ns = xmlSearchNs(style->doc, cur, prefix); if (ns == NULL) { xsltTransformError(NULL, style, cur, "xsl:attribute-set : No namespace found for QName '%s:%s'\n", prefix, ncname); style->errors++; return; } nsUri = ns->href; } 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; set = xmlHashLookup2(style->attributeSets, ncname, nsUri); if (set == NULL) { set = xsltNewAttrSet(); if (set == NULL) return; xmlHashAddEntry2(style->attributeSets, ncname, nsUri, set); } /* * 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 WITH_XSLT_DEBUG_ATTRIBUTES xsltGenericDebug(xsltGenericDebugContext, "add attribute to list %s\n", ncname); #endif xsltStylePreCompute(style, child); if (child->children != NULL) { #ifdef XSLT_REFACTORED xsltParseSequenceConstructor(XSLT_CCTXT(style), child->children); #else xsltParseTemplateContent(style, child); #endif } if (child->psvi == NULL) { xsltTransformError(NULL, style, child, "xsl:attribute-set : internal error, attribute %s not " "compiled\n", child->name); } else { set->attrs = xsltAddAttrElemList(set->attrs, child); } } child = child->next; } /* * Process attribute "use-attribute-sets". */ value = xmlGetNsProp(cur, BAD_CAST "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; const xmlChar *nsUri2 = NULL; #ifdef WITH_XSLT_DEBUG_ATTRIBUTES xsltGenericDebug(xsltGenericDebugContext, "xsl:attribute-set : %s adds use %s\n", ncname, curval); #endif if (xmlValidateQName(curval, 0)) { xsltTransformError(NULL, style, cur, "xsl:attribute-set : The name '%s' in " "use-attribute-sets is not a valid QName.\n", curval); style->errors++; xmlFree(value); return; } ncname2 = xsltSplitQName(style->dict, curval, &prefix2); if (prefix2 != NULL) { xmlNsPtr ns2 = xmlSearchNs(style->doc, cur, prefix2); if (ns2 == NULL) { xsltTransformError(NULL, style, cur, "xsl:attribute-set : No namespace found for QName " "'%s:%s' in use-attribute-sets\n", prefix2, ncname2); style->errors++; xmlFree(value); return; } nsUri2 = ns2->href; } set->useAttrSets = xsltAddUseAttrSetList(set->useAttrSets, ncname2, nsUri2); } curval = endval; } xmlFree(value); value = NULL; } #ifdef WITH_XSLT_DEBUG_ATTRIBUTES xsltGenericDebug(xsltGenericDebugContext, "updated attribute list %s\n", ncname); #endif } /** * xsltResolveUseAttrSets: * @set: the attribute set * @asctx: the context for attribute set resolution * @depth: recursion depth * * Process "use-attribute-sets". */ static void xsltResolveUseAttrSets(xsltAttrSetPtr set, xsltStylesheetPtr topStyle, int depth) { xsltStylesheetPtr cur; xsltAttrSetPtr other; xsltUseAttrSetPtr use = set->useAttrSets; xsltUseAttrSetPtr next; while (use != NULL) { /* * Iterate top stylesheet and all imports. */ cur = topStyle; while (cur != NULL) { if (cur->attributeSets) { other = xmlHashLookup2(cur->attributeSets, use->ncname, use->ns); if (other != NULL) { xsltResolveAttrSet(other, topStyle, cur, use->ncname, use->ns, depth + 1); xsltMergeAttrSets(set, other); break; } } cur = xsltNextImport(cur); } next = use->next; /* Free useAttrSets early. */ xsltFreeUseAttrSet(use); use = next; } set->useAttrSets = NULL; } /** * xsltResolveAttrSet: * @set: the attribute set * @asctx: the context for attribute set resolution * @name: the local name of the attirbute set * @ns: the namespace of the attribute set * @depth: recursion depth * * resolve the references in an attribute set. */ static void xsltResolveAttrSet(xsltAttrSetPtr set, xsltStylesheetPtr topStyle, xsltStylesheetPtr style, const xmlChar *name, const xmlChar *ns, int depth) { xsltStylesheetPtr cur; xsltAttrSetPtr other; if (set->state == ATTRSET_RESOLVED) return; if (set->state == ATTRSET_RESOLVING) { xsltTransformError(NULL, topStyle, NULL, "xsl:attribute-set : use-attribute-sets recursion detected" " on %s\n", name); topStyle->errors++; set->state = ATTRSET_RESOLVED; return; } if (depth > 100) { xsltTransformError(NULL, topStyle, NULL, "xsl:attribute-set : use-attribute-sets maximum recursion " "depth exceeded on %s\n", name); topStyle->errors++; return; } set->state = ATTRSET_RESOLVING; xsltResolveUseAttrSets(set, topStyle, depth); /* Merge imported sets. */ cur = xsltNextImport(style); while (cur != NULL) { if (cur->attributeSets != NULL) { other = xmlHashLookup2(cur->attributeSets, name, ns); if (other != NULL) { #ifdef WITH_XSLT_DEBUG_ATTRIBUTES xsltGenericDebug(xsltGenericDebugContext, "xsl:attribute-set : merging import for %s\n", name); #endif xsltResolveUseAttrSets(other, topStyle, depth); xsltMergeAttrSets(set, other); xmlHashRemoveEntry2(cur->attributeSets, name, ns, NULL); xsltFreeAttrSet(other); } } cur = xsltNextImport(cur); } set->state = ATTRSET_RESOLVED; } /** * xsltResolveSASCallback: * @set: the attribute set * @asctx: the context for attribute set resolution * @name: the local name of the attirbute set * @ns: the namespace of the attribute set * * resolve the references in an attribute set. */ static void xsltResolveSASCallback(void *payload, void *data, const xmlChar *name, const xmlChar *ns, ATTRIBUTE_UNUSED const xmlChar *ignored) { xsltAttrSetPtr set = (xsltAttrSetPtr) payload; xsltAttrSetContextPtr asctx = (xsltAttrSetContextPtr) data; xsltStylesheetPtr topStyle = asctx->topStyle; xsltStylesheetPtr style = asctx->style; xsltResolveAttrSet(set, topStyle, style, name, ns, 1); /* Move attribute sets to top stylesheet. */ if (style != topStyle) { /* * This imported stylesheet won't be visited anymore. Don't bother * removing the hash entry. */ if (xmlHashAddEntry2(topStyle->attributeSets, name, ns, set) < 0) { xsltGenericError(xsltGenericErrorContext, "xsl:attribute-set : internal error, can't move imported " " attribute set %s\n", name); } } } /** * xsltResolveStylesheetAttributeSet: * @style: the XSLT stylesheet * * resolve the references between attribute sets. */ void xsltResolveStylesheetAttributeSet(xsltStylesheetPtr style) { xsltStylesheetPtr cur; xsltAttrSetContext asctx; #ifdef WITH_XSLT_DEBUG_ATTRIBUTES xsltGenericDebug(xsltGenericDebugContext, "Resolving attribute sets references\n"); #endif asctx.topStyle = style; cur = 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); } asctx.style = cur; xmlHashScanFull(cur->attributeSets, xsltResolveSASCallback, &asctx); if (cur != style) { /* * the attribute lists have either been migrated to style * or freed directly in xsltResolveSASCallback() */ xmlHashFree(cur->attributeSets, NULL); cur->attributeSets = NULL; } } cur = xsltNextImport(cur); } } /** * xsltAttribute: * @ctxt: a XSLT process context * @contextNode: the current node in the source tree * @inst: the xsl:attribute element * @castedComp: precomputed information * * Process the xslt attribute node on the source node */ void xsltAttribute(xsltTransformContextPtr ctxt, xmlNodePtr contextNode, xmlNodePtr inst, xsltElemPreCompPtr castedComp) { #ifdef XSLT_REFACTORED xsltStyleItemAttributePtr comp = (xsltStyleItemAttributePtr) castedComp; #else xsltStylePreCompPtr comp = (xsltStylePreCompPtr) 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 xsltAttribute(): " "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; } /* * 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; } /** * 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; xsltAttrSetPtr set; 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) { xmlNsPtr ns; const xmlChar *nsUri = NULL; #ifdef WITH_XSLT_DEBUG_ATTRIBUTES xsltGenericDebug(xsltGenericDebugContext, "apply attribute set %s\n", curstr); #endif if (xmlValidateQName(curstr, 0)) { xsltTransformError(ctxt, NULL, inst, "The name '%s' in use-attribute-sets is not a valid " "QName.\n", curstr); return; } ncname = xsltSplitQName(ctxt->dict, curstr, &prefix); if (prefix != NULL) { ns = xmlSearchNs(inst->doc, inst, prefix); if (ns == NULL) { xsltTransformError(ctxt, NULL, inst, "use-attribute-set : No namespace found for QName " "'%s:%s'\n", prefix, ncname); return; } nsUri = ns->href; } style = ctxt->style; #ifdef WITH_DEBUGGER if ((style != NULL) && (style->attributeSets != NULL) && (ctxt->debugStatus != XSLT_DEBUG_NONE)) { set = xmlHashLookup2(style->attributeSets, ncname, nsUri); if ((set != NULL) && (set->attrs != NULL) && (set->attrs->attr != NULL)) xslHandleDebugger(set->attrs->attr->parent, node, NULL, ctxt); } #endif /* * Lookup the referenced attribute-set. All attribute sets were * moved to the top stylesheet so there's no need to iterate * imported stylesheets */ set = xmlHashLookup2(style->attributeSets, ncname, nsUri); if (set != NULL) { xsltAttrElemPtr cur = set->attrs; while (cur != NULL) { if (cur->attr != NULL) { xsltAttribute(ctxt, node, cur->attr, cur->attr->psvi); } cur = cur->next; } } } curstr = endstr; } } static void xsltFreeAttributeSetsEntry(void *payload, const xmlChar *name ATTRIBUTE_UNUSED) { xsltFreeAttrSet((xsltAttrSetPtr) payload); } /** * 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, xsltFreeAttributeSetsEntry); style->attributeSets = NULL; }