# HG changeset patch # User Daniel Veillard <veillard@src.gnome.org> # Date 979424991 0 # Sat Jan 13 22:29:51 2001 +0000 # Node ID a835fc05e776428b147b5643247d9a4843fcb0db # Parent d482e049d59142305c2a96bdfb3b3130d129d958 More general work, added for-each: - test/Makefile.am test/REC*/Makefile.am: added first test - libxslt/pattern.c libxslt/transform.c libxslt/xslt.c: cleanup of nodes at reading of stylesheet, added support for xsl:for-each and fixed a few recursion bugs Daniel diff --git a/ChangeLog b/ChangeLog --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +Sat Jan 13 23:26:21 CET 2001 Daniel Veillard <Daniel.Veillard@imag.fr> + + * test/Makefile.am test/REC*/Makefile.am: added first test + * libxslt/pattern.c libxslt/transform.c libxslt/xslt.c: + cleanup of nodes at reading of stylesheet, added support + for xsl:for-each and fixed a few recursion bugs + Fri Jan 12 22:33:07 CET 2001 Daniel Veillard <Daniel.Veillard@imag.fr> * pattern.c, xslt.c: removed debug diff --git a/Makefile.am b/Makefile.am --- a/Makefile.am +++ b/Makefile.am @@ -24,6 +24,7 @@ < $(srcdir)/xsltConf.sh.in > xsltConf.tmp \ && mv xsltConf.tmp xsltConf.sh +test: + @(cd tests ; make test) - diff --git a/configure.in b/configure.in --- a/configure.in +++ b/configure.in @@ -120,5 +120,7 @@ libxslt/Makefile libxslt/xsltconfig.h tests/Makefile +tests/REC1/Makefile +tests/REC2/Makefile xslt-config ]) diff --git a/libxslt/pattern.c b/libxslt/pattern.c --- a/libxslt/pattern.c +++ b/libxslt/pattern.c @@ -571,8 +571,8 @@ NEXT; SKIP_BLANKS; PUSH(XSLT_OP_ROOT, NULL, NULL); - PUSH(XSLT_OP_PARENT, NULL, NULL); if ((CUR != 0) || (CUR == '|')) { + PUSH(XSLT_OP_PARENT, NULL, NULL); xsltCompileRelativePathPattern(ctxt, NULL); } } else { diff --git a/libxslt/transform.c b/libxslt/transform.c --- a/libxslt/transform.c +++ b/libxslt/transform.c @@ -89,7 +89,6 @@ xmlNodePtr insert; /* the insertion node */ xmlXPathContextPtr xpathCtxt; /* the XPath context */ - xmlXPathParserContextPtr xpathParserCtxt;/* the XPath parser context */ }; /************************************************************************ @@ -142,6 +141,8 @@ ************************************************************************/ void xsltProcessOneNode(xsltTransformContextPtr ctxt, xmlNodePtr node); +void xsltForEach(xsltTransformContextPtr ctxt, xmlNodePtr node, + xmlNodePtr inst); /** * xsltValueOf: @@ -157,6 +158,7 @@ xmlChar *prop; int disableEscaping = 0; xmlXPathObjectPtr res, tmp; + xmlXPathParserContextPtr xpathParserCtxt; xmlNodePtr copy = NULL; if ((ctxt == NULL) || (node == NULL) || (inst == NULL)) @@ -181,7 +183,7 @@ prop = xmlGetNsProp(inst, (const xmlChar *)"select", XSLT_NAMESPACE); if (prop == NULL) { xsltGenericError(xsltGenericErrorContext, - "xsltValueOf: select is not defined\n", prop); + "xsltValueOf: select is not defined\n"); return; } #ifdef DEBUG_PROCESS @@ -195,17 +197,17 @@ if (ctxt->xpathCtxt == NULL) goto error; } - ctxt->xpathParserCtxt = + xpathParserCtxt = xmlXPathNewParserContext(prop, ctxt->xpathCtxt); - if (ctxt->xpathParserCtxt == NULL) + if (xpathParserCtxt == NULL) goto error; ctxt->xpathCtxt->node = node; - valuePush(ctxt->xpathParserCtxt, xmlXPathNewNodeSet(node)); - xmlXPathEvalExpr(ctxt->xpathParserCtxt); - xmlXPathStringFunction(ctxt->xpathParserCtxt, 1); - res = valuePop(ctxt->xpathParserCtxt); + valuePush(xpathParserCtxt, xmlXPathNewNodeSet(node)); + xmlXPathEvalExpr(xpathParserCtxt); + xmlXPathStringFunction(xpathParserCtxt, 1); + res = valuePop(xpathParserCtxt); do { - tmp = valuePop(ctxt->xpathParserCtxt); + tmp = valuePop(xpathParserCtxt); if (tmp != NULL) { xmlXPathFreeObject(tmp); } @@ -228,8 +230,10 @@ "xsltValueOf: result %s\n", res->stringval); #endif error: - if (ctxt->xpathParserCtxt != NULL) - xmlXPathFreeParserContext(ctxt->xpathParserCtxt); + if (xpathParserCtxt != NULL) { + xmlXPathFreeParserContext(xpathParserCtxt); + xpathParserCtxt = NULL; + } if (prop != NULL) xmlFree(prop); if (res != NULL) @@ -253,6 +257,7 @@ xmlNodePtr copy; copy = xmlCopyNode(node, 0); + copy->doc = ctxt->output; if (copy != NULL) { xmlAddChild(insert, copy); /* @@ -290,7 +295,7 @@ } } else { xsltGenericError(xsltGenericErrorContext, - "xsltProcessOneNode: copy %s failed\n", node->name); + "xsltCopyNode: copy %s failed\n", node->name); } return(copy); } @@ -319,6 +324,7 @@ void xsltDefaultProcessOneNode(xsltTransformContextPtr ctxt, xmlNodePtr node) { xmlNodePtr copy; + xmlNodePtr delete = NULL; switch (node->type) { case XML_DOCUMENT_NODE: @@ -347,8 +353,10 @@ xmlHashLookup(ctxt->style->stripSpaces, node->parent->name); if ((val != NULL) && - (xmlStrEqual(val, (xmlChar *) "strip"))) + (xmlStrEqual(val, (xmlChar *) "strip"))) { + delete = node; break; + } } /* no break on purpose */ case XML_CDATA_SECTION_NODE: @@ -361,9 +369,23 @@ } break; default: - TODO +#ifdef DEBUG_PROCESS + xsltGenericError(xsltGenericErrorContext, + "xsltDefaultProcessOneNode: skipping node type %d\n", + node->type); +#endif + delete = node; } node = node->next; + if (delete != NULL) { +#ifdef DEBUG_PROCESS + xsltGenericError(xsltGenericErrorContext, + "xsltDefaultProcessOneNode: removing ignorable blank node\n"); +#endif + xmlUnlinkNode(delete); + xmlFreeNode(delete); + delete = NULL; + } } } @@ -396,38 +418,48 @@ } /** - * xsltProcessOneNode: + * xsltApplyOneTemplate: * @ctxt: a XSLT process context * @node: the node in the source tree. + * @list: the template replacement nodelist * - * Process the source node. + * Process the apply-templates node on the source node */ void -xsltProcessOneNode(xsltTransformContextPtr ctxt, xmlNodePtr node) { - xsltTemplatePtr template; - xmlNodePtr cur, insert, copy; +xsltApplyOneTemplate(xsltTransformContextPtr ctxt, xmlNodePtr node, + xmlNodePtr list) { + xmlNodePtr cur, insert, copy, delete = NULL; xmlNodePtr oldInsert; oldInsert = insert = ctxt->insert; - template = xsltGetTemplate(ctxt->style, node); - /* - * If no template is found, apply the deafult rule. - */ - if (template == NULL) { -#ifdef DEBUG_PROCESS - xsltGenericError(xsltGenericErrorContext, - "xsltProcessOneNode: no template found for %s\n", node->name); -#endif - - xsltDefaultProcessOneNode(ctxt, node); - return; - } - /* * Insert all non-XSLT nodes found in the template */ - cur = template->content; + cur = list; while (cur != NULL) { + /* + * test, we must have a valid insertion point + */ + if (insert == NULL) { +#ifdef DEBUG_PROCESS + xsltGenericError(xsltGenericErrorContext, + "xsltApplyOneTemplate: insert == NULL !\n"); +#endif + return; + } + + /* + * Cleanup of ignorable blank node detected + */ + if (delete != NULL) { +#ifdef DEBUG_PROCESS + xsltGenericError(xsltGenericErrorContext, + "xsltApplyOneTemplate: removing ignorable blank node\n"); +#endif + xmlUnlinkNode(delete); + xmlFreeNode(delete); + delete = NULL; + } if (IS_XSLT_ELEM(cur)) { if (IS_XSLT_NAME(cur, "apply-templates")) { ctxt->insert = insert; @@ -437,13 +469,18 @@ ctxt->insert = insert; xsltValueOf(ctxt, node, cur); ctxt->insert = oldInsert; + } else if (IS_XSLT_NAME(cur, "for-each")) { + ctxt->insert = insert; + xsltForEach(ctxt, node, cur); + ctxt->insert = oldInsert; } else { #ifdef DEBUG_PROCESS xsltGenericError(xsltGenericErrorContext, - "xsltProcessOneNode: found xslt:%s\n", cur->name); + "xsltApplyOneTemplate: found xslt:%s\n", cur->name); #endif TODO } + goto skip_children; } else if (cur->type == XML_TEXT_NODE) { /* * This text comes from the stylesheet @@ -453,20 +490,22 @@ if (!(IS_BLANK_NODE(cur))) { #ifdef DEBUG_PROCESS xsltGenericError(xsltGenericErrorContext, - "xsltProcessOneNode: copy text %s\n", cur->content); + "xsltApplyOneTemplate: copy text %s\n", cur->content); #endif copy = xmlCopyNode(cur, 0); if (copy != NULL) { xmlAddChild(insert, copy); } else { xsltGenericError(xsltGenericErrorContext, - "xsltProcessOneNode: text copy failed\n"); + "xsltApplyOneTemplate: text copy failed\n"); } + } else { + delete = cur; } - } else { + } else if (cur->type == XML_ELEMENT_NODE) { #ifdef DEBUG_PROCESS xsltGenericError(xsltGenericErrorContext, - "xsltProcessOneNode: copy node %s\n", cur->name); + "xsltApplyOneTemplate: copy node %s\n", cur->name); #endif copy = xsltCopyNode(ctxt, cur, insert); /* @@ -476,17 +515,19 @@ if (cur->properties != NULL) copy->properties = xmlCopyPropList(copy, cur->properties); } + /* - * Skip to next node + * Skip to next node, in document order. */ - if (cur->children != NULL) { if (cur->children->type != XML_ENTITY_DECL) { cur = cur->children; - insert = copy; + if (copy != NULL) + insert = copy; continue; } } +skip_children: if (cur->next != NULL) { cur = cur->next; continue; @@ -497,7 +538,7 @@ insert = insert->parent; if (cur == NULL) break; - if (cur == template->content) { + if (cur == list->parent) { cur = NULL; break; } @@ -507,13 +548,126 @@ } } while (cur != NULL); } - /******** - if (ctxt->style->indent) { - copy = xmlNewText("\n"); - if (copy != NULL) - xmlAddChild(ctxt->insert, copy); +} + +/** + * xsltForEach: + * @ctxt: a XSLT process context + * @node: the node in the source tree. + * @inst: the xslt for-each node + * + * Process the xslt for-each node on the source node + */ +void +xsltForEach(xsltTransformContextPtr ctxt, xmlNodePtr node, + xmlNodePtr inst) { + xmlChar *prop; + xmlXPathObjectPtr res, tmp; + xmlNodePtr replacement; + xmlNodeSetPtr list = NULL, oldlist; + xmlXPathParserContextPtr xpathParserCtxt; + int i; + + if ((ctxt == NULL) || (node == NULL) || (inst == NULL)) + return; + + prop = xmlGetNsProp(inst, (const xmlChar *)"select", XSLT_NAMESPACE); + if (prop == NULL) { + xsltGenericError(xsltGenericErrorContext, + "xsltForEach: select is not defined\n"); + return; + } +#ifdef DEBUG_PROCESS + xsltGenericError(xsltGenericErrorContext, + "xsltForEach: select %s\n", prop); +#endif + + if (ctxt->xpathCtxt == NULL) { + xmlXPathInit(); + ctxt->xpathCtxt = xmlXPathNewContext(ctxt->doc); + if (ctxt->xpathCtxt == NULL) + goto error; } - ********/ + xpathParserCtxt = xmlXPathNewParserContext(prop, ctxt->xpathCtxt); + if (xpathParserCtxt == NULL) + goto error; + ctxt->xpathCtxt->node = node; + valuePush(xpathParserCtxt, xmlXPathNewNodeSet(node)); + xmlXPathEvalExpr(xpathParserCtxt); + res = valuePop(xpathParserCtxt); + do { + tmp = valuePop(xpathParserCtxt); + if (tmp != NULL) { + xmlXPathFreeObject(tmp); + } + } while (tmp != NULL); + + if (res != NULL) { + if (res->type == XPATH_NODESET) + list = res->nodesetval; + else { +#ifdef DEBUG_PROCESS + xsltGenericError(xsltGenericErrorContext, + "xsltForEach: select didn't evaluate to a node list\n"); +#endif + goto error; + } + } + +#ifdef DEBUG_PROCESS + xsltGenericError(xsltGenericErrorContext, + "xsltForEach: select evaluate to %d nodes\n", list->nodeNr); +#endif + /* TODO: handle and skip the xsl:sort */ + replacement = inst->children; + + oldlist = ctxt->nodeList; + ctxt->nodeList = list; + for (i = 0;i < list->nodeNr;i++) { + ctxt->node = list->nodeTab[i]; + xsltApplyOneTemplate(ctxt, list->nodeTab[i], replacement); + } + ctxt->nodeList = oldlist; + +error: + if (xpathParserCtxt != NULL) + xmlXPathFreeParserContext(xpathParserCtxt); + if (prop != NULL) + xmlFree(prop); + if (res != NULL) + xmlXPathFreeObject(res); +} + +/** + * xsltProcessOneNode: + * @ctxt: a XSLT process context + * @node: the node in the source tree. + * + * Process the source node. + */ +void +xsltProcessOneNode(xsltTransformContextPtr ctxt, xmlNodePtr node) { + xsltTemplatePtr template; + template = xsltGetTemplate(ctxt->style, node); + + /* + * If no template is found, apply the default rule. + */ + if (template == NULL) { +#ifdef DEBUG_PROCESS + if (node->type == XML_DOCUMENT_NODE) + xsltGenericError(xsltGenericErrorContext, + "xsltProcessOneNode: no template found for /\n"); + else + xsltGenericError(xsltGenericErrorContext, + "xsltProcessOneNode: no template found for %s\n", node->name); +#endif + + xsltDefaultProcessOneNode(ctxt, node); + return; + } + + xsltApplyOneTemplate(ctxt, node, template->content); } /** @@ -569,17 +723,10 @@ /* * Start. */ - root = xmlDocGetRootElement(doc); - if (root == NULL) { - xsltGenericError(xsltGenericErrorContext, - "xsltApplyStylesheet: document has no root\n"); - goto error; - } ctxt->output = res; ctxt->insert = (xmlNodePtr) res; - ctxt->node = root; - ctxt->nodeList = xmlXPathNodeSetCreate(root); - xsltProcessOneNode(ctxt, root); + ctxt->node = (xmlNodePtr) doc; + xsltProcessOneNode(ctxt, ctxt->node); if ((ctxt->type = XSLT_OUTPUT_XML) && diff --git a/libxslt/xslt.c b/libxslt/xslt.c --- a/libxslt/xslt.c +++ b/libxslt/xslt.c @@ -477,7 +477,7 @@ void xsltParseStylesheetTemplate(xsltStylesheetPtr style, xmlNodePtr template) { xsltTemplatePtr ret; - xmlNodePtr cur; + xmlNodePtr cur, delete; xmlChar *prop; if (template == NULL) @@ -540,6 +540,65 @@ } /* + * Clean-up the template content from unwanted ignorable blank nodes + * This content comes from the stylesheet + * For stylesheets, the set of whitespace-preserving + * element names consists of just xsl:text. + */ + cur = template->children; + delete = NULL; + while (cur != NULL) { + if (delete != NULL) { +#ifdef DEBUG_PARSING + xsltGenericError(xsltGenericErrorContext, + "xsltParseStylesheetTemplate: removing ignorable blank node\n"); +#endif + xmlUnlinkNode(delete); + xmlFreeNode(delete); + delete = NULL; + } + if (IS_XSLT_ELEM(cur)) { + if (IS_XSLT_NAME(cur, "text")) + goto skip_children; + } else if (cur->type == XML_TEXT_NODE) { + if (IS_BLANK_NODE(cur)) { + delete = cur; + } + } else if (cur->type != XML_ELEMENT_NODE) { + delete = cur; + } + + /* + * 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 == template) { + cur = NULL; + break; + } + if (cur->next != NULL) { + cur = cur->next; + break; + } + } while (cur != NULL); + } + + /* * Find and handle the params */ cur = template->children; @@ -726,22 +785,24 @@ return(NULL); } + ret->doc = doc; if ((IS_XSLT_ELEM(cur)) && (IS_XSLT_NAME(cur, "stylesheet"))) { #ifdef DEBUG_PARSING xsltGenericError(xsltGenericErrorContext, "xsltParseStylesheetDoc : found stylesheet\n"); #endif + + xsltParseStylesheetTop(ret, cur); } else { - - TODO /* lookup the stylesheet element down in the tree */ + /* + * the document itself is the template. + */ +#ifdef DEBUG_PARSING xsltGenericError(xsltGenericErrorContext, - "xsltParseStylesheetDoc : root is not stylesheet\n"); - xsltFreeStylesheet(ret); - return(NULL); + "xsltParseStylesheetDoc : document is stylesheet\n"); +#endif + TODO } - ret->doc = doc; - - xsltParseStylesheetTop(ret, cur); return(ret); } diff --git a/tests/Makefile.am b/tests/Makefile.am --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -17,3 +17,7 @@ #testevents_LDFLAGS = #testevents_DEPENDENCIES = $(DEPS) #testevents_LDADD = $(LDADDS) + +test: $(top_builddir)/libxslt/xsltproc + @(cd REC1 ; make test) + @(cd REC2 ; make test) diff --git a/tests/REC1/Makefile.am b/tests/REC1/Makefile.am new file mode 100644 --- /dev/null +++ b/tests/REC1/Makefile.am @@ -0,0 +1,13 @@ +## Process this file with automake to produce Makefile.in + +$(top_builddir)/libxslt/xsltproc: + @(cd ../../libxslt ; make xsltproc) + +test: $(top_builddir)/libxslt/xsltproc + @(rm -f .memdump ; touch .memdump) + @($(top_builddir)/libxslt/xsltproc doc.xsl doc.xml > doc.res ; \ + diff result.xml doc.res ; \ + grep "MORY ALLO" .memdump | grep -v "MEMORY ALLOCATED : 0";\ + rm -f doc.res) + + diff --git a/tests/REC1/result.xml b/tests/REC1/result.xml --- a/tests/REC1/result.xml +++ b/tests/REC1/result.xml @@ -1,18 +1,16 @@ <?xml version="1.0" encoding="iso-8859-1"?> <html xmlns="http://www.w3.org/TR/xhtml1/strict"> -<head> -<title>Document Title</title> -</head> -<body> -<h1>Document Title</h1> -<h2>Chapter Title</h2> -<h3>Section Title</h3> -<p>This is a test.</p> -<p class="note"> -<b>NOTE: </b>This is a note.</p> -<h3>Another Section Title</h3> -<p>This is <em>another</em> test.</p> -<p class="note"> -<b>NOTE: </b>This is another note.</p> -</body> + <head> + <title>Document Title</title> + </head> + <body> + <h1>Document Title</h1> + <h2>Chapter Title</h2> + <h3>Section Title</h3> + <p>This is a test.</p> + <p class="note"><b>NOTE: </b>This is a note.</p> + <h3>Another Section Title</h3> + <p>This is <em>another</em> test.</p> + <p class="note"><b>NOTE: </b>This is another note.</p> + </body> </html> diff --git a/tests/REC2/Makefile.am b/tests/REC2/Makefile.am new file mode 100644 --- /dev/null +++ b/tests/REC2/Makefile.am @@ -0,0 +1,13 @@ +## Process this file with automake to produce Makefile.in + +$(top_builddir)/libxslt/xsltproc: + @(cd ../../libxslt ; make xsltproc) + +test: $(top_builddir)/libxslt/xsltproc + @(rm -f .memdump ; touch .memdump) + @($(top_builddir)/libxslt/xsltproc doc.xsl doc.xml > doc.res ; \ + diff result.xml doc.res ; \ + grep "MORY ALLO" .memdump | grep -v "MEMORY ALLOCATED : 0";\ + rm -f doc.res) + +