| /* |
| * xslt.c: Implemetation of an XSL Transformation 1.0 engine |
| * |
| * Reference: |
| * XSLT specification |
| * http://www.w3.org/TR/1999/REC-xslt-19991116 |
| * |
| * Associating Style Sheets with XML documents |
| * http://www.w3.org/1999/06/REC-xml-stylesheet-19990629 |
| * |
| * 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/parser.h> |
| #include <libxml/tree.h> |
| #include <libxml/valid.h> |
| #include <libxml/hash.h> |
| #include <libxml/uri.h> |
| #include <libxml/xmlerror.h> |
| #include <libxml/parserInternals.h> |
| #include <libxml/xpathInternals.h> |
| #include <libxml/xpath.h> |
| #include "xslt.h" |
| #include "xsltInternals.h" |
| #include "pattern.h" |
| #include "variables.h" |
| #include "namespaces.h" |
| #include "attributes.h" |
| #include "xsltutils.h" |
| #include "imports.h" |
| #include "keys.h" |
| #include "documents.h" |
| #include "extensions.h" |
| #include "preproc.h" |
| #include "extra.h" |
| #include "security.h" |
| |
| #ifdef WITH_XSLT_DEBUG |
| #define WITH_XSLT_DEBUG_PARSING |
| /* #define WITH_XSLT_DEBUG_BLANKS */ |
| #endif |
| |
| const char *xsltEngineVersion = LIBXSLT_VERSION_STRING LIBXSLT_VERSION_EXTRA; |
| const int xsltLibxsltVersion = LIBXSLT_VERSION; |
| const int xsltLibxmlVersion = LIBXML_VERSION; |
| |
| #ifdef XSLT_REFACTORED |
| |
| const xmlChar *xsltConstNamespaceNameXSLT = (const xmlChar *) XSLT_NAMESPACE; |
| |
| #define XSLT_ELEMENT_CATEGORY_XSLT 0 |
| #define XSLT_ELEMENT_CATEGORY_EXTENSION 1 |
| #define XSLT_ELEMENT_CATEGORY_LRE 2 |
| |
| /* |
| * xsltLiteralResultMarker: |
| * Marker for Literal result elements, in order to avoid multiple attempts |
| * to recognize such elements in the stylesheet's tree. |
| * This marker is set on node->psvi during the initial traversal |
| * of a stylesheet's node tree. |
| * |
| const xmlChar *xsltLiteralResultMarker = |
| (const xmlChar *) "Literal Result Element"; |
| */ |
| |
| /* |
| * xsltXSLTTextMarker: |
| * Marker for xsl:text elements. Used to recognize xsl:text elements |
| * for post-processing of the stylesheet's tree, where those |
| * elements are removed from the tree. |
| */ |
| const xmlChar *xsltXSLTTextMarker = (const xmlChar *) "XSLT Text Element"; |
| |
| /* |
| * xsltXSLTAttrMarker: |
| * Marker for XSLT attribute on Literal Result Elements. |
| */ |
| const xmlChar *xsltXSLTAttrMarker = (const xmlChar *) "LRE XSLT Attr"; |
| |
| #endif |
| |
| #ifdef XSLT_LOCALE_WINAPI |
| extern xmlRMutexPtr xsltLocaleMutex; |
| #endif |
| /* |
| * Harmless but avoiding a problem when compiling against a |
| * libxml <= 2.3.11 without LIBXML_DEBUG_ENABLED |
| */ |
| #ifndef LIBXML_DEBUG_ENABLED |
| double xmlXPathStringEvalNumber(const xmlChar *str); |
| #endif |
| /* |
| * Useful macros |
| */ |
| |
| #ifdef IS_BLANK |
| #undef IS_BLANK |
| #endif |
| #define IS_BLANK(c) (((c) == 0x20) || ((c) == 0x09) || ((c) == 0xA) || \ |
| ((c) == 0x0D)) |
| |
| #ifdef IS_BLANK_NODE |
| #undef IS_BLANK_NODE |
| #endif |
| #define IS_BLANK_NODE(n) \ |
| (((n)->type == XML_TEXT_NODE) && (xsltIsBlank((n)->content))) |
| |
| /** |
| * xsltParseContentError: |
| * |
| * @style: the stylesheet |
| * @node: the node where the error occured |
| * |
| * Compile-time error function. |
| */ |
| static void |
| xsltParseContentError(xsltStylesheetPtr style, |
| xmlNodePtr node) |
| { |
| if ((style == NULL) || (node == NULL)) |
| return; |
| |
| if (IS_XSLT_ELEM(node)) |
| xsltTransformError(NULL, style, node, |
| "The XSLT-element '%s' is not allowed at this position.\n", |
| node->name); |
| else |
| xsltTransformError(NULL, style, node, |
| "The element '%s' is not allowed at this position.\n", |
| node->name); |
| style->errors++; |
| } |
| |
| #ifdef XSLT_REFACTORED |
| #else |
| /** |
| * exclPrefixPush: |
| * @style: the transformation stylesheet |
| * @value: the excluded namespace name to push on the stack |
| * |
| * Push an excluded namespace name on the stack |
| * |
| * Returns the new index in the stack or -1 if already present or |
| * in case of error |
| */ |
| static int |
| exclPrefixPush(xsltStylesheetPtr style, xmlChar * value) |
| { |
| int i; |
| |
| if (style->exclPrefixMax == 0) { |
| style->exclPrefixMax = 4; |
| style->exclPrefixTab = |
| (xmlChar * *)xmlMalloc(style->exclPrefixMax * |
| sizeof(style->exclPrefixTab[0])); |
| if (style->exclPrefixTab == NULL) { |
| xmlGenericError(xmlGenericErrorContext, "malloc failed !\n"); |
| return (-1); |
| } |
| } |
| /* do not push duplicates */ |
| for (i = 0;i < style->exclPrefixNr;i++) { |
| if (xmlStrEqual(style->exclPrefixTab[i], value)) |
| return(-1); |
| } |
| if (style->exclPrefixNr >= style->exclPrefixMax) { |
| style->exclPrefixMax *= 2; |
| style->exclPrefixTab = |
| (xmlChar * *)xmlRealloc(style->exclPrefixTab, |
| style->exclPrefixMax * |
| sizeof(style->exclPrefixTab[0])); |
| if (style->exclPrefixTab == NULL) { |
| xmlGenericError(xmlGenericErrorContext, "realloc failed !\n"); |
| return (-1); |
| } |
| } |
| style->exclPrefixTab[style->exclPrefixNr] = value; |
| style->exclPrefix = value; |
| return (style->exclPrefixNr++); |
| } |
| /** |
| * exclPrefixPop: |
| * @style: the transformation stylesheet |
| * |
| * Pop an excluded prefix value from the stack |
| * |
| * Returns the stored excluded prefix value |
| */ |
| static xmlChar * |
| exclPrefixPop(xsltStylesheetPtr style) |
| { |
| xmlChar *ret; |
| |
| if (style->exclPrefixNr <= 0) |
| return (0); |
| style->exclPrefixNr--; |
| if (style->exclPrefixNr > 0) |
| style->exclPrefix = style->exclPrefixTab[style->exclPrefixNr - 1]; |
| else |
| style->exclPrefix = NULL; |
| ret = style->exclPrefixTab[style->exclPrefixNr]; |
| style->exclPrefixTab[style->exclPrefixNr] = 0; |
| return (ret); |
| } |
| #endif |
| |
| /************************************************************************ |
| * * |
| * Helper functions * |
| * * |
| ************************************************************************/ |
| |
| static int initialized = 0; |
| /** |
| * xsltInit: |
| * |
| * Initializes the processor (e.g. registers built-in extensions, |
| * etc.) |
| */ |
| void |
| xsltInit (void) { |
| if (initialized == 0) { |
| initialized = 1; |
| #ifdef XSLT_LOCALE_WINAPI |
| xsltLocaleMutex = xmlNewRMutex(); |
| #endif |
| xsltRegisterAllExtras(); |
| } |
| } |
| |
| /** |
| * xsltUninit: |
| * |
| * Uninitializes the processor. |
| */ |
| void |
| xsltUninit (void) { |
| initialized = 0; |
| } |
| |
| /** |
| * xsltIsBlank: |
| * @str: a string |
| * |
| * Check if a string is ignorable |
| * |
| * Returns 1 if the string is NULL or made of blanks chars, 0 otherwise |
| */ |
| int |
| xsltIsBlank(xmlChar *str) { |
| if (str == NULL) |
| return(1); |
| while (*str != 0) { |
| if (!(IS_BLANK(*str))) return(0); |
| str++; |
| } |
| return(1); |
| } |
| |
| /************************************************************************ |
| * * |
| * Routines to handle XSLT data structures * |
| * * |
| ************************************************************************/ |
| static xsltDecimalFormatPtr |
| xsltNewDecimalFormat(xmlChar *name) |
| { |
| xsltDecimalFormatPtr self; |
| /* UTF-8 for 0x2030 */ |
| static const xmlChar permille[4] = {0xe2, 0x80, 0xb0, 0}; |
| |
| self = xmlMalloc(sizeof(xsltDecimalFormat)); |
| if (self != NULL) { |
| self->next = NULL; |
| self->name = name; |
| |
| /* Default values */ |
| self->digit = xmlStrdup(BAD_CAST("#")); |
| self->patternSeparator = xmlStrdup(BAD_CAST(";")); |
| self->decimalPoint = xmlStrdup(BAD_CAST(".")); |
| self->grouping = xmlStrdup(BAD_CAST(",")); |
| self->percent = xmlStrdup(BAD_CAST("%")); |
| self->permille = xmlStrdup(BAD_CAST(permille)); |
| self->zeroDigit = xmlStrdup(BAD_CAST("0")); |
| self->minusSign = xmlStrdup(BAD_CAST("-")); |
| self->infinity = xmlStrdup(BAD_CAST("Infinity")); |
| self->noNumber = xmlStrdup(BAD_CAST("NaN")); |
| } |
| return self; |
| } |
| |
| static void |
| xsltFreeDecimalFormat(xsltDecimalFormatPtr self) |
| { |
| if (self != NULL) { |
| if (self->digit) |
| xmlFree(self->digit); |
| if (self->patternSeparator) |
| xmlFree(self->patternSeparator); |
| if (self->decimalPoint) |
| xmlFree(self->decimalPoint); |
| if (self->grouping) |
| xmlFree(self->grouping); |
| if (self->percent) |
| xmlFree(self->percent); |
| if (self->permille) |
| xmlFree(self->permille); |
| if (self->zeroDigit) |
| xmlFree(self->zeroDigit); |
| if (self->minusSign) |
| xmlFree(self->minusSign); |
| if (self->infinity) |
| xmlFree(self->infinity); |
| if (self->noNumber) |
| xmlFree(self->noNumber); |
| if (self->name) |
| xmlFree(self->name); |
| xmlFree(self); |
| } |
| } |
| |
| static void |
| xsltFreeDecimalFormatList(xsltStylesheetPtr self) |
| { |
| xsltDecimalFormatPtr iter; |
| xsltDecimalFormatPtr tmp; |
| |
| if (self == NULL) |
| return; |
| |
| iter = self->decimalFormat; |
| while (iter != NULL) { |
| tmp = iter->next; |
| xsltFreeDecimalFormat(iter); |
| iter = tmp; |
| } |
| } |
| |
| /** |
| * xsltDecimalFormatGetByName: |
| * @style: the XSLT stylesheet |
| * @name: the decimal-format name to find |
| * |
| * Find decimal-format by name |
| * |
| * Returns the xsltDecimalFormatPtr |
| */ |
| xsltDecimalFormatPtr |
| xsltDecimalFormatGetByName(xsltStylesheetPtr style, xmlChar *name) |
| { |
| xsltDecimalFormatPtr result = NULL; |
| |
| if (name == NULL) |
| return style->decimalFormat; |
| |
| while (style != NULL) { |
| for (result = style->decimalFormat->next; |
| result != NULL; |
| result = result->next) { |
| if (xmlStrEqual(name, result->name)) |
| return result; |
| } |
| style = xsltNextImport(style); |
| } |
| return result; |
| } |
| |
| |
| /** |
| * xsltNewTemplate: |
| * |
| * Create a new XSLT Template |
| * |
| * Returns the newly allocated xsltTemplatePtr or NULL in case of error |
| */ |
| static xsltTemplatePtr |
| xsltNewTemplate(void) { |
| xsltTemplatePtr cur; |
| |
| cur = (xsltTemplatePtr) xmlMalloc(sizeof(xsltTemplate)); |
| if (cur == NULL) { |
| xsltTransformError(NULL, NULL, NULL, |
| "xsltNewTemplate : malloc failed\n"); |
| return(NULL); |
| } |
| memset(cur, 0, sizeof(xsltTemplate)); |
| cur->priority = XSLT_PAT_NO_PRIORITY; |
| return(cur); |
| } |
| |
| /** |
| * xsltFreeTemplate: |
| * @template: an XSLT template |
| * |
| * Free up the memory allocated by @template |
| */ |
| static void |
| xsltFreeTemplate(xsltTemplatePtr template) { |
| if (template == NULL) |
| return; |
| if (template->match) xmlFree(template->match); |
| /* |
| * NOTE: @name and @nameURI are put into the string dict now. |
| * if (template->name) xmlFree(template->name); |
| * if (template->nameURI) xmlFree(template->nameURI); |
| */ |
| /* |
| if (template->mode) xmlFree(template->mode); |
| if (template->modeURI) xmlFree(template->modeURI); |
| */ |
| if (template->inheritedNs) xmlFree(template->inheritedNs); |
| memset(template, -1, sizeof(xsltTemplate)); |
| xmlFree(template); |
| } |
| |
| /** |
| * xsltFreeTemplateList: |
| * @template: an XSLT template list |
| * |
| * Free up the memory allocated by all the elements of @template |
| */ |
| static void |
| xsltFreeTemplateList(xsltTemplatePtr template) { |
| xsltTemplatePtr cur; |
| |
| while (template != NULL) { |
| cur = template; |
| template = template->next; |
| xsltFreeTemplate(cur); |
| } |
| } |
| |
| #ifdef XSLT_REFACTORED |
| |
| static void |
| xsltFreeNsAliasList(xsltNsAliasPtr item) |
| { |
| xsltNsAliasPtr tmp; |
| |
| while (item) { |
| tmp = item; |
| item = item->next; |
| xmlFree(tmp); |
| } |
| return; |
| } |
| |
| #ifdef XSLT_REFACTORED_XSLT_NSCOMP |
| static void |
| xsltFreeNamespaceMap(xsltNsMapPtr item) |
| { |
| xsltNsMapPtr tmp; |
| |
| while (item) { |
| tmp = item; |
| item = item->next; |
| xmlFree(tmp); |
| } |
| return; |
| } |
| |
| static xsltNsMapPtr |
| xsltNewNamespaceMapItem(xsltCompilerCtxtPtr cctxt, |
| xmlDocPtr doc, |
| xmlNsPtr ns, |
| xmlNodePtr elem) |
| { |
| xsltNsMapPtr ret; |
| |
| if ((cctxt == NULL) || (doc == NULL) || (ns == NULL)) |
| return(NULL); |
| |
| ret = (xsltNsMapPtr) xmlMalloc(sizeof(xsltNsMap)); |
| if (ret == NULL) { |
| xsltTransformError(NULL, cctxt->style, elem, |
| "Internal error: (xsltNewNamespaceMapItem) " |
| "memory allocation failed.\n"); |
| return(NULL); |
| } |
| memset(ret, 0, sizeof(xsltNsMap)); |
| ret->doc = doc; |
| ret->ns = ns; |
| ret->origNsName = ns->href; |
| /* |
| * Store the item at current stylesheet-level. |
| */ |
| if (cctxt->psData->nsMap != NULL) |
| ret->next = cctxt->psData->nsMap; |
| cctxt->psData->nsMap = ret; |
| |
| return(ret); |
| } |
| #endif /* XSLT_REFACTORED_XSLT_NSCOMP */ |
| |
| /** |
| * xsltCompilerVarInfoFree: |
| * @cctxt: the compilation context |
| * |
| * Frees the list of information for vars/params. |
| */ |
| static void |
| xsltCompilerVarInfoFree(xsltCompilerCtxtPtr cctxt) |
| { |
| xsltVarInfoPtr ivar = cctxt->ivars, ivartmp; |
| |
| while (ivar) { |
| ivartmp = ivar; |
| ivar = ivar->next; |
| xmlFree(ivartmp); |
| } |
| } |
| |
| /** |
| * xsltCompilerCtxtFree: |
| * |
| * Free an XSLT compiler context. |
| */ |
| static void |
| xsltCompilationCtxtFree(xsltCompilerCtxtPtr cctxt) |
| { |
| if (cctxt == NULL) |
| return; |
| #ifdef WITH_XSLT_DEBUG_PARSING |
| xsltGenericDebug(xsltGenericDebugContext, |
| "Freeing compilation context\n"); |
| xsltGenericDebug(xsltGenericDebugContext, |
| "### Max inodes: %d\n", cctxt->maxNodeInfos); |
| xsltGenericDebug(xsltGenericDebugContext, |
| "### Max LREs : %d\n", cctxt->maxLREs); |
| #endif |
| /* |
| * Free node-infos. |
| */ |
| if (cctxt->inodeList != NULL) { |
| xsltCompilerNodeInfoPtr tmp, cur = cctxt->inodeList; |
| while (cur != NULL) { |
| tmp = cur; |
| cur = cur->next; |
| xmlFree(tmp); |
| } |
| } |
| if (cctxt->tmpList != NULL) |
| xsltPointerListFree(cctxt->tmpList); |
| #ifdef XSLT_REFACTORED_XPATHCOMP |
| if (cctxt->xpathCtxt != NULL) |
| xmlXPathFreeContext(cctxt->xpathCtxt); |
| #endif |
| if (cctxt->nsAliases != NULL) |
| xsltFreeNsAliasList(cctxt->nsAliases); |
| |
| if (cctxt->ivars) |
| xsltCompilerVarInfoFree(cctxt); |
| |
| xmlFree(cctxt); |
| } |
| |
| /** |
| * xsltCompilerCreate: |
| * |
| * Creates an XSLT compiler context. |
| * |
| * Returns the pointer to the created xsltCompilerCtxt or |
| * NULL in case of an internal error. |
| */ |
| static xsltCompilerCtxtPtr |
| xsltCompilationCtxtCreate(xsltStylesheetPtr style) { |
| xsltCompilerCtxtPtr ret; |
| |
| ret = (xsltCompilerCtxtPtr) xmlMalloc(sizeof(xsltCompilerCtxt)); |
| if (ret == NULL) { |
| xsltTransformError(NULL, style, NULL, |
| "xsltCompilerCreate: allocation of compiler " |
| "context failed.\n"); |
| return(NULL); |
| } |
| memset(ret, 0, sizeof(xsltCompilerCtxt)); |
| |
| ret->errSeverity = XSLT_ERROR_SEVERITY_ERROR; |
| ret->tmpList = xsltPointerListCreate(20); |
| if (ret->tmpList == NULL) { |
| goto internal_err; |
| } |
| #ifdef XSLT_REFACTORED_XPATHCOMP |
| /* |
| * Create the XPath compilation context in order |
| * to speed up precompilation of XPath expressions. |
| */ |
| ret->xpathCtxt = xmlXPathNewContext(NULL); |
| if (ret->xpathCtxt == NULL) |
| goto internal_err; |
| #endif |
| |
| return(ret); |
| |
| internal_err: |
| xsltCompilationCtxtFree(ret); |
| return(NULL); |
| } |
| |
| static void |
| xsltLREEffectiveNsNodesFree(xsltEffectiveNsPtr first) |
| { |
| xsltEffectiveNsPtr tmp; |
| |
| while (first != NULL) { |
| tmp = first; |
| first = first->nextInStore; |
| xmlFree(tmp); |
| } |
| } |
| |
| static void |
| xsltFreePrincipalStylesheetData(xsltPrincipalStylesheetDataPtr data) |
| { |
| if (data == NULL) |
| return; |
| |
| if (data->inScopeNamespaces != NULL) { |
| int i; |
| xsltNsListContainerPtr nsi; |
| xsltPointerListPtr list = |
| (xsltPointerListPtr) data->inScopeNamespaces; |
| |
| for (i = 0; i < list->number; i++) { |
| /* |
| * REVISIT TODO: Free info of in-scope namespaces. |
| */ |
| nsi = (xsltNsListContainerPtr) list->items[i]; |
| if (nsi->list != NULL) |
| xmlFree(nsi->list); |
| xmlFree(nsi); |
| } |
| xsltPointerListFree(list); |
| data->inScopeNamespaces = NULL; |
| } |
| |
| if (data->exclResultNamespaces != NULL) { |
| int i; |
| xsltPointerListPtr list = (xsltPointerListPtr) |
| data->exclResultNamespaces; |
| |
| for (i = 0; i < list->number; i++) |
| xsltPointerListFree((xsltPointerListPtr) list->items[i]); |
| |
| xsltPointerListFree(list); |
| data->exclResultNamespaces = NULL; |
| } |
| |
| if (data->extElemNamespaces != NULL) { |
| xsltPointerListPtr list = (xsltPointerListPtr) |
| data->extElemNamespaces; |
| int i; |
| |
| for (i = 0; i < list->number; i++) |
| xsltPointerListFree((xsltPointerListPtr) list->items[i]); |
| |
| xsltPointerListFree(list); |
| data->extElemNamespaces = NULL; |
| } |
| if (data->effectiveNs) { |
| xsltLREEffectiveNsNodesFree(data->effectiveNs); |
| data->effectiveNs = NULL; |
| } |
| #ifdef XSLT_REFACTORED_XSLT_NSCOMP |
| xsltFreeNamespaceMap(data->nsMap); |
| #endif |
| xmlFree(data); |
| } |
| |
| static xsltPrincipalStylesheetDataPtr |
| xsltNewPrincipalStylesheetData(void) |
| { |
| xsltPrincipalStylesheetDataPtr ret; |
| |
| ret = (xsltPrincipalStylesheetDataPtr) |
| xmlMalloc(sizeof(xsltPrincipalStylesheetData)); |
| if (ret == NULL) { |
| xsltTransformError(NULL, NULL, NULL, |
| "xsltNewPrincipalStylesheetData: memory allocation failed.\n"); |
| return(NULL); |
| } |
| memset(ret, 0, sizeof(xsltPrincipalStylesheetData)); |
| |
| /* |
| * Global list of in-scope namespaces. |
| */ |
| ret->inScopeNamespaces = xsltPointerListCreate(-1); |
| if (ret->inScopeNamespaces == NULL) |
| goto internal_err; |
| /* |
| * Global list of excluded result ns-decls. |
| */ |
| ret->exclResultNamespaces = xsltPointerListCreate(-1); |
| if (ret->exclResultNamespaces == NULL) |
| goto internal_err; |
| /* |
| * Global list of extension instruction namespace names. |
| */ |
| ret->extElemNamespaces = xsltPointerListCreate(-1); |
| if (ret->extElemNamespaces == NULL) |
| goto internal_err; |
| |
| return(ret); |
| |
| internal_err: |
| |
| return(NULL); |
| } |
| |
| #endif |
| |
| /** |
| * xsltNewStylesheet: |
| * |
| * Create a new XSLT Stylesheet |
| * |
| * Returns the newly allocated xsltStylesheetPtr or NULL in case of error |
| */ |
| xsltStylesheetPtr |
| xsltNewStylesheet(void) { |
| xsltStylesheetPtr ret = NULL; |
| |
| ret = (xsltStylesheetPtr) xmlMalloc(sizeof(xsltStylesheet)); |
| if (ret == NULL) { |
| xsltTransformError(NULL, NULL, NULL, |
| "xsltNewStylesheet : malloc failed\n"); |
| goto internal_err; |
| } |
| memset(ret, 0, sizeof(xsltStylesheet)); |
| |
| ret->omitXmlDeclaration = -1; |
| ret->standalone = -1; |
| ret->decimalFormat = xsltNewDecimalFormat(NULL); |
| ret->indent = -1; |
| ret->errors = 0; |
| ret->warnings = 0; |
| ret->exclPrefixNr = 0; |
| ret->exclPrefixMax = 0; |
| ret->exclPrefixTab = NULL; |
| ret->extInfos = NULL; |
| ret->extrasNr = 0; |
| ret->internalized = 1; |
| ret->literal_result = 0; |
| ret->dict = xmlDictCreate(); |
| #ifdef WITH_XSLT_DEBUG |
| xsltGenericDebug(xsltGenericDebugContext, |
| "creating dictionary for stylesheet\n"); |
| #endif |
| |
| xsltInit(); |
| |
| return(ret); |
| |
| internal_err: |
| if (ret != NULL) |
| xsltFreeStylesheet(ret); |
| return(NULL); |
| } |
| |
| /** |
| * xsltAllocateExtra: |
| * @style: an XSLT stylesheet |
| * |
| * Allocate an extra runtime information slot statically while compiling |
| * the stylesheet and return its number |
| * |
| * Returns the number of the slot |
| */ |
| int |
| xsltAllocateExtra(xsltStylesheetPtr style) |
| { |
| return(style->extrasNr++); |
| } |
| |
| /** |
| * xsltAllocateExtraCtxt: |
| * @ctxt: an XSLT transformation context |
| * |
| * Allocate an extra runtime information slot at run-time |
| * and return its number |
| * This make sure there is a slot ready in the transformation context |
| * |
| * Returns the number of the slot |
| */ |
| int |
| xsltAllocateExtraCtxt(xsltTransformContextPtr ctxt) |
| { |
| if (ctxt->extrasNr >= ctxt->extrasMax) { |
| int i; |
| if (ctxt->extrasNr == 0) { |
| ctxt->extrasMax = 20; |
| ctxt->extras = (xsltRuntimeExtraPtr) |
| xmlMalloc(ctxt->extrasMax * sizeof(xsltRuntimeExtra)); |
| if (ctxt->extras == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xsltAllocateExtraCtxt: out of memory\n"); |
| ctxt->state = XSLT_STATE_ERROR; |
| return(0); |
| } |
| for (i = 0;i < ctxt->extrasMax;i++) { |
| ctxt->extras[i].info = NULL; |
| ctxt->extras[i].deallocate = NULL; |
| ctxt->extras[i].val.ptr = NULL; |
| } |
| |
| } else { |
| xsltRuntimeExtraPtr tmp; |
| |
| ctxt->extrasMax += 100; |
| tmp = (xsltRuntimeExtraPtr) xmlRealloc(ctxt->extras, |
| ctxt->extrasMax * sizeof(xsltRuntimeExtra)); |
| if (tmp == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xsltAllocateExtraCtxt: out of memory\n"); |
| ctxt->state = XSLT_STATE_ERROR; |
| return(0); |
| } |
| ctxt->extras = tmp; |
| for (i = ctxt->extrasNr;i < ctxt->extrasMax;i++) { |
| ctxt->extras[i].info = NULL; |
| ctxt->extras[i].deallocate = NULL; |
| ctxt->extras[i].val.ptr = NULL; |
| } |
| } |
| } |
| return(ctxt->extrasNr++); |
| } |
| |
| /** |
| * xsltFreeStylesheetList: |
| * @style: an XSLT stylesheet list |
| * |
| * Free up the memory allocated by the list @style |
| */ |
| static void |
| xsltFreeStylesheetList(xsltStylesheetPtr style) { |
| xsltStylesheetPtr next; |
| |
| while (style != NULL) { |
| next = style->next; |
| xsltFreeStylesheet(style); |
| style = next; |
| } |
| } |
| |
| /** |
| * xsltCleanupStylesheetTree: |
| * |
| * @doc: the document-node |
| * @node: the element where the stylesheet is rooted at |
| * |
| * Actually @node need not be the document-element, but |
| * currently Libxslt does not support embedded stylesheets. |
| * |
| * Returns 0 if OK, -1 on API or internal errors. |
| */ |
| static int |
| xsltCleanupStylesheetTree(xmlDocPtr doc ATTRIBUTE_UNUSED, |
| xmlNodePtr rootElem ATTRIBUTE_UNUSED) |
| { |
| #if 0 /* TODO: Currently disabled, since probably not needed. */ |
| xmlNodePtr cur; |
| |
| if ((doc == NULL) || (rootElem == NULL) || |
| (rootElem->type != XML_ELEMENT_NODE) || |
| (doc != rootElem->doc)) |
| return(-1); |
| |
| /* |
| * Cleanup was suggested by Aleksey Sanin: |
| * Clear the PSVI field to avoid problems if the |
| * node-tree of the stylesheet is intended to be used for |
| * further processing by the user (e.g. for compiling it |
| * once again - although not recommended). |
| */ |
| |
| cur = rootElem; |
| while (cur != NULL) { |
| if (cur->type == XML_ELEMENT_NODE) { |
| /* |
| * Clear the PSVI field. |
| */ |
| cur->psvi = NULL; |
| if (cur->children) { |
| cur = cur->children; |
| continue; |
| } |
| } |
| |
| leave_node: |
| if (cur == rootElem) |
| break; |
| if (cur->next != NULL) |
| cur = cur->next; |
| else { |
| cur = cur->parent; |
| if (cur == NULL) |
| break; |
| goto leave_node; |
| } |
| } |
| #endif /* #if 0 */ |
| return(0); |
| } |
| |
| /** |
| * xsltFreeStylesheet: |
| * @style: an XSLT stylesheet |
| * |
| * Free up the memory allocated by @style |
| */ |
| void |
| xsltFreeStylesheet(xsltStylesheetPtr style) |
| { |
| if (style == NULL) |
| return; |
| |
| #ifdef XSLT_REFACTORED |
| /* |
| * Start with a cleanup of the main stylesheet's doc. |
| */ |
| if ((style->principal == style) && (style->doc)) |
| xsltCleanupStylesheetTree(style->doc, |
| xmlDocGetRootElement(style->doc)); |
| #ifdef XSLT_REFACTORED_XSLT_NSCOMP |
| /* |
| * Restore changed ns-decls before freeing the document. |
| */ |
| if ((style->doc != NULL) && |
| XSLT_HAS_INTERNAL_NSMAP(style)) |
| { |
| xsltRestoreDocumentNamespaces(XSLT_GET_INTERNAL_NSMAP(style), |
| style->doc); |
| } |
| #endif /* XSLT_REFACTORED_XSLT_NSCOMP */ |
| #else |
| /* |
| * Start with a cleanup of the main stylesheet's doc. |
| */ |
| if ((style->parent == NULL) && (style->doc)) |
| xsltCleanupStylesheetTree(style->doc, |
| xmlDocGetRootElement(style->doc)); |
| #endif /* XSLT_REFACTORED */ |
| |
| xsltFreeKeys(style); |
| xsltFreeExts(style); |
| xsltFreeTemplateHashes(style); |
| xsltFreeDecimalFormatList(style); |
| xsltFreeTemplateList(style->templates); |
| xsltFreeAttributeSetsHashes(style); |
| xsltFreeNamespaceAliasHashes(style); |
| xsltFreeStylePreComps(style); |
| /* |
| * Free documents of all included stylsheet modules of this |
| * stylesheet level. |
| */ |
| xsltFreeStyleDocuments(style); |
| /* |
| * TODO: Best time to shutdown extension stuff? |
| */ |
| xsltShutdownExts(style); |
| |
| if (style->variables != NULL) |
| xsltFreeStackElemList(style->variables); |
| if (style->cdataSection != NULL) |
| xmlHashFree(style->cdataSection, NULL); |
| if (style->stripSpaces != NULL) |
| xmlHashFree(style->stripSpaces, NULL); |
| if (style->nsHash != NULL) |
| xmlHashFree(style->nsHash, NULL); |
| if (style->exclPrefixTab != NULL) |
| xmlFree(style->exclPrefixTab); |
| if (style->method != NULL) |
| xmlFree(style->method); |
| if (style->methodURI != NULL) |
| xmlFree(style->methodURI); |
| if (style->version != NULL) |
| xmlFree(style->version); |
| if (style->encoding != NULL) |
| xmlFree(style->encoding); |
| if (style->doctypePublic != NULL) |
| xmlFree(style->doctypePublic); |
| if (style->doctypeSystem != NULL) |
| xmlFree(style->doctypeSystem); |
| if (style->mediaType != NULL) |
| xmlFree(style->mediaType); |
| if (style->attVTs) |
| xsltFreeAVTList(style->attVTs); |
| if (style->imports != NULL) |
| xsltFreeStylesheetList(style->imports); |
| |
| #ifdef XSLT_REFACTORED |
| /* |
| * If this is the principal stylesheet, then |
| * free its internal data. |
| */ |
| if (style->principal == style) { |
| if (style->principalData) { |
| xsltFreePrincipalStylesheetData(style->principalData); |
| style->principalData = NULL; |
| } |
| } |
| #endif |
| /* |
| * Better to free the main document of this stylesheet level |
| * at the end - so here. |
| */ |
| if (style->doc != NULL) { |
| xmlFreeDoc(style->doc); |
| } |
| |
| #ifdef WITH_XSLT_DEBUG |
| xsltGenericDebug(xsltGenericDebugContext, |
| "freeing dictionary from stylesheet\n"); |
| #endif |
| xmlDictFree(style->dict); |
| |
| memset(style, -1, sizeof(xsltStylesheet)); |
| xmlFree(style); |
| } |
| |
| /************************************************************************ |
| * * |
| * Parsing of an XSLT Stylesheet * |
| * * |
| ************************************************************************/ |
| |
| #ifdef XSLT_REFACTORED |
| /* |
| * This is now performed in an optimized way in xsltParseXSLTTemplate. |
| */ |
| #else |
| /** |
| * xsltGetInheritedNsList: |
| * @style: the stylesheet |
| * @template: the template |
| * @node: the current node |
| * |
| * Search all the namespace applying to a given element except the ones |
| * from excluded output prefixes currently in scope. Initialize the |
| * template inheritedNs list with it. |
| * |
| * Returns the number of entries found |
| */ |
| static int |
| xsltGetInheritedNsList(xsltStylesheetPtr style, |
| xsltTemplatePtr template, |
| xmlNodePtr node) |
| { |
| xmlNsPtr cur; |
| xmlNsPtr *ret = NULL; |
| int nbns = 0; |
| int maxns = 10; |
| int i; |
| |
| if ((style == NULL) || (template == NULL) || (node == NULL) || |
| (template->inheritedNsNr != 0) || (template->inheritedNs != NULL)) |
| return(0); |
| while (node != NULL) { |
| if (node->type == XML_ELEMENT_NODE) { |
| cur = node->nsDef; |
| while (cur != NULL) { |
| if (xmlStrEqual(cur->href, XSLT_NAMESPACE)) |
| goto skip_ns; |
| |
| if ((cur->prefix != NULL) && |
| (xsltCheckExtPrefix(style, cur->prefix))) |
| goto skip_ns; |
| /* |
| * Check if this namespace was excluded. |
| * Note that at this point only the exclusions defined |
| * on the topmost stylesheet element are in the exclusion-list. |
| */ |
| for (i = 0;i < style->exclPrefixNr;i++) { |
| if (xmlStrEqual(cur->href, style->exclPrefixTab[i])) |
| goto skip_ns; |
| } |
| if (ret == NULL) { |
| ret = |
| (xmlNsPtr *) xmlMalloc((maxns + 1) * |
| sizeof(xmlNsPtr)); |
| if (ret == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xsltGetInheritedNsList : out of memory!\n"); |
| return(0); |
| } |
| ret[nbns] = NULL; |
| } |
| /* |
| * Skip shadowed namespace bindings. |
| */ |
| for (i = 0; i < nbns; i++) { |
| if ((cur->prefix == ret[i]->prefix) || |
| (xmlStrEqual(cur->prefix, ret[i]->prefix))) |
| break; |
| } |
| if (i >= nbns) { |
| if (nbns >= maxns) { |
| maxns *= 2; |
| ret = (xmlNsPtr *) xmlRealloc(ret, |
| (maxns + |
| 1) * |
| sizeof(xmlNsPtr)); |
| if (ret == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xsltGetInheritedNsList : realloc failed!\n"); |
| return(0); |
| } |
| } |
| ret[nbns++] = cur; |
| ret[nbns] = NULL; |
| } |
| skip_ns: |
| cur = cur->next; |
| } |
| } |
| node = node->parent; |
| } |
| if (nbns != 0) { |
| #ifdef WITH_XSLT_DEBUG_PARSING |
| xsltGenericDebug(xsltGenericDebugContext, |
| "template has %d inherited namespaces\n", nbns); |
| #endif |
| template->inheritedNsNr = nbns; |
| template->inheritedNs = ret; |
| } |
| return (nbns); |
| } |
| #endif /* else of XSLT_REFACTORED */ |
| |
| /** |
| * xsltParseStylesheetOutput: |
| * @style: the XSLT stylesheet |
| * @cur: the "output" element |
| * |
| * parse an XSLT stylesheet output element and record |
| * information related to the stylesheet output |
| */ |
| |
| void |
| xsltParseStylesheetOutput(xsltStylesheetPtr style, xmlNodePtr cur) |
| { |
| xmlChar *elements, |
| *prop; |
| xmlChar *element, |
| *end; |
| |
| if ((cur == NULL) || (style == NULL)) |
| return; |
| |
| prop = xmlGetNsProp(cur, (const xmlChar *) "version", NULL); |
| if (prop != NULL) { |
| if (style->version != NULL) |
| xmlFree(style->version); |
| style->version = prop; |
| } |
| |
| prop = xmlGetNsProp(cur, (const xmlChar *) "encoding", NULL); |
| if (prop != NULL) { |
| if (style->encoding != NULL) |
| xmlFree(style->encoding); |
| style->encoding = prop; |
| } |
| |
| /* relaxed to support xt:document |
| * TODO KB: What does "relaxed to support xt:document" mean? |
| */ |
| prop = xmlGetNsProp(cur, (const xmlChar *) "method", NULL); |
| if (prop != NULL) { |
| const xmlChar *URI; |
| |
| if (style->method != NULL) |
| xmlFree(style->method); |
| style->method = NULL; |
| if (style->methodURI != NULL) |
| xmlFree(style->methodURI); |
| style->methodURI = NULL; |
| |
| /* |
| * TODO: Don't use xsltGetQNameURI(). |
| */ |
| URI = xsltGetQNameURI(cur, &prop); |
| if (prop == NULL) { |
| if (style != NULL) style->errors++; |
| } else if (URI == NULL) { |
| if ((xmlStrEqual(prop, (const xmlChar *) "xml")) || |
| (xmlStrEqual(prop, (const xmlChar *) "html")) || |
| (xmlStrEqual(prop, (const xmlChar *) "text"))) { |
| style->method = prop; |
| } else { |
| xsltTransformError(NULL, style, cur, |
| "invalid value for method: %s\n", prop); |
| if (style != NULL) style->warnings++; |
| } |
| } else { |
| style->method = prop; |
| style->methodURI = xmlStrdup(URI); |
| } |
| } |
| |
| prop = xmlGetNsProp(cur, (const xmlChar *) "doctype-system", NULL); |
| if (prop != NULL) { |
| if (style->doctypeSystem != NULL) |
| xmlFree(style->doctypeSystem); |
| style->doctypeSystem = prop; |
| } |
| |
| prop = xmlGetNsProp(cur, (const xmlChar *) "doctype-public", NULL); |
| if (prop != NULL) { |
| if (style->doctypePublic != NULL) |
| xmlFree(style->doctypePublic); |
| style->doctypePublic = prop; |
| } |
| |
| prop = xmlGetNsProp(cur, (const xmlChar *) "standalone", NULL); |
| if (prop != NULL) { |
| if (xmlStrEqual(prop, (const xmlChar *) "yes")) { |
| style->standalone = 1; |
| } else if (xmlStrEqual(prop, (const xmlChar *) "no")) { |
| style->standalone = 0; |
| } else { |
| xsltTransformError(NULL, style, cur, |
| "invalid value for standalone: %s\n", prop); |
| style->errors++; |
| } |
| xmlFree(prop); |
| } |
| |
| prop = xmlGetNsProp(cur, (const xmlChar *) "indent", NULL); |
| if (prop != NULL) { |
| if (xmlStrEqual(prop, (const xmlChar *) "yes")) { |
| style->indent = 1; |
| } else if (xmlStrEqual(prop, (const xmlChar *) "no")) { |
| style->indent = 0; |
| } else { |
| xsltTransformError(NULL, style, cur, |
| "invalid value for indent: %s\n", prop); |
| style->errors++; |
| } |
| xmlFree(prop); |
| } |
| |
| prop = xmlGetNsProp(cur, (const xmlChar *) "omit-xml-declaration", NULL); |
| if (prop != NULL) { |
| if (xmlStrEqual(prop, (const xmlChar *) "yes")) { |
| style->omitXmlDeclaration = 1; |
| } else if (xmlStrEqual(prop, (const xmlChar *) "no")) { |
| style->omitXmlDeclaration = 0; |
| } else { |
| xsltTransformError(NULL, style, cur, |
| "invalid value for omit-xml-declaration: %s\n", |
| prop); |
| style->errors++; |
| } |
| xmlFree(prop); |
| } |
| |
| elements = xmlGetNsProp(cur, (const xmlChar *) "cdata-section-elements", |
| NULL); |
| if (elements != NULL) { |
| if (style->cdataSection == NULL) |
| style->cdataSection = xmlHashCreate(10); |
| if (style->cdataSection == NULL) |
| return; |
| |
| element = elements; |
| while (*element != 0) { |
| while (IS_BLANK(*element)) |
| element++; |
| if (*element == 0) |
| break; |
| end = element; |
| while ((*end != 0) && (!IS_BLANK(*end))) |
| end++; |
| element = xmlStrndup(element, end - element); |
| if (element) { |
| #ifdef WITH_XSLT_DEBUG_PARSING |
| xsltGenericDebug(xsltGenericDebugContext, |
| "add cdata section output element %s\n", |
| element); |
| #endif |
| if (xmlValidateQName(BAD_CAST element, 0) != 0) { |
| xsltTransformError(NULL, style, cur, |
| "Attribute 'cdata-section-elements': The value " |
| "'%s' is not a valid QName.\n", element); |
| xmlFree(element); |
| style->errors++; |
| } else { |
| const xmlChar *URI; |
| |
| /* |
| * TODO: Don't use xsltGetQNameURI(). |
| */ |
| URI = xsltGetQNameURI(cur, &element); |
| if (element == NULL) { |
| /* |
| * TODO: We'll report additionally an error |
| * via the stylesheet's error handling. |
| */ |
| xsltTransformError(NULL, style, cur, |
| "Attribute 'cdata-section-elements': The value " |
| "'%s' is not a valid QName.\n", element); |
| style->errors++; |
| } else { |
| xmlNsPtr ns; |
| |
| /* |
| * XSLT-1.0 "Each QName is expanded into an |
| * expanded-name using the namespace declarations in |
| * effect on the xsl:output element in which the QName |
| * occurs; if there is a default namespace, it is used |
| * for QNames that do not have a prefix" |
| * NOTE: Fix of bug #339570. |
| */ |
| if (URI == NULL) { |
| ns = xmlSearchNs(style->doc, cur, NULL); |
| if (ns != NULL) |
| URI = ns->href; |
| } |
| xmlHashAddEntry2(style->cdataSection, element, URI, |
| (void *) "cdata"); |
| xmlFree(element); |
| } |
| } |
| } |
| element = end; |
| } |
| xmlFree(elements); |
| } |
| |
| prop = xmlGetNsProp(cur, (const xmlChar *) "media-type", NULL); |
| if (prop != NULL) { |
| if (style->mediaType) |
| xmlFree(style->mediaType); |
| style->mediaType = prop; |
| } |
| if (cur->children != NULL) { |
| xsltParseContentError(style, cur->children); |
| } |
| } |
| |
| /** |
| * xsltParseStylesheetDecimalFormat: |
| * @style: the XSLT stylesheet |
| * @cur: the "decimal-format" element |
| * |
| * <!-- Category: top-level-element --> |
| * <xsl:decimal-format |
| * name = qname, decimal-separator = char, grouping-separator = char, |
| * infinity = string, minus-sign = char, NaN = string, percent = char |
| * per-mille = char, zero-digit = char, digit = char, |
| * pattern-separator = char /> |
| * |
| * parse an XSLT stylesheet decimal-format element and |
| * and record the formatting characteristics |
| */ |
| static void |
| xsltParseStylesheetDecimalFormat(xsltStylesheetPtr style, xmlNodePtr cur) |
| { |
| xmlChar *prop; |
| xsltDecimalFormatPtr format; |
| xsltDecimalFormatPtr iter; |
| |
| if ((cur == NULL) || (style == NULL)) |
| return; |
| |
| format = style->decimalFormat; |
| |
| prop = xmlGetNsProp(cur, BAD_CAST("name"), NULL); |
| if (prop != NULL) { |
| format = xsltDecimalFormatGetByName(style, prop); |
| if (format != NULL) { |
| xsltTransformError(NULL, style, cur, |
| "xsltParseStylestyleDecimalFormat: %s already exists\n", prop); |
| if (style != NULL) style->warnings++; |
| return; |
| } |
| format = xsltNewDecimalFormat(prop); |
| if (format == NULL) { |
| xsltTransformError(NULL, style, cur, |
| "xsltParseStylestyleDecimalFormat: failed creating new decimal-format\n"); |
| if (style != NULL) style->errors++; |
| return; |
| } |
| /* Append new decimal-format structure */ |
| for (iter = style->decimalFormat; iter->next; iter = iter->next) |
| ; |
| if (iter) |
| iter->next = format; |
| } |
| |
| prop = xmlGetNsProp(cur, (const xmlChar *)"decimal-separator", NULL); |
| if (prop != NULL) { |
| if (format->decimalPoint != NULL) xmlFree(format->decimalPoint); |
| format->decimalPoint = prop; |
| } |
| |
| prop = xmlGetNsProp(cur, (const xmlChar *)"grouping-separator", NULL); |
| if (prop != NULL) { |
| if (format->grouping != NULL) xmlFree(format->grouping); |
| format->grouping = prop; |
| } |
| |
| prop = xmlGetNsProp(cur, (const xmlChar *)"infinity", NULL); |
| if (prop != NULL) { |
| if (format->infinity != NULL) xmlFree(format->infinity); |
| format->infinity = prop; |
| } |
| |
| prop = xmlGetNsProp(cur, (const xmlChar *)"minus-sign", NULL); |
| if (prop != NULL) { |
| if (format->minusSign != NULL) xmlFree(format->minusSign); |
| format->minusSign = prop; |
| } |
| |
| prop = xmlGetNsProp(cur, (const xmlChar *)"NaN", NULL); |
| if (prop != NULL) { |
| if (format->noNumber != NULL) xmlFree(format->noNumber); |
| format->noNumber = prop; |
| } |
| |
| prop = xmlGetNsProp(cur, (const xmlChar *)"percent", NULL); |
| if (prop != NULL) { |
| if (format->percent != NULL) xmlFree(format->percent); |
| format->percent = prop; |
| } |
| |
| prop = xmlGetNsProp(cur, (const xmlChar *)"per-mille", NULL); |
| if (prop != NULL) { |
| if (format->permille != NULL) xmlFree(format->permille); |
| format->permille = prop; |
| } |
| |
| prop = xmlGetNsProp(cur, (const xmlChar *)"zero-digit", NULL); |
| if (prop != NULL) { |
| if (format->zeroDigit != NULL) xmlFree(format->zeroDigit); |
| format->zeroDigit = prop; |
| } |
| |
| prop = xmlGetNsProp(cur, (const xmlChar *)"digit", NULL); |
| if (prop != NULL) { |
| if (format->digit != NULL) xmlFree(format->digit); |
| format->digit = prop; |
| } |
| |
| prop = xmlGetNsProp(cur, (const xmlChar *)"pattern-separator", NULL); |
| if (prop != NULL) { |
| if (format->patternSeparator != NULL) xmlFree(format->patternSeparator); |
| format->patternSeparator = prop; |
| } |
| if (cur->children != NULL) { |
| xsltParseContentError(style, cur->children); |
| } |
| } |
| |
| /** |
| * xsltParseStylesheetPreserveSpace: |
| * @style: the XSLT stylesheet |
| * @cur: the "preserve-space" element |
| * |
| * parse an XSLT stylesheet preserve-space element and record |
| * elements needing preserving |
| */ |
| |
| static void |
| xsltParseStylesheetPreserveSpace(xsltStylesheetPtr style, xmlNodePtr cur) { |
| xmlChar *elements; |
| xmlChar *element, *end; |
| |
| if ((cur == NULL) || (style == NULL)) |
| return; |
| |
| elements = xmlGetNsProp(cur, (const xmlChar *)"elements", NULL); |
| if (elements == NULL) { |
| xsltTransformError(NULL, style, cur, |
| "xsltParseStylesheetPreserveSpace: missing elements attribute\n"); |
| if (style != NULL) style->warnings++; |
| return; |
| } |
| |
| if (style->stripSpaces == NULL) |
| style->stripSpaces = xmlHashCreate(10); |
| if (style->stripSpaces == NULL) |
| return; |
| |
| element = elements; |
| while (*element != 0) { |
| while (IS_BLANK(*element)) element++; |
| if (*element == 0) |
| break; |
| end = element; |
| while ((*end != 0) && (!IS_BLANK(*end))) end++; |
| element = xmlStrndup(element, end - element); |
| if (element) { |
| #ifdef WITH_XSLT_DEBUG_PARSING |
| xsltGenericDebug(xsltGenericDebugContext, |
| "add preserved space element %s\n", element); |
| #endif |
| if (xmlStrEqual(element, (const xmlChar *)"*")) { |
| style->stripAll = -1; |
| } else { |
| const xmlChar *URI; |
| |
| /* |
| * TODO: Don't use xsltGetQNameURI(). |
| */ |
| URI = xsltGetQNameURI(cur, &element); |
| |
| xmlHashAddEntry2(style->stripSpaces, element, URI, |
| (xmlChar *) "preserve"); |
| } |
| xmlFree(element); |
| } |
| element = end; |
| } |
| xmlFree(elements); |
| if (cur->children != NULL) { |
| xsltParseContentError(style, cur->children); |
| } |
| } |
| |
| #ifdef XSLT_REFACTORED |
| #else |
| /** |
| * xsltParseStylesheetExtPrefix: |
| * @style: the XSLT stylesheet |
| * @template: the "extension-element-prefixes" prefix |
| * |
| * parse an XSLT stylesheet's "extension-element-prefix" attribute value |
| * and register the namespaces of extension instruction. |
| * SPEC "A namespace is designated as an extension namespace by using |
| * an extension-element-prefixes attribute on: |
| * 1) an xsl:stylesheet element |
| * 2) an xsl:extension-element-prefixes attribute on a |
| * literal result element |
| * 3) an extension instruction." |
| */ |
| static void |
| xsltParseStylesheetExtPrefix(xsltStylesheetPtr style, xmlNodePtr cur, |
| int isXsltElem) { |
| xmlChar *prefixes; |
| xmlChar *prefix, *end; |
| |
| if ((cur == NULL) || (style == NULL)) |
| return; |
| |
| if (isXsltElem) { |
| /* For xsl:stylesheet/xsl:transform. */ |
| prefixes = xmlGetNsProp(cur, |
| (const xmlChar *)"extension-element-prefixes", NULL); |
| } else { |
| /* For literal result elements and extension instructions. */ |
| prefixes = xmlGetNsProp(cur, |
| (const xmlChar *)"extension-element-prefixes", XSLT_NAMESPACE); |
| } |
| if (prefixes == NULL) { |
| return; |
| } |
| |
| prefix = prefixes; |
| while (*prefix != 0) { |
| while (IS_BLANK(*prefix)) prefix++; |
| if (*prefix == 0) |
| break; |
| end = prefix; |
| while ((*end != 0) && (!IS_BLANK(*end))) end++; |
| prefix = xmlStrndup(prefix, end - prefix); |
| if (prefix) { |
| xmlNsPtr ns; |
| |
| if (xmlStrEqual(prefix, (const xmlChar *)"#default")) |
| ns = xmlSearchNs(style->doc, cur, NULL); |
| else |
| ns = xmlSearchNs(style->doc, cur, prefix); |
| if (ns == NULL) { |
| xsltTransformError(NULL, style, cur, |
| "xsl:extension-element-prefix : undefined namespace %s\n", |
| prefix); |
| if (style != NULL) style->warnings++; |
| } else { |
| #ifdef WITH_XSLT_DEBUG_PARSING |
| xsltGenericDebug(xsltGenericDebugContext, |
| "add extension prefix %s\n", prefix); |
| #endif |
| xsltRegisterExtPrefix(style, prefix, ns->href); |
| } |
| xmlFree(prefix); |
| } |
| prefix = end; |
| } |
| xmlFree(prefixes); |
| } |
| #endif /* else of XSLT_REFACTORED */ |
| |
| /** |
| * xsltParseStylesheetStripSpace: |
| * @style: the XSLT stylesheet |
| * @cur: the "strip-space" element |
| * |
| * parse an XSLT stylesheet's strip-space element and record |
| * the elements needing stripping |
| */ |
| |
| static void |
| xsltParseStylesheetStripSpace(xsltStylesheetPtr style, xmlNodePtr cur) { |
| xmlChar *elements; |
| xmlChar *element, *end; |
| |
| if ((cur == NULL) || (style == NULL)) |
| return; |
| |
| elements = xmlGetNsProp(cur, (const xmlChar *)"elements", NULL); |
| if (elements == NULL) { |
| xsltTransformError(NULL, style, cur, |
| "xsltParseStylesheetStripSpace: missing elements attribute\n"); |
| if (style != NULL) style->warnings++; |
| return; |
| } |
| |
| if (style->stripSpaces == NULL) |
| style->stripSpaces = xmlHashCreate(10); |
| if (style->stripSpaces == NULL) |
| return; |
| |
| element = elements; |
| while (*element != 0) { |
| while (IS_BLANK(*element)) element++; |
| if (*element == 0) |
| break; |
| end = element; |
| while ((*end != 0) && (!IS_BLANK(*end))) end++; |
| element = xmlStrndup(element, end - element); |
| if (element) { |
| #ifdef WITH_XSLT_DEBUG_PARSING |
| xsltGenericDebug(xsltGenericDebugContext, |
| "add stripped space element %s\n", element); |
| #endif |
| if (xmlStrEqual(element, (const xmlChar *)"*")) { |
| style->stripAll = 1; |
| } else { |
| const xmlChar *URI; |
| |
| /* |
| * TODO: Don't use xsltGetQNameURI(). |
| */ |
| URI = xsltGetQNameURI(cur, &element); |
| |
| xmlHashAddEntry2(style->stripSpaces, element, URI, |
| (xmlChar *) "strip"); |
| } |
| xmlFree(element); |
| } |
| element = end; |
| } |
| xmlFree(elements); |
| if (cur->children != NULL) { |
| xsltParseContentError(style, cur->children); |
| } |
| } |
| |
| #ifdef XSLT_REFACTORED |
| #else |
| /** |
| * xsltParseStylesheetExcludePrefix: |
| * @style: the XSLT stylesheet |
| * @cur: the current point in the stylesheet |
| * |
| * parse an XSLT stylesheet exclude prefix and record |
| * namespaces needing stripping |
| * |
| * Returns the number of Excluded prefixes added at that level |
| */ |
| |
| static int |
| xsltParseStylesheetExcludePrefix(xsltStylesheetPtr style, xmlNodePtr cur, |
| int isXsltElem) |
| { |
| int nb = 0; |
| xmlChar *prefixes; |
| xmlChar *prefix, *end; |
| |
| if ((cur == NULL) || (style == NULL)) |
| return(0); |
| |
| if (isXsltElem) |
| prefixes = xmlGetNsProp(cur, |
| (const xmlChar *)"exclude-result-prefixes", NULL); |
| else |
| prefixes = xmlGetNsProp(cur, |
| (const xmlChar *)"exclude-result-prefixes", XSLT_NAMESPACE); |
| |
| if (prefixes == NULL) { |
| return(0); |
| } |
| |
| prefix = prefixes; |
| while (*prefix != 0) { |
| while (IS_BLANK(*prefix)) prefix++; |
| if (*prefix == 0) |
| break; |
| end = prefix; |
| while ((*end != 0) && (!IS_BLANK(*end))) end++; |
| prefix = xmlStrndup(prefix, end - prefix); |
| if (prefix) { |
| xmlNsPtr ns; |
| |
| if (xmlStrEqual(prefix, (const xmlChar *)"#default")) |
| ns = xmlSearchNs(style->doc, cur, NULL); |
| else |
| ns = xmlSearchNs(style->doc, cur, prefix); |
| if (ns == NULL) { |
| xsltTransformError(NULL, style, cur, |
| "xsl:exclude-result-prefixes : undefined namespace %s\n", |
| prefix); |
| if (style != NULL) style->warnings++; |
| } else { |
| if (exclPrefixPush(style, (xmlChar *) ns->href) >= 0) { |
| #ifdef WITH_XSLT_DEBUG_PARSING |
| xsltGenericDebug(xsltGenericDebugContext, |
| "exclude result prefix %s\n", prefix); |
| #endif |
| nb++; |
| } |
| } |
| xmlFree(prefix); |
| } |
| prefix = end; |
| } |
| xmlFree(prefixes); |
| return(nb); |
| } |
| #endif /* else of XSLT_REFACTORED */ |
| |
| #ifdef XSLT_REFACTORED |
| |
| /* |
| * xsltTreeEnsureXMLDecl: |
| * @doc: the doc |
| * |
| * BIG NOTE: |
| * This was copy&pasted from Libxml2's xmlTreeEnsureXMLDecl() in "tree.c". |
| * Ensures that there is an XML namespace declaration on the doc. |
| * |
| * Returns the XML ns-struct or NULL on API and internal errors. |
| */ |
| static xmlNsPtr |
| xsltTreeEnsureXMLDecl(xmlDocPtr doc) |
| { |
| if (doc == NULL) |
| return (NULL); |
| if (doc->oldNs != NULL) |
| return (doc->oldNs); |
| { |
| xmlNsPtr ns; |
| ns = (xmlNsPtr) xmlMalloc(sizeof(xmlNs)); |
| if (ns == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xsltTreeEnsureXMLDecl: Failed to allocate " |
| "the XML namespace.\n"); |
| return (NULL); |
| } |
| memset(ns, 0, sizeof(xmlNs)); |
| ns->type = XML_LOCAL_NAMESPACE; |
| /* |
| * URGENT TODO: revisit this. |
| */ |
| #ifdef LIBXML_NAMESPACE_DICT |
| if (doc->dict) |
| ns->href = xmlDictLookup(doc->dict, XML_XML_NAMESPACE, -1); |
| else |
| ns->href = xmlStrdup(XML_XML_NAMESPACE); |
| #else |
| ns->href = xmlStrdup(XML_XML_NAMESPACE); |
| #endif |
| ns->prefix = xmlStrdup((const xmlChar *)"xml"); |
| doc->oldNs = ns; |
| return (ns); |
| } |
| } |
| |
| /* |
| * xsltTreeAcquireStoredNs: |
| * @doc: the doc |
| * @nsName: the namespace name |
| * @prefix: the prefix |
| * |
| * BIG NOTE: |
| * This was copy&pasted from Libxml2's xmlDOMWrapStoreNs() in "tree.c". |
| * Creates or reuses an xmlNs struct on doc->oldNs with |
| * the given prefix and namespace name. |
| * |
| * Returns the aquired ns struct or NULL in case of an API |
| * or internal error. |
| */ |
| static xmlNsPtr |
| xsltTreeAcquireStoredNs(xmlDocPtr doc, |
| const xmlChar *nsName, |
| const xmlChar *prefix) |
| { |
| xmlNsPtr ns; |
| |
| if (doc == NULL) |
| return (NULL); |
| if (doc->oldNs != NULL) |
| ns = doc->oldNs; |
| else |
| ns = xsltTreeEnsureXMLDecl(doc); |
| if (ns == NULL) |
| return (NULL); |
| if (ns->next != NULL) { |
| /* Reuse. */ |
| ns = ns->next; |
| while (ns != NULL) { |
| if ((ns->prefix == NULL) != (prefix == NULL)) { |
| /* NOP */ |
| } else if (prefix == NULL) { |
| if (xmlStrEqual(ns->href, nsName)) |
| return (ns); |
| } else { |
| if ((ns->prefix[0] == prefix[0]) && |
| xmlStrEqual(ns->prefix, prefix) && |
| xmlStrEqual(ns->href, nsName)) |
| return (ns); |
| |
| } |
| if (ns->next == NULL) |
| break; |
| ns = ns->next; |
| } |
| } |
| /* Create. */ |
| ns->next = xmlNewNs(NULL, nsName, prefix); |
| return (ns->next); |
| } |
| |
| /** |
| * xsltLREBuildEffectiveNs: |
| * |
| * Apply ns-aliasing on the namespace of the given @elem and |
| * its attributes. |
| */ |
| static int |
| xsltLREBuildEffectiveNs(xsltCompilerCtxtPtr cctxt, |
| xmlNodePtr elem) |
| { |
| xmlNsPtr ns; |
| xsltNsAliasPtr alias; |
| |
| if ((cctxt == NULL) || (elem == NULL)) |
| return(-1); |
| if ((cctxt->nsAliases == NULL) || (! cctxt->hasNsAliases)) |
| return(0); |
| |
| alias = cctxt->nsAliases; |
| while (alias != NULL) { |
| if ( /* If both namespaces are NULL... */ |
| ( (elem->ns == NULL) && |
| ((alias->literalNs == NULL) || |
| (alias->literalNs->href == NULL)) ) || |
| /* ... or both namespace are equal */ |
| ( (elem->ns != NULL) && |
| (alias->literalNs != NULL) && |
| xmlStrEqual(elem->ns->href, alias->literalNs->href) ) ) |
| { |
| if ((alias->targetNs != NULL) && |
| (alias->targetNs->href != NULL)) |
| { |
| /* |
| * Convert namespace. |
| */ |
| if (elem->doc == alias->docOfTargetNs) { |
| /* |
| * This is the nice case: same docs. |
| * This will eventually assign a ns-decl which |
| * is shadowed, but this has no negative effect on |
| * the generation of the result tree. |
| */ |
| elem->ns = alias->targetNs; |
| } else { |
| /* |
| * This target xmlNs originates from a different |
| * stylesheet tree. Try to locate it in the |
| * in-scope namespaces. |
| * OPTIMIZE TODO: Use the compiler-node-info inScopeNs. |
| */ |
| ns = xmlSearchNs(elem->doc, elem, |
| alias->targetNs->prefix); |
| /* |
| * If no matching ns-decl found, then assign a |
| * ns-decl stored in xmlDoc. |
| */ |
| if ((ns == NULL) || |
| (! xmlStrEqual(ns->href, alias->targetNs->href))) |
| { |
| /* |
| * BIG NOTE: The use of xsltTreeAcquireStoredNs() |
| * is not very efficient, but currently I don't |
| * see an other way of *safely* changing a node's |
| * namespace, since the xmlNs struct in |
| * alias->targetNs might come from an other |
| * stylesheet tree. So we need to anchor it in the |
| * current document, without adding it to the tree, |
| * which would otherwise change the in-scope-ns |
| * semantic of the tree. |
| */ |
| ns = xsltTreeAcquireStoredNs(elem->doc, |
| alias->targetNs->href, |
| alias->targetNs->prefix); |
| |
| if (ns == NULL) { |
| xsltTransformError(NULL, cctxt->style, elem, |
| "Internal error in " |
| "xsltLREBuildEffectiveNs(): " |
| "failed to acquire a stored " |
| "ns-declaration.\n"); |
| cctxt->style->errors++; |
| return(-1); |
| |
| } |
| } |
| elem->ns = ns; |
| } |
| } else { |
| /* |
| * Move into or leave in the NULL namespace. |
| */ |
| elem->ns = NULL; |
| } |
| break; |
| } |
| alias = alias->next; |
| } |
| /* |
| * Same with attributes of literal result elements. |
| */ |
| if (elem->properties != NULL) { |
| xmlAttrPtr attr = elem->properties; |
| |
| while (attr != NULL) { |
| if (attr->ns == NULL) { |
| attr = attr->next; |
| continue; |
| } |
| alias = cctxt->nsAliases; |
| while (alias != NULL) { |
| if ( /* If both namespaces are NULL... */ |
| ( (elem->ns == NULL) && |
| ((alias->literalNs == NULL) || |
| (alias->literalNs->href == NULL)) ) || |
| /* ... or both namespace are equal */ |
| ( (elem->ns != NULL) && |
| (alias->literalNs != NULL) && |
| xmlStrEqual(elem->ns->href, alias->literalNs->href) ) ) |
| { |
| if ((alias->targetNs != NULL) && |
| (alias->targetNs->href != NULL)) |
| { |
| if (elem->doc == alias->docOfTargetNs) { |
| elem->ns = alias->targetNs; |
| } else { |
| ns = xmlSearchNs(elem->doc, elem, |
| alias->targetNs->prefix); |
| if ((ns == NULL) || |
| (! xmlStrEqual(ns->href, alias->targetNs->href))) |
| { |
| ns = xsltTreeAcquireStoredNs(elem->doc, |
| alias->targetNs->href, |
| alias->targetNs->prefix); |
| |
| if (ns == NULL) { |
| xsltTransformError(NULL, cctxt->style, elem, |
| "Internal error in " |
| "xsltLREBuildEffectiveNs(): " |
| "failed to acquire a stored " |
| "ns-declaration.\n"); |
| cctxt->style->errors++; |
| return(-1); |
| |
| } |
| } |
| elem->ns = ns; |
| } |
| } else { |
| /* |
| * Move into or leave in the NULL namespace. |
| */ |
| elem->ns = NULL; |
| } |
| break; |
| } |
| alias = alias->next; |
| } |
| |
| attr = attr->next; |
| } |
| } |
| return(0); |
| } |
| |
| /** |
| * xsltLREBuildEffectiveNsNodes: |
| * |
| * Computes the effective namespaces nodes for a literal result |
| * element. |
| * @effectiveNs is the set of effective ns-nodes |
| * on the literal result element, which will be added to the result |
| * element if not already existing in the result tree. |
| * This means that excluded namespaces (via exclude-result-prefixes, |
| * extension-element-prefixes and the XSLT namespace) not added |
| * to the set. |
| * Namespace-aliasing was applied on the @effectiveNs. |
| */ |
| static int |
| xsltLREBuildEffectiveNsNodes(xsltCompilerCtxtPtr cctxt, |
| xsltStyleItemLRElementInfoPtr item, |
| xmlNodePtr elem, |
| int isLRE) |
| { |
| xmlNsPtr ns, tmpns; |
| xsltEffectiveNsPtr effNs, lastEffNs = NULL; |
| int i, j, holdByElem; |
| xsltPointerListPtr extElemNs = cctxt->inode->extElemNs; |
| xsltPointerListPtr exclResultNs = cctxt->inode->exclResultNs; |
| |
| if ((cctxt == NULL) || (cctxt->inode == NULL) || (elem == NULL) || |
| (item == NULL) || (item->effectiveNs != NULL)) |
| return(-1); |
| |
| if (item->inScopeNs == NULL) |
| return(0); |
| |
| extElemNs = cctxt->inode->extElemNs; |
| exclResultNs = cctxt->inode->exclResultNs; |
| |
| for (i = 0; i < item->inScopeNs->totalNumber; i++) { |
| ns = item->inScopeNs->list[i]; |
| /* |
| * Skip namespaces designated as excluded namespaces |
| * ------------------------------------------------- |
| * |
| * XSLT-20 TODO: In XSLT 2.0 we need to keep namespaces |
| * which are target namespaces of namespace-aliases |
| * regardless if designated as excluded. |
| * |
| * Exclude the XSLT namespace. |
| */ |
| if (xmlStrEqual(ns->href, XSLT_NAMESPACE)) |
| goto skip_ns; |
| |
| /* |
| * Apply namespace aliasing |
| * ------------------------ |
| * |
| * SPEC XSLT 2.0 |
| * "- A namespace node whose string value is a literal namespace |
| * URI is not copied to the result tree. |
| * - A namespace node whose string value is a target namespace URI |
| * is copied to the result tree, whether or not the URI |
| * identifies an excluded namespace." |
| * |
| * NOTE: The ns-aliasing machanism is non-cascading. |
| * (checked with Saxon, Xalan and MSXML .NET). |
| * URGENT TODO: is style->nsAliases the effective list of |
| * ns-aliases, or do we need to lookup the whole |
| * import-tree? |
| * TODO: Get rid of import-tree lookup. |
| */ |
| if (cctxt->hasNsAliases) { |
| xsltNsAliasPtr alias; |
| /* |
| * First check for being a target namespace. |
| */ |
| alias = cctxt->nsAliases; |
| do { |
| /* |
| * TODO: Is xmlns="" handled already? |
| */ |
| if ((alias->targetNs != NULL) && |
| (xmlStrEqual(alias->targetNs->href, ns->href))) |
| { |
| /* |
| * Recognized as a target namespace; use it regardless |
| * if excluded otherwise. |
| */ |
| goto add_effective_ns; |
| } |
| alias = alias->next; |
| } while (alias != NULL); |
| |
| alias = cctxt->nsAliases; |
| do { |
| /* |
| * TODO: Is xmlns="" handled already? |
| */ |
| if ((alias->literalNs != NULL) && |
| (xmlStrEqual(alias->literalNs->href, ns->href))) |
| { |
| /* |
| * Recognized as an namespace alias; do not use it. |
| */ |
| goto skip_ns; |
| } |
| alias = alias->next; |
| } while (alias != NULL); |
| } |
| |
| /* |
| * Exclude excluded result namespaces. |
| */ |
| if (exclResultNs) { |
| for (j = 0; j < exclResultNs->number; j++) |
| if (xmlStrEqual(ns->href, BAD_CAST exclResultNs->items[j])) |
| goto skip_ns; |
| } |
| /* |
| * Exclude extension-element namespaces. |
| */ |
| if (extElemNs) { |
| for (j = 0; j < extElemNs->number; j++) |
| if (xmlStrEqual(ns->href, BAD_CAST extElemNs->items[j])) |
| goto skip_ns; |
| } |
| |
| add_effective_ns: |
| /* |
| * OPTIMIZE TODO: This information may not be needed. |
| */ |
| if (isLRE && (elem->nsDef != NULL)) { |
| holdByElem = 0; |
| tmpns = elem->nsDef; |
| do { |
| if (tmpns == ns) { |
| holdByElem = 1; |
| break; |
| } |
| tmpns = tmpns->next; |
| } while (tmpns != NULL); |
| } else |
| holdByElem = 0; |
| |
| |
| /* |
| * Add the effective namespace declaration. |
| */ |
| effNs = (xsltEffectiveNsPtr) xmlMalloc(sizeof(xsltEffectiveNs)); |
| if (effNs == NULL) { |
| xsltTransformError(NULL, cctxt->style, elem, |
| "Internal error in xsltLREBuildEffectiveNs(): " |
| "failed to allocate memory.\n"); |
| cctxt->style->errors++; |
| return(-1); |
| } |
| if (cctxt->psData->effectiveNs == NULL) { |
| cctxt->psData->effectiveNs = effNs; |
| effNs->nextInStore = NULL; |
| } else { |
| effNs->nextInStore = cctxt->psData->effectiveNs; |
| cctxt->psData->effectiveNs = effNs; |
| } |
| |
| effNs->next = NULL; |
| effNs->prefix = ns->prefix; |
| effNs->nsName = ns->href; |
| effNs->holdByElem = holdByElem; |
| |
| if (lastEffNs == NULL) |
| item->effectiveNs = effNs; |
| else |
| lastEffNs->next = effNs; |
| lastEffNs = effNs; |
| |
| skip_ns: |
| {} |
| } |
| return(0); |
| } |
| |
| |
| /** |
| * xsltLREInfoCreate: |
| * |
| * @isLRE: indicates if the given @elem is a literal result element |
| * |
| * Creates a new info for a literal result element. |
| */ |
| static int |
| xsltLREInfoCreate(xsltCompilerCtxtPtr cctxt, |
| xmlNodePtr elem, |
| int isLRE) |
| { |
| xsltStyleItemLRElementInfoPtr item; |
| |
| if ((cctxt == NULL) || (cctxt->inode == NULL)) |
| return(-1); |
| |
| item = (xsltStyleItemLRElementInfoPtr) |
| xmlMalloc(sizeof(xsltStyleItemLRElementInfo)); |
| if (item == NULL) { |
| xsltTransformError(NULL, cctxt->style, NULL, |
| "Internal error in xsltLREInfoCreate(): " |
| "memory allocation failed.\n"); |
| cctxt->style->errors++; |
| return(-1); |
| } |
| memset(item, 0, sizeof(xsltStyleItemLRElementInfo)); |
| item->type = XSLT_FUNC_LITERAL_RESULT_ELEMENT; |
| /* |
| * Store it in the stylesheet. |
| */ |
| item->next = cctxt->style->preComps; |
| cctxt->style->preComps = (xsltElemPreCompPtr) item; |
| /* |
| * @inScopeNs are used for execution of XPath expressions |
| * in AVTs. |
| */ |
| item->inScopeNs = cctxt->inode->inScopeNs; |
| |
| if (elem) |
| xsltLREBuildEffectiveNsNodes(cctxt, item, elem, isLRE); |
| |
| cctxt->inode->litResElemInfo = item; |
| cctxt->inode->nsChanged = 0; |
| cctxt->maxLREs++; |
| return(0); |
| } |
| |
| /** |
| * xsltCompilerVarInfoPush: |
| * @cctxt: the compilation context |
| * |
| * Pushes a new var/param info onto the stack. |
| * |
| * Returns the acquired variable info. |
| */ |
| static xsltVarInfoPtr |
| xsltCompilerVarInfoPush(xsltCompilerCtxtPtr cctxt, |
| xmlNodePtr inst, |
| const xmlChar *name, |
| const xmlChar *nsName) |
| { |
| xsltVarInfoPtr ivar; |
| |
| if ((cctxt->ivar != NULL) && (cctxt->ivar->next != NULL)) { |
| ivar = cctxt->ivar->next; |
| } else if ((cctxt->ivar == NULL) && (cctxt->ivars != NULL)) { |
| ivar = cctxt->ivars; |
| } else { |
| ivar = (xsltVarInfoPtr) xmlMalloc(sizeof(xsltVarInfo)); |
| if (ivar == NULL) { |
| xsltTransformError(NULL, cctxt->style, inst, |
| "xsltParseInScopeVarPush: xmlMalloc() failed!\n"); |
| cctxt->style->errors++; |
| return(NULL); |
| } |
| /* memset(retVar, 0, sizeof(xsltInScopeVar)); */ |
| if (cctxt->ivars == NULL) { |
| cctxt->ivars = ivar; |
| ivar->prev = NULL; |
| } else { |
| cctxt->ivar->next = ivar; |
| ivar->prev = cctxt->ivar; |
| } |
| cctxt->ivar = ivar; |
| ivar->next = NULL; |
| } |
| ivar->depth = cctxt->depth; |
| ivar->name = name; |
| ivar->nsName = nsName; |
| return(ivar); |
| } |
| |
| /** |
| * xsltCompilerVarInfoPop: |
| * @cctxt: the compilation context |
| * |
| * Pops all var/param infos from the stack, which |
| * have the current depth. |
| */ |
| static void |
| xsltCompilerVarInfoPop(xsltCompilerCtxtPtr cctxt) |
| { |
| |
| while ((cctxt->ivar != NULL) && |
| (cctxt->ivar->depth > cctxt->depth)) |
| { |
| cctxt->ivar = cctxt->ivar->prev; |
| } |
| } |
| |
| /* |
| * xsltCompilerNodePush: |
| * |
| * @cctxt: the compilation context |
| * @node: the node to be pushed (this can also be the doc-node) |
| * |
| * |
| * |
| * Returns the current node info structure or |
| * NULL in case of an internal error. |
| */ |
| static xsltCompilerNodeInfoPtr |
| xsltCompilerNodePush(xsltCompilerCtxtPtr cctxt, xmlNodePtr node) |
| { |
| xsltCompilerNodeInfoPtr inode, iprev; |
| |
| if ((cctxt->inode != NULL) && (cctxt->inode->next != NULL)) { |
| inode = cctxt->inode->next; |
| } else if ((cctxt->inode == NULL) && (cctxt->inodeList != NULL)) { |
| inode = cctxt->inodeList; |
| } else { |
| /* |
| * Create a new node-info. |
| */ |
| inode = (xsltCompilerNodeInfoPtr) |
| xmlMalloc(sizeof(xsltCompilerNodeInfo)); |
| if (inode == NULL) { |
| xsltTransformError(NULL, cctxt->style, NULL, |
| "xsltCompilerNodePush: malloc failed.\n"); |
| return(NULL); |
| } |
| memset(inode, 0, sizeof(xsltCompilerNodeInfo)); |
| if (cctxt->inodeList == NULL) |
| cctxt->inodeList = inode; |
| else { |
| cctxt->inodeLast->next = inode; |
| inode->prev = cctxt->inodeLast; |
| } |
| cctxt->inodeLast = inode; |
| cctxt->maxNodeInfos++; |
| if (cctxt->inode == NULL) { |
| cctxt->inode = inode; |
| /* |
| * Create an initial literal result element info for |
| * the root of the stylesheet. |
| */ |
| xsltLREInfoCreate(cctxt, NULL, 0); |
| } |
| } |
| cctxt->depth++; |
| cctxt->inode = inode; |
| /* |
| * REVISIT TODO: Keep the reset always complete. |
| * NOTE: Be carefull with the @node, since it might be |
| * a doc-node. |
| */ |
| inode->node = node; |
| inode->depth = cctxt->depth; |
| inode->templ = NULL; |
| inode->category = XSLT_ELEMENT_CATEGORY_XSLT; |
| inode->type = 0; |
| inode->item = NULL; |
| inode->curChildType = 0; |
| inode->extContentHandled = 0; |
| inode->isRoot = 0; |
| |
| if (inode->prev != NULL) { |
| iprev = inode->prev; |
| /* |
| * Inherit the following information: |
| * --------------------------------- |
| * |
| * In-scope namespaces |
| */ |
| inode->inScopeNs = iprev->inScopeNs; |
| /* |
| * Info for literal result elements |
| */ |
| inode->litResElemInfo = iprev->litResElemInfo; |
| inode->nsChanged = iprev->nsChanged; |
| /* |
| * Excluded result namespaces |
| */ |
| inode->exclResultNs = iprev->exclResultNs; |
| /* |
| * Extension instruction namespaces |
| */ |
| inode->extElemNs = iprev->extElemNs; |
| /* |
| * Whitespace preservation |
| */ |
| inode->preserveWhitespace = iprev->preserveWhitespace; |
| /* |
| * Forwards-compatible mode |
| */ |
| inode->forwardsCompat = iprev->forwardsCompat; |
| } else { |
| inode->inScopeNs = NULL; |
| inode->exclResultNs = NULL; |
| inode->extElemNs = NULL; |
| inode->preserveWhitespace = 0; |
| inode->forwardsCompat = 0; |
| } |
| |
| return(inode); |
| } |
| |
| /* |
| * xsltCompilerNodePop: |
| * |
| * @cctxt: the compilation context |
| * @node: the node to be pushed (this can also be the doc-node) |
| * |
| * Pops the current node info. |
| */ |
| static void |
| xsltCompilerNodePop(xsltCompilerCtxtPtr cctxt, xmlNodePtr node) |
| { |
| if (cctxt->inode == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xsltCompilerNodePop: Top-node mismatch.\n"); |
| return; |
| } |
| /* |
| * NOTE: Be carefull with the @node, since it might be |
| * a doc-node. |
| */ |
| if (cctxt->inode->node != node) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xsltCompilerNodePop: Node mismatch.\n"); |
| goto mismatch; |
| } |
| if (cctxt->inode->depth != cctxt->depth) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xsltCompilerNodePop: Depth mismatch.\n"); |
| goto mismatch; |
| } |
| /* |
| * Pop information of variables. |
| */ |
| if ((cctxt->ivar) && (cctxt->ivar->depth > cctxt->depth)) |
| xsltCompilerVarInfoPop(cctxt); |
| |
| cctxt->depth--; |
| cctxt->inode = cctxt->inode->prev; |
| if (cctxt->inode != NULL) |
| cctxt->inode->curChildType = 0; |
| return; |
| |
| mismatch: |
| { |
| const xmlChar *nsName = NULL, *name = NULL; |
| const xmlChar *infnsName = NULL, *infname = NULL; |
| |
| if (node) { |
| if (node->type == XML_ELEMENT_NODE) { |
| name = node->name; |
| if (node->ns != NULL) |
| nsName = node->ns->href; |
| else |
| nsName = BAD_CAST ""; |
| } else { |
| name = BAD_CAST "#document"; |
| nsName = BAD_CAST ""; |
| } |
| } else |
| name = BAD_CAST "Not given"; |
| |
| if (cctxt->inode->node) { |
| if (node->type == XML_ELEMENT_NODE) { |
| infname = cctxt->inode->node->name; |
| if (cctxt->inode->node->ns != NULL) |
| infnsName = cctxt->inode->node->ns->href; |
| else |
| infnsName = BAD_CAST ""; |
| } else { |
| infname = BAD_CAST "#document"; |
| infnsName = BAD_CAST ""; |
| } |
| } else |
| infname = BAD_CAST "Not given"; |
| |
| |
| xmlGenericError(xmlGenericErrorContext, |
| "xsltCompilerNodePop: Given : '%s' URI '%s'\n", |
| name, nsName); |
| xmlGenericError(xmlGenericErrorContext, |
| "xsltCompilerNodePop: Expected: '%s' URI '%s'\n", |
| infname, infnsName); |
| } |
| } |
| |
| /* |
| * xsltCompilerBuildInScopeNsList: |
| * |
| * Create and store the list of in-scope namespaces for the given |
| * node in the stylesheet. If there are no changes in the in-scope |
| * namespaces then the last ns-info of the ancestor axis will be returned. |
| * Compilation-time only. |
| * |
| * Returns the ns-info or NULL if there are no namespaces in scope. |
| */ |
| static xsltNsListContainerPtr |
| xsltCompilerBuildInScopeNsList(xsltCompilerCtxtPtr cctxt, xmlNodePtr node) |
| { |
| xsltNsListContainerPtr nsi = NULL; |
| xmlNsPtr *list = NULL, ns; |
| int i, maxns = 5; |
| /* |
| * Create a new ns-list for this position in the node-tree. |
| * xmlGetNsList() will return NULL, if there are no ns-decls in the |
| * tree. Note that the ns-decl for the XML namespace is not added |
| * to the resulting list; the XPath module handles the XML namespace |
| * internally. |
| */ |
| while (node != NULL) { |
| if (node->type == XML_ELEMENT_NODE) { |
| ns = node->nsDef; |
| while (ns != NULL) { |
| if (nsi == NULL) { |
| nsi = (xsltNsListContainerPtr) |
| xmlMalloc(sizeof(xsltNsListContainer)); |
| if (nsi == NULL) { |
| xsltTransformError(NULL, cctxt->style, NULL, |
| "xsltCompilerBuildInScopeNsList: " |
| "malloc failed!\n"); |
| goto internal_err; |
| } |
| memset(nsi, 0, sizeof(xsltNsListContainer)); |
| nsi->list = |
| (xmlNsPtr *) xmlMalloc(maxns * sizeof(xmlNsPtr)); |
| if (nsi->list == NULL) { |
| xsltTransformError(NULL, cctxt->style, NULL, |
| "xsltCompilerBuildInScopeNsList: " |
| "malloc failed!\n"); |
| goto internal_err; |
| } |
| nsi->list[0] = NULL; |
| } |
| /* |
| * Skip shadowed namespace bindings. |
| */ |
| for (i = 0; i < nsi->totalNumber; i++) { |
| if ((ns->prefix == nsi->list[i]->prefix) || |
| (xmlStrEqual(ns->prefix, nsi->list[i]->prefix))) |
| break; |
| } |
| if (i >= nsi->totalNumber) { |
| if (nsi->totalNumber +1 >= maxns) { |
| maxns *= 2; |
| nsi->list = |
| (xmlNsPtr *) xmlRealloc(nsi->list, |
| maxns * sizeof(xmlNsPtr)); |
| if (nsi->list == NULL) { |
| xsltTransformError(NULL, cctxt->style, NULL, |
| "xsltCompilerBuildInScopeNsList: " |
| "realloc failed!\n"); |
| goto internal_err; |
| } |
| } |
| nsi->list[nsi->totalNumber++] = ns; |
| nsi->list[nsi->totalNumber] = NULL; |
| } |
| |
| ns = ns->next; |
| } |
| } |
| node = node->parent; |
| } |
| if (nsi == NULL) |
| return(NULL); |
| /* |
| * Move the default namespace to last position. |
| */ |
| nsi->xpathNumber = nsi->totalNumber; |
| for (i = 0; i < nsi->totalNumber; i++) { |
| if (nsi->list[i]->prefix == NULL) { |
| ns = nsi->list[i]; |
| nsi->list[i] = nsi->list[nsi->totalNumber-1]; |
| nsi->list[nsi->totalNumber-1] = ns; |
| nsi->xpathNumber--; |
| break; |
| } |
| } |
| /* |
| * Store the ns-list in the stylesheet. |
| */ |
| if (xsltPointerListAddSize( |
| (xsltPointerListPtr)cctxt->psData->inScopeNamespaces, |
| (void *) nsi, 5) == -1) |
| { |
| xmlFree(nsi); |
| nsi = NULL; |
| xsltTransformError(NULL, cctxt->style, NULL, |
| "xsltCompilerBuildInScopeNsList: failed to add ns-info.\n"); |
| goto internal_err; |
| } |
| /* |
| * Notify of change in status wrt namespaces. |
| */ |
| if (cctxt->inode != NULL) |
| cctxt->inode->nsChanged = 1; |
| |
| return(nsi); |
| |
| internal_err: |
| if (list != NULL) |
| xmlFree(list); |
| cctxt->style->errors++; |
| return(NULL); |
| } |
| |
| static int |
| xsltParseNsPrefixList(xsltCompilerCtxtPtr cctxt, |
| xsltPointerListPtr list, |
| xmlNodePtr node, |
| const xmlChar *value) |
| { |
| xmlChar *cur, *end; |
| xmlNsPtr ns; |
| |
| if ((cctxt == NULL) || (value == NULL) || (list == NULL)) |
| return(-1); |
| |
| list->number = 0; |
| |
| cur = (xmlChar *) value; |
| while (*cur != 0) { |
| while (IS_BLANK(*cur)) cur++; |
| if (*cur == 0) |
| break; |
| end = cur; |
| while ((*end != 0) && (!IS_BLANK(*end))) end++; |
| cur = xmlStrndup(cur, end - cur); |
| if (cur == NULL) { |
| cur = end; |
| continue; |
| } |
| /* |
| * TODO: Export and use xmlSearchNsByPrefixStrict() |
| * in Libxml2, tree.c, since xmlSearchNs() is in most |
| * cases not efficient and in some cases not correct. |
| * |
| * XSLT-2 TODO: XSLT 2.0 allows an additional "#all" value. |
| */ |
| if ((cur[0] == '#') && |
| xmlStrEqual(cur, (const xmlChar *)"#default")) |
| ns = xmlSearchNs(cctxt->style->doc, node, NULL); |
| else |
| ns = xmlSearchNs(cctxt->style->doc, node, cur); |
| |
| if (ns == NULL) { |
| /* |
| * TODO: Better to report the attr-node, otherwise |
| * the user won't know which attribute was invalid. |
| */ |
| xsltTransformError(NULL, cctxt->style, node, |
| "No namespace binding in scope for prefix '%s'.\n", cur); |
| /* |
| * XSLT-1.0: "It is an error if there is no namespace |
| * bound to the prefix on the element bearing the |
| * exclude-result-prefixes or xsl:exclude-result-prefixes |
| * attribute." |
| */ |
| cctxt->style->errors++; |
| } else { |
| #ifdef WITH_XSLT_DEBUG_PARSING |
| xsltGenericDebug(xsltGenericDebugContext, |
| "resolved prefix '%s'\n", cur); |
| #endif |
| /* |
| * Note that we put the namespace name into the dict. |
| */ |
| if (xsltPointerListAddSize(list, |
| (void *) xmlDictLookup(cctxt->style->dict, |
| ns->href, -1), 5) == -1) |
| { |
| xmlFree(cur); |
| goto internal_err; |
| } |
| } |
| xmlFree(cur); |
| |
| cur = end; |
| } |
| return(0); |
| |
| internal_err: |
| cctxt->style->errors++; |
| return(-1); |
| } |
| |
| /** |
| * xsltCompilerUtilsCreateMergedList: |
| * @dest: the destination list (optional) |
| * @first: the first list |
| * @second: the second list (optional) |
| * |
| * Appends the content of @second to @first into @destination. |
| * If @destination is NULL a new list will be created. |
| * |
| * Returns the merged list of items or NULL if there's nothing to merge. |
| */ |
| static xsltPointerListPtr |
| xsltCompilerUtilsCreateMergedList(xsltPointerListPtr first, |
| xsltPointerListPtr second) |
| { |
| xsltPointerListPtr ret; |
| size_t num; |
| |
| if (first) |
| num = first->number; |
| else |
| num = 0; |
| if (second) |
| num += second->number; |
| if (num == 0) |
| return(NULL); |
| ret = xsltPointerListCreate(num); |
| if (ret == NULL) |
| return(NULL); |
| /* |
| * Copy contents. |
| */ |
| if ((first != NULL) && (first->number != 0)) { |
| memcpy(ret->items, first->items, |
| first->number * sizeof(void *)); |
| if ((second != NULL) && (second->number != 0)) |
| memcpy(ret->items + first->number, second->items, |
| second->number * sizeof(void *)); |
| } else if ((second != NULL) && (second->number != 0)) |
| memcpy(ret->items, (void *) second->items, |
| second->number * sizeof(void *)); |
| ret->number = num; |
| return(ret); |
| } |
| |
| /* |
| * xsltParseExclResultPrefixes: |
| * |
| * Create and store the list of in-scope namespaces for the given |
| * node in the stylesheet. If there are no changes in the in-scope |
| * namespaces then the last ns-info of the ancestor axis will be returned. |
| * Compilation-time only. |
| * |
| * Returns the ns-info or NULL if there are no namespaces in scope. |
| */ |
| static xsltPointerListPtr |
| xsltParseExclResultPrefixes(xsltCompilerCtxtPtr cctxt, xmlNodePtr node, |
| xsltPointerListPtr def, |
| int instrCategory) |
| { |
| xsltPointerListPtr list = NULL; |
| xmlChar *value; |
| xmlAttrPtr attr; |
| |
| if ((cctxt == NULL) || (node == NULL)) |
| return(NULL); |
| |
| if (instrCategory == XSLT_ELEMENT_CATEGORY_XSLT) |
| attr = xmlHasNsProp(node, BAD_CAST "exclude-result-prefixes", NULL); |
| else |
| attr = xmlHasNsProp(node, BAD_CAST "exclude-result-prefixes", |
| XSLT_NAMESPACE); |
| if (attr == NULL) |
| return(def); |
| |
| if (attr && (instrCategory == XSLT_ELEMENT_CATEGORY_LRE)) { |
| /* |
| * Mark the XSLT attr. |
| */ |
| attr->psvi = (void *) xsltXSLTAttrMarker; |
| } |
| |
| if ((attr->children != NULL) && |
| (attr->children->content != NULL)) |
| value = attr->children->content; |
| else { |
| xsltTransformError(NULL, cctxt->style, node, |
| "Attribute 'exclude-result-prefixes': Invalid value.\n"); |
| cctxt->style->errors++; |
| return(def); |
| } |
| |
| if (xsltParseNsPrefixList(cctxt, cctxt->tmpList, node, |
| BAD_CAST value) != 0) |
| goto exit; |
| if (cctxt->tmpList->number == 0) |
| goto exit; |
| /* |
| * Merge the list with the inherited list. |
| */ |
| list = xsltCompilerUtilsCreateMergedList(def, cctxt->tmpList); |
| if (list == NULL) |
| goto exit; |
| /* |
| * Store the list in the stylesheet/compiler context. |
| */ |
| if (xsltPointerListAddSize( |
| cctxt->psData->exclResultNamespaces, list, 5) == -1) |
| { |
| xsltPointerListFree(list); |
| list = NULL; |
| goto exit; |
| } |
| /* |
| * Notify of change in status wrt namespaces. |
| */ |
| if (cctxt->inode != NULL) |
| cctxt->inode->nsChanged = 1; |
| |
| exit: |
| if (list != NULL) |
| return(list); |
| else |
| return(def); |
| } |
| |
| /* |
| * xsltParseExtElemPrefixes: |
| * |
| * Create and store the list of in-scope namespaces for the given |
| * node in the stylesheet. If there are no changes in the in-scope |
| * namespaces then the last ns-info of the ancestor axis will be returned. |
| * Compilation-time only. |
| * |
| * Returns the ns-info or NULL if there are no namespaces in scope. |
| */ |
| static xsltPointerListPtr |
| xsltParseExtElemPrefixes(xsltCompilerCtxtPtr cctxt, xmlNodePtr node, |
| xsltPointerListPtr def, |
| int instrCategory) |
| { |
| xsltPointerListPtr list = NULL; |
| xmlAttrPtr attr; |
| xmlChar *value; |
| int i; |
| |
| if ((cctxt == NULL) || (node == NULL)) |
| return(NULL); |
| |
| if (instrCategory == XSLT_ELEMENT_CATEGORY_XSLT) |
| attr = xmlHasNsProp(node, BAD_CAST "extension-element-prefixes", NULL); |
| else |
| attr = xmlHasNsProp(node, BAD_CAST "extension-element-prefixes", |
| XSLT_NAMESPACE); |
| if (attr == NULL) |
| return(def); |
| |
| if (attr && (instrCategory == XSLT_ELEMENT_CATEGORY_LRE)) { |
| /* |
| * Mark the XSLT attr. |
| */ |
| attr->psvi = (void *) xsltXSLTAttrMarker; |
|