I'm having difficulty with a table I'm generating from an XSLT.
My XML is a recipe. Any line that is bold will be formatted as a table. My problem is that the table is duplicated on output. I've tried various grouping scenarios but haven't had any luck resolving. Does anyone have insight into what I'm missing? Thanks.
The XML:
<p>Some text goes here</p>
<p>Some text goes here</p>
<p>Some text goes here</p>
<p>Some text goes here</p>
<p>Ingredients</p>
<p><b>1 cup of flour</b></p>
<p><b>2 eggs</b></p>
<p><b>1/4 stick of butter</b></p>
<p><b>1/4 cup of sugar</b></p>
<p>Some text goes here</p>
<p>Some text goes here</p>
<p>Some text goes here</p>
<p>Some text goes here</p>
The Code:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:strip-space elements="*" />
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*" />
</xsl:copy>
</xsl:template>
<xsl:template match="*/p/b" >
<table>
<xsl:apply-templates select="//b" mode="test"/>
</table>
</xsl:template>
<xsl:template match="//b" mode="test">
<tr>
<td>
<xsl:value-of select="substring-before(., ' ')" />
</td>
<td>
<xsl:value-of select="substring-after(., ' ')" />
</td>
</tr>
</xsl:template>
</xsl:stylesheet>
The Output:
<p>Some text goes here</p>
<p>Some text goes here</p>
<p>Some text goes here</p>
<p>Some text goes here</p>
<p>Ingredients</p>
<p><table><tr><td>1</td><td>cup of flour</td></tr>
<tr><td>2</td><td>eggs</td></tr>
<tr><td>1/4</td><td>stick of butter</td></tr>
<tr><td>1/4</td><td>cup of sugar</td></tr>
</table></p>
<p><table><tr><td>1</td><td>cup of flour</td></tr>
<tr><td>2</td><td>eggs</td></tr>
<tr><td>1/4</td><td>stick of butter</td></tr>
<tr><td>1/4</td><td>cup of sugar</td></tr>
</table></p>
<p><table><tr><td>1</td><td>cup of flour</td></tr>
<tr><td>2</td><td>eggs</td></tr>
<tr><td>1/4</td><td>stick of butter</td></tr>
<tr><td>1/4</td><td>cup of sugar</td></tr>
</table></p>
<p><table><tr><td>1</td><td>cup of flour</td></tr>
<tr><td>2</td><td>eggs</td></tr>
<tr><td>1/4</td><td>stick of butter</td></tr>
<tr><td>1/4</td><td>cup of sugar</td></tr>
</table></p>
<p>Some text goes here</p>
<p>Some text goes here</p>
<p>Some text goes here</p>
<p>Some text goes here</p>
This simple transformation (no xsl:if, no count(), no preceding-sibling:: axis) is just a slight modification of the provided code:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*" />
</xsl:copy>
</xsl:template>
<xsl:template match="p[b][1]/b" >
<table>
<xsl:apply-templates select="//b" mode="test"/>
</table>
</xsl:template>
<xsl:template match="//b" mode="test">
<tr>
<td>
<xsl:value-of select="substring-before(., ' ')" />
</td>
<td>
<xsl:value-of select="substring-after(., ' ')" />
</td>
</tr>
</xsl:template>
<xsl:template match="p[b][position() > 1]"/>
</xsl:stylesheet>
when applied on the provided XML document:
<t>
<p>Some text goes here</p>
<p>Some text goes here</p>
<p>Some text goes here</p>
<p>Some text goes here</p>
<p>Ingredients</p>
<p><b>1 cup of flour</b></p>
<p><b>2 eggs</b></p>
<p><b>1/4 stick of butter</b></p>
<p><b>1/4 cup of sugar</b></p>
<p>Some text goes here</p>
<p>Some text goes here</p>
<p>Some text goes here</p>
<p>Some text goes here</p>
</t>
produces the wanted result:
<t>
<p>Some text goes here</p>
<p>Some text goes here</p>
<p>Some text goes here</p>
<p>Some text goes here</p>
<p>Ingredients</p>
<p>
<table>
<tr>
<td>1</td>
<td>cup of flour</td>
</tr>
<tr>
<td>2</td>
<td>eggs</td>
</tr>
<tr>
<td>1/4</td>
<td>stick of butter</td>
</tr>
<tr>
<td>1/4</td>
<td>cup of sugar</td>
</tr>
</table>
</p>
<p>Some text goes here</p>
<p>Some text goes here</p>
<p>Some text goes here</p>
<p>Some text goes here</p>
</t>
You are nearly there!
Your problem lies in how you are handling the b[old] nodes. By using //b you are effectively selecting along the wrong axis so you will pick up all 4 b nodes (hence the repetition).
The following transformation outputs the below result on my machine:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html"/>
<xsl:strip-space elements="*" />
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*" />
</xsl:copy>
</xsl:template>
<xsl:template match="*/p[b]" >
<xsl:if test="count(preceding-sibling::p[b]) = 0">
<table>
<xsl:apply-templates select="parent::node()//b" mode="test"/>
</table>
</xsl:if>
</xsl:template>
<xsl:template match="b" mode="test">
<tr>
<td>
<xsl:value-of select="substring-before(., ' ')" />
</td>
<td>
<xsl:value-of select="substring-after(., ' ')" />
</td>
</tr>
</xsl:template>
</xsl:stylesheet>
Result:
<Recipe>
<p>Some text goes here</p>
<p>Some text goes here</p>
<p>Some text goes here</p>
<p>Some text goes here</p>
<p>Ingredients</p>
<table>
<tr>
<td>1</td>
<td>cup of flour</td>
</tr>
<tr>
<td>2</td>
<td>eggs</td>
</tr>
<tr>
<td>1/4</td>
<td>stick of butter</td>
</tr>
<tr>
<td>1/4</td>
<td>cup of sugar</td>
</tr>
</table>
<p>Some text goes here</p>
<p>Some text goes here</p>
<p>Some text goes here</p>
<p>Some text goes here</p>
</Recipe>
(Note I enclosed your original XML fragment with a node to create a well-formed document)
How does this work? Well, the trick is in how many times we match the p/b pattern - although it exists four times, we only want one table with all of the subsequent b nodes as children.
So we test for the first p with a child b p[b] by counting how many times this pattern has occured before - count(preceding-sibling::p[b]) = 0 gives me the first one. We then climb back up a node and select all of the b children from here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With