0

I have a very similar problem to this: Need XSLT transform to remove duplicate elements - sorted by an attribute. I took the solution from the best answer and slightly modified it. I have to count the statuses, but taking into account only the latest results.

My template looks like this:

<xsl:template match='Results/Result' mode='countstatus'>
    <xsl:param name='status' select='"Pass"'/>
    <xsl:for-each select="key('sn-key', SerialNumber)">
        <xsl:sort select='./Date' order='descending'/>
        <xsl:if test='((position() = 1) and (./Status=$status))'>
            <xsl:copy-of select="."/>
        </xsl:if>
    </xsl:for-each>
</xsl:template>

I don't want to display all the matched results but only the number of items returned after this template is applied.

My key definition is <xsl:key name='sn-key' match='Results/Result' use='SerialNumber'/> and I call this template with <xsl:apply-templates select='Results/Result[generate-id() = generate-id(key("sn-key", SerialNumber)[1])]' mode='countstatus'/>.

EDIT

With fresh mind I see that my question is a bit unclear. Here are more details.

My input looks like this:

<Results>
    <Result ID="0">
        <SerialNumber>3333</SerialNumber>
        <Status>Fail</Status>
        <Date>21</Date>
    </Result>
    <Result ID="1">
        <SerialNumber>1111</SerialNumber>
        <Status>Fail</Status>
        <Date>34</Date>
    </Result>
    <Result ID="2">
        <SerialNumber>1111</SerialNumber>
        <Status>Pass</Status>
        <Date>67</Date>
    </Result>
    <Result ID="3">
        <SerialNumber>2222</SerialNumber>
        <Status>Fail</Status>
        <Date>40</Date>
    </Result>
    <Result ID="4">
        <SerialNumber>1111</SerialNumber>
        <Status>Fail</Status>
        <Date>55</Date>
    </Result>
    <Result ID="5">
        <SerialNumber>1111</SerialNumber>
        <Status>Fail</Status>
        <Date>88</Date>
    </Result>
    <Result ID="6">
        <SerialNumber>2222</SerialNumber>
        <Status>Fail</Status>
        <Date>22</Date>
    </Result>
    <Result ID="7">
        <SerialNumber>1111</SerialNumber>
        <Status>Fail</Status>
        <Date>86</Date>
    </Result>
    <Result ID="8">
        <SerialNumber>3333</SerialNumber>
        <Status>Pass</Status>
        <Date>99</Date>
    </Result>
</Results>

I would like to count e.g. how many times the Fail status occurs, but considering only the latest results for SerialNumber. The template I showed earlier displays all the latest results for SerialNumber (but for status Pass). I would like to display just the number. In example when looking for the number of fails I would like to display 2 instead of [1111 Fail 88, 2222 Fail 40].

My transformation look like this now (but I need something else):

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:key name='sn-key' match='Results/Result' use='SerialNumber' />

<xsl:template match="/">
    <xsl:apply-templates select="Results/Result[generate-id()=generate-id(key('sn-key', SerialNumber)[1])]"/>
</xsl:template>

<xsl:template match="Results/Result">
    <xsl:param name='status' select='"Fail"'/>
    <xsl:for-each select="key('sn-key', SerialNumber)">
        <xsl:sort select="./Date" order="descending"/>
        <xsl:if test='((position() = 1) and (./Status=$status))'>
            <xsl:value-of select="."/><br />
        </xsl:if>
    </xsl:for-each>
</xsl:template>

</xsl:stylesheet>
bienieck
  • 33
  • 1
  • 8

1 Answers1

0

--- edited in response to clarification ---

I would like to count e.g. how many times the Fail status occurs, but considering only the latest results for SerialNumber.

I believe that translates to:

  • group the Results by SerialNumber;
  • count the groups whose most recent Result has Status of "Fail"

The exact method depends on whether the Results are listed in chronological order in the input XML.

If the answer is yes, you can simply modify the Muenchian method to select the last (instead of the usual first) Result in each group, add another predicate to keep only those whose Status is "Fail" and count this set. This is a one-liner:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:key name="result-by-sn" match="Result" use="SerialNumber" />

<xsl:template match="/Results">
    <output>
        <xsl:value-of select="count(Result[count(. | key('result-by-sn', SerialNumber)[last()]) = 1][Status='Fail'])"/>
    </output>
</xsl:template>

</xsl:stylesheet>

If the answer is no, then you have no choice but to actually perform the grouping, sorting and filtering into a variable, then count the result:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:key name="result-by-sn" match="Result" use="SerialNumber" />

<xsl:template match="/Results">
    <xsl:variable name="temp">
        <xsl:for-each select="Result[count(. | key('result-by-sn', SerialNumber)[1]) = 1]">
            <xsl:for-each select="key('result-by-sn', SerialNumber)">
                <xsl:sort select="Date" order="descending"/>
                <xsl:if test="position()=1 and Status='Fail'">x</xsl:if>
            </xsl:for-each>
        </xsl:for-each>
    </xsl:variable>
    <output>
        <xsl:value-of select="string-length($temp)"/>
    </output>
</xsl:template>

</xsl:stylesheet>
michael.hor257k
  • 113,275
  • 6
  • 33
  • 51
  • What if the group is not sorted (in my case I need to sort by Date first)? Can you explain what is the input parameter for `count(. | key('record_by_category', category)`? – bienieck Jul 02 '21 at 07:39
  • I have revised my post based on your clarifications. Hopefully that answers any questions you might have. – michael.hor257k Jul 02 '21 at 10:00
  • And is it possible to store in the global variable sorted Results and then use the first method? I tried with exslt:node-set() function but it looks like I have some context issue. – bienieck Jul 02 '21 at 10:14
  • It is possible, but it will be more complex due to the node-set vs. RTF issue. I suggest you post a separate question if you want to go that way and are unable to work it out. – michael.hor257k Jul 02 '21 at 10:21
  • Can I also ask about short explanation of `` line? – bienieck Jul 02 '21 at 10:44
  • 1
    That's Muenchian grouping. I am using an alternative expression to your `Result[generate-id() = generate-id(key("sn-key", SerialNumber)[1])]`. You will find the explanation in the original article: http://www.jenitennison.com/xslt/grouping/muenchian.html – michael.hor257k Jul 02 '21 at 10:49
  • How hard it would be to do a similar think with XLS-FO? I'm trying this and I'm getting `Unknown formatting object "{}output" encountered`. – bienieck Jul 28 '21 at 13:23
  • @bienieck I don't know how to answer that. I suggest you post a new question with this new problem. – michael.hor257k Jul 28 '21 at 14:28
  • Ok. Here it is: https://stackoverflow.com/questions/68581918/how-to-count-elements-returned-from-applied-template-using-xsl-fo-and-apache-fo – bienieck Jul 29 '21 at 19:33