0

I have developed a simple web page which is generated using a basic XML and XSLT (1.0) file combination approach (without JavaScript).

With the help of a <xsl:for-each> loop, my XSLT file constructs a (two column) HTML table containing a new table row (<tr><td></td><td></td></tr>) for each unique <bookstore/book/promotions/promotionDate> XML element value, but importantly, the row will only be created if the <promotionDate> value passes a conditional <xsl:if test> statement, which is nested in the loop.

The Conditional Statement

Firstly, I setup a variable within my <xsl:for-each> loop (<xsl:variable name ="our-date" select="promotionDate"></xsl:variable>), using the <promotionDate> value (e.g. 20160915) as my select parameter.

I then cross-reference my (promotion date) variable against a complimentary numerical representation of today's date (<xsl:if test="$our-date &gt;= 20160212">).

If the $our-date variable is greater than or equal to today's date we run the rest of the loop, produce a new row and populate the cells.

Great! Well nearly......

As you can see in my XML file and Browser Output (below), for the 'Harry Potter and the Philosopher's Stone' <book> attribute, I have two separate <promotions> attributes, where the <promotionDate> values are both greater than today's date. Therefore, when the <xsl:for-each> loop logic runs, the row for this particular <book> attribute is outputted twice. Not good!

XSLT:

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

<xsl:output indent="yes"/>

<xsl:key name="prom-by-id" match="bookstore/promotion" use="promotionID"/>
<xsl:key name="location-by-id" match="bookstore/locations" use="locationID"/>

  <xsl:template match ="/">
    <table class="upcomingpromotions" border="1" style="width: 680px">
    <thead>
        <tr>
          <th width='45%' class='noselect'>Title</th>
          <th width='15%' class='noselect'>Location(s)</th>
        </tr>
    </thead>
    <xsl:for-each select="bookstore/book/promotions[not(promotionDate = preceding-sibling::promotions/promotionDate)]">
            <xsl:variable name ="our-date" select="promotionDate"></xsl:variable>
            <xsl:if test="$our-date &gt;= 20160212">
              <tr>
                <td>
                  <xsl:value-of select="../title"/><br/>Date: <xsl:value-of select="$our-date"/>*
                </td>
                <td>
                  <xsl:for-each select="key('location-by-id', key('prom-by-id', ../promotions/promotionNum)/promotionLocation)">
                    <xsl:value-of select="location"/><br/>
                   </xsl:for-each>
                </td>
              </tr>
            </xsl:if>
</xsl:for-each>
</table>
<br/>
<p>*Just for reference, proving XPath <em>'bookstore/book/promotions/promotionDate'</em> passes the logic.</p>

  </xsl:template>

</xsl:stylesheet>

XML:

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="books.xsl" ?>
<bookstore>
  <book>
    <title>Harry Potter and the Philosopher's Stone</title>
    <author>J K. Rowling</author>
    <year>1997</year>
    <price>3.99</price>
    <publisher>Bloomsbury (UK)</publisher>
    <synopsis>
         Harry Potter and the Philosopher's Stone is the first novel in the Harry Potter series and J. K. Rowling's debut novel.

         The plot follows Harry Potter, a young wizard who discovers his magical heritage as he makes close friends and a few enemies in his first year at the Hogwarts School of Witchcraft and Wizardry.

         With the help of his friends, Harry faces an attempted comeback by the dark wizard Lord Voldemort, who killed Harry's parents, but failed to kill Harry when he was just a year old.
   </synopsis>
    <promotions>
      <promotionDate>20160915</promotionDate>
      <promotionCode>1</promotionCode>
      <promotionNum>1</promotionNum>
      <promotionNum>3</promotionNum>
   </promotions>
    <promotions>
      <promotionDate>20160917</promotionDate>
      <promotionCode>2</promotionCode>
      <promotionNum>7</promotionNum>
   </promotions>
 </book>
 <book>
    <title>The Girl with the Dragon Tattoo</title>
    <author>Stieg Larsson</author>
    <year>2005</year>
    <price>5.99</price>
    <publisher>Norstedts Förlag (SWE)</publisher>
    <synopsis>
         In Stockholm, Sweden, journalist Mikael Blomkvist, co-owner of Millennium magazine, has lost a libel case brought against him by businessman Hans-Erik Wennerström. Lisbeth Salander, a brilliant but troubled investigator and hacker, compiles an extensive background check on Blomkvist for business magnate Henrik Vanger, who has a special task for him. 

         In exchange for the promise of damning information about Wennerström, Blomkvist agrees to investigate the disappearance and assumed murder of Henrik's grandniece, Harriet, 40 years ago. 

         After moving to the Vanger family's compound, Blomkvist uncovers a notebook containing a list of names and numbers that no one has been able to decipher.
    </synopsis>
    <promotions>
      <promotionDate>20160117</promotionDate>
      <promotionCode>3</promotionCode>
      <promotionNum>1</promotionNum>
      <promotionNum>4</promotionNum>
      <promotionNum>6</promotionNum>
      <promotionNum>4</promotionNum>
   </promotions>
  </book>
   <book>
    <title>Grandpa's Great Escape</title>
    <author>David Walliams</author>
    <year>2015</year>
    <price>5.00</price>
    <publisher>Harper Collins Children's Books</publisher>
    <synopsis>
         An exquisite portrait of the bond between a small boy and his beloved Grandpa – this book takes readers on an incredible journey with Spitfires over London and Great Escapes through the city in a high octane adventure full of comedy and heart. Illustrated by the award-winning Tony Ross.
    </synopsis>
    <promotions>
      <promotionDate>20160117</promotionDate>
      <promotionCode>4</promotionCode>
      <promotionNum>1</promotionNum>
      <promotionNum>4</promotionNum>
      <promotionNum>6</promotionNum>
      <promotionNum>4</promotionNum>
   </promotions>
 </book>
