| /* |
| * 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; |
| } |
| |
| if ((attr->children != NULL) && |
| (attr->children->content != NULL)) |
| value = attr->children->content; |
| else { |
| xsltTransformError(NULL, cctxt->style, node, |
| "Attribute 'extension-element-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; |
| /* |
| * REVISIT: Register the extension namespaces. |
| */ |
| for (i = 0; i < cctxt->tmpList->number; i++) |
| xsltRegisterExtPrefix(cctxt->style, NULL, |
| BAD_CAST cctxt->tmpList->items[i]); |
| /* |
| * Merge the list with the inherited list. |
| */ |
| list = xsltCompilerUtilsCreateMergedList(def, cctxt->tmpList); |
| if (list == NULL) |
| goto exit; |
| /* |
| * Store the list in the stylesheet. |
| */ |
| if (xsltPointerListAddSize( |
| cctxt->psData->extElemNamespaces, 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); |
| } |
| |
| /* |
| * xsltParseAttrXSLTVersion: |
| * |
| * @cctxt: the compilation context |
| * @node: the element-node |
| * @isXsltElem: whether this is an XSLT element |
| * |
| * Parses the attribute xsl:version. |
| * |
| * Returns 1 if there was such an attribute, 0 if not and |
| * -1 if an internal or API error occured. |
| */ |
| static int |
| xsltParseAttrXSLTVersion(xsltCompilerCtxtPtr cctxt, xmlNodePtr node, |
| int instrCategory) |
| { |
| xmlChar *value; |
| xmlAttrPtr attr; |
| |
| if ((cctxt == NULL) || (node == NULL)) |
| return(-1); |
| |
| if (instrCategory == XSLT_ELEMENT_CATEGORY_XSLT) |
| attr = xmlHasNsProp(node, BAD_CAST "version", NULL); |
| else |
| attr = xmlHasNsProp(node, BAD_CAST "version", XSLT_NAMESPACE); |
| |
| if (attr == NULL) |
| return(0); |
| |
| attr->psvi = (void *) xsltXSLTAttrMarker; |
| |
| if ((attr->children != NULL) && |
| (attr->children->content != NULL)) |
| value = attr->children->content; |
| else { |
| xsltTransformError(NULL, cctxt->style, node, |
| "Attribute 'version': Invalid value.\n"); |
| cctxt->style->errors++; |
| return(1); |
| } |
| |
| if (! xmlStrEqual(value, (const xmlChar *)"1.0")) { |
| cctxt->inode->forwardsCompat = 1; |
| /* |
| * TODO: To what extent do we support the |
| * forwards-compatible mode? |
| */ |
| /* |
| * Report this only once per compilation episode. |
| */ |
| if (! cctxt->hasForwardsCompat) { |
| cctxt->hasForwardsCompat = 1; |
| cctxt->errSeverity = XSLT_ERROR_SEVERITY_WARNING; |
| xsltTransformError(NULL, cctxt->style, node, |
| "Warning: the attribute xsl:version specifies a value " |
| "different from '1.0'. Switching to forwards-compatible " |
| "mode. Only features of XSLT 1.0 are supported by this " |
| "processor.\n"); |
| cctxt->style->warnings++; |
| cctxt->errSeverity = XSLT_ERROR_SEVERITY_ERROR; |
| } |
| } else { |
| cctxt->inode->forwardsCompat = 0; |
| } |
| |
| if (attr && (instrCategory == XSLT_ELEMENT_CATEGORY_LRE)) { |
| /* |
| * Set a marker on XSLT attributes. |
| */ |
| attr->psvi = (void *) xsltXSLTAttrMarker; |
| } |
| return(1); |
| } |
| |
| static int |
| xsltParsePreprocessStylesheetTree(xsltCompilerCtxtPtr cctxt, xmlNodePtr node) |
| { |
| xmlNodePtr deleteNode, cur, txt, textNode = NULL; |
| xmlDocPtr doc; |
| xsltStylesheetPtr style; |
| int internalize = 0, findSpaceAttr; |
| int xsltStylesheetElemDepth; |
| xmlAttrPtr attr; |
| xmlChar *value; |
| const xmlChar *name, *nsNameXSLT = NULL; |
| int strictWhitespace, inXSLText = 0; |
| #ifdef XSLT_REFACTORED_XSLT_NSCOMP |
| xsltNsMapPtr nsMapItem; |
| #endif |
| |
| if ((cctxt == NULL) || (cctxt->style == NULL) || |
| (node == NULL) || (node->type != XML_ELEMENT_NODE)) |
| return(-1); |
| |
| doc = node->doc; |
| if (doc == NULL) |
| goto internal_err; |
| |
| style = cctxt->style; |
| if ((style->dict != NULL) && (doc->dict == style->dict)) |
| internalize = 1; |
| else |
| style->internalized = 0; |
| |
| /* |
| * Init value of xml:space. Since this might be an embedded |
| * stylesheet, this is needed to be performed on the element |
| * where the stylesheet is rooted at, taking xml:space of |
| * ancestors into account. |
| */ |
| if (! cctxt->simplified) |
| xsltStylesheetElemDepth = cctxt->depth +1; |
| else |
| xsltStylesheetElemDepth = 0; |
| |
| if (xmlNodeGetSpacePreserve(node) != 1) |
| cctxt->inode->preserveWhitespace = 0; |
| else |
| cctxt->inode->preserveWhitespace = 1; |
| |
| /* |
| * Eval if we should keep the old incorrect behaviour. |
| */ |
| strictWhitespace = (cctxt->strict != 0) ? 1 : 0; |
| |
| nsNameXSLT = xsltConstNamespaceNameXSLT; |
| |
| deleteNode = NULL; |
| cur = node; |
| while (cur != NULL) { |
| if (deleteNode != NULL) { |
| |
| #ifdef WITH_XSLT_DEBUG_BLANKS |
| xsltGenericDebug(xsltGenericDebugContext, |
| "xsltParsePreprocessStylesheetTree: removing node\n"); |
| #endif |
| xmlUnlinkNode(deleteNode); |
| xmlFreeNode(deleteNode); |
| deleteNode = NULL; |
| } |
| if (cur->type == XML_ELEMENT_NODE) { |
| |
| /* |
| * Clear the PSVI field. |
| */ |
| cur->psvi = NULL; |
| |
| xsltCompilerNodePush(cctxt, cur); |
| |
| inXSLText = 0; |
| textNode = NULL; |
| findSpaceAttr = 1; |
| cctxt->inode->stripWhitespace = 0; |
| /* |
| * TODO: I'd love to use a string pointer comparison here :-/ |
| */ |
| if (IS_XSLT_ELEM(cur)) { |
| #ifdef XSLT_REFACTORED_XSLT_NSCOMP |
| if (cur->ns->href != nsNameXSLT) { |
| nsMapItem = xsltNewNamespaceMapItem(cctxt, |
| doc, cur->ns, cur); |
| if (nsMapItem == NULL) |
| goto internal_err; |
| cur->ns->href = nsNameXSLT; |
| } |
| #endif |
| |
| if (cur->name == NULL) |
| goto process_attributes; |
| /* |
| * Mark the XSLT element for later recognition. |
| * TODO: Using the marker is still too dangerous, since if |
| * the parsing mechanism leaves out an XSLT element, then |
| * this might hit the transformation-mechanism, which |
| * will break if it doesn't expect such a marker. |
| */ |
| /* cur->psvi = (void *) xsltXSLTElemMarker; */ |
| |
| /* |
| * XSLT 2.0: "Any whitespace text node whose parent is |
| * one of the following elements is removed from the " |
| * tree, regardless of any xml:space attributes:..." |
| * xsl:apply-imports, |
| * xsl:apply-templates, |
| * xsl:attribute-set, |
| * xsl:call-template, |
| * xsl:choose, |
| * xsl:stylesheet, xsl:transform. |
| * XSLT 2.0: xsl:analyze-string, |
| * xsl:character-map, |
| * xsl:next-match |
| * |
| * TODO: I'd love to use a string pointer comparison here :-/ |
| */ |
| name = cur->name; |
| switch (*name) { |
| case 't': |
| if ((name[0] == 't') && (name[1] == 'e') && |
| (name[2] == 'x') && (name[3] == 't') && |
| (name[4] == 0)) |
| { |
| /* |
| * Process the xsl:text element. |
| * ---------------------------- |
| * Mark it for later recognition. |
| */ |
| cur->psvi = (void *) xsltXSLTTextMarker; |
| /* |
| * For stylesheets, the set of |
| * whitespace-preserving element names |
| * consists of just xsl:text. |
| */ |
| findSpaceAttr = 0; |
| cctxt->inode->preserveWhitespace = 1; |
| inXSLText = 1; |
| } |
| break; |
| case 'c': |
| if (xmlStrEqual(name, BAD_CAST "choose") || |
| xmlStrEqual(name, BAD_CAST "call-template")) |
| cctxt->inode->stripWhitespace = 1; |
| break; |
| case 'a': |
| if (xmlStrEqual(name, BAD_CAST "apply-templates") || |
| xmlStrEqual(name, BAD_CAST "apply-imports") || |
| xmlStrEqual(name, BAD_CAST "attribute-set")) |
| |
| cctxt->inode->stripWhitespace = 1; |
| break; |
| default: |
| if (xsltStylesheetElemDepth == cctxt->depth) { |
| /* |
| * This is a xsl:stylesheet/xsl:transform. |
| */ |
| cctxt->inode->stripWhitespace = 1; |
| break; |
| } |
| |
| if ((cur->prev != NULL) && |
| (cur->prev->type == XML_TEXT_NODE)) |
| { |
| /* |
| * XSLT 2.0 : "Any whitespace text node whose |
| * following-sibling node is an xsl:param or |
| * xsl:sort element is removed from the tree, |
| * regardless of any xml:space attributes." |
| */ |
| if (((*name == 'p') || (*name == 's')) && |
| (xmlStrEqual(name, BAD_CAST "param") || |
| xmlStrEqual(name, BAD_CAST "sort"))) |
| { |
| do { |
| if (IS_BLANK_NODE(cur->prev)) { |
| txt = cur->prev; |
| xmlUnlinkNode(txt); |
| xmlFreeNode(txt); |
| } else { |
| /* |
| * This will result in a content |
| * error, when hitting the parsing |
| * functions. |
| */ |
| break; |
| } |
| } while (cur->prev); |
| } |
| } |
| break; |
| } |
| } |
| |
| process_attributes: |
| /* |
| * Process attributes. |
| * ------------------ |
| */ |
| if (cur->properties != NULL) { |
| if (cur->children == NULL) |
| findSpaceAttr = 0; |
| attr = cur->properties; |
| do { |
| #ifdef XSLT_REFACTORED_XSLT_NSCOMP |
| if ((attr->ns) && (attr->ns->href != nsNameXSLT) && |
| xmlStrEqual(attr->ns->href, nsNameXSLT)) |
| { |
| nsMapItem = xsltNewNamespaceMapItem(cctxt, |
| doc, attr->ns, cur); |
| if (nsMapItem == NULL) |
| goto internal_err; |
| attr->ns->href = nsNameXSLT; |
| } |
| #endif |
| if (internalize) { |
| /* |
| * Internalize the attribute's value; the goal is to |
| * speed up operations and minimize used space by |
| * compiled stylesheets. |
| */ |
| txt = attr->children; |
| /* |
| * NOTE that this assumes only one |
| * text-node in the attribute's content. |
| */ |
| if ((txt != NULL) && (txt->content != NULL) && |
| (!xmlDictOwns(style->dict, txt->content))) |
| { |
| value = (xmlChar *) xmlDictLookup(style->dict, |
| txt->content, -1); |
| xmlNodeSetContent(txt, NULL); |
| txt->content = value; |
| } |
| } |
| /* |
| * Process xml:space attributes. |
| * ---------------------------- |
| */ |
| if ((findSpaceAttr != 0) && |
| (attr->ns != NULL) && |
| (attr->name != NULL) && |
| (attr->name[0] == 's') && |
| (attr->ns->prefix != NULL) && |
| (attr->ns->prefix[0] == 'x') && |
| (attr->ns->prefix[1] == 'm') && |
| (attr->ns->prefix[2] == 'l') && |
| (attr->ns->prefix[3] == 0)) |
| { |
| value = xmlGetNsProp(cur, BAD_CAST "space", |
| XML_XML_NAMESPACE); |
| if (value != NULL) { |
| if (xmlStrEqual(value, BAD_CAST "preserve")) { |
| cctxt->inode->preserveWhitespace = 1; |
| } else if (xmlStrEqual(value, BAD_CAST "default")) { |
| cctxt->inode->preserveWhitespace = 0; |
| } else { |
| /* Invalid value for xml:space. */ |
| xsltTransformError(NULL, style, cur, |
| "Attribute xml:space: Invalid value.\n"); |
| cctxt->style->warnings++; |
| } |
| findSpaceAttr = 0; |
| xmlFree(value); |
| } |
| |
| } |
| attr = attr->next; |
| } while (attr != NULL); |
| } |
| /* |
| * We'll descend into the children of element nodes only. |
| */ |
| if (cur->children != NULL) { |
| cur = cur->children; |
| continue; |
| } |
| } else if ((cur->type == XML_TEXT_NODE) || |
| (cur->type == XML_CDATA_SECTION_NODE)) |
| { |
| /* |
| * Merge adjacent text/CDATA-section-nodes |
| * --------------------------------------- |
| * In order to avoid breaking of existing stylesheets, |
| * if the old behaviour is wanted (strictWhitespace == 0), |
| * then we *won't* merge adjacent text-nodes |
| * (except in xsl:text); this will ensure that whitespace-only |
| * text nodes are (incorrectly) not stripped in some cases. |
| * |
| * Example: : <foo> <!-- bar -->zoo</foo> |
| * Corrent (strict) result: <foo> zoo</foo> |
| * Incorrect (old) result : <foo>zoo</foo> |
| * |
| * NOTE that we *will* merge adjacent text-nodes if |
| * they are in xsl:text. |
| * Example, the following: |
| * <xsl:text> <!-- bar -->zoo<xsl:text> |
| * will result in both cases in: |
| * <xsl:text> zoo<xsl:text> |
| */ |
| cur->type = XML_TEXT_NODE; |
| if ((strictWhitespace != 0) || (inXSLText != 0)) { |
| /* |
| * New behaviour; merge nodes. |
| */ |
| if (textNode == NULL) |
| textNode = cur; |
| else { |
| if (cur->content != NULL) |
| xmlNodeAddContent(textNode, cur->content); |
| deleteNode = cur; |
| } |
| if ((cur->next == NULL) || |
| (cur->next->type == XML_ELEMENT_NODE)) |
| goto end_of_text; |
| else |
| goto next_sibling; |
| } else { |
| /* |
| * Old behaviour. |
| */ |
| if (textNode == NULL) |
| textNode = cur; |
| goto end_of_text; |
| } |
| } else if ((cur->type == XML_COMMENT_NODE) || |
| (cur->type == XML_PI_NODE)) |
| { |
| /* |
| * Remove processing instructions and comments. |
| */ |
| deleteNode = cur; |
| if ((cur->next == NULL) || |
| (cur->next->type == XML_ELEMENT_NODE)) |
| goto end_of_text; |
| else |
| goto next_sibling; |
| } else { |
| textNode = NULL; |
| /* |
| * Invalid node-type for this data-model. |
| */ |
| xsltTransformError(NULL, style, cur, |
| "Invalid type of node for the XSLT data model.\n"); |
| cctxt->style->errors++; |
| goto next_sibling; |
| } |
| |
| end_of_text: |
| if (textNode) { |
| value = textNode->content; |
| /* |
| * At this point all adjacent text/CDATA-section nodes |
| * have been merged. |
| * |
| * Strip whitespace-only text-nodes. |
| * (cctxt->inode->stripWhitespace) |
| */ |
| if ((value == NULL) || (*value == 0) || |
| (((cctxt->inode->stripWhitespace) || |
| (! cctxt->inode->preserveWhitespace)) && |
| IS_BLANK(*value) && |
| xsltIsBlank(value))) |
| { |
| if (textNode != cur) { |
| xmlUnlinkNode(textNode); |
| xmlFreeNode(textNode); |
| } else |
| deleteNode = textNode; |
| textNode = NULL; |
| goto next_sibling; |
| } |
| /* |
| * Convert CDATA-section nodes to text-nodes. |
| * TODO: Can this produce problems? |
| */ |
| if (textNode->type != XML_TEXT_NODE) { |
| textNode->type = XML_TEXT_NODE; |
| textNode->name = xmlStringText; |
| } |
| if (internalize && |
| (textNode->content != NULL) && |
| (!xmlDictOwns(style->dict, textNode->content))) |
| { |
| /* |
| * Internalize the string. |
| */ |
| value = (xmlChar *) xmlDictLookup(style->dict, |
| textNode->content, -1); |
| xmlNodeSetContent(textNode, NULL); |
| textNode->content = value; |
| } |
| textNode = NULL; |
| /* |
| * Note that "disable-output-escaping" of the xsl:text |
| * element will be applied at a later level, when |
| * XSLT elements are processed. |
| */ |
| } |
| |
| next_sibling: |
| if (cur->type == XML_ELEMENT_NODE) { |
| xsltCompilerNodePop(cctxt, cur); |
| } |
| if (cur == node) |
| break; |
| if (cur->next != NULL) { |
| cur = cur->next; |
| } else { |
| cur = cur->parent; |
| inXSLText = 0; |
| goto next_sibling; |
| }; |
| } |
| if (deleteNode != NULL) { |
| #ifdef WITH_XSLT_DEBUG_PARSING |
| xsltGenericDebug(xsltGenericDebugContext, |
| "xsltParsePreprocessStylesheetTree: removing node\n"); |
| #endif |
| xmlUnlinkNode(deleteNode); |
| xmlFreeNode(deleteNode); |
| } |
| return(0); |
| |
| internal_err: |
| return(-1); |
| } |
| |
| #endif /* XSLT_REFACTORED */ |
| |
| #ifdef XSLT_REFACTORED |
| #else |
| static void |
| xsltPrecomputeStylesheet(xsltStylesheetPtr style, xmlNodePtr cur) |
| { |
| xmlNodePtr deleteNode, styleelem; |
| int internalize = 0; |
| |
| if ((style == NULL) || (cur == NULL)) |
| return; |
| |
| if ((cur->doc != NULL) && (style->dict != NULL) && |
| (cur->doc->dict == style->dict)) |
| internalize = 1; |
| else |
| style->internalized = 0; |
| |
| if ((cur != NULL) && (IS_XSLT_ELEM(cur)) && |
| (IS_XSLT_NAME(cur, "stylesheet"))) { |
| styleelem = cur; |
| } else { |
| styleelem = NULL; |
| } |
| |
| /* |
| * This content comes from the stylesheet |
| * For stylesheets, the set of whitespace-preserving |
| * element names consists of just xsl:text. |
| */ |
| deleteNode = NULL; |
| while (cur != NULL) { |
| if (deleteNode != NULL) { |
| #ifdef WITH_XSLT_DEBUG_BLANKS |
| xsltGenericDebug(xsltGenericDebugContext, |
| "xsltPrecomputeStylesheet: removing ignorable blank node\n"); |
| #endif |
| xmlUnlinkNode(deleteNode); |
| xmlFreeNode(deleteNode); |
| deleteNode = NULL; |
| } |
| if (cur->type == XML_ELEMENT_NODE) { |
| int exclPrefixes; |
| /* |
| * Internalize attributes values. |
| */ |
| if ((internalize) && (cur->properties != NULL)) { |
| xmlAttrPtr attr = cur->properties; |
| xmlNodePtr txt; |
| |
| while (attr != NULL) { |
| txt = attr->children; |
| if ((txt != NULL) && (txt->type == XML_TEXT_NODE) && |
| (txt->content != NULL) && |
| (!xmlDictOwns(style->dict, txt->content))) |
| { |
| xmlChar *tmp; |
| |
| /* |
| * internalize the text string, goal is to speed |
| * up operations and minimize used space by compiled |
| * stylesheets. |
| */ |
| tmp = (xmlChar *) xmlDictLookup(style->dict, |
| txt->content, -1); |
| if (tmp != txt->content) { |
| xmlNodeSetContent(txt, NULL); |
| txt->content = tmp; |
| } |
| } |
| attr = attr->next; |
| } |
| } |
| if (IS_XSLT_ELEM(cur)) { |
| exclPrefixes = 0; |
| xsltStylePreCompute(style, cur); |
| if (IS_XSLT_NAME(cur, "text")) { |
| for (;exclPrefixes > 0;exclPrefixes--) |
| exclPrefixPop(style); |
| goto skip_children; |
| } |
| } else { |
| exclPrefixes = xsltParseStylesheetExcludePrefix(style, cur, 0); |
| } |
| |
| if ((cur->nsDef != NULL) && (style->exclPrefixNr > 0)) { |
| xmlNsPtr ns = cur->nsDef, prev = NULL, next; |
| xmlNodePtr root = NULL; |
| int i, moved; |
| |
| root = xmlDocGetRootElement(cur->doc); |
| if ((root != NULL) && (root != cur)) { |
| while (ns != NULL) { |
| moved = 0; |
| next = ns->next; |
| for (i = 0;i < style->exclPrefixNr;i++) { |
| if ((ns->prefix != NULL) && |
| (xmlStrEqual(ns->href, |
| style->exclPrefixTab[i]))) { |
| /* |
| * Move the namespace definition on the root |
| * element to avoid duplicating it without |
| * loosing it. |
| */ |
| if (prev == NULL) { |
| cur->nsDef = ns->next; |
| } else { |
| prev->next = ns->next; |
| } |
| ns->next = root->nsDef; |
| root->nsDef = ns; |
| moved = 1; |
| break; |
| } |
| } |
| if (moved == 0) |
| prev = ns; |
| ns = next; |
| } |
| } |
| } |
| /* |
| * If we have prefixes locally, recurse and pop them up when |
| * going back |
| */ |
| if (exclPrefixes > 0) { |
| xsltPrecomputeStylesheet(style, cur->children); |
| for (;exclPrefixes > 0;exclPrefixes--) |
| exclPrefixPop(style); |
| goto skip_children; |
| } |
| } else if (cur->type == XML_TEXT_NODE) { |
| if (IS_BLANK_NODE(cur)) { |
| if (xmlNodeGetSpacePreserve(cur) != 1) { |
| deleteNode = cur; |
| } |
| } else if ((cur->content != NULL) && (internalize) && |
| (!xmlDictOwns(style->dict, cur->content))) { |
| xmlChar *tmp; |
| |
| /* |
| * internalize the text string, goal is to speed |
| * up operations and minimize used space by compiled |
| * stylesheets. |
| */ |
| tmp = (xmlChar *) xmlDictLookup(style->dict, cur->content, -1); |
| xmlNodeSetContent(cur, NULL); |
| cur->content = tmp; |
| } |
| } else if ((cur->type != XML_ELEMENT_NODE) && |
| (cur->type != XML_CDATA_SECTION_NODE)) { |
| deleteNode = cur; |
| goto skip_children; |
| } |
| |
| /* |
| * Skip to next node. In case of a namespaced element children of |
| * the stylesheet and not in the XSLT namespace and not an extension |
| * element, ignore its content. |
| */ |
| if ((cur->type == XML_ELEMENT_NODE) && (cur->ns != NULL) && |
| (styleelem != NULL) && (cur->parent == styleelem) && |
| (!xmlStrEqual(cur->ns->href, XSLT_NAMESPACE)) && |
| (!xsltCheckExtURI(style, cur->ns->href))) { |
| goto skip_children; |
| } else if (cur->children != NULL) { |
| if ((cur->children->type != XML_ENTITY_DECL) && |
| (cur->children->type != XML_ENTITY_REF_NODE) && |
| (cur->children->type != XML_ENTITY_NODE)) { |
| cur = cur->children; |
| continue; |
| } |
| } |
| |
| skip_children: |
| if (cur->next != NULL) { |
| cur = cur->next; |
| continue; |
| } |
| do { |
| |
| cur = cur->parent; |
| if (cur == NULL) |
| break; |
| if (cur == (xmlNodePtr) style->doc) { |
| cur = NULL; |
| break; |
| } |
| if (cur->next != NULL) { |
| cur = cur->next; |
| break; |
| } |
| } while (cur != NULL); |
| } |
| if (deleteNode != NULL) { |
| #ifdef WITH_XSLT_DEBUG_PARSING |
| xsltGenericDebug(xsltGenericDebugContext, |
| "xsltPrecomputeStylesheet: removing ignorable blank node\n"); |
| #endif |
| xmlUnlinkNode(deleteNode); |
| xmlFreeNode(deleteNode); |
| } |
| } |
| #endif /* end of else XSLT_REFACTORED */ |
| |
| /** |
| * xsltGatherNamespaces: |
| * @style: the XSLT stylesheet |
| * |
| * Browse the stylesheet and build the namspace hash table which |
| * will be used for XPath interpretation. If needed do a bit of normalization |
| */ |
| |
| static void |
| xsltGatherNamespaces(xsltStylesheetPtr style) { |
| xmlNodePtr cur; |
| const xmlChar *URI; |
| |
| if (style == NULL) |
| return; |
| /* |
| * TODO: basically if the stylesheet uses the same prefix for different |
| * patterns, well they may be in problem, hopefully they will get |
| * a warning first. |
| */ |
| /* |
| * TODO: Eliminate the use of the hash for XPath expressions. |
| * An expression should be evaluated in the context of the in-scope |
| * namespaces; eliminate the restriction of an XML document to contain |
| * no duplicate prefixes for different namespace names. |
| * |
| */ |
| cur = xmlDocGetRootElement(style->doc); |
| while (cur != NULL) { |
| if (cur->type == XML_ELEMENT_NODE) { |
| xmlNsPtr ns = cur->nsDef; |
| while (ns != NULL) { |
| if (ns->prefix != NULL) { |
| if (style->nsHash == NULL) { |
| style->nsHash = xmlHashCreate(10); |
| if (style->nsHash == NULL) { |
| xsltTransformError(NULL, style, cur, |
| "xsltGatherNamespaces: failed to create hash table\n"); |
| style->errors++; |
| return; |
| } |
| } |
| URI = xmlHashLookup(style->nsHash, ns->prefix); |
| if ((URI != NULL) && (!xmlStrEqual(URI, ns->href))) { |
| xsltTransformError(NULL, style, cur, |
| "Namespaces prefix %s used for multiple namespaces\n",ns->prefix); |
| style->warnings++; |
| } else if (URI == NULL) { |
| xmlHashUpdateEntry(style->nsHash, ns->prefix, |
| (void *) ns->href, (xmlHashDeallocator)xmlFree); |
| |
| #ifdef WITH_XSLT_DEBUG_PARSING |
| xsltGenericDebug(xsltGenericDebugContext, |
| "Added namespace: %s mapped to %s\n", ns->prefix, ns->href); |
| #endif |
| } |
| } |
| ns = ns->next; |
| } |
| } |
| |
| /* |
| * Skip to next node |
| */ |
| if (cur->children != NULL) { |
| if (cur->children->type != XML_ENTITY_DECL) { |
| cur = cur->children; |
| continue; |
| } |
| } |
| if (cur->next != NULL) { |
| cur = cur->next; |
| continue; |
| } |
| |
| do { |
| cur = cur->parent; |
| if (cur == NULL) |
| break; |
| if (cur == (xmlNodePtr) style->doc) { |
| cur = NULL; |
| break; |
| } |
| if (cur->next != NULL) { |
| cur = cur->next; |
| break; |
| } |
| } while (cur != NULL); |
| } |
| } |
| |
| #ifdef XSLT_REFACTORED |
| |
| static xsltStyleType |
| xsltGetXSLTElementTypeByNode(xsltCompilerCtxtPtr cctxt, |
| xmlNodePtr node) |
| { |
| if ((node == NULL) || (node->type != XML_ELEMENT_NODE) || |
| (node->name == NULL)) |
| return(0); |
| |
| if (node->name[0] == 'a') { |
| if (IS_XSLT_NAME(node, "apply-templates")) |
| return(XSLT_FUNC_APPLYTEMPLATES); |
| else if (IS_XSLT_NAME(node, "attribute")) |
| return(XSLT_FUNC_ATTRIBUTE); |
| else if (IS_XSLT_NAME(node, "apply-imports")) |
| return(XSLT_FUNC_APPLYIMPORTS); |
| else if (IS_XSLT_NAME(node, "attribute-set")) |
| return(0); |
| |
| } else if (node->name[0] == 'c') { |
| if (IS_XSLT_NAME(node, "choose")) |
| return(XSLT_FUNC_CHOOSE); |
| else if (IS_XSLT_NAME(node, "copy")) |
| return(XSLT_FUNC_COPY); |
| else if (IS_XSLT_NAME(node, "copy-of")) |
| return(XSLT_FUNC_COPYOF); |
| else if (IS_XSLT_NAME(node, "call-template")) |
| return(XSLT_FUNC_CALLTEMPLATE); |
| else if (IS_XSLT_NAME(node, "comment")) |
| return(XSLT_FUNC_COMMENT); |
| |
| } else if (node->name[0] == 'd') { |
| if (IS_XSLT_NAME(node, "document")) |
| return(XSLT_FUNC_DOCUMENT); |
| else if (IS_XSLT_NAME(node, "decimal-format")) |
| return(0); |
| |
| } else if (node->name[0] == 'e') { |
| if (IS_XSLT_NAME(node, "element")) |
| return(XSLT_FUNC_ELEMENT); |
| |
| } else if (node->name[0] == 'f') { |
| if (IS_XSLT_NAME(node, "for-each")) |
| return(XSLT_FUNC_FOREACH); |
| else if (IS_XSLT_NAME(node, "fallback")) |
| return(XSLT_FUNC_FALLBACK); |
| |
| } else if (*(node->name) == 'i') { |
| if (IS_XSLT_NAME(node, "if")) |
| return(XSLT_FUNC_IF); |
| else if (IS_XSLT_NAME(node, "include")) |
| return(0); |
| else if (IS_XSLT_NAME(node, "import")) |
| return(0); |
| |
| } else if (*(node->name) == 'k') { |
| if (IS_XSLT_NAME(node, "key")) |
| return(0); |
| |
| } else if (*(node->name) == 'm') { |
| if (IS_XSLT_NAME(node, "message")) |
| return(XSLT_FUNC_MESSAGE); |
| |
| } else if (*(node->name) == 'n') { |
| if (IS_XSLT_NAME(node, "number")) |
| return(XSLT_FUNC_NUMBER); |
| else if (IS_XSLT_NAME(node, "namespace-alias")) |
| return(0); |
| |
| } else if (*(node->name) == 'o') { |
| if (IS_XSLT_NAME(node, "otherwise")) |
| return(XSLT_FUNC_OTHERWISE); |
| else if (IS_XSLT_NAME(node, "output")) |
| return(0); |
| |
| } else if (*(node->name) == 'p') { |
| if (IS_XSLT_NAME(node, "param")) |
| return(XSLT_FUNC_PARAM); |
| else if (IS_XSLT_NAME(node, "processing-instruction")) |
| return(XSLT_FUNC_PI); |
| else if (IS_XSLT_NAME(node, "preserve-space")) |
| return(0); |
| |
| } else if (*(node->name) == 's') { |
| if (IS_XSLT_NAME(node, "sort")) |
| return(XSLT_FUNC_SORT); |
| else if (IS_XSLT_NAME(node, "strip-space")) |
| return(0); |
| else if (IS_XSLT_NAME(node, "stylesheet")) |
| return(0); |
| |
| } else if (node->name[0] == 't') { |
| if (IS_XSLT_NAME(node, "text")) |
| return(XSLT_FUNC_TEXT); |
| else if (IS_XSLT_NAME(node, "template")) |
| return(0); |
| else if (IS_XSLT_NAME(node, "transform")) |
| return(0); |
| |
| } else if (*(node->name) == 'v') { |
| if (IS_XSLT_NAME(node, "value-of")) |
| return(XSLT_FUNC_VALUEOF); |
| else if (IS_XSLT_NAME(node, "variable")) |
| return(XSLT_FUNC_VARIABLE); |
| |
| } else if (*(node->name) == 'w') { |
| if (IS_XSLT_NAME(node, "when")) |
| return(XSLT_FUNC_WHEN); |
| if (IS_XSLT_NAME(node, "with-param")) |
| return(XSLT_FUNC_WITHPARAM); |
| } |
| return(0); |
| } |
| |
| /** |
| * xsltParseAnyXSLTElem: |
| * |
| * @cctxt: the compilation context |
| * @elem: the element node of the XSLT instruction |
| * |
| * Parses, validates the content models and compiles XSLT instructions. |
| * |
| * Returns 0 if everything's fine; |
| * -1 on API or internal errors. |
| */ |
| int |
| xsltParseAnyXSLTElem(xsltCompilerCtxtPtr cctxt, xmlNodePtr elem) |
| { |
| if ((cctxt == NULL) || (elem == NULL) || |
| (elem->type != XML_ELEMENT_NODE)) |
| return(-1); |
| |
| elem->psvi = NULL; |
| |
| if (! (IS_XSLT_ELEM_FAST(elem))) |
| return(-1); |
| /* |
| * Detection of handled content of extension instructions. |
| */ |
| if (cctxt->inode->category == XSLT_ELEMENT_CATEGORY_EXTENSION) { |
| cctxt->inode->extContentHandled = 1; |
| } |
| |
| xsltCompilerNodePush(cctxt, elem); |
| /* |
| * URGENT TODO: Find a way to speed up this annoying redundant |
| * textual node-name and namespace comparison. |
| */ |
| if (cctxt->inode->prev->curChildType != 0) |
| cctxt->inode->type = cctxt->inode->prev->curChildType; |
| else |
| cctxt->inode->type = xsltGetXSLTElementTypeByNode(cctxt, elem); |
| /* |
| * Update the in-scope namespaces if needed. |
| */ |
| if (elem->nsDef != NULL) |
| cctxt->inode->inScopeNs = |
| xsltCompilerBuildInScopeNsList(cctxt, elem); |
| /* |
| * xsltStylePreCompute(): |
| * This will compile the information found on the current |
| * element's attributes. NOTE that this won't process the |
| * children of the instruction. |
| */ |
| xsltStylePreCompute(cctxt->style, elem); |
| /* |
| * TODO: How to react on errors in xsltStylePreCompute() ? |
| */ |
| |
| /* |
| * Validate the content model of the XSLT-element. |
| */ |
| switch (cctxt->inode->type) { |
| case XSLT_FUNC_APPLYIMPORTS: |
| /* EMPTY */ |
| goto empty_content; |
| case XSLT_FUNC_APPLYTEMPLATES: |
| /* <!-- Content: (xsl:sort | xsl:with-param)* --> */ |
| goto apply_templates; |
| case XSLT_FUNC_ATTRIBUTE: |
| /* <!-- Content: template --> */ |
| goto sequence_constructor; |
| case XSLT_FUNC_CALLTEMPLATE: |
| /* <!-- Content: xsl:with-param* --> */ |
| goto call_template; |
| case XSLT_FUNC_CHOOSE: |
| /* <!-- Content: (xsl:when+, xsl:otherwise?) --> */ |
| goto choose; |
| case XSLT_FUNC_COMMENT: |
| /* <!-- Content: template --> */ |
| goto sequence_constructor; |
| case XSLT_FUNC_COPY: |
| /* <!-- Content: template --> */ |
| goto sequence_constructor; |
| case XSLT_FUNC_COPYOF: |
| /* EMPTY */ |
| goto empty_content; |
| case XSLT_FUNC_DOCUMENT: /* Extra one */ |
| /* ?? template ?? */ |
| goto sequence_constructor; |
| case XSLT_FUNC_ELEMENT: |
| /* <!-- Content: template --> */ |
| goto sequence_constructor; |
| case XSLT_FUNC_FALLBACK: |
| /* <!-- Content: template --> */ |
| goto sequence_constructor; |
| case XSLT_FUNC_FOREACH: |
| /* <!-- Content: (xsl:sort*, template) --> */ |
| goto for_each; |
| case XSLT_FUNC_IF: |
| /* <!-- Content: template --> */ |
| goto sequence_constructor; |
| case XSLT_FUNC_OTHERWISE: |
| /* <!-- Content: template --> */ |
| goto sequence_constructor; |
| case XSLT_FUNC_MESSAGE: |
| /* <!-- Content: template --> */ |
| goto sequence_constructor; |
| case XSLT_FUNC_NUMBER: |
| /* EMPTY */ |
| goto empty_content; |
| case XSLT_FUNC_PARAM: |
| /* |
| * Check for redefinition. |
| */ |
| if ((elem->psvi != NULL) && (cctxt->ivar != NULL)) { |
| xsltVarInfoPtr ivar = cctxt->ivar; |
| |
| do { |
| if ((ivar->name == |
| ((xsltStyleItemParamPtr) elem->psvi)->name) && |
| (ivar->nsName == |
| ((xsltStyleItemParamPtr) elem->psvi)->ns)) |
| { |
| elem->psvi = NULL; |
| xsltTransformError(NULL, cctxt->style, elem, |
| "Redefinition of variable or parameter '%s'.\n", |
| ivar->name); |
| cctxt->style->errors++; |
| goto error; |
| } |
| ivar = ivar->prev; |
| } while (ivar != NULL); |
| } |
| /* <!-- Content: template --> */ |
| goto sequence_constructor; |
| case XSLT_FUNC_PI: |
| /* <!-- Content: template --> */ |
| goto sequence_constructor; |
| case XSLT_FUNC_SORT: |
| /* EMPTY */ |
| goto empty_content; |
| case XSLT_FUNC_TEXT: |
| /* <!-- Content: #PCDATA --> */ |
| goto text; |
| case XSLT_FUNC_VALUEOF: |
| /* EMPTY */ |
| goto empty_content; |
| case XSLT_FUNC_VARIABLE: |
| /* |
| * Check for redefinition. |
| */ |
| if ((elem->psvi != NULL) && (cctxt->ivar != NULL)) { |
| xsltVarInfoPtr ivar = cctxt->ivar; |
| |
| do { |
| if ((ivar->name == |
| ((xsltStyleItemVariablePtr) elem->psvi)->name) && |
| (ivar->nsName == |
| ((xsltStyleItemVariablePtr) elem->psvi)->ns)) |
| { |
| elem->psvi = NULL; |
| xsltTransformError(NULL, cctxt->style, elem, |
| "Redefinition of variable or parameter '%s'.\n", |
| ivar->name); |
| cctxt->style->errors++; |
| goto error; |
| } |
| ivar = ivar->prev; |
| } while (ivar != NULL); |
| } |
| /* <!-- Content: template --> */ |
| goto sequence_constructor; |
| case XSLT_FUNC_WHEN: |
| /* <!-- Content: template --> */ |
| goto sequence_constructor; |
| case XSLT_FUNC_WITHPARAM: |
| /* <!-- Content: template --> */ |
| goto sequence_constructor; |
| default: |
| #ifdef WITH_XSLT_DEBUG_PARSING |
| xsltGenericDebug(xsltGenericDebugContext, |
| "xsltParseXSLTNode: Unhandled XSLT element '%s'.\n", |
| elem->name); |
| #endif |
| xsltTransformError(NULL, cctxt->style, elem, |
| "xsltParseXSLTNode: Internal error; " |
| "unhandled XSLT element '%s'.\n", elem->name); |
| cctxt->style->errors++; |
| goto internal_err; |
| } |
| |
| apply_templates: |
| /* <!-- Content: (xsl:sort | xsl:with-param)* --> */ |
| if (elem->children != NULL) { |
| xmlNodePtr child = elem->children; |
| do { |
| if (child->type == XML_ELEMENT_NODE) { |
| if (IS_XSLT_ELEM_FAST(child)) { |
| if (xmlStrEqual(child->name, BAD_CAST "with-param")) { |
| cctxt->inode->curChildType = XSLT_FUNC_WITHPARAM; |
| xsltParseAnyXSLTElem(cctxt, child); |
| } else if (xmlStrEqual(child->name, BAD_CAST "sort")) { |
| cctxt->inode->curChildType = XSLT_FUNC_SORT; |
| xsltParseAnyXSLTElem(cctxt, child); |
| } else |
| xsltParseContentError(cctxt->style, child); |
| } else |
| xsltParseContentError(cctxt->style, child); |
| } |
| child = child->next; |
| } while (child != NULL); |
| } |
| goto exit; |
| |
| call_template: |
| /* <!-- Content: xsl:with-param* --> */ |
| if (elem->children != NULL) { |
| xmlNodePtr child = elem->children; |
| do { |
| if (child->type == XML_ELEMENT_NODE) { |
| if (IS_XSLT_ELEM_FAST(child)) { |
| xsltStyleType type; |
| |
| type = xsltGetXSLTElementTypeByNode(cctxt, child); |
| if (type == XSLT_FUNC_WITHPARAM) { |
| cctxt->inode->curChildType = XSLT_FUNC_WITHPARAM; |
| xsltParseAnyXSLTElem(cctxt, child); |
| } else { |
| xsltParseContentError(cctxt->style, child); |
| } |
| } else |
| xsltParseContentError(cctxt->style, child); |
| } |
| child = child->next; |
| } while (child != NULL); |
| } |
| goto exit; |
| |
| text: |
| if (elem->children != NULL) { |
| xmlNodePtr child = elem->children; |
| do { |
| if ((child->type != XML_TEXT_NODE) && |
| (child->type != XML_CDATA_SECTION_NODE)) |
| { |
| xsltTransformError(NULL, cctxt->style, elem, |
| "The XSLT 'text' element must have only character " |
| "data as content.\n"); |
| } |
| child = child->next; |
| } while (child != NULL); |
| } |
| goto exit; |
| |
| empty_content: |
| if (elem->children != NULL) { |
| xmlNodePtr child = elem->children; |
| /* |
| * Relaxed behaviour: we will allow whitespace-only text-nodes. |
| */ |
| do { |
| if (((child->type != XML_TEXT_NODE) && |
| (child->type != XML_CDATA_SECTION_NODE)) || |
| (! IS_BLANK_NODE(child))) |
| { |
| xsltTransformError(NULL, cctxt->style, elem, |
| "This XSLT element must have no content.\n"); |
| cctxt->style->errors++; |
| break; |
| } |
| child = child->next; |
| } while (child != NULL); |
| } |
| goto exit; |
| |
| choose: |
| /* <!-- Content: (xsl:when+, xsl:otherwise?) --> */ |
| /* |
| * TODO: text-nodes in between are *not* allowed in XSLT 1.0. |
| * The old behaviour did not check this. |
| * NOTE: In XSLT 2.0 they are stripped beforehand |
| * if whitespace-only (regardless of xml:space). |
| */ |
| if (elem->children != NULL) { |
| xmlNodePtr child = elem->children; |
| int nbWhen = 0, nbOtherwise = 0, err = 0; |
| do { |
| if (child->type == XML_ELEMENT_NODE) { |
| if (IS_XSLT_ELEM_FAST(child)) { |
| xsltStyleType type; |
| |
| type = xsltGetXSLTElementTypeByNode(cctxt, child); |
| if (type == XSLT_FUNC_WHEN) { |
| nbWhen++; |
| if (nbOtherwise) { |
| xsltParseContentError(cctxt->style, child); |
| err = 1; |
| break; |
| } |
| cctxt->inode->curChildType = XSLT_FUNC_WHEN; |
| xsltParseAnyXSLTElem(cctxt, child); |
| } else if (type == XSLT_FUNC_OTHERWISE) { |
| if (! nbWhen) { |
| xsltParseContentError(cctxt->style, child); |
| err = 1; |
| break; |
| } |
| if (nbOtherwise) { |
| xsltTransformError(NULL, cctxt->style, elem, |
| "The XSLT 'choose' element must not contain " |
| "more than one XSLT 'otherwise' element.\n"); |
| cctxt->style->errors++; |
| err = 1; |
| break; |
| } |
| nbOtherwise++; |
| cctxt->inode->curChildType = XSLT_FUNC_OTHERWISE; |
| xsltParseAnyXSLTElem(cctxt, child); |
| } else |
| xsltParseContentError(cctxt->style, child); |
| } else |
| xsltParseContentError(cctxt->style, child); |
| } |
| /* |
| else |
| xsltParseContentError(cctxt, child); |
| */ |
| child = child->next; |
| } while (child != NULL); |
| if ((! err) && (! nbWhen)) { |
| xsltTransformError(NULL, cctxt->style, elem, |
| "The XSLT element 'choose' must contain at least one " |
| "XSLT element 'when'.\n"); |
| cctxt->style->errors++; |
| } |
| } |
| goto exit; |
| |
| for_each: |
| /* <!-- Content: (xsl:sort*, template) --> */ |
| /* |
| * NOTE: Text-nodes before xsl:sort are *not* allowed in XSLT 1.0. |
| * The old behaviour did not allow this, but it catched this |
| * only at transformation-time. |
| * In XSLT 2.0 they are stripped beforehand if whitespace-only |
| * (regardless of xml:space). |
| */ |
| if (elem->children != NULL) { |
| xmlNodePtr child = elem->children; |
| /* |
| * Parse xsl:sort first. |
| */ |
| do { |
| if ((child->type == XML_ELEMENT_NODE) && |
| IS_XSLT_ELEM_FAST(child)) |
| { |
| if (xsltGetXSLTElementTypeByNode(cctxt, child) == |
| XSLT_FUNC_SORT) |
| { |
| cctxt->inode->curChildType = XSLT_FUNC_SORT; |
| xsltParseAnyXSLTElem(cctxt, child); |
| } else |
| break; |
| } else |
| break; |
| child = child->next; |
| } while (child != NULL); |
| /* |
| * Parse the sequece constructor. |
| */ |
| if (child != NULL) |
| xsltParseSequenceConstructor(cctxt, child); |
| } |
| goto exit; |
| |
| sequence_constructor: |
| /* |
| * Parse the sequence constructor. |
| */ |
| if (elem->children != NULL) |
| xsltParseSequenceConstructor(cctxt, elem->children); |
| |
| /* |
| * Register information for vars/params. Only needed if there |
| * are any following siblings. |
| */ |
| if ((elem->next != NULL) && |
| ((cctxt->inode->type == XSLT_FUNC_VARIABLE) || |
| (cctxt->inode->type == XSLT_FUNC_PARAM))) |
| { |
| if ((elem->psvi != NULL) && |
| (((xsltStyleBasicItemVariablePtr) elem->psvi)->name)) |
| { |
| xsltCompilerVarInfoPush(cctxt, elem, |
| ((xsltStyleBasicItemVariablePtr) elem->psvi)->name, |
| ((xsltStyleBasicItemVariablePtr) elem->psvi)->ns); |
| } |
| } |
| |
| error: |
| exit: |
| xsltCompilerNodePop(cctxt, elem); |
| return(0); |
| |
| internal_err: |
| xsltCompilerNodePop(cctxt, elem); |
| return(-1); |
| } |
| |
| /** |
| * xsltForwardsCompatUnkownItemCreate: |
| * |
| * @cctxt: the compilation context |
| * |
| * Creates a compiled representation of the unknown |
| * XSLT instruction. |
| * |
| * Returns the compiled representation. |
| */ |
| static xsltStyleItemUknownPtr |
| xsltForwardsCompatUnkownItemCreate(xsltCompilerCtxtPtr cctxt) |
| { |
| xsltStyleItemUknownPtr item; |
| |
| item = (xsltStyleItemUknownPtr) xmlMalloc(sizeof(xsltStyleItemUknown)); |
| if (item == NULL) { |
| xsltTransformError(NULL, cctxt->style, NULL, |
| "Internal error in xsltForwardsCompatUnkownItemCreate(): " |
| "Failed to allocate memory.\n"); |
| cctxt->style->errors++; |
| return(NULL); |
| } |
| memset(item, 0, sizeof(xsltStyleItemUknown)); |
| item->type = XSLT_FUNC_UNKOWN_FORWARDS_COMPAT; |
| /* |
| * Store it in the stylesheet. |
| */ |
| item->next = cctxt->style->preComps; |
| cctxt->style->preComps = (xsltElemPreCompPtr) item; |
| return(item); |
| } |
| |
| /** |
| * xsltParseUnknownXSLTElem: |
| * |
| * @cctxt: the compilation context |
| * @node: the element of the unknown XSLT instruction |
| * |
| * Parses an unknown XSLT element. |
| * If forwards compatible mode is enabled this will allow |
| * such an unknown XSLT and; otherwise it is rejected. |
| * |
| * Returns 1 in the unknown XSLT instruction is rejected, |
| * 0 if everything's fine and |
| * -1 on API or internal errors. |
| */ |
| static int |
| xsltParseUnknownXSLTElem(xsltCompilerCtxtPtr cctxt, |
| xmlNodePtr node) |
| { |
| if ((cctxt == NULL) || (node == NULL)) |
| return(-1); |
| |
| /* |
| * Detection of handled content of extension instructions. |
| */ |
| if (cctxt->inode->category == XSLT_ELEMENT_CATEGORY_EXTENSION) { |
| cctxt->inode->extContentHandled = 1; |
| } |
| if (cctxt->inode->forwardsCompat == 0) { |
| /* |
| * We are not in forwards-compatible mode, so raise an error. |
| */ |
| xsltTransformError(NULL, cctxt->style, node, |
| "Unknown XSLT element '%s'.\n", node->name); |
| cctxt->style->errors++; |
| return(1); |
| } |
| /* |
| * Forwards-compatible mode. |
| * ------------------------ |
| * |
| * Parse/compile xsl:fallback elements. |
| * |
| * QUESTION: Do we have to raise an error if there's no xsl:fallback? |
| * ANSWER: No, since in the stylesheet the fallback behaviour might |
| * also be provided by using the XSLT function "element-available". |
| */ |
| if (cctxt->unknownItem == NULL) { |
| /* |
| * Create a singleton for all unknown XSLT instructions. |
| */ |
| cctxt->unknownItem = xsltForwardsCompatUnkownItemCreate(cctxt); |
| if (cctxt->unknownItem == NULL) { |
| node->psvi = NULL; |
| return(-1); |
| } |
| } |
| node->psvi = cctxt->unknownItem; |
| if (node->children == NULL) |
| return(0); |
| else { |
| xmlNodePtr child = node->children; |
| |
| xsltCompilerNodePush(cctxt, node); |
| /* |
| * Update the in-scope namespaces if needed. |
| */ |
| if (node->nsDef != NULL) |
| cctxt->inode->inScopeNs = |
| xsltCompilerBuildInScopeNsList(cctxt, node); |
| /* |
| * Parse all xsl:fallback children. |
| */ |
| do { |
| if ((child->type == XML_ELEMENT_NODE) && |
| IS_XSLT_ELEM_FAST(child) && |
| IS_XSLT_NAME(child, "fallback")) |
| { |
| cctxt->inode->curChildType = XSLT_FUNC_FALLBACK; |
| xsltParseAnyXSLTElem(cctxt, child); |
| } |
| child = child->next; |
| } while (child != NULL); |
| |
| xsltCompilerNodePop(cctxt, node); |
| } |
| return(0); |
| } |
| |
| /** |
| * xsltParseSequenceConstructor: |
| * |
| * @cctxt: the compilation context |
| * @cur: the start-node of the content to be parsed |
| * |
| * Parses a "template" content (or "sequence constructor" in XSLT 2.0 terms). |
| * This will additionally remove xsl:text elements from the tree. |
| */ |
| void |
| xsltParseSequenceConstructor(xsltCompilerCtxtPtr cctxt, xmlNodePtr cur) |
| { |
| xsltStyleType type; |
| xmlNodePtr deleteNode = NULL; |
| |
| if (cctxt == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xsltParseSequenceConstructor: Bad arguments\n"); |
| cctxt->style->errors++; |
| return; |
| } |
| /* |
| * Detection of handled content of extension instructions. |
| */ |
| if (cctxt->inode->category == XSLT_ELEMENT_CATEGORY_EXTENSION) { |
| cctxt->inode->extContentHandled = 1; |
| } |
| if (cur == NULL) |
| return; |
| /* |
| * This is the content reffered to as a "template". |
| * E.g. an xsl:element has such content model: |
| * <xsl:element |
| * name = { qname } |
| * namespace = { uri-reference } |
| * use-attribute-sets = qnames> |
| * <!-- Content: template --> |
| * |
| * NOTE that in XSLT-2 the term "template" was abandoned due to |
| * confusion with xsl:template and the term "sequence constructor" |
| * was introduced instead. |
| * |
| * The following XSLT-instructions are allowed to appear: |
| * xsl:apply-templates, xsl:call-template, xsl:apply-imports, |
| * xsl:for-each, xsl:value-of, xsl:copy-of, xsl:number, |
| * xsl:choose, xsl:if, xsl:text, xsl:copy, xsl:variable, |
| * xsl:message, xsl:fallback, |
| * xsl:processing-instruction, xsl:comment, xsl:element |
| * xsl:attribute. |
| * Additional allowed content: |
| * 1) extension instructions |
| * 2) literal result elements |
| * 3) PCDATA |
| * |
| * NOTE that this content model does *not* allow xsl:param. |
| */ |
| while (cur != NULL) { |
| if (deleteNode != NULL) { |
| #ifdef WITH_XSLT_DEBUG_BLANKS |
| xsltGenericDebug(xsltGenericDebugContext, |
| "xsltParseSequenceConstructor: removing xsl:text element\n"); |
| #endif |
| xmlUnlinkNode(deleteNode); |
| xmlFreeNode(deleteNode); |
| deleteNode = NULL; |
| } |
| if (cur->type == XML_ELEMENT_NODE) { |
| |
| if (cur->psvi == xsltXSLTTextMarker) { |
| /* |
| * xsl:text elements |
| * -------------------------------------------------------- |
| */ |
| xmlNodePtr tmp; |
| |
| cur->psvi = NULL; |
| /* |
| * Mark the xsl:text element for later deletion. |
| */ |
| deleteNode = cur; |
| /* |
| * Validate content. |
| */ |
| tmp = cur->children; |
| if (tmp) { |
| /* |
| * We don't expect more than one text-node in the |
| * content, since we already merged adjacent |
| * text/CDATA-nodes and eliminated PI/comment-nodes. |
| */ |
| if ((tmp->type == XML_TEXT_NODE) || |
| (tmp->next == NULL)) |
| { |
| /* |
| * Leave the contained text-node in the tree. |
| */ |
| xmlUnlinkNode(tmp); |
| xmlAddPrevSibling(cur, tmp); |
| } else { |
| tmp = NULL; |
| xsltTransformError(NULL, cctxt->style, cur, |
| "Element 'xsl:text': Invalid type " |
| "of node found in content.\n"); |
| cctxt->style->errors++; |
| } |
| } |
| if (cur->properties) { |
| xmlAttrPtr attr; |
| /* |
| * TODO: We need to report errors for |
| * invalid attrs. |
| */ |
| attr = cur->properties; |
| do { |
| if ((attr->ns == NULL) && |
| (attr->name != NULL) && |
| (attr->name[0] == 'd') && |
| xmlStrEqual(attr->name, |
| BAD_CAST "disable-output-escaping")) |
| { |
| /* |
| * Attr "disable-output-escaping". |
| * XSLT-2: This attribute is deprecated. |
| */ |
| if ((attr->children != NULL) && |
| xmlStrEqual(attr->children->content, |
| BAD_CAST "yes")) |
| { |
| /* |
| * Disable output escaping for this |
| * text node. |
| */ |
| if (tmp) |
| tmp->name = xmlStringTextNoenc; |
| } else if ((attr->children == NULL) || |
| (attr->children->content == NULL) || |
| (!xmlStrEqual(attr->children->content, |
| BAD_CAST "no"))) |
| { |
| xsltTransformError(NULL, cctxt->style, |
| cur, |
| "Attribute 'disable-output-escaping': " |
| "Invalid value. Expected is " |
| "'yes' or 'no'.\n"); |
| cctxt->style->errors++; |
| } |
| break; |
| } |
| attr = attr->next; |
| } while (attr != NULL); |
| } |
| } else if (IS_XSLT_ELEM_FAST(cur)) { |
| /* |
| * TODO: Using the XSLT-marker is still not stable yet. |
| */ |
| /* if (cur->psvi == xsltXSLTElemMarker) { */ |
| /* |
| * XSLT instructions |
| * -------------------------------------------------------- |
| */ |
| cur->psvi = NULL; |
| type = xsltGetXSLTElementTypeByNode(cctxt, cur); |
| switch (type) { |
| case XSLT_FUNC_APPLYIMPORTS: |
| case XSLT_FUNC_APPLYTEMPLATES: |
| case XSLT_FUNC_ATTRIBUTE: |
| case XSLT_FUNC_CALLTEMPLATE: |
| case XSLT_FUNC_CHOOSE: |
| case XSLT_FUNC_COMMENT: |
| case XSLT_FUNC_COPY: |
| case XSLT_FUNC_COPYOF: |
| case XSLT_FUNC_DOCUMENT: /* Extra one */ |
| case XSLT_FUNC_ELEMENT: |
| case XSLT_FUNC_FALLBACK: |
| case XSLT_FUNC_FOREACH: |
| case XSLT_FUNC_IF: |
| case XSLT_FUNC_MESSAGE: |
| case XSLT_FUNC_NUMBER: |
| case XSLT_FUNC_PI: |
| case XSLT_FUNC_TEXT: |
| case XSLT_FUNC_VALUEOF: |
| case XSLT_FUNC_VARIABLE: |
| /* |
| * Parse the XSLT element. |
| */ |
| cctxt->inode->curChildType = type; |
| xsltParseAnyXSLTElem(cctxt, cur); |
| break; |
| default: |
| xsltParseUnknownXSLTElem(cctxt, cur); |
| cur = cur->next; |
| continue; |
| } |
| } else { |
| /* |
| * Non-XSLT elements |
| * ----------------- |
| */ |
| xsltCompilerNodePush(cctxt, cur); |
| /* |
| * Update the in-scope namespaces if needed. |
| */ |
| if (cur->nsDef != NULL) |
| cctxt->inode->inScopeNs = |
| xsltCompilerBuildInScopeNsList(cctxt, cur); |
| /* |
| * The current element is either a literal result element |
| * or an extension instruction. |
| * |
| * Process attr "xsl:extension-element-prefixes". |
| * FUTURE TODO: IIRC in XSLT 2.0 this attribute must be |
| * processed by the implementor of the extension function; |
| * i.e., it won't be handled by the XSLT processor. |
| */ |
| /* SPEC 1.0: |
| * "exclude-result-prefixes" is only allowed on literal |
| * result elements and "xsl:exclude-result-prefixes" |
| * on xsl:stylesheet/xsl:transform. |
| * SPEC 2.0: |
| * "There are a number of standard attributes |
| * that may appear on any XSLT element: specifically |
| * version, exclude-result-prefixes, |
| * extension-element-prefixes, xpath-default-namespace, |
| * default-collation, and use-when." |
| * |
| * SPEC 2.0: |
| * For literal result elements: |
| * "xsl:version, xsl:exclude-result-prefixes, |
| * xsl:extension-element-prefixes, |
| * xsl:xpath-default-namespace, |
| * xsl:default-collation, or xsl:use-when." |
| */ |
| if (cur->properties) |
| cctxt->inode->extElemNs = |
| xsltParseExtElemPrefixes(cctxt, |
| cur, cctxt->inode->extElemNs, |
| XSLT_ELEMENT_CATEGORY_LRE); |
| /* |
| * Eval if we have an extension instruction here. |
| */ |
| if ((cur->ns != NULL) && |
| (cctxt->inode->extElemNs != NULL) && |
| (xsltCheckExtPrefix(cctxt->style, cur->ns->href) == 1)) |
| { |
| /* |
| * Extension instructions |
| * ---------------------------------------------------- |
| * Mark the node information. |
| */ |
| cctxt->inode->category = XSLT_ELEMENT_CATEGORY_EXTENSION; |
| cctxt->inode->extContentHandled = 0; |
| if (cur->psvi != NULL) { |
| cur->psvi = NULL; |
| /* |
| * TODO: Temporary sanity check. |
| */ |
| xsltTransformError(NULL, cctxt->style, cur, |
| "Internal error in xsltParseSequenceConstructor(): " |
| "Occupied PSVI field.\n"); |
| cctxt->style->errors++; |
| cur = cur->next; |
| continue; |
| } |
| cur->psvi = (void *) |
| xsltPreComputeExtModuleElement(cctxt->style, cur); |
| |
| if (cur->psvi == NULL) { |
| /* |
| * OLD COMMENT: "Unknown element, maybe registered |
| * at the context level. Mark it for later |
| * recognition." |
| * QUESTION: What does the xsltExtMarker mean? |
| * ANSWER: It is used in |
| * xsltApplySequenceConstructor() at |
| * transformation-time to look out for extension |
| * registered in the transformation context. |
| */ |
| cur->psvi = (void *) xsltExtMarker; |
| } |
| /* |
| * BIG NOTE: Now the ugly part. In previous versions |
| * of Libxslt (until 1.1.16), all the content of an |
| * extension instruction was processed and compiled without |
| * the need of the extension-author to explicitely call |
| * such a processing;.We now need to mimic this old |
| * behaviour in order to avoid breaking old code |
| * on the extension-author's side. |
| * The mechanism: |
| * 1) If the author does *not* set the |
| * compile-time-flag @extContentHandled, then we'll |
| * parse the content assuming that it's a "template" |
| * (or "sequence constructor in XSLT 2.0 terms). |
| * NOTE: If the extension is registered at |
| * transformation-time only, then there's no way of |
| * knowing that content shall be valid, and we'll |
| * process the content the same way. |
| * 2) If the author *does* set the flag, then we'll assume |
| * that the author has handled the parsing him/herself |
| * (e.g. called xsltParseSequenceConstructor(), etc. |
| * explicitely in his/her code). |
| */ |
| if ((cur->children != NULL) && |
| (cctxt->inode->extContentHandled == 0)) |
| { |
| /* |
| * Default parsing of the content using the |
| * sequence-constructor model. |
| */ |
| xsltParseSequenceConstructor(cctxt, cur->children); |
| } |
| } else { |
| /* |
| * Literal result element |
| * ---------------------------------------------------- |
| * Allowed XSLT attributes: |
| * xsl:extension-element-prefixes CDATA #IMPLIED |
| * xsl:exclude-result-prefixes CDATA #IMPLIED |
| * TODO: xsl:use-attribute-sets %qnames; #IMPLIED |
| * xsl:version NMTOKEN #IMPLIED |
| */ |
| cur->psvi = NULL; |
| cctxt->inode->category = XSLT_ELEMENT_CATEGORY_LRE; |
| if (cur->properties != NULL) { |
| xmlAttrPtr attr = cur->properties; |
| /* |
| * Attribute "xsl:exclude-result-prefixes". |
| */ |
| cctxt->inode->exclResultNs = |
| xsltParseExclResultPrefixes(cctxt, cur, |
| cctxt->inode->exclResultNs, |
| XSLT_ELEMENT_CATEGORY_LRE); |
| /* |
| * Attribute "xsl:version". |
| */ |
| xsltParseAttrXSLTVersion(cctxt, cur, |
| XSLT_ELEMENT_CATEGORY_LRE); |
| /* |
| * Report invalid XSLT attributes. |
| * For XSLT 1.0 only xsl:use-attribute-sets is allowed |
| * next to xsl:version, xsl:exclude-result-prefixes and |
| * xsl:extension-element-prefixes. |
| * |
| * Mark all XSLT attributes, in order to skip such |
| * attributes when instantiating the LRE. |
| */ |
| do { |
| if ((attr->psvi != xsltXSLTAttrMarker) && |
| IS_XSLT_ATTR_FAST(attr)) |
| { |
| if (! xmlStrEqual(attr->name, |
| BAD_CAST "use-attribute-sets")) |
| { |
| xsltTransformError(NULL, cctxt->style, |
| cur, |
| "Unknown XSLT attribute '%s'.\n", |
| attr->name); |
| cctxt->style->errors++; |
| } else { |
| /* |
| * XSLT attr marker. |
| */ |
| attr->psvi = (void *) xsltXSLTAttrMarker; |
| } |
| } |
| attr = attr->next; |
| } while (attr != NULL); |
| } |
| /* |
| * Create/reuse info for the literal result element. |
| */ |
| if (cctxt->inode->nsChanged) |
| xsltLREInfoCreate(cctxt, cur, 1); |
| cur->psvi = cctxt->inode->litResElemInfo; |
| /* |
| * Apply ns-aliasing on the element and on its attributes. |
| */ |
| if (cctxt->hasNsAliases) |
| xsltLREBuildEffectiveNs(cctxt, cur); |
| /* |
| * Compile attribute value templates (AVT). |
| */ |
| if (cur->properties) { |
| xmlAttrPtr attr = cur->properties; |
| |
| while (attr != NULL) { |
| xsltCompileAttr(cctxt->style, attr); |
| attr = attr->next; |
| } |
| } |
| /* |
| * Parse the content, which is defined to be a "template" |
| * (or "sequence constructor" in XSLT 2.0 terms). |
| */ |
| if (cur->children != NULL) { |
| xsltParseSequenceConstructor(cctxt, cur->children); |
| } |
| } |
| /* |
| * Leave the non-XSLT element. |
| */ |
| xsltCompilerNodePop(cctxt, cur); |
| } |
| } |
| cur = cur->next; |
| } |
| if (deleteNode != NULL) { |
| #ifdef WITH_XSLT_DEBUG_BLANKS |
| xsltGenericDebug(xsltGenericDebugContext, |
| "xsltParseSequenceConstructor: removing xsl:text element\n"); |
| #endif |
| xmlUnlinkNode(deleteNode); |
| xmlFreeNode(deleteNode); |
| deleteNode = NULL; |
| } |
| } |
| |
| /** |
| * xsltParseTemplateContent: |
| * @style: the XSLT stylesheet |
| * @templ: the node containing the content to be parsed |
| * |
| * Parses and compiles the content-model of an xsl:template element. |
| * Note that this is *not* the "template" content model (or "sequence |
| * constructor" in XSLT 2.0); it it allows addional xsl:param |
| * elements as immediate children of @templ. |
| * |
| * Called by: |
| * exsltFuncFunctionComp() (EXSLT, functions.c) |
| * So this is intended to be called from extension functions. |
| */ |
| void |
| xsltParseTemplateContent(xsltStylesheetPtr style, xmlNodePtr templ) { |
| if ((style == NULL) || (templ == NULL)) |
| return; |
| |
| /* |
| * Detection of handled content of extension instructions. |
| */ |
| if (XSLT_CCTXT(style)->inode->category == XSLT_ELEMENT_CATEGORY_EXTENSION) { |
| XSLT_CCTXT(style)->inode->extContentHandled = 1; |
| } |
| |
| if (templ->children != NULL) { |
| xmlNodePtr child = templ->children; |
| /* |
| * Process xsl:param elements, which can only occur as the |
| * immediate children of xsl:template (well, and of any |
| * user-defined extension instruction if needed). |
| */ |
| do { |
| if ((child->type == XML_ELEMENT_NODE) && |
| IS_XSLT_ELEM_FAST(child) && |
| IS_XSLT_NAME(child, "param")) |
| { |
| XSLT_CCTXT(style)->inode->curChildType = XSLT_FUNC_PARAM; |
| xsltParseAnyXSLTElem(XSLT_CCTXT(style), child); |
| } else |
| break; |
| child = child->next; |
| } while (child != NULL); |
| /* |
| * Parse the content and register the pattern. |
| */ |
| xsltParseSequenceConstructor(XSLT_CCTXT(style), child); |
| } |
| } |
| |
| #else /* XSLT_REFACTORED */ |
| |
| /** |
| * xsltParseTemplateContent: |
| * @style: the XSLT stylesheet |
| * @templ: the container node (can be a document for literal results) |
| * |
| * parse a template content-model |
| * Clean-up the template content from unwanted ignorable blank nodes |
| * and process xslt:text |
| */ |
| void |
| xsltParseTemplateContent(xsltStylesheetPtr style, xmlNodePtr templ) { |
| xmlNodePtr cur, delete; |
| /* |
| * This content comes from the stylesheet |
| * For stylesheets, the set of whitespace-preserving |
| * element names consists of just xsl:text. |
| */ |
| cur = templ->children; |
| delete = NULL; |
| while (cur != NULL) { |
| if (delete != NULL) { |
| #ifdef WITH_XSLT_DEBUG_BLANKS |
| xsltGenericDebug(xsltGenericDebugContext, |
| "xsltParseTemplateContent: removing text\n"); |
| #endif |
| xmlUnlinkNode(delete); |
| xmlFreeNode(delete); |
| delete = NULL; |
| } |
| if (IS_XSLT_ELEM(cur)) { |
| if (IS_XSLT_NAME(cur, "text")) { |
| /* |
| * TODO: Processing of xsl:text should be moved to |
| * xsltPrecomputeStylesheet(), since otherwise this |
| * will be performed for every multiply included |
| * stylesheet; i.e. this here is not skipped with |
| * the use of the style->nopreproc flag. |
| */ |
| if (cur->children != NULL) { |
| xmlChar *prop; |
| xmlNodePtr text = cur->children, next; |
| int noesc = 0; |
| |
| prop = xmlGetNsProp(cur, |
| (const xmlChar *)"disable-output-escaping", |
| NULL); |
| if (prop != NULL) { |
| #ifdef WITH_XSLT_DEBUG_PARSING |
| xsltGenericDebug(xsltGenericDebugContext, |
| "Disable escaping: %s\n", text->content); |
| #endif |
| if (xmlStrEqual(prop, (const xmlChar *)"yes")) { |
| noesc = 1; |
| } else if (!xmlStrEqual(prop, |
| (const xmlChar *)"no")){ |
| xsltTransformError(NULL, style, cur, |
| "xsl:text: disable-output-escaping allows only yes or no\n"); |
| style->warnings++; |
| |
| } |
| xmlFree(prop); |
| } |
| |
| while (text != NULL) { |
| if (text->type == XML_COMMENT_NODE) { |
| text = text->next; |
| continue; |
| } |
| if ((text->type != XML_TEXT_NODE) && |
| (text->type != XML_CDATA_SECTION_NODE)) { |
| xsltTransformError(NULL, style, cur, |
| "xsltParseTemplateContent: xslt:text content problem\n"); |
| style->errors++; |
| break; |
| } |
| if ((noesc) && (text->type != XML_CDATA_SECTION_NODE)) |
| text->name = xmlStringTextNoenc; |
| text = text->next; |
| } |
| |
| /* |
| * replace xsl:text by the list of childs |
| */ |
| if (text == NULL) { |
| text = cur->children; |
| while (text != NULL) { |
| if ((style->internalized) && |
| (text->content != NULL) && |
| (!xmlDictOwns(style->dict, text->content))) { |
| |
| /* |
| * internalize the text string |
| */ |
| if (text->doc->dict != NULL) { |
| const xmlChar *tmp; |
| |
| tmp = xmlDictLookup(text->doc->dict, |
| text->content, -1); |
| if (tmp != text->content) { |
| xmlNodeSetContent(text, NULL); |
| text->content = (xmlChar *) tmp; |
| } |
| } |
| } |
| |
| next = text->next; |
| xmlUnlinkNode(text); |
| xmlAddPrevSibling(cur, text); |
| text = next; |
| } |
| } |
| } |
| delete = cur; |
| goto skip_children; |
| } |
| } |
| else if ((cur->ns != NULL) && (style->nsDefs != NULL) && |
| (xsltCheckExtPrefix(style, cur->ns->prefix))) |
| { |
| /* |
| * okay this is an extension element compile it too |
| */ |
| xsltStylePreCompute(style, cur); |
| } |
| else if (cur->type == XML_ELEMENT_NODE) |
| { |
| /* |
| * This is an element which will be output as part of the |
| * template exectution, precompile AVT if found. |
| */ |
| if ((cur->ns == NULL) && (style->defaultAlias != NULL)) { |
| cur->ns = xmlSearchNsByHref(cur->doc, cur, |
| style->defaultAlias); |
| } |
| if (cur->properties != NULL) { |
| xmlAttrPtr attr = cur->properties; |
| |
| while (attr != NULL) { |
| xsltCompileAttr(style, attr); |
| attr = attr->next; |
| } |
| } |
| } |
| /* |
| * Skip to next node |
| */ |
| if (cur->children != NULL) { |
| if (cur->children->type != XML_ENTITY_DECL) { |
| cur = cur->children; |
| continue; |
| } |
| } |
| skip_children: |
| if (cur->next != NULL) { |
| cur = cur->next; |
| continue; |
| } |
| |
| do { |
| cur = cur->parent; |
| if (cur == NULL) |
| break; |
| if (cur == templ) { |
| cur = NULL; |
| break; |
| } |
| if (cur->next != NULL) { |
| cur = cur->next; |
| break; |
| } |
| } while (cur != NULL); |
| } |
| if (delete != NULL) { |
| #ifdef WITH_XSLT_DEBUG_PARSING |
| xsltGenericDebug(xsltGenericDebugContext, |
| "xsltParseTemplateContent: removing text\n"); |
| #endif |
| xmlUnlinkNode(delete); |
| xmlFreeNode(delete); |
| delete = NULL; |
| } |
| |
| /* |
| * Skip the first params |
| */ |
| cur = templ->children; |
| while (cur != NULL) { |
| if ((IS_XSLT_ELEM(cur)) && (!(IS_XSLT_NAME(cur, "param")))) |
| break; |
| cur = cur->next; |
| } |
| |
| /* |
| * Browse the remainder of the template |
| */ |
| while (cur != NULL) { |
| if ((IS_XSLT_ELEM(cur)) && (IS_XSLT_NAME(cur, "param"))) { |
| xmlNodePtr param = cur; |
| |
| xsltTransformError(NULL, style, cur, |
| "xsltParseTemplateContent: ignoring misplaced param element\n"); |
| if (style != NULL) style->warnings++; |
| cur = cur->next; |
| xmlUnlinkNode(param); |
| xmlFreeNode(param); |
| } else |
| break; |
| } |
| } |
| |
| #endif /* else XSLT_REFACTORED */ |
| |
| /** |
| * xsltParseStylesheetKey: |
| * @style: the XSLT stylesheet |
| * @key: the "key" element |
| * |
| * <!-- Category: top-level-element --> |
| * <xsl:key name = qname, match = pattern, use = expression /> |
| * |
| * parse an XSLT stylesheet key definition and register it |
| */ |
| |
| static void |
| xsltParseStylesheetKey(xsltStylesheetPtr style, xmlNodePtr key) { |
| xmlChar *prop = NULL; |
| xmlChar *use = NULL; |
| xmlChar *match = NULL; |
| xmlChar *name = NULL; |
| xmlChar *nameURI = NULL; |
| |
| if ((style == NULL) || (key == NULL)) |
| return; |
| |
| /* |
| * Get arguments |
| */ |
| prop = xmlGetNsProp(key, (const xmlChar *)"name", NULL); |
| if (prop != NULL) { |
| const xmlChar *URI; |
| |
| /* |
| * TODO: Don't use xsltGetQNameURI(). |
| */ |
| URI = xsltGetQNameURI(key, &prop); |
| if (prop == NULL) { |
| if (style != NULL) style->errors++; |
| goto error; |
| } else { |
| name = prop; |
| if (URI != NULL) |
| nameURI = xmlStrdup(URI); |
| } |
| #ifdef WITH_XSLT_DEBUG_PARSING |
| xsltGenericDebug(xsltGenericDebugContext, |
| "xsltParseStylesheetKey: name %s\n", name); |
| #endif |
| } else { |
| xsltTransformError(NULL, style, key, |
| "xsl:key : error missing name\n"); |
| if (style != NULL) style->errors++; |
| goto error; |
| } |
| |
| match = xmlGetNsProp(key, (const xmlChar *)"match", NULL); |
| if (match == NULL) { |
| xsltTransformError(NULL, style, key, |
| "xsl:key : error missing match\n"); |
| if (style != NULL) style->errors++; |
| goto error; |
| } |
| |
| use = xmlGetNsProp(key, (const xmlChar *)"use", NULL); |
| if (use == NULL) { |
| xsltTransformError(NULL, style, key, |
| "xsl:key : error missing use\n"); |
| if (style != NULL) style->errors++; |
| goto error; |
| } |
| |
| /* |
| * register the keys |
| */ |
| xsltAddKey(style, name, nameURI, match, use, key); |
| |
| |
| error: |
| if (use != NULL) |
| xmlFree(use); |
| if (match != NULL) |
| xmlFree(match); |
| if (name != NULL) |
| xmlFree(name); |
| if (nameURI != NULL) |
| xmlFree(nameURI); |
| |
| if (key->children != NULL) { |
| xsltParseContentError(style, key->children); |
| } |
| } |
| |
| #ifdef XSLT_REFACTORED |
| /** |
| * xsltParseXSLTTemplate: |
| * @style: the XSLT stylesheet |
| * @template: the "template" element |
| * |
| * parse an XSLT stylesheet template building the associated structures |
| * TODO: Is @style ever expected to be NULL? |
| * |
| * Called from: |
| * xsltParseXSLTStylesheet() |
| * xsltParseStylesheetTop() |
| */ |
| |
| static void |
| xsltParseXSLTTemplate(xsltCompilerCtxtPtr cctxt, xmlNodePtr templNode) { |
| xsltTemplatePtr templ; |
| xmlChar *prop; |
| double priority; |
| |
| if ((cctxt == NULL) || (templNode == NULL)) |
| return; |
| |
| /* |
| * Create and link the structure |
| */ |
| templ = xsltNewTemplate(); |
| if (templ == NULL) |
| return; |
| |
| xsltCompilerNodePush(cctxt, templNode); |
| if (templNode->nsDef != NULL) |
| cctxt->inode->inScopeNs = |
| xsltCompilerBuildInScopeNsList(cctxt, templNode); |
| |
| templ->next = cctxt->style->templates; |
| cctxt->style->templates = templ; |
| templ->style = cctxt->style; |
| |
| /* |
| * Attribute "mode". |
| */ |
| prop = xmlGetNsProp(templNode, (const xmlChar *)"mode", NULL); |
| if (prop != NULL) { |
| const xmlChar *modeURI; |
| |
| /* |
| * TODO: We need a standardized function for extraction |
| * of namespace names and local names from QNames. |
| * Don't use xsltGetQNameURI() as it cannot channeö |
| * reports through the context. |
| */ |
| modeURI = xsltGetQNameURI(templNode, &prop); |
| if (prop == NULL) { |
| cctxt->style->errors++; |
| goto error; |
| } |
| templ->mode = xmlDictLookup(cctxt->style->dict, prop, -1); |
| xmlFree(prop); |
| prop = NULL; |
| if (xmlValidateNCName(templ->mode, 0)) { |
| xsltTransformError(NULL, cctxt->style, templNode, |
| "xsl:template: Attribute 'mode': The local part '%s' " |
| "of the value is not a valid NCName.\n", templ->name); |
| cctxt->style->errors++; |
| goto error; |
| } |
| if (modeURI != NULL) |
| templ->modeURI = xmlDictLookup(cctxt->style->dict, modeURI, -1); |
| #ifdef WITH_XSLT_DEBUG_PARSING |
| xsltGenericDebug(xsltGenericDebugContext, |
| "xsltParseXSLTTemplate: mode %s\n", templ->mode); |
| #endif |
| } |
| /* |
| * Attribute "match". |
| */ |
| prop = xmlGetNsProp(templNode, (const xmlChar *)"match", NULL); |
| if (prop != NULL) { |
| templ->match = prop; |
| prop = NULL; |
| } |
| /* |
| * Attribute "priority". |
| */ |
| prop = xmlGetNsProp(templNode, (const xmlChar *)"priority", NULL); |
| if (prop != NULL) { |
| priority = xmlXPathStringEvalNumber(prop); |
| templ->priority = (float) priority; |
| xmlFree(prop); |
| prop = NULL; |
| } |
| /* |
| * Attribute "name". |
| */ |
| prop = xmlGetNsProp(templNode, (const xmlChar *)"name", NULL); |
| if (prop != NULL) { |
| const xmlChar *nameURI; |
| xsltTemplatePtr curTempl; |
| |
| /* |
| * TODO: Don't use xsltGetQNameURI(). |
| */ |
| nameURI = xsltGetQNameURI(templNode, &prop); |
| if (prop == NULL) { |
| cctxt->style->errors++; |
| goto error; |
| } |
| templ->name = xmlDictLookup(cctxt->style->dict, prop, -1); |
| xmlFree(prop); |
| prop = NULL; |
| if (xmlValidateNCName(templ->name, 0)) { |
| xsltTransformError(NULL, cctxt->style, templNode, |
| "xsl:template: Attribute 'name': The local part '%s' of " |
| "the value is not a valid NCName.\n", templ->name); |
| cctxt->style->errors++; |
| goto error; |
| } |
| if (nameURI != NULL) |
| templ->nameURI = xmlDictLookup(cctxt->style->dict, nameURI, -1); |
| curTempl = templ->next; |
| while (curTempl != NULL) { |
| if ((nameURI != NULL && xmlStrEqual(curTempl->name, templ->name) && |
| xmlStrEqual(curTempl->nameURI, nameURI) ) || |
| (nameURI == NULL && curTempl->nameURI == NULL && |
| xmlStrEqual(curTempl->name, templ->name))) |
| { |
| xsltTransformError(NULL, cctxt->style, templNode, |
| "xsl:template: error duplicate name '%s'\n", templ->name); |
| cctxt->style->errors++; |
| goto error; |
| } |
| curTempl = curTempl->next; |
| } |
| } |
| if (templNode->children != NULL) { |
| xsltParseTemplateContent(cctxt->style, templNode); |
| /* |
| * MAYBE TODO: Custom behaviour: In order to stay compatible with |
| * Xalan and MSXML(.NET), we could allow whitespace |
| * to appear before an xml:param element; this whitespace |
| * will additionally become part of the "template". |
| * NOTE that this is totally deviates from the spec, but |
| * is the de facto behaviour of Xalan and MSXML(.NET). |
| * Personally I wouldn't allow this, since if we have: |
| * <xsl:template ...xml:space="preserve"> |
| * <xsl:param name="foo"/> |
| * <xsl:param name="bar"/> |
| * <xsl:param name="zoo"/> |
| * ... the whitespace between every xsl:param would be |
| * added to the result tree. |
| */ |
| } |
| |
| templ->elem = templNode; |
| templ->content = templNode->children; |
| xsltAddTemplate(cctxt->style, templ, templ->mode, templ->modeURI); |
| |
| error: |
| xsltCompilerNodePop(cctxt, templNode); |
| return; |
| } |
| |
| #else /* XSLT_REFACTORED */ |
| |
| /** |
| * xsltParseStylesheetTemplate: |
| * @style: the XSLT stylesheet |
| * @template: the "template" element |
| * |
| * parse an XSLT stylesheet template building the associated structures |
| */ |
| |
| static void |
| xsltParseStylesheetTemplate(xsltStylesheetPtr style, xmlNodePtr template) { |
| xsltTemplatePtr ret; |
| xmlChar *prop; |
| xmlChar *mode = NULL; |
| xmlChar *modeURI = NULL; |
| double priority; |
| |
| if (template == NULL) |
| return; |
| |
| /* |
| * Create and link the structure |
| */ |
| ret = xsltNewTemplate(); |
| if (ret == NULL) |
| return; |
| ret->next = style->templates; |
| style->templates = ret; |
| ret->style = style; |
| |
| /* |
| * Get inherited namespaces |
| */ |
| /* |
| * TODO: Apply the optimized in-scope-namespace mechanism |
| * as for the other XSLT instructions. |
| */ |
| xsltGetInheritedNsList(style, ret, template); |
| |
| /* |
| * Get arguments |
| */ |
| prop = xmlGetNsProp(template, (const xmlChar *)"mode", NULL); |
| if (prop != NULL) { |
| const xmlChar *URI; |
| |
| /* |
| * TODO: Don't use xsltGetQNameURI(). |
| */ |
| URI = xsltGetQNameURI(template, &prop); |
| if (prop == NULL) { |
| if (style != NULL) style->errors++; |
| goto error; |
| } else { |
| mode = prop; |
| if (URI != NULL) |
| modeURI = xmlStrdup(URI); |
| } |
| ret->mode = xmlDictLookup(style->dict, mode, -1); |
| ret->modeURI = xmlDictLookup(style->dict, modeURI, -1); |
| #ifdef WITH_XSLT_DEBUG_PARSING |
| xsltGenericDebug(xsltGenericDebugContext, |
| "xsltParseStylesheetTemplate: mode %s\n", mode); |
| #endif |
| if (mode != NULL) xmlFree(mode); |
| if (modeURI != NULL) xmlFree(modeURI); |
| } |
| prop = xmlGetNsProp(template, (const xmlChar *)"match", NULL); |
| if (prop != NULL) { |
| if (ret->match != NULL) xmlFree(ret->match); |
| ret->match = prop; |
| } |
| |
| prop = xmlGetNsProp(template, (const xmlChar *)"priority", NULL); |
| if (prop != NULL) { |
| priority = xmlXPathStringEvalNumber(prop); |
| ret->priority = (float) priority; |
| xmlFree(prop); |
| } |
| |
| prop = xmlGetNsProp(template, (const xmlChar *)"name", NULL); |
| if (prop != NULL) { |
| const xmlChar *URI; |
| xsltTemplatePtr cur; |
| |
| /* |
| * TODO: Don't use xsltGetQNameURI(). |
| */ |
| URI = xsltGetQNameURI(template, &prop); |
| if (prop == NULL) { |
| if (style != NULL) style->errors++; |
| goto error; |
| } else { |
| if (xmlValidateNCName(prop,0)) { |
| xsltTransformError(NULL, style, template, |
| "xsl:template : error invalid name '%s'\n", prop); |
| if (style != NULL) style->errors++; |
| goto error; |
| } |
| ret->name = xmlDictLookup(style->dict, BAD_CAST prop, -1); |
| xmlFree(prop); |
| prop = NULL; |
| if (URI != NULL) |
| ret->nameURI = xmlDictLookup(style->dict, BAD_CAST URI, -1); |
| else |
| ret->nameURI = NULL; |
| cur = ret->next; |
| while (cur != NULL) { |
| if ((URI != NULL && xmlStrEqual(cur->name, ret->name) && |
| xmlStrEqual(cur->nameURI, URI) ) || |
| (URI == NULL && cur->nameURI == NULL && |
| xmlStrEqual(cur->name, ret->name))) { |
| xsltTransformError(NULL, style, template, |
| "xsl:template: error duplicate name '%s'\n", ret->name); |
| style->errors++; |
| goto error; |
| } |
| cur = cur->next; |
| } |
| } |
| } |
| |
| /* |
| * parse the content and register the pattern |
| */ |
| xsltParseTemplateContent(style, template); |
| ret->elem = template; |
| ret->content = template->children; |
| xsltAddTemplate(style, ret, ret->mode, ret->modeURI); |
| |
| error: |
| return; |
| } |
| |
| #endif /* else XSLT_REFACTORED */ |
| |
| #ifdef XSLT_REFACTORED |
| |
| /** |
| * xsltIncludeComp: |
| * @cctxt: the compilation contenxt |
| * @node: the xsl:include node |
| * |
| * Process the xslt include node on the source node |
| */ |
| static xsltStyleItemIncludePtr |
| xsltCompileXSLTIncludeElem(xsltCompilerCtxtPtr cctxt, xmlNodePtr node) { |
| xsltStyleItemIncludePtr item; |
| |
| if ((cctxt == NULL) || (node == NULL)) |
| return(NULL); |
| |
| node->psvi = NULL; |
| item = (xsltStyleItemIncludePtr) xmlMalloc(sizeof(xsltStyleItemInclude)); |
| if (item == NULL) { |
| xsltTransformError(NULL, cctxt->style, node, |
| "xsltIncludeComp : malloc failed\n"); |
| cctxt->style->errors++; |
| return(NULL); |
| } |
| memset(item, 0, sizeof(xsltStyleItemInclude)); |
| |
| node->psvi = item; |
| item->inst = node; |
| item->type = XSLT_FUNC_INCLUDE; |
| |
| item->next = cctxt->style->preComps; |
| cctxt->style->preComps = (xsltElemPreCompPtr) item; |
| |
| return(item); |
| } |
| |
| /** |
| * xsltParseFindTopLevelElem: |
| */ |
| static int |
| xsltParseFindTopLevelElem(xsltCompilerCtxtPtr cctxt, |
| xmlNodePtr cur, |
| const xmlChar *name, |
| const xmlChar *namespaceURI, |
| int breakOnOtherElem, |
| xmlNodePtr *resultNode) |
| { |
| if (name == NULL) |
| return(-1); |
| |
| *resultNode = NULL; |
| while (cur != NULL) { |
| if (cur->type == XML_ELEMENT_NODE) { |
| if ((cur->ns != NULL) && (cur->name != NULL)) { |
| if ((*(cur->name) == *name) && |
| xmlStrEqual(cur->name, name) && |
| xmlStrEqual(cur->ns->href, namespaceURI)) |
| { |
| *resultNode = cur; |
| return(1); |
| } |
| } |
| if (breakOnOtherElem) |
| break; |
| } |
| cur = cur->next; |
| } |
| *resultNode = cur; |
| return(0); |
| } |
| |
| static int |
| xsltParseTopLevelXSLTElem(xsltCompilerCtxtPtr cctxt, |
| xmlNodePtr node, |
| xsltStyleType type) |
| { |
| int ret = 0; |
| |
| /* |
| * TODO: The reason why this function exists: |
| * due to historical reasons some of the |
| * top-level declarations are processed by functions |
| * in other files. Since we need still to set |
| * up the node-info and generate information like |
| * in-scope namespaces, this is a wrapper around |
| * those old parsing functions. |
| */ |
| xsltCompilerNodePush(cctxt, node); |
| if (node->nsDef != NULL) |
| cctxt->inode->inScopeNs = |
| xsltCompilerBuildInScopeNsList(cctxt, node); |
| cctxt->inode->type = type; |
| |
| switch (type) { |
| case XSLT_FUNC_INCLUDE: |
| { |
| int oldIsInclude; |
| |
| if (xsltCompileXSLTIncludeElem(cctxt, node) == NULL) |
| goto exit; |
| /* |
| * Mark this stylesheet tree as being currently included. |
| */ |
| oldIsInclude = cctxt->isInclude; |
| cctxt->isInclude = 1; |
| |
| if (xsltParseStylesheetInclude(cctxt->style, node) != 0) { |
| cctxt->style->errors++; |
| } |
| cctxt->isInclude = oldIsInclude; |
| } |
| break; |
| case XSLT_FUNC_PARAM: |
| xsltStylePreCompute(cctxt->style, node); |
| xsltParseGlobalParam(cctxt->style, node); |
| break; |
| case XSLT_FUNC_VARIABLE: |
| xsltStylePreCompute(cctxt->style, node); |
| xsltParseGlobalVariable(cctxt->style, node); |
| break; |
| case XSLT_FUNC_ATTRSET: |
| xsltParseStylesheetAttributeSet(cctxt->style, node); |
| break; |
| default: |
| xsltTransformError(NULL, cctxt->style, node, |
| "Internal error: (xsltParseTopLevelXSLTElem) " |
| "Cannot handle this top-level declaration.\n"); |
| cctxt->style->errors++; |
| ret = -1; |
| } |
| |
| exit: |
| xsltCompilerNodePop(cctxt, node); |
| |
| return(ret); |
| } |
| |
| #if 0 |
| static int |
| xsltParseRemoveWhitespace(xmlNodePtr node) |
| { |
| if ((node == NULL) || (node->children == NULL)) |
| return(0); |
| else { |
| xmlNodePtr delNode = NULL, child = node->children; |
| |
| do { |
| if (delNode) { |
| xmlUnlinkNode(delNode); |
| xmlFreeNode(delNode); |
| delNode = NULL; |
| } |
| if (((child->type == XML_TEXT_NODE) || |
| (child->type == XML_CDATA_SECTION_NODE)) && |
| (IS_BLANK_NODE(child))) |
| delNode = child; |
| child = child->next; |
| } while (child != NULL); |
| if (delNode) { |
| xmlUnlinkNode(delNode); |
| xmlFreeNode(delNode); |
| delNode = NULL; |
| } |
| } |
| return(0); |
| } |
| #endif |
| |
| static int |
| xsltParseXSLTStylesheetElemCore(xsltCompilerCtxtPtr cctxt, xmlNodePtr node) |
| { |
| #ifdef WITH_XSLT_DEBUG_PARSING |
| int templates = 0; |
| #endif |
| xmlNodePtr cur, start = NULL; |
| xsltStylesheetPtr style; |
| |
| if ((cctxt == NULL) || (node == NULL) || |
| (node->type != XML_ELEMENT_NODE)) |
| return(-1); |
| |
| style = cctxt->style; |
| /* |
| * At this stage all import declarations of all stylesheet modules |
| * with the same stylesheet level have been processed. |
| * Now we can safely parse the rest of the declarations. |
| */ |
| if (IS_XSLT_ELEM_FAST(node) && IS_XSLT_NAME(node, "include")) |
| { |
| xsltDocumentPtr include; |
| /* |
| * URGENT TODO: Make this work with simplified stylesheets! |
| * I.e., when we won't find an xsl:stylesheet element. |
| */ |
| /* |
| * This is as include declaration. |
| */ |
| include = ((xsltStyleItemIncludePtr) node->psvi)->include; |
| if (include == NULL) { |
| /* TODO: raise error? */ |
| return(-1); |
| } |
| /* |
| * TODO: Actually an xsl:include should locate an embedded |
| * stylesheet as well; so the document-element won't always |
| * be the element where the actual stylesheet is rooted at. |
| * But such embedded stylesheets are not supported by Libxslt yet. |
| */ |
| node = xmlDocGetRootElement(include->doc); |
| if (node == NULL) { |
| return(-1); |
| } |
| } |
| |
| if (node->children == NULL) |
| return(0); |
| /* |
| * Push the xsl:stylesheet/xsl:transform element. |
| */ |
| xsltCompilerNodePush(cctxt, node); |
| cctxt->inode->isRoot = 1; |
| cctxt->inode->nsChanged = 0; |
| /* |
| * Start with the naked dummy info for literal result elements. |
| */ |
| cctxt->inode->litResElemInfo = cctxt->inodeList->litResElemInfo; |
| |
| /* |
| * In every case, we need to have |
| * the in-scope namespaces of the element, where the |
| * stylesheet is rooted at, regardless if it's an XSLT |
| * instruction or a literal result instruction (or if |
| * this is an embedded stylesheet). |
| */ |
| cctxt->inode->inScopeNs = |
| xsltCompilerBuildInScopeNsList(cctxt, node); |
| |
| /* |
| * Process attributes of xsl:stylesheet/xsl:transform. |
| * -------------------------------------------------- |
| * Allowed are: |
| * id = id |
| * extension-element-prefixes = tokens |
| * exclude-result-prefixes = tokens |
| * version = number (mandatory) |
| */ |
| if (xsltParseAttrXSLTVersion(cctxt, node, |
| XSLT_ELEMENT_CATEGORY_XSLT) == 0) |
| { |
| /* |
| * Attribute "version". |
| * XSLT 1.0: "An xsl:stylesheet element *must* have a version |
| * attribute, indicating the version of XSLT that the |
| * stylesheet requires". |
| * The root element of a simplified stylesheet must also have |
| * this attribute. |
| */ |
| #ifdef XSLT_REFACTORED_MANDATORY_VERSION |
| if (isXsltElem) |
| xsltTransformError(NULL, cctxt->style, node, |
| "The attribute 'version' is missing.\n"); |
| cctxt->style->errors++; |
| #else |
| /* OLD behaviour. */ |
| xsltTransformError(NULL, cctxt->style, node, |
| "xsl:version is missing: document may not be a stylesheet\n"); |
| cctxt->style->warnings++; |
| #endif |
| } |
| /* |
| * The namespaces declared by the attributes |
| * "extension-element-prefixes" and |
| * "exclude-result-prefixes" are local to *this* |
| * stylesheet tree; i.e., they are *not* visible to |
| * other stylesheet-modules, whether imported or included. |
| * |
| * Attribute "extension-element-prefixes". |
| */ |
| cctxt->inode->extElemNs = |
| xsltParseExtElemPrefixes(cctxt, node, NULL, |
| XSLT_ELEMENT_CATEGORY_XSLT); |
| /* |
| * Attribute "exclude-result-prefixes". |
| */ |
| cctxt->inode->exclResultNs = |
| xsltParseExclResultPrefixes(cctxt, node, NULL, |
| XSLT_ELEMENT_CATEGORY_XSLT); |
| /* |
| * Create/reuse info for the literal result element. |
| */ |
| if (cctxt->inode->nsChanged) |
| xsltLREInfoCreate(cctxt, node, 0); |
| /* |
| * Processed top-level elements: |
| * ---------------------------- |
| * xsl:variable, xsl:param (QName, in-scope ns, |
| * expression (vars allowed)) |
| * xsl:attribute-set (QName, in-scope ns) |
| * xsl:strip-space, xsl:preserve-space (XPath NameTests, |
| * in-scope ns) |
| * I *think* global scope, merge with includes |
| * xsl:output (QName, in-scope ns) |
| * xsl:key (QName, in-scope ns, pattern, |
| * expression (vars *not* allowed)) |
| * xsl:decimal-format (QName, needs in-scope ns) |
| * xsl:namespace-alias (in-scope ns) |
| * global scope, merge with includes |
| * xsl:template (last, QName, pattern) |
| * |
| * (whitespace-only text-nodes have *not* been removed |
| * yet; this will be done in xsltParseSequenceConstructor) |
| * |
| * Report misplaced child-nodes first. |
| */ |
| cur = node->children; |
| while (cur != NULL) { |
| if (cur->type == XML_TEXT_NODE) { |
| xsltTransformError(NULL, style, cur, |
| "Misplaced text node (content: '%s').\n", |
| (cur->content != NULL) ? cur->content : BAD_CAST ""); |
| style->errors++; |
| } else if (cur->type != XML_ELEMENT_NODE) { |
| xsltTransformError(NULL, style, cur, "Misplaced node.\n"); |
| style->errors++; |
| } |
| cur = cur->next; |
| } |
| /* |
| * Skip xsl:import elements; they have been processed |
| * already. |
| */ |
| cur = node->children; |
| while ((cur != NULL) && xsltParseFindTopLevelElem(cctxt, cur, |
| BAD_CAST "import", XSLT_NAMESPACE, 1, &cur) == 1) |
| cur = cur->next; |
| if (cur == NULL) |
| goto exit; |
| |
| start = cur; |
| /* |
| * Process all top-level xsl:param elements. |
| */ |
| while ((cur != NULL) && |
| xsltParseFindTopLevelElem(cctxt, cur, |
| BAD_CAST "param", XSLT_NAMESPACE, 0, &cur) == 1) |
| { |
| xsltParseTopLevelXSLTElem(cctxt, cur, XSLT_FUNC_PARAM); |
| cur = cur->next; |
| } |
| /* |
| * Process all top-level xsl:variable elements. |
| */ |
| cur = start; |
| while ((cur != NULL) && |
| xsltParseFindTopLevelElem(cctxt, cur, |
| BAD_CAST "variable", XSLT_NAMESPACE, 0, &cur) == 1) |
| { |
| xsltParseTopLevelXSLTElem(cctxt, cur, XSLT_FUNC_VARIABLE); |
| cur = cur->next; |
| } |
| /* |
| * Process all the rest of top-level elements. |
| */ |
| cur = start; |
| while (cur != NULL) { |
| /* |
| * Process element nodes. |
| */ |
| if (cur->type == XML_ELEMENT_NODE) { |
| if (cur->ns == NULL) { |
| xsltTransformError(NULL, style, cur, |
| "Unexpected top-level element in no namespace.\n"); |
| style->errors++; |
| cur = cur->next; |
| continue; |
| } |
| /* |
| * Process all XSLT elements. |
| */ |
| if (IS_XSLT_ELEM_FAST(cur)) { |
| /* |
| * xsl:import is only allowed at the beginning. |
| */ |
| if (IS_XSLT_NAME(cur, "import")) { |
| xsltTransformError(NULL, style, cur, |
| "Misplaced xsl:import element.\n"); |
| style->errors++; |
| cur = cur->next; |
| continue; |
| } |
| /* |
| * TODO: Change the return type of the parsing functions |
| * to int. |
| */ |
| if (IS_XSLT_NAME(cur, "template")) { |
| #ifdef WITH_XSLT_DEBUG_PARSING |
| templates++; |
| #endif |
| /* |
| * TODO: Is the position of xsl:template in the |
| * tree significant? If not it would be easier to |
| * parse them at a later stage. |
| */ |
| xsltParseXSLTTemplate(cctxt, cur); |
| } else if (IS_XSLT_NAME(cur, "variable")) { |
| /* NOP; done already */ |
| } else if (IS_XSLT_NAME(cur, "param")) { |
| /* NOP; done already */ |
| } else if (IS_XSLT_NAME(cur, "include")) { |
| if (cur->psvi != NULL) |
| xsltParseXSLTStylesheetElemCore(cctxt, cur); |
| else { |
| xsltTransformError(NULL, style, cur, |
| "Internal error: " |
| "(xsltParseXSLTStylesheetElemCore) " |
| "The xsl:include element was not compiled.\n"); |
| style->errors++; |
| } |
| } else if (IS_XSLT_NAME(cur, "strip-space")) { |
| /* No node info needed. */ |
| xsltParseStylesheetStripSpace(style, cur); |
| } else if (IS_XSLT_NAME(cur, "preserve-space")) { |
| /* No node info needed. */ |
| xsltParseStylesheetPreserveSpace(style, cur); |
| } else if (IS_XSLT_NAME(cur, "output")) { |
| /* No node-info needed. */ |
| xsltParseStylesheetOutput(style, cur); |
| } else if (IS_XSLT_NAME(cur, "key")) { |
| /* TODO: node-info needed for expressions ? */ |
| xsltParseStylesheetKey(style, cur); |
| } else if (IS_XSLT_NAME(cur, "decimal-format")) { |
| /* No node-info needed. */ |
| xsltParseStylesheetDecimalFormat(style, cur); |
| } else if (IS_XSLT_NAME(cur, "attribute-set")) { |
| xsltParseTopLevelXSLTElem(cctxt, cur, |
| XSLT_FUNC_ATTRSET); |
| } else if (IS_XSLT_NAME(cur, "namespace-alias")) { |
| /* NOP; done already */ |
| } else { |
| if (cctxt->inode->forwardsCompat) { |
| /* |
| * Forwards-compatible mode: |
| * |
| * XSLT-1: "if it is a top-level element and |
| * XSLT 1.0 does not allow such elements as top-level |
| * elements, then the element must be ignored along |
| * with its content;" |
| */ |
| /* |
| * TODO: I don't think we should generate a warning. |
| */ |
| xsltTransformError(NULL, style, cur, |
| "Forwards-compatible mode: Ignoring unknown XSLT " |
| "element '%s'.\n", cur->name); |
| style->warnings++; |
| } else { |
| xsltTransformError(NULL, style, cur, |
| "Unknown XSLT element '%s'.\n", cur->name); |
| style->errors++; |
| } |
| } |
| } else { |
| xsltTopLevelFunction function; |
| |
| /* |
| * Process non-XSLT elements, which are in a |
| * non-NULL namespace. |
| */ |
| /* |
| * QUESTION: What does xsltExtModuleTopLevelLookup() |
| * do exactly? |
| */ |
| function = xsltExtModuleTopLevelLookup(cur->name, |
| cur->ns->href); |
| if (function != NULL) |
| function(style, cur); |
| #ifdef WITH_XSLT_DEBUG_PARSING |
| xsltGenericDebug(xsltGenericDebugContext, |
| "xsltParseXSLTStylesheetElemCore : User-defined " |
| "data element '%s'.\n", cur->name); |
| #endif |
| } |
| } |
| cur = cur->next; |
| } |
| |
| exit: |
| |
| #ifdef WITH_XSLT_DEBUG_PARSING |
| xsltGenericDebug(xsltGenericDebugContext, |
| "### END of parsing top-level elements of doc '%s'.\n", |
| node->doc->URL); |
| xsltGenericDebug(xsltGenericDebugContext, |
| "### Templates: %d\n", templates); |
| #ifdef XSLT_REFACTORED |
| xsltGenericDebug(xsltGenericDebugContext, |
| "### Max inodes: %d\n", cctxt->maxNodeInfos); |
| xsltGenericDebug(xsltGenericDebugContext, |
| "### Max LREs : %d\n", cctxt->maxLREs); |
| #endif /* XSLT_REFACTORED */ |
| #endif /* WITH_XSLT_DEBUG_PARSING */ |
| |
| xsltCompilerNodePop(cctxt, node); |
| return(0); |
| } |
| |
| /** |
| * xsltParseXSLTStylesheet: |
| * @cctxt: the compiler context |
| * @node: the xsl:stylesheet/xsl:transform element-node |
| * |
| * Parses the xsl:stylesheet and xsl:transform element. |
| * |
| * <xsl:stylesheet |
| * id = id |
| * extension-element-prefixes = tokens |
| * exclude-result-prefixes = tokens |
| * version = number> |
| * <!-- Content: (xsl:import*, top-level-elements) --> |
| * </xsl:stylesheet> |
| * |
| * BIG TODO: The xsl:include stuff. |
| * |
| * Called by xsltParseStylesheetTree() |
| * |
| * Returns 0 on success, a positive result on errors and |
| * -1 on API or internal errors. |
| */ |
| static int |
| xsltParseXSLTStylesheetElem(xsltCompilerCtxtPtr cctxt, xmlNodePtr node) |
| { |
| xmlNodePtr cur, start; |
| |
| if ((cctxt == NULL) || (node == NULL)) |
| return(-1); |
| |
| if (node->children == NULL) |
| goto exit; |
| |
| /* |
| * Process top-level elements: |
| * xsl:import (must be first) |
| * xsl:include (this is just a pre-processing) |
| */ |
| cur = node->children; |
| /* |
| * Process xsl:import elements. |
| * XSLT 1.0: "The xsl:import element children must precede all |
| * other element children of an xsl:stylesheet element, |
| * including any xsl:include element children." |
| */ |
| while ((cur != NULL) && |
| xsltParseFindTopLevelElem(cctxt, cur, |
| BAD_CAST "import", XSLT_NAMESPACE, 1, &cur) == 1) |
| { |
| if (xsltParseStylesheetImport(cctxt->style, cur) != 0) { |
| cctxt->style->errors++; |
| } |
| cur = cur->next; |
| } |
| if (cur == NULL) |
| goto exit; |
| start = cur; |
| /* |
| * Pre-process all xsl:include elements. |
| */ |
| cur = start; |
| while ((cur != NULL) && |
| xsltParseFindTopLevelElem(cctxt, cur, |
| BAD_CAST "include", XSLT_NAMESPACE, 0, &cur) == 1) |
| { |
| xsltParseTopLevelXSLTElem(cctxt, cur, XSLT_FUNC_INCLUDE); |
| cur = cur->next; |
| } |
| /* |
| * Pre-process all xsl:namespace-alias elements. |
| * URGENT TODO: This won't work correctly: the order of included |
| * aliases and aliases defined here is significant. |
| */ |
| cur = start; |
| while ((cur != NULL) && |
| xsltParseFindTopLevelElem(cctxt, cur, |
| BAD_CAST "namespace-alias", XSLT_NAMESPACE, 0, &cur) == 1) |
| { |
| xsltNamespaceAlias(cctxt->style, cur); |
| cur = cur->next; |
| } |
| |
| if (cctxt->isInclude) { |
| /* |
| * If this stylesheet is intended for inclusion, then |
| * we will process only imports and includes. |
| */ |
| goto exit; |
| } |
| /* |
| * Now parse the rest of the top-level elements. |
| */ |
| xsltParseXSLTStylesheetElemCore(cctxt, node); |
| exit: |
| |
| return(0); |
| } |
| |
| #else /* XSLT_REFACTORED */ |
| |
| /** |
| * xsltParseStylesheetTop: |
| * @style: the XSLT stylesheet |
| * @top: the top level "stylesheet" or "transform" element |
| * |
| * scan the top level elements of an XSL stylesheet |
| */ |
| static void |
| xsltParseStylesheetTop(xsltStylesheetPtr style, xmlNodePtr top) { |
| xmlNodePtr cur; |
| xmlChar *prop; |
| #ifdef WITH_XSLT_DEBUG_PARSING |
| int templates = 0; |
| #endif |
| |
| if (top == NULL) |
| return; |
| |
| prop = xmlGetNsProp(top, (const xmlChar *)"version", NULL); |
| if (prop == NULL) { |
| xsltTransformError(NULL, style, top, |
| "xsl:version is missing: document may not be a stylesheet\n"); |
| if (style != NULL) style->warnings++; |
| } else { |
| if ((!xmlStrEqual(prop, (const xmlChar *)"1.0")) && |
| (!xmlStrEqual(prop, (const xmlChar *)"1.1"))) { |
| xsltTransformError(NULL, style, top, |
| "xsl:version: only 1.0 features are supported\n"); |
| /* TODO set up compatibility when not XSLT 1.0 */ |
| if (style != NULL) style->warnings++; |
| } |
| xmlFree(prop); |
| } |
| |
| /* |
| * process xsl:import elements |
| */ |
| cur = top->children; |
| while (cur != NULL) { |
| if (IS_BLANK_NODE(cur)) { |
| cur = cur->next; |
| continue; |
| } |
| if (IS_XSLT_ELEM(cur) && IS_XSLT_NAME(cur, "import")) { |
| if (xsltParseStylesheetImport(style, cur) != 0) |
| if (style != NULL) style->errors++; |
| } else |
| break; |
| cur = cur->next; |
| } |
| |
| /* |
| * process other top-level elements |
| */ |
| while (cur != NULL) { |
| if (IS_BLANK_NODE(cur)) { |
| cur = cur->next; |
| continue; |
| } |
| if (cur->type == XML_TEXT_NODE) { |
| if (cur->content != NULL) { |
| xsltTransformError(NULL, style, cur, |
| "misplaced text node: '%s'\n", cur->content); |
| } |
| if (style != NULL) style->errors++; |
| cur = cur->next; |
| continue; |
| } |
| if ((cur->type == XML_ELEMENT_NODE) && (cur->ns == NULL)) { |
| xsltGenericError(xsltGenericErrorContext, |
| "Found a top-level element %s with null namespace URI\n", |
| cur->name); |
| if (style != NULL) style->errors++; |
| cur = cur->next; |
| continue; |
| } |
| if ((cur->type == XML_ELEMENT_NODE) && (!(IS_XSLT_ELEM(cur)))) { |
| xsltTopLevelFunction function; |
| |
| function = xsltExtModuleTopLevelLookup(cur->name, |
| cur->ns->href); |
| if (function != NULL) |
| function(style, cur); |
| |
| #ifdef WITH_XSLT_DEBUG_PARSING |
| xsltGenericDebug(xsltGenericDebugContext, |
| "xsltParseStylesheetTop : found foreign element %s\n", |
| cur->name); |
| #endif |
| cur = cur->next; |
| continue; |
| } |
| if (IS_XSLT_NAME(cur, "import")) { |
| xsltTransformError(NULL, style, cur, |
| "xsltParseStylesheetTop: ignoring misplaced import element\n"); |
| if (style != NULL) style->errors++; |
| } else if (IS_XSLT_NAME(cur, "include")) { |
| if (xsltParseStylesheetInclude(style, cur) != 0) |
| if (style != NULL) style->errors++; |
| } else if (IS_XSLT_NAME(cur, "strip-space")) { |
| xsltParseStylesheetStripSpace(style, cur); |
| } else if (IS_XSLT_NAME(cur, "preserve-space")) { |
| xsltParseStylesheetPreserveSpace(style, cur); |
| } else if (IS_XSLT_NAME(cur, "output")) { |
| xsltParseStylesheetOutput(style, cur); |
| } else if (IS_XSLT_NAME(cur, "key")) { |
| xsltParseStylesheetKey(style, cur); |
| } else if (IS_XSLT_NAME(cur, "decimal-format")) { |
| xsltParseStylesheetDecimalFormat(style, cur); |
| } else if (IS_XSLT_NAME(cur, "attribute-set")) { |
| xsltParseStylesheetAttributeSet(style, cur); |
| } else if (IS_XSLT_NAME(cur, "variable")) { |
| xsltParseGlobalVariable(style, cur); |
| } else if (IS_XSLT_NAME(cur, "param")) { |
| xsltParseGlobalParam(style, cur); |
| } else if (IS_XSLT_NAME(cur, "template")) { |
| #ifdef WITH_XSLT_DEBUG_PARSING |
| templates++; |
| #endif |
| xsltParseStylesheetTemplate(style, cur); |
| } else if (IS_XSLT_NAME(cur, "namespace-alias")) { |
| xsltNamespaceAlias(style, cur); |
| } else { |
| /* |
| * BUG TODO: The version of the *doc* is irrelevant for |
| * the forwards-compatible mode. |
| */ |
| if ((style != NULL) && (style->doc->version != NULL) && |
| (!strncmp((const char *) style->doc->version, "1.0", 3))) { |
| xsltTransformError(NULL, style, cur, |
| "xsltParseStylesheetTop: unknown %s element\n", |
| cur->name); |
| if (style != NULL) style->errors++; |
| } |
| else { |
| /* do Forwards-Compatible Processing */ |
| xsltTransformError(NULL, style, cur, |
| "xsltParseStylesheetTop: ignoring unknown %s element\n", |
| cur->name); |
| if (style != NULL) style->warnings++; |
| } |
| } |
| cur = cur->next; |
| } |
| #ifdef WITH_XSLT_DEBUG_PARSING |
| xsltGenericDebug(xsltGenericDebugContext, |
| "parsed %d templates\n", templates); |
| #endif |
| } |
| |
| #endif /* else of XSLT_REFACTORED */ |
| |
| #ifdef XSLT_REFACTORED |
| /** |
| * xsltParseSimplifiedStylesheetTree: |
| * |
| * @style: the stylesheet (TODO: Change this to the compiler context) |
| * @doc: the document containing the stylesheet. |
| * @node: the node where the stylesheet is rooted at |
| * |
| * Returns 0 in case of success, a positive result if an error occurred |
| * and -1 on API and internal errors. |
| */ |
| static int |
| xsltParseSimplifiedStylesheetTree(xsltCompilerCtxtPtr cctxt, |
| xmlDocPtr doc, |
| xmlNodePtr node) |
| { |
| xsltTemplatePtr templ; |
| |
| if ((cctxt == NULL) || (node == NULL)) |
| return(-1); |
| |
| if (xsltParseAttrXSLTVersion(cctxt, node, 0) == XSLT_ELEMENT_CATEGORY_LRE) |
| { |
| /* |
| * TODO: Adjust report, since this might be an |
| * embedded stylesheet. |
| */ |
| xsltTransformError(NULL, cctxt->style, node, |
| "The attribute 'xsl:version' is missing; cannot identify " |
| "this document as an XSLT stylesheet document.\n"); |
| cctxt->style->errors++; |
| return(1); |
| } |
| |
| #ifdef WITH_XSLT_DEBUG_PARSING |
| xsltGenericDebug(xsltGenericDebugContext, |
| "xsltParseSimplifiedStylesheetTree: document is stylesheet\n"); |
| #endif |
| |
| /* |
| * Create and link the template |
| */ |
| templ = xsltNewTemplate(); |
| if (templ == NULL) { |
| return(-1); |
| } |
| templ->next = cctxt->style->templates; |
| cctxt->style->templates = templ; |
| templ->match = xmlStrdup(BAD_CAST "/"); |
| |
| /* |
| * Note that we push the document-node in this special case. |
| */ |
| xsltCompilerNodePush(cctxt, (xmlNodePtr) doc); |
| /* |
| * In every case, we need to have |
| * the in-scope namespaces of the element, where the |
| * stylesheet is rooted at, regardless if it's an XSLT |
| * instruction or a literal result instruction (or if |
| * this is an embedded stylesheet). |
| */ |
| cctxt->inode->inScopeNs = |
| xsltCompilerBuildInScopeNsList(cctxt, node); |
| /* |
| * Parse the content and register the match-pattern. |
| */ |
| xsltParseSequenceConstructor(cctxt, node); |
| xsltCompilerNodePop(cctxt, (xmlNodePtr) doc); |
| |
| templ->elem = (xmlNodePtr) doc; |
| templ->content = node; |
| xsltAddTemplate(cctxt->style, templ, NULL, NULL); |
| cctxt->style->literal_result = 1; |
| return(0); |
| } |
| |
| #ifdef XSLT_REFACTORED_XSLT_NSCOMP |
| /** |
| * xsltRestoreDocumentNamespaces: |
| * @ns: map of namespaces |
| * @doc: the document |
| * |
| * Restore the namespaces for the document |
| * |
| * Returns 0 in case of success, -1 in case of failure |
| */ |
| int |
| xsltRestoreDocumentNamespaces(xsltNsMapPtr ns, xmlDocPtr doc) |
| { |
| if (doc == NULL) |
| return(-1); |
| /* |
| * Revert the changes we have applied to the namespace-URIs of |
| * ns-decls. |
| */ |
| while (ns != NULL) { |
| if ((ns->doc == doc) && (ns->ns != NULL)) { |
| ns->ns->href = ns->origNsName; |
| ns->origNsName = NULL; |
| ns->ns = NULL; |
| } |
| ns = ns->next; |
| } |
| return(0); |
| } |
| #endif /* XSLT_REFACTORED_XSLT_NSCOMP */ |
| |
| /** |
| * xsltParseStylesheetProcess: |
| * @style: the XSLT stylesheet (the current stylesheet-level) |
| * @doc: and xmlDoc parsed XML |
| * |
| * Parses an XSLT stylesheet, adding the associated structures. |
| * Called by: |
| * xsltParseStylesheetImportedDoc() (xslt.c) |
| * xsltParseStylesheetInclude() (imports.c) |
| * |
| * Returns the value of the @style parameter if everything |
| * went right, NULL if something went amiss. |
| */ |
| xsltStylesheetPtr |
| xsltParseStylesheetProcess(xsltStylesheetPtr style, xmlDocPtr doc) |
| { |
| xsltCompilerCtxtPtr cctxt; |
| xmlNodePtr cur; |
| int oldIsSimplifiedStylesheet; |
| |
| xsltInitGlobals(); |
| |
| if ((style == NULL) || (doc == NULL)) |
| return(NULL); |
| |
| cctxt = XSLT_CCTXT(style); |
| |
| cur = xmlDocGetRootElement(doc); |
| if (cur == NULL) { |
| xsltTransformError(NULL, style, (xmlNodePtr) doc, |
| "xsltParseStylesheetProcess : empty stylesheet\n"); |
| return(NULL); |
| } |
| oldIsSimplifiedStylesheet = cctxt->simplified; |
| |
| if ((IS_XSLT_ELEM(cur)) && |
| ((IS_XSLT_NAME(cur, "stylesheet")) || |
| (IS_XSLT_NAME(cur, "transform")))) { |
| #ifdef WITH_XSLT_DEBUG_PARSING |
| xsltGenericDebug(xsltGenericDebugContext, |
| "xsltParseStylesheetProcess : found stylesheet\n"); |
| #endif |
| cctxt->simplified = 0; |
| style->literal_result = 0; |
| } else { |
| cctxt->simplified = 1; |
| style->literal_result = 1; |
| } |
| /* |
| * Pre-process the stylesheet if not already done before. |
| * This will remove PIs and comments, merge adjacent |
| * text nodes, internalize strings, etc. |
| */ |
| if (! style->nopreproc) |
| xsltParsePreprocessStylesheetTree(cctxt, cur); |
| /* |
| * Parse and compile the stylesheet. |
| */ |
| if (style->literal_result == 0) { |
| if (xsltParseXSLTStylesheetElem(cctxt, cur) != 0) |
| return(NULL); |
| } else { |
| if (xsltParseSimplifiedStylesheetTree(cctxt, doc, cur) != 0) |
| return(NULL); |
| } |
| |
| cctxt->simplified = oldIsSimplifiedStylesheet; |
| |
| return(style); |
| } |
| |
| #else /* XSLT_REFACTORED */ |
| |
| /** |
| * xsltParseStylesheetProcess: |
| * @ret: the XSLT stylesheet (the current stylesheet-level) |
| * @doc: and xmlDoc parsed XML |
| * |
| * Parses an XSLT stylesheet, adding the associated structures. |
| * Called by: |
| * xsltParseStylesheetImportedDoc() (xslt.c) |
| * xsltParseStylesheetInclude() (imports.c) |
| * |
| * Returns the value of the @style parameter if everything |
| * went right, NULL if something went amiss. |
| */ |
| xsltStylesheetPtr |
| xsltParseStylesheetProcess(xsltStylesheetPtr ret, xmlDocPtr doc) { |
| xmlNodePtr cur; |
| |
| xsltInitGlobals(); |
| |
| if (doc == NULL) |
| return(NULL); |
| if (ret == NULL) |
| return(ret); |
| |
| /* |
| * First steps, remove blank nodes, |
| * locate the xsl:stylesheet element and the |
| * namespace declaration. |
| */ |
| cur = xmlDocGetRootElement(doc); |
| if (cur == NULL) { |
| xsltTransformError(NULL, ret, (xmlNodePtr) doc, |
| "xsltParseStylesheetProcess : empty stylesheet\n"); |
| return(NULL); |
| } |
| |
| if ((IS_XSLT_ELEM(cur)) && |
| ((IS_XSLT_NAME(cur, "stylesheet")) || |
| (IS_XSLT_NAME(cur, "transform")))) { |
| #ifdef WITH_XSLT_DEBUG_PARSING |
| xsltGenericDebug(xsltGenericDebugContext, |
| "xsltParseStylesheetProcess : found stylesheet\n"); |
| #endif |
| ret->literal_result = 0; |
| xsltParseStylesheetExcludePrefix(ret, cur, 1); |
| xsltParseStylesheetExtPrefix(ret, cur, 1); |
| } else { |
| xsltParseStylesheetExcludePrefix(ret, cur, 0); |
| xsltParseStylesheetExtPrefix(ret, cur, 0); |
| ret->literal_result = 1; |
| } |
| if (!ret->nopreproc) { |
| xsltPrecomputeStylesheet(ret, cur); |
| } |
| if (ret->literal_result == 0) { |
| xsltParseStylesheetTop(ret, cur); |
| } else { |
| xmlChar *prop; |
| xsltTemplatePtr template; |
| |
| /* |
| * the document itself might be the template, check xsl:version |
| */ |
| prop = xmlGetNsProp(cur, (const xmlChar *)"version", XSLT_NAMESPACE); |
| if (prop == NULL) { |
| xsltTransformError(NULL, ret, cur, |
| "xsltParseStylesheetProcess : document is not a stylesheet\n"); |
| return(NULL); |
| } |
| |
| #ifdef WITH_XSLT_DEBUG_PARSING |
| xsltGenericDebug(xsltGenericDebugContext, |
| "xsltParseStylesheetProcess : document is stylesheet\n"); |
| #endif |
| |
| if (!xmlStrEqual(prop, (const xmlChar *)"1.0")) { |
| xsltTransformError(NULL, ret, cur, |
| "xsl:version: only 1.0 features are supported\n"); |
| /* TODO set up compatibility when not XSLT 1.0 */ |
| ret->warnings++; |
| } |
| xmlFree(prop); |
| |
| /* |
| * Create and link the template |
| */ |
| template = xsltNewTemplate(); |
| if (template == NULL) { |
| return(NULL); |
| } |
| template->next = ret->templates; |
| ret->templates = template; |
| template->match = xmlStrdup((const xmlChar *)"/"); |
| |
| /* |
| * parse the content and register the pattern |
| */ |
| xsltParseTemplateContent(ret, (xmlNodePtr) doc); |
| template->elem = (xmlNodePtr) doc; |
| template->content = doc->children; |
| xsltAddTemplate(ret, template, NULL, NULL); |
| ret->literal_result = 1; |
| } |
| |
| return(ret); |
| } |
| |
| #endif /* else of XSLT_REFACTORED */ |
| |
| /** |
| * xsltParseStylesheetImportedDoc: |
| * @doc: an xmlDoc parsed XML |
| * @parentStyle: pointer to the parent stylesheet (if it exists) |
| * |
| * parse an XSLT stylesheet building the associated structures |
| * except the processing not needed for imported documents. |
| * |
| * Returns a new XSLT stylesheet structure. |
| */ |
| |
| xsltStylesheetPtr |
| xsltParseStylesheetImportedDoc(xmlDocPtr doc, |
| xsltStylesheetPtr parentStyle) { |
| xsltStylesheetPtr retStyle; |
| |
| if (doc == NULL) |
| return(NULL); |
| |
| retStyle = xsltNewStylesheet(); |
| if (retStyle == NULL) |
| return(NULL); |
| /* |
| * Set the importing stylesheet module; also used to detect recursion. |
| */ |
| retStyle->parent = parentStyle; |
| /* |
| * Adjust the string dict. |
| */ |
| if (doc->dict != NULL) { |
| xmlDictFree(retStyle->dict); |
| retStyle->dict = doc->dict; |
| #ifdef WITH_XSLT_DEBUG |
| xsltGenericDebug(xsltGenericDebugContext, |
| "reusing dictionary from %s for stylesheet\n", |
| doc->URL); |
| #endif |
| xmlDictReference(retStyle->dict); |
| } |
| |
| /* |
| * TODO: Eliminate xsltGatherNamespaces(); we must not restrict |
| * the stylesheet to containt distinct namespace prefixes. |
| */ |
| xsltGatherNamespaces(retStyle); |
| |
| #ifdef XSLT_REFACTORED |
| { |
| xsltCompilerCtxtPtr cctxt; |
| xsltStylesheetPtr oldCurSheet; |
| |
| if (parentStyle == NULL) { |
| xsltPrincipalStylesheetDataPtr principalData; |
| /* |
| * Principal stylesheet |
| * -------------------- |
| */ |
| retStyle->principal = retStyle; |
| /* |
| * Create extra data for the principal stylesheet. |
| */ |
| principalData = xsltNewPrincipalStylesheetData(); |
| if (principalData == NULL) { |
| xsltFreeStylesheet(retStyle); |
| return(NULL); |
| } |
| retStyle->principalData = principalData; |
| /* |
| * Create the compilation context |
| * ------------------------------ |
| * (only once; for the principal stylesheet). |
| * This is currently the only function where the |
| * compilation context is created. |
| */ |
| cctxt = xsltCompilationCtxtCreate(retStyle); |
| if (cctxt == NULL) { |
| xsltFreeStylesheet(retStyle); |
| return(NULL); |
| } |
| retStyle->compCtxt = (void *) cctxt; |
| cctxt->style = retStyle; |
| cctxt->dict = retStyle->dict; |
| cctxt->psData = principalData; |
| /* |
| * Push initial dummy node info. |
| */ |
| cctxt->depth = -1; |
| xsltCompilerNodePush(cctxt, (xmlNodePtr) doc); |
| } else { |
| /* |
| * Imported stylesheet. |
| */ |
| retStyle->principal = parentStyle->principal; |
| cctxt = parentStyle->compCtxt; |
| retStyle->compCtxt = cctxt; |
| } |
| /* |
| * Save the old and set the current stylesheet structure in the |
| * compilation context. |
| */ |
| oldCurSheet = cctxt->style; |
| cctxt->style = retStyle; |
| |
| retStyle->doc = doc; |
| xsltParseStylesheetProcess(retStyle, doc); |
| |
| cctxt->style = oldCurSheet; |
| if (parentStyle == NULL) { |
| /* |
| * Pop the initial dummy node info. |
| */ |
| xsltCompilerNodePop(cctxt, (xmlNodePtr) doc); |
| } else { |
| /* |
| * Clear the compilation context of imported |
| * stylesheets. |
| * TODO: really? |
| */ |
| /* retStyle->compCtxt = NULL; */ |
| } |
| /* |
| * Free the stylesheet if there were errors. |
| */ |
| if (retStyle != NULL) { |
| if (retStyle->errors != 0) { |
| #ifdef XSLT_REFACTORED_XSLT_NSCOMP |
| /* |
| * Restore all changes made to namespace URIs of ns-decls. |
| */ |
| if (cctxt->psData->nsMap) |
| xsltRestoreDocumentNamespaces(cctxt->psData->nsMap, doc); |
| #endif |
| /* |
| * Detach the doc from the stylesheet; otherwise the doc |
| * will be freed in xsltFreeStylesheet(). |
| */ |
| retStyle->doc = NULL; |
| /* |
| * Cleanup the doc if its the main stylesheet. |
| */ |
| if (parentStyle == NULL) { |
| xsltCleanupStylesheetTree(doc, xmlDocGetRootElement(doc)); |
| if (retStyle->compCtxt != NULL) { |
| xsltCompilationCtxtFree(retStyle->compCtxt); |
| retStyle->compCtxt = NULL; |
| } |
| } |
| |
| xsltFreeStylesheet(retStyle); |
| retStyle = NULL; |
| } |
| } |
| } |
| |
| #else /* XSLT_REFACTORED */ |
| /* |
| * Old behaviour. |
| */ |
| retStyle->doc = doc; |
| if (xsltParseStylesheetProcess(retStyle, doc) == NULL) { |
| retStyle->doc = NULL; |
| xsltFreeStylesheet(retStyle); |
| retStyle = NULL; |
| } |
| if (retStyle != NULL) { |
| if (retStyle->errors != 0) { |
| retStyle->doc = NULL; |
| if (parentStyle == NULL) |
| xsltCleanupStylesheetTree(doc, |
| xmlDocGetRootElement(doc)); |
| xsltFreeStylesheet(retStyle); |
| retStyle = NULL; |
| } |
| } |
| #endif /* else of XSLT_REFACTORED */ |
| |
| return(retStyle); |
| } |
| |
| /** |
| * xsltParseStylesheetDoc: |
| * @doc: and xmlDoc parsed XML |
| * |
| * parse an XSLT stylesheet, building the associated structures. doc |
| * is kept as a reference within the returned stylesheet, so changes |
| * to doc after the parsing will be reflected when the stylesheet |
| * is applied, and the doc is automatically freed when the |
| * stylesheet is closed. |
| * |
| * Returns a new XSLT stylesheet structure. |
| */ |
| |
| xsltStylesheetPtr |
| xsltParseStylesheetDoc(xmlDocPtr doc) { |
| xsltStylesheetPtr ret; |
| |
| xsltInitGlobals(); |
| |
| ret = xsltParseStylesheetImportedDoc(doc, NULL); |
| if (ret == NULL) |
| return(NULL); |
| |
| xsltResolveStylesheetAttributeSet(ret); |
| #ifdef XSLT_REFACTORED |
| /* |
| * Free the compilation context. |
| * TODO: Check if it's better to move this cleanup to |
| * xsltParseStylesheetImportedDoc(). |
| */ |
| if (ret->compCtxt != NULL) { |
| xsltCompilationCtxtFree(XSLT_CCTXT(ret)); |
| ret->compCtxt = NULL; |
| } |
| #endif |
| return(ret); |
| } |
| |
| /** |
| * xsltParseStylesheetFile: |
| * @filename: the filename/URL to the stylesheet |
| * |
| * Load and parse an XSLT stylesheet |
| * |
| * Returns a new XSLT stylesheet structure. |
| */ |
| |
| xsltStylesheetPtr |
| xsltParseStylesheetFile(const xmlChar* filename) { |
| xsltSecurityPrefsPtr sec; |
| xsltStylesheetPtr ret; |
| xmlDocPtr doc; |
| |
| xsltInitGlobals(); |
| |
| if (filename == NULL) |
| return(NULL); |
| |
| #ifdef WITH_XSLT_DEBUG_PARSING |
| xsltGenericDebug(xsltGenericDebugContext, |
| "xsltParseStylesheetFile : parse %s\n", filename); |
| #endif |
| |
| /* |
| * Security framework check |
| */ |
| sec = xsltGetDefaultSecurityPrefs(); |
| if (sec != NULL) { |
| int res; |
| |
| res = xsltCheckRead(sec, NULL, filename); |
| if (res == 0) { |
| xsltTransformError(NULL, NULL, NULL, |
| "xsltParseStylesheetFile: read rights for %s denied\n", |
| filename); |
| return(NULL); |
| } |
| } |
| |
| doc = xsltDocDefaultLoader(filename, NULL, XSLT_PARSE_OPTIONS, |
| NULL, XSLT_LOAD_START); |
| if (doc == NULL) { |
| xsltTransformError(NULL, NULL, NULL, |
| "xsltParseStylesheetFile : cannot parse %s\n", filename); |
| return(NULL); |
| } |
| ret = xsltParseStylesheetDoc(doc); |
| if (ret == NULL) { |
| xmlFreeDoc(doc); |
| return(NULL); |
| } |
| |
| return(ret); |
| } |
| |
| /************************************************************************ |
| * * |
| * Handling of Stylesheet PI * |
| * * |
| ************************************************************************/ |
| |
| #define CUR (*cur) |
| #define SKIP(val) cur += (val) |
| #define NXT(val) cur[(val)] |
| #define SKIP_BLANKS \ |
| while (IS_BLANK(CUR)) NEXT |
| #define NEXT ((*cur) ? cur++ : cur) |
| |
| /** |
| * xsltParseStylesheetPI: |
| * @value: the value of the PI |
| * |
| * This function checks that the type is text/xml and extracts |
| * the URI-Reference for the stylesheet |
| * |
| * Returns the URI-Reference for the stylesheet or NULL (it need to |
| * be freed by the caller) |
| */ |
| static xmlChar * |
| xsltParseStylesheetPI(const xmlChar *value) { |
| const xmlChar *cur; |
| const xmlChar *start; |
| xmlChar *val; |
| xmlChar tmp; |
| xmlChar *href = NULL; |
| int isXml = 0; |
| |
| if (value == NULL) |
| return(NULL); |
| |
| cur = value; |
| while (CUR != 0) { |
| SKIP_BLANKS; |
| if ((CUR == 't') && (NXT(1) == 'y') && (NXT(2) == 'p') && |
| (NXT(3) == 'e')) { |
| SKIP(4); |
| SKIP_BLANKS; |
| if (CUR != '=') |
| continue; |
| NEXT; |
| if ((CUR != '\'') && (CUR != '"')) |
| continue; |
| tmp = CUR; |
| NEXT; |
| start = cur; |
| while ((CUR != 0) && (CUR != tmp)) |
| NEXT; |
| if (CUR != tmp) |
| continue; |
| val = xmlStrndup(start, cur - start); |
| NEXT; |
| if (val == NULL) |
| return(NULL); |
| if ((xmlStrcasecmp(val, BAD_CAST "text/xml")) && |
| (xmlStrcasecmp(val, BAD_CAST "text/xsl"))) { |
| xmlFree(val); |
| break; |
| } |
| isXml = 1; |
| xmlFree(val); |
| } else if ((CUR == 'h') && (NXT(1) == 'r') && (NXT(2) == 'e') && |
| (NXT(3) == 'f')) { |
| SKIP(4); |
| SKIP_BLANKS; |
| if (CUR != '=') |
| continue; |
| NEXT; |
| if ((CUR != '\'') && (CUR != '"')) |
| continue; |
| tmp = CUR; |
| NEXT; |
| start = cur; |
| while ((CUR != 0) && (CUR != tmp)) |
| NEXT; |
| if (CUR != tmp) |
| continue; |
| if (href == NULL) |
| href = xmlStrndup(start, cur - start); |
| NEXT; |
| } else { |
| while ((CUR != 0) && (!IS_BLANK(CUR))) |
| NEXT; |
| } |
| |
| } |
| |
| if (!isXml) { |
| if (href != NULL) |
| xmlFree(href); |
| href = NULL; |
| } |
| return(href); |
| } |
| |
| /** |
| * xsltLoadStylesheetPI: |
| * @doc: a document to process |
| * |
| * This function tries to locate the stylesheet PI in the given document |
| * If found, and if contained within the document, it will extract |
| * that subtree to build the stylesheet to process @doc (doc itself will |
| * be modified). If found but referencing an external document it will |
| * attempt to load it and generate a stylesheet from it. In both cases, |
| * the resulting stylesheet and the document need to be freed once the |
| * transformation is done. |
| * |
| * Returns a new XSLT stylesheet structure or NULL if not found. |
| */ |
| xsltStylesheetPtr |
| xsltLoadStylesheetPI(xmlDocPtr doc) { |
| xmlNodePtr child; |
| xsltStylesheetPtr ret = NULL; |
| xmlChar *href = NULL; |
| xmlURIPtr URI; |
| |
| xsltInitGlobals(); |
| |
| if (doc == NULL) |
| return(NULL); |
| |
| /* |
| * Find the text/xml stylesheet PI id any before the root |
| */ |
| child = doc->children; |
| while ((child != NULL) && (child->type != XML_ELEMENT_NODE)) { |
| if ((child->type == XML_PI_NODE) && |
| (xmlStrEqual(child->name, BAD_CAST "xml-stylesheet"))) { |
| href = xsltParseStylesheetPI(child->content); |
| if (href != NULL) |
| break; |
| } |
| child = child->next; |
| } |
| |
| /* |
| * If found check the href to select processing |
| */ |
| if (href != NULL) { |
| #ifdef WITH_XSLT_DEBUG_PARSING |
| xsltGenericDebug(xsltGenericDebugContext, |
| "xsltLoadStylesheetPI : found PI href=%s\n", href); |
| #endif |
| URI = xmlParseURI((const char *) href); |
| if (URI == NULL) { |
| xsltTransformError(NULL, NULL, child, |
| "xml-stylesheet : href %s is not valid\n", href); |
| xmlFree(href); |
| return(NULL); |
| } |
| if ((URI->fragment != NULL) && (URI->scheme == NULL) && |
| (URI->opaque == NULL) && (URI->authority == NULL) && |
| (URI->server == NULL) && (URI->user == NULL) && |
| (URI->path == NULL) && (URI->query == NULL)) { |
| xmlAttrPtr ID; |
| |
| #ifdef WITH_XSLT_DEBUG_PARSING |
| xsltGenericDebug(xsltGenericDebugContext, |
| "xsltLoadStylesheetPI : Reference to ID %s\n", href); |
| #endif |
| if (URI->fragment[0] == '#') |
| ID = xmlGetID(doc, (const xmlChar *) &(URI->fragment[1])); |
| else |
| ID = xmlGetID(doc, (const xmlChar *) URI->fragment); |
| if (ID == NULL) { |
| xsltTransformError(NULL, NULL, child, |
| "xml-stylesheet : no ID %s found\n", URI->fragment); |
| } else { |
| xmlDocPtr fake; |
| xmlNodePtr subtree, newtree; |
| xmlNsPtr ns; |
| |
| #ifdef WITH_XSLT_DEBUG |
| xsltGenericDebug(xsltGenericDebugContext, |
| "creating new document from %s for embedded stylesheet\n", |
| doc->URL); |
| #endif |
| /* |
| * move the subtree in a new document passed to |
| * the stylesheet analyzer |
| */ |
| subtree = ID->parent; |
| fake = xmlNewDoc(NULL); |
| if (fake != NULL) { |
| /* |
| * Should the dictionary still be shared even though |
| * the nodes are being copied rather than moved? |
| */ |
| fake->dict = doc->dict; |
| xmlDictReference(doc->dict); |
| #ifdef WITH_XSLT_DEBUG |
| xsltGenericDebug(xsltGenericDebugContext, |
| "reusing dictionary from %s for embedded stylesheet\n", |
| doc->URL); |
| #endif |
| |
| newtree = xmlDocCopyNode(subtree, fake, 1); |
| |
| fake->URL = xmlNodeGetBase(doc, subtree->parent); |
| #ifdef WITH_XSLT_DEBUG |
| xsltGenericDebug(xsltGenericDebugContext, |
| "set base URI for embedded stylesheet as %s\n", |
| fake->URL); |
| #endif |
| |
| /* |
| * Add all namespaces in scope of embedded stylesheet to |
| * root element of newly created stylesheet document |
| */ |
| while ((subtree = subtree->parent) != (xmlNodePtr)doc) { |
| for (ns = subtree->ns; ns; ns = ns->next) { |
| xmlNewNs(newtree, ns->href, ns->prefix); |
| } |
| } |
| |
| xmlAddChild((xmlNodePtr)fake, newtree); |
| ret = xsltParseStylesheetDoc(fake); |
| if (ret == NULL) |
| xmlFreeDoc(fake); |
| } |
| } |
| } else { |
| xmlChar *URL, *base; |
| |
| /* |
| * Reference to an external stylesheet |
| */ |
| |
| base = xmlNodeGetBase(doc, (xmlNodePtr) doc); |
| URL = xmlBuildURI(href, base); |
| if (URL != NULL) { |
| #ifdef WITH_XSLT_DEBUG_PARSING |
| xsltGenericDebug(xsltGenericDebugContext, |
| "xsltLoadStylesheetPI : fetching %s\n", URL); |
| #endif |
| ret = xsltParseStylesheetFile(URL); |
| xmlFree(URL); |
| } else { |
| #ifdef WITH_XSLT_DEBUG_PARSING |
| xsltGenericDebug(xsltGenericDebugContext, |
| "xsltLoadStylesheetPI : fetching %s\n", href); |
| #endif |
| ret = xsltParseStylesheetFile(href); |
| } |
| if (base != NULL) |
| xmlFree(base); |
| } |
| xmlFreeURI(URI); |
| xmlFree(href); |
| } |
| return(ret); |
| } |