1

I have an XML Structure, which looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<Customers>
    <Customer UD="XYZ">
        <Info>
                ...
            </Info>
            <Travel>
                <Trip Country="US" Year="2013" LengthOfStay="P13D"/>
                <Trip Country="IT" Year="2015" LengthOfStay="P9D"/>
                <Trip Country="JP" Year="2015" LengthOfStay="P5D"/>
            </Travel>
        </Customer>
        <Customer UD="ABC">
            ...
        </Customer>
</Customers>

I would like to create a complex select condition to return the customers that travelled more than X amount of times Per Year in the Last X Years.

Would it be possible? Many thanks!

  • 1
    **1.** There is only one customer in your XML. -- **2.** The condition "*more than X amount of times Per Year in the Last X Years*" is ambiguous: it could mean more than X times in **every one** of the last Y years, or more than X times per year **in average** in the last Y years. – michael.hor257k Feb 21 '16 at 21:30
  • Agreed, there is only one customer in the XML, however there would be more, under a root element. I would like to get Customers who travelled more than X times in Ever One of the Last Y Years. Hope it makes more sense now. Thank you for your help in advance. – user2135025 Feb 21 '16 at 22:37

2 Answers2

1

I don't think it's possible to select the qualifying customers directly, but you could use a recursive template to process the customers and output a result only if they pass the test.

Here's an example:

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:strip-space elements="*"/>

<xsl:key name="trip" match="Trip" use="concat(@Year, '|', ../../@UD)" />

<!-- identity transform -->
<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="Customer">
    <xsl:call-template name="test-years"/>
</xsl:template>

<xsl:template name="test-years">
    <xsl:param name="year" select="2016"/>
    <xsl:param name="years-to-test" select="3"/>
    <xsl:param name="min-trips" select="3"/>
    <xsl:variable name="n" select="count(key('trip', concat($year, '|', @UD)))" />
    <xsl:choose>
        <xsl:when test="$n &lt; $min-trips">
            <!-- exit with no result -->
        </xsl:when>
        <xsl:when test="$years-to-test > 1">
            <!-- recursive call -->
            <xsl:call-template name="test-years">
                <xsl:with-param name="year" select="$year - 1"/>
                <xsl:with-param name="years-to-test" select="$years-to-test - 1"/>
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <!-- test passed: output the result -->
            <xsl:copy-of select="."/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

</xsl:stylesheet>

Note: XSLT 1.0 has no concept of current year - you must either use an extension function (depending on what your specific processor supports), or pass the current date as a parameter to the stylesheet at runtime.

michael.hor257k
  • 113,275
  • 6
  • 33
  • 51
1

There's possibly a flaw in my logic somewhere, but try this XSLT

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

    <xsl:param name="firstYear" select="2012" />
    <xsl:param name="lastYear" select="2015" />
    <xsl:param name="times" select="2" />

    <xsl:variable name="Years" select="$lastYear - $firstYear + 1" />

    <xsl:key name="Trip" match="Trip" use="concat(@Year, '|', ../../@UD)" />

    <xsl:template match="Customers">
        <xsl:copy>
            <xsl:apply-templates select="Customer[count(.//Trip
                               [@Year >= $firstYear and @Year &lt;= $lastYear]
                               [generate-id() = generate-id(key('Trip', concat(@Year, '|', ../../@UD))[1])]
                               [count(key('Trip', concat(@Year, '|', ../../@UD))) >= $times]
                            ) = $Years]" />
        </xsl:copy>
    </xsl:template>

    <xsl:template match="@*|node()" name="identity">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

For each customer, this gets the number of distinct trips between a year range, (so for a three year range, for example, you would need three distinct years, otherwise it meant they didn't travel each year) and for each distinct year it counts the number of times they travel in the year to see it meets the requirement. If the total count of such trips equals the number of years required it can output the customer.

Tim C
  • 70,053
  • 14
  • 74
  • 93