<book>
    <title>A Brief History of Seven Killings</title>
    <author>Marlon James</author>
    <year>2015</year>
    <price>6.29</price>
    <publisher>Oneworld Publications</publisher>
    <synopsis>
         From the acclaimed author of The Book of Night Women comes a dazzling display of masterful storytelling exploring this near-mythic event. Spanning three decades and crossing continents, A Brief History of Seven Killings chronicles the lives of a host of unforgettable characters – slum kids, one-night stands, drug lords, girlfriends, gunmen, journalists, and even the CIA. 

         Gripping and inventive, ambitious and mesmerising, A Brief History of Seven Killings is one of the most remarkable and extraordinary novels of the twenty-first century.
    </synopsis>
    <promotions>
      <promotionDate>20160617</promotionDate>
      <promotionCode>5</promotionCode>
      <promotionNum>1</promotionNum>
      <promotionNum>4</promotionNum>
      <promotionNum>6</promotionNum>
      <promotionNum>4</promotionNum>
   </promotions>
  </book>
  <book>
    <title>Leading</title>
    <author>Sir Alex Ferguson</author>
    <year>2015</year>
    <price>7.99</price>
    <publisher>Hodder and Stoughton</publisher>
     <synopsis>
         In this revelatory new book, Sir Alex Ferguson dissects his 38 record-breaking years of management - first in Scotland and then, of course, at Manchester United - to reveal the key tools he used to deliver sustained success on and off the field. From tactics to teamwork, from hiring to firing, from dealing with the boardroom to responding to failure, LEADING investigates the pivotal leadership decisions of an astonishing career.
    </synopsis>
    <promotions>
      <promotionDate>20160117</promotionDate>
      <promotionCode>6</promotionCode>
      <promotionNum>1</promotionNum>
      <promotionNum>4</promotionNum>
      <promotionNum>6</promotionNum>
      <promotionNum>4</promotionNum>
   </promotions>
  </book>
  <book>
    <title>The Girl on the Train</title>
    <author>Paula Hawkins</author>
    <year>2015</year>
    <price>6.99</price>
    <publisher>Doubleday</publisher>
    <synopsis>
          Rachel catches the same commuter train every morning. She knows it will wait at the same signal each time, overlooking a row of back gardens. She’s even started to feel like she knows the people who live in one of the houses. ‘Jess and Jason’, she calls them. Their life – as she sees it – is perfect. If only Rachel could be that happy.

        And then she sees something shocking. It’s only a minute until the train moves on, but it’s enough. 

        Now everything’s changed. Now Rachel has a chance to become a part of the lives she’s only watched from afar.

        Now they’ll see; she’s much more than just the girl on the train…
    </synopsis>
    <promotions>
      <promotionDate>20160417</promotionDate>
      <promotionCode>7</promotionCode>
      <promotionNum>1</promotionNum>
      <promotionNum>4</promotionNum>
      <promotionNum>6</promotionNum>
      <promotionNum>4</promotionNum>
   </promotions>
  </book>

  <promotion>
    <promotionID>1</promotionID>
    <percentageOff>10</percentageOff>
    <promotionalMerchandise>No</promotionalMerchandise>
    <promotionStartDate>2015-10-14T00:00:00</promotionStartDate>
    <promotionEndDate>2015-10-19T00:00:00</promotionEndDate>
    <promotionLocation>1</promotionLocation>
  </promotion>
  <promotion>
    <promotionID>2</promotionID>
    <percentageOff>15</percentageOff>
    <promotionalMerchandise>No</promotionalMerchandise>
    <promotionStartDate>2015-10-11T00:00:00</promotionStartDate>
    <promotionEndDate>2015-10-16T00:00:00</promotionEndDate>
    <promotionLocation>2</promotionLocation>
  </promotion>
  <promotion>
    <promotionID>3</promotionID>
    <percentageOff>30</percentageOff>
    <promotionalMerchandise>Yes</promotionalMerchandise>
    <promotionStartDate>2015-09-02T00:00:00</promotionStartDate>
    <promotionEndDate>2015-09-07T00:00:00</promotionEndDate>
    <promotionLocation>2</promotionLocation>
  </promotion>
  <promotion>
    <promotionID>4</promotionID>
    <percentageOff>5</percentageOff>
    <promotionalMerchandise>Yes</promotionalMerchandise>
    <promotionStartDate>2015-11-22T00:00:00</promotionStartDate>
    <promotionEndDate>2015-11-27T00:00:00</promotionEndDate>
    <promotionLocation>3</promotionLocation>
 </promotion>
  <promotion>
    <promotionID>5</promotionID>
    <percentageOff>50</percentageOff>
    <promotionalMerchandise>No</promotionalMerchandise>
    <promotionStartDate>2015-08-13T00:00:00</promotionStartDate>
    <promotionEndDate>2015-08-18T00:00:00</promotionEndDate>
    <promotionLocation>1</promotionLocation>
  </promotion>
  <promotion>
     <promotionID>6</promotionID>
     <percentageOff>80</percentageOff>
    <promotionalMerchandise>No</promotionalMerchandise>
    <promotionStartDate>2015-07-01T00:00:00</promotionStartDate>
    <promotionEndDate>2015-07-05T00:00:00</promotionEndDate>
    <promotionLocation>1</promotionLocation>
 </promotion>
    <promotion>
    <promotionID>7</promotionID>
    <percentageOff>80</percentageOff>
    <promotionalMerchandise>No</promotionalMerchandise>
    <promotionStartDate>2015-07-01T00:00:00</promotionStartDate>
    <promotionEndDate>2015-07-05T00:00:00</promotionEndDate>
    <promotionLocation>4</promotionLocation>
  </promotion>

  <locations>
     <locationID>1</locationID>
    <location>York</location>
  </locations>
     <locations>
      <locationID>2</locationID>
    <location>London</location>
  </locations>
    <locations>
      <locationID>3</locationID>
    <location>Glasgow</location>
  </locations>
      <locations>
      <locationID>4</locationID>
    <location>Cardiff</location>
  </locations>
