Formatting table columns to ignore duplicates XSLT-1.0

372 views Asked by At

I was hoping that someone could point me in the correct direction with a concept in XSLT 1.0. I am generating a PDF so the code is a bit long and I thought it would be more appropriate to include only the relevant bits.

I have XML similar to the following skeleton (the full XML has dozens of rows that each contain more than just producer and publication):

<root>
    <table>
        <row>
            <PRODUCER/>
            <PUBLICATION_CODE_-_NAME/>
        </row>
        <row>
            <PRODUCER/>
            <PUBLICATION_CODE_-_NAME/>
        </row>
    </table>
</root>

I am currently able to generate a table using XSLT that looks similar to this that contains multiple rows:

|  Producer Name  |  Publication  |
----------------------------------            
|Producer 1       |Publication A  |
|Producer 1       |Publication B  |
|Producer 1       |Publication C  |
|Producer 2       |Publication D  |
|Producer 2       |Publication E  |
|Producer 2       |Publication F  |

And so on in this fashion.

The main portion of the code that produces this in my XSLT is:

<xsl:template match ="table">
    <fo:table>
        <fo:table-body  font-size="10pt"
                        font-family="sans-serif"
                        line-height="10pt"
                        space-after.optimum="3pt">
            <xsl:for-each select="row">
                <fo:table-row>
                    <fo:table-cell  width="2.125in"
                                    height="0.2in">
                        <xsl:choose>
                        <xsl:apply-templates select="PRODUCER"/>
                        </xsl:choose>
                    </fo:table-cell>
                    <fo:table-cell  width="3.25in"
                                    height="0.2in">
                        <xsl:apply-templates select="PUBLICATION_CODE_-_NAME"/>
                    </fo:table-cell>
                </fo:table-row>
            </xsl:for-each>
        </fo:table-body>
    </fo:table>
</xsl:template>

<xsl:template match="PRODUCER">
    <fo:block>
            <xsl:value-of select="@value"/>
    </fo:block>
</xsl:template>

<xsl:template match="PUBLICATION_CODE_-_NAME">
    <fo:block>
        <xsl:value-of select="@value"/>
    </fo:block>
</xsl:template>

Now the question arises where I want the output of the table to look more like this as opposed to the table above.

|  Producer Name  |  Publication  |
----------------------------------            
|Producer 1       |Publication A  |
|                 |Publication B  |
|                 |Publication C  |
|Producer 2       |Publication D  |
|                 |Publication E  |
|                 |Publication F  |

The way that I am attempting to do this is within this portion of the XSLT

<xsl:template match="PRODUCER">
    <fo:block>
            <xsl:value-of select="@value"/>
    </fo:block>
</xsl:template>

At this point, as far as I understand, the context node is PRODUCER. So in order to compare PRODUCER values in previous rows I would need to use something to the effect of ../preceding-sibling in order to find the preceding-sibling of last row (instead of PRODUCER). Additionally, the information is already ordered by producer, so it is only necessary for me to look at the closest preceding-sibling as opposed to all of them.

The code that I am attempted to use to solve this issue is the following:

<xsl:template match="PRODUCER">
    <fo:block>
        <xsl:if test="not(../preceding-sibling::PRODUCER/@value = self/@value>
            <xsl:value-of select="@value"/>
        </xsl:if>
    </fo:block>
</xsl:template>

I don't know if the syntax is incorrect, or whether this a good way to go about doing this, but any and all input would be greatly appreciated. If there is any other information I can provide that would help clarify or if there are any issues with my question please let me know.

Thank you

1

There are 1 answers

2
Kevin Brown On BEST ANSWER

It could be done with grouping, it can also be done with preceding-sibling. I would note that you say "dozens" ... I would not worry about using grouping. I ran this sample with 2000 rows, execution is 2.3 seconds (in debugging mode in oXygen with Saxon), 0.1 seconds without debugging. Dozens would take a fraction of a second. I have extended your sample here, including a test for the first occurrence which has no preceding-sibling. You could also combine things a bit but I left this so you can see the different decisions:

Sample XML:

<root>
    <table>
        <row>
            <PRODUCER>1</PRODUCER>
            <PUBLICATION>A</PUBLICATION>
        </row>
        <row>
            <PRODUCER>1</PRODUCER>
            <PUBLICATION>B</PUBLICATION>
        </row>
        <row>
            <PRODUCER>1</PRODUCER>
            <PUBLICATION>C</PUBLICATION>
        </row>
        <row>
            <PRODUCER>2</PRODUCER>
            <PUBLICATION>B</PUBLICATION>
        </row>
        <row>
            <PRODUCER>2</PRODUCER>
            <PUBLICATION>C</PUBLICATION>
        </row>
        <row>
            <PRODUCER>3</PRODUCER>
            <PUBLICATION>A</PUBLICATION>
        </row>
        <row>
            <PRODUCER>4</PRODUCER>
            <PUBLICATION>B</PUBLICATION>
        </row>
    </table>
