Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Table generated by XSLT repeating

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>
like image 308
user1170097 Avatar asked Mar 01 '26 20:03

user1170097


2 Answers

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>
like image 107
Dimitre Novatchev Avatar answered Mar 03 '26 12:03

Dimitre Novatchev


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.

like image 40
dash Avatar answered Mar 03 '26 12:03

dash



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!