</bookstore>

Browser Output:

enter image description here

The Question:

So, when you have a situation like this, where an attribute (<book>) can contain many elements of the same name (<promotions/promotionDate>), and the values of these elements could theoretically all pass a conditional <xsl:if> statement nested in a <xsl:for-each> loop, how can you manage the data to ensure that only one row is ever produced per <book> attribute?

enter image description here

Joel
  • 247
  • 5
  • 15

1 Answers1

1

Just run your code over the book elements then:

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

<xsl:param name="date" select="20160212"/>

<xsl:output indent="yes"/>

<xsl:key name="prom-by-date" match="bookstore/book/promotions" use="promotionDate"/>

<xsl:key name="prom-by-id" match="bookstore/promotion" use="promotionID"/>
<xsl:key name="location-by-id" match="bookstore/locations" use="locationID"/>

  <xsl:template match ="/">
    <table class="upcomingpromotions" border="1" style="width: 680px">
    <thead>
        <tr>
          <th width='45%' class='noselect'>Title</th>
          <th width='15%' class='noselect'>Location(s)</th>
        </tr>
    </thead>
    <xsl:for-each select="bookstore/book[promotions[generate-id() = generate-id(key('prom-by-date', promotionDate)[1])][promotionDate >= $date]]">

              <tr>
                <td>
                  <xsl:value-of select="title"/><br/>
                </td>
                <td>
                  <xsl:for-each select="key('location-by-id', key('prom-by-id', promotions/promotionNum)/promotionLocation)">
                    <xsl:value-of select="location"/><br/>
                   </xsl:for-each>
                </td>
              </tr>
</xsl:for-each>
</table>
<br/>
<p>*Just for reference, proving XPath <em>'bookstore/book/promotions/promotionDate'</em> passes the logic.</p>

  </xsl:template>

</xsl:stylesheet>
Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
  • Using the Muenchian Grouping approach you've implemented in your accepted answer (i.e. line 22 - `generate-id()`), how could this same structure/method be applied to a key nested within another key (such as the example on line 29 of the accepted answer)? For example, if you wanted to target `` in this instance as the unique value/id (`[1])`) to monitor? I'd just be really interested to know how this would be constructed more than anything, as most examples of `generate-id()` I have seen applied to keys usually deal with just one key at a time? – Joel Feb 18 '16 at 10:49
  • 1
    Well, if you want, you can identify unique `promotionLocation`s first, but as you pass them on to the other key function call, it does not really make a difference, as e.g. `key('location-by-id', (1, 3, 1))` will yield the same result as `key('location-by-id', (1, 3))`, you get a node-set of all referenced items, if the second argument contains a duplicate value you don't get any more items, respectively you don't get less items by first eliminating duplicates. – Martin Honnen Feb 18 '16 at 14:33
  • That's useful to know. Regarding the XSL programmatic construction of this `` approach, would line 29 (using two reference keys) from the accepted answer begin to look something like this: `
    `?
    – Joel Feb 25 '16 at 12:14
  • @Joel, I think it is better you ask a new question where you state what you want to achieve, what you have tried, which result you want and which result you get if your current attempts do not achieve what you want. It is difficult exchanging code in comments and I have lost track of the question anyway. As for your latest ` – Martin Honnen Feb 25 '16 at 12:29