</root>

XSL with testing for preceding-sibling, put into a variable to work with and only selecting [1] which is the immediately preceding one:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format"
    version="1.0">
    <xsl:output indent="yes"/>
    <xsl:template match ="table">
        <fo:table>
            <fo:table-body  font-size="10pt"
                font-family="sans-serif"
                line-height="10pt"
                space-after.optimum="3pt">
                <xsl:for-each select="row">
                    <fo:table-row>
                        <fo:table-cell  width="2.125in"
                            height="0.2in">
                                <xsl:apply-templates select="PRODUCER"/>

                        </fo:table-cell>
                        <fo:table-cell  width="3.25in"
                            height="0.2in">
                            <xsl:apply-templates select="PUBLICATION"/>
                        </fo:table-cell>
                    </fo:table-row>
                </xsl:for-each>
            </fo:table-body>
        </fo:table>
    </xsl:template>

        <xsl:template match="PRODUCER">
    <fo:block>
        <!-- Get the previous row element to the one I am in -->
        <xsl:variable name="test" select="parent::row/preceding-sibling::row[1]"/>
        <xsl:choose>
            <!-- First test, do we have a row? -->
            <xsl:when test="$test">
                <!-- Yes we have a previous row -->
                <xsl:choose>
                    <!-- Next, is the previous row's PRODUCER text the same as ours? -->
                    <xsl:when test="$test/PRODUCER/text() = text()">
                        <!-- It is, output nothing -->
                        <fo:leader/>
                    </xsl:when>
                    <xsl:otherwise>
                        <!-- It is not, so output it -->
                        <xsl:value-of select="concat('Producer ',.)"/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:when>
            <!-- We are the first row (no previous one) so output it  -->
            <xsl:otherwise>
                <xsl:value-of select="concat('Producer ',.)"/>
            </xsl:otherwise>
        </xsl:choose>
    </fo:block>
</xsl:template>

    <xsl:template match="PUBLICATION">
        <fo:block>
            <xsl:value-of select="concat('Publication ',.)"/>
        </fo:block>
    </xsl:template>
</xsl:stylesheet>

Output:

<fo:table xmlns:fo="http://www.w3.org/1999/XSL/Format">
<fo:table-body font-size="10pt" font-family="sans-serif" line-height="10pt" space-after.optimum="3pt">
  <fo:table-row>
     <fo:table-cell width="2.125in" height="0.2in">
        <fo:block>Producer 1</fo:block>
     </fo:table-cell>
     <fo:table-cell width="3.25in" height="0.2in">
        <fo:block>Publication A</fo:block>
     </fo:table-cell>
  </fo:table-row>
  <fo:table-row>
     <fo:table-cell width="2.125in" height="0.2in">
        <fo:block>
           <fo:leader/>
        </fo:block>
     </fo:table-cell>
     <fo:table-cell width="3.25in" height="0.2in">
        <fo:block>Publication B</fo:block>
     </fo:table-cell>
  </fo:table-row>
  <fo:table-row>
     <fo:table-cell width="2.125in" height="0.2in">
        <fo:block>
           <fo:leader/>
        </fo:block>
     </fo:table-cell>
     <fo:table-cell width="3.25in" height="0.2in">
        <fo:block>Publication C</fo:block>
     </fo:table-cell>
  </fo:table-row>
  <fo:table-row>
     <fo:table-cell width="2.125in" height="0.2in">
        <fo:block>Producer 2</fo:block>
     </fo:table-cell>
     <fo:table-cell width="3.25in" height="0.2in">
        <fo:block>Publication B</fo:block>
     </fo:table-cell>
  </fo:table-row>
  <fo:table-row>
     <fo:table-cell width="2.125in" height="0.2in">
        <fo:block>
           <fo:leader/>
        </fo:block>
     </fo:table-cell>
     <fo:table-cell width="3.25in" height="0.2in">
        <fo:block>Publication C</fo:block>
     </fo:table-cell>
  </fo:table-row>
  <fo:table-row>
     <fo:table-cell width="2.125in" height="0.2in">
        <fo:block>Producer 3</fo:block>
     </fo:table-cell>
     <fo:table-cell width="3.25in" height="0.2in">
        <fo:block>Publication A</fo:block>
     </fo:table-cell>
  </fo:table-row>
  <fo:table-row>
     <fo:table-cell width="2.125in" height="0.2in">
        <fo:block>Producer 4</fo:block>
     </fo:table-cell>
     <fo:table-cell width="3.25in" height="0.2in">
        <fo:block>Publication B</fo:block>
     </fo:table-cell>
  </fo:table-row>
</fo:table-body>
</fo:table>