# 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)
+
+