2
votes

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
This is a grouping question. See: jenitennison.com/xslt/grouping/muenchian.html and many, many examples here on SO.michael.hor257k
This skill of grouping in Xslt 1.0 is a necessary one. The language is very capable of it but it isn't straight foward, nor is grouping anywhere to be found in the language. After several good efforts at it though you'll be an expert!kstubs
Thank you for your input. I have been reviewing the muenchian method. I just began working with this technology this week and have found the learning curve a bit steep. I'm hoping to be able to avoid asking any more questions after I fully understand this report I'm generating.speezy

1 Answers

2
votes

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>