0

I need to filter a list of items and only keep items where a date element is more than one day greater than another date element. And then I also need to know how many filtered items are left and terminate the whole thing if there are none. I've simplified my data but it's more or less this:

<data>
    <person>
        <name>Tyler</name>
    </person>
    <items>
        <item>
            <title>A</title>
            <start_date>10/31/2021</start_date>
            <end_date>11/01/2021</end_date>
        </item>
        <item>
            <title>B</title>
            <start_date>08/05/2021</start_date>
            <end_date>08/10/2021</end_date>
        </item>
        <item>
            <title>C</title>
            <start_date>09/04/2021</start_date>
            <end_date>09/05/2021</end_date>
        </item>
    </items>
</data>

So in that example only B would be kept and the message would send. But if B was instead

        <item>
            <title>B</title>
            <start_date>08/05/2021</start_date>
            <end_date>08/06/2021</end_date>
        </item>

The message wouldn't send.

So far I've worked out a way to transform the text dates using the method suggested here. And that works for filtering the list, but I have no idea how to figure out if the resulting list has any elements in it and then how to use that in the terminate statement. Any help would be greatly appreciated and thank you in advance! Here's where I am on the xsl:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:template match="/data">

        <xsl:if test="???? Whatever can figure out the number of elements left ????">
            <xsl:message terminate="yes">There are no items left</xsl:message>
        </xsl:if>
        <html>
            <head>
                <title></title>
            </head>
            <body>
                <p>
                    <xsl:text>Name: </xsl:text>
                    <xsl:value-of select="person/name"/>
                </p>
                <table>
                    <thead>
                        <tr><th>Title</th></tr>
                    </thead>

                    <xsl:for-each select="items/item">

                        <xsl:variable name="JDN_start_date">
                            <xsl:call-template name="JDN">
                                <xsl:with-param name="date" select="start_date" />
                            </xsl:call-template>
                        </xsl:variable>

                        <xsl:variable name="JDN_end_date">
                            <xsl:call-template name="JDN">
                                <xsl:with-param name="date" select="end_date" />
                            </xsl:call-template>
                        </xsl:variable>

                        <xsl:if test="($JDN_end_date - $JDN_start_date) &gt; 1">
                            <tr>
                                <td><xsl:value-of select="title"/></td>
                            </tr>
                        </xsl:if>
                    </xsl:for-each>

                </table>
            </body>
        </html>
    </xsl:template>

    <xsl:template name="JDN">        <!-- Date string to Julian day number -->
        <xsl:param name="date"/>
        <xsl:param name="year" select="substring($date, 7, 4)"/>
        <xsl:param name="month" select="substring($date, 1, 2)"/>
        <xsl:param name="day" select="substring($date, 4, 2)"/>
        <xsl:param name="a" select="floor((14 - $month) div 12)"/>
        <xsl:param name="y" select="$year + 4800 - $a"/>
        <xsl:param name="m" select="$month + 12*$a - 3"/>
        <xsl:value-of select="$day + floor((153*$m + 2) div 5) + 365*$y + floor($y div 4) - floor($y div 100) + floor($y div 400) - 32045" />
    </xsl:template>

</xsl:stylesheet>
Liam
  • 33
  • 5
  • 1
    You need to do this in two passes: first, calculate the difference between each pair of dates; then process the result and count the items where the difference is > 1. Which XSLT processor will you be using? Maybe some extension functions could be helpful here. – michael.hor257k Nov 12 '21 at 00:09
  • Thank you for the information. This is all happening inside a module for customizing emails built into in a third-party platform so I don't know much about what's under the hood, but using `` returns "Apache Software Foundation (Xalan XSLTC)". – Liam Nov 12 '21 at 01:25
  • I posted an almost pure XSLT 1.0 solution, using only the ubiquitous node-set() extension function. But you should know that Xalan XSLTC supports using Java as an extension. – michael.hor257k Nov 12 '21 at 02:06

1 Answers1

2

Consider the following example:

XSLT 1.0

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

<xsl:template match="/data">
    <!-- 1. calculate durations -->
    <xsl:variable name="items">
        <xsl:for-each select="items/item">
            <xsl:copy>
                <xsl:attribute name="duration">
                    <xsl:call-template name="date-difference">
                        <xsl:with-param name="date1" select="start_date" />
                        <xsl:with-param name="date2" select="end_date" />
                    </xsl:call-template>
                </xsl:attribute>
                <xsl:copy-of select="*"/>
            </xsl:copy>
        </xsl:for-each>
    </xsl:variable>
    <!-- 2. find items with duration > 1 -->
    <xsl:variable name="pass-items" select="exsl:node-set($items)/item[@duration > 1]" />
    <xsl:if test="not($pass-items)">
        <xsl:message terminate="yes">no items pass</xsl:message>
    </xsl:if>
    <!-- 3. output -->
    <output>
        <xsl:copy-of select="$pass-items"/>
    </output>
</xsl:template> 

<xsl:template name="date-difference">
    <xsl:param name="date1"/>
    <xsl:param name="date2"/>
    <xsl:param name="JDN1">
        <xsl:call-template name="JDN">
            <xsl:with-param name="date" select="$date1" />
        </xsl:call-template>
    </xsl:param>
    <xsl:param name="JDN2">
        <xsl:call-template name="JDN">
            <xsl:with-param name="date" select="$date2" />
        </xsl:call-template>
    </xsl:param>
    <xsl:value-of select="$JDN2 - $JDN1"/>
</xsl:template> 

<xsl:template name="JDN">
    <xsl:param name="date"/>
    <xsl:param name="year" select="substring($date, 7, 4)"/>
    <xsl:param name="month" select="substring($date, 1, 2)"/>
    <xsl:param name="day" select="substring($date, 4, 2)"/>
    <xsl:param name="a" select="floor((14 - $month) div 12)"/>
    <xsl:param name="y" select="$year + 4800 - $a"/>
    <xsl:param name="m" select="$month + 12*$a - 3"/>
    <xsl:value-of select="$day + floor((153*$m + 2) div 5) + 365*$y + floor($y div 4) - floor($y div 100) + floor($y div 400) - 32045" />
</xsl:template> 

</xsl:stylesheet>
michael.hor257k
  • 113,275
  • 6
  • 33
  • 51