tags:

views:

156

answers:

4

In this the catalog.xml file. I have two books who have the same inventory (i.e. 20). I want to write an XSL file that will display the highest number of copies of a book in a catalog. If there are two or more books of the same inventory then they have to be displayed.

<catalog>
  <Book>
    <sku>12345</sku>
    <title>Beauty Secrets</title>
    <condition>New</condition>
    <current_inventory>20</current_inventory>
    <price>99.99</price>
  </Book>
  <Book>
    <sku>54321</sku>
    <title>Picturescapes</title>
    <current_inventory>20</current_inventory>
    <condition>New</condition>
    <price>50.00</price>
  </Book> 
  <Book>
    <sku>33333</sku>
    <title>Tourist Perspectives</title>
    <condition>New</condition>
    <current_inventory>0</current_inventory>
    <price>75.00</price>
  </Book>
  <Book>
    <sku>10001</sku>
    <title>Fire in the Sky</title>
    <condition>Used</condition>
    <current_inventory>0</current_inventory>
    <price>10.00</price>
  </Book>
</catalog>

Below is my catalog3.xsl file which is able to display only one out of the two books:

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

  <xsl:template match="/">
    <html>
      <body>
        <h2>Titles of Books for which Most Copies are Available</h2>
        <table border="2">
          <tr bgcolor="#9acd32">
            <th>Title</th>
            <th>No of Copies</th>
          </tr>
          <xsl:apply-templates/>
        </table>
      </body>
    </html>
  </xsl:template>

  <xsl:template match="catalog">
    <xsl:for-each select="Book">
      <xsl:sort select="current_inventory" data-type="number" order="descending"/>
      <tr>
        <xsl:if test="position()= 1">
          <p><xsl:value-of select="$max = "/></p>
          <td><xsl:value-of select="title"/></td>
          <td><xsl:value-of select="current_inventory"/></td>
        </xsl:if>
      </tr>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

Could anybody correct me to achieve my goal of displaying all the copies having the same maximum inventory in the catalog. Thanks.

A: 

That sort of thing is not fun with XSL. To figure out "if there are two or more books of the same inventory" in the markup when the inventory count can be arbitrary will require a data structure in which you can store a count of encountered inventories. I'd suggest traversing the XML DOM prior to transformation and doing your count on it (much easier), then create a new Document of the nodes you want transformed and apply the XSL to it. That will be more straight forward and less messy than trying to achieve everything in the stylesheet.

Chris
What he is asking to do is really not a big deal in XSLT. His data structure **does** store a count of inventories in the `current_inventory` element, which makes this trivial.
Mads Hansen
This kind of task is one of the easiest for XSLT. It is a good advice to anyone to read more on a subject before issuing definitive statements.
Dimitre Novatchev
+1  A: 

The maximum current_inventory can be calculated in the following manner:

<xsl:variable name="max">
  <xsl:for-each select="/catalog/Book/current_inventory">
    <xsl:sort data-type="number" order="descending"/>
    <xsl:if test="position()=1"><xsl:value-of select="."/></xsl:if>
  </xsl:for-each>
</xsl:variable>

Adjusting the criteria for the xsl:if to compare the current_inventory of the current node in the for-each to the $max variable achieves the desired result.

You were evaluating position()=1 inside of the for-each, which will only be true for the first item in the sorted collection.

I set it to look for current_inventory that is equal to the $max:

<xsl:if test="current_inventory = $max">

Applying these changes to your stylesheet:

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

<!--Determine the maximum current_inventory -->
<xsl:variable name="max">
    <xsl:for-each select="/catalog/Book/current_inventory">
        <xsl:sort data-type="number" order="descending"/>
        <xsl:if test="position()=1"><xsl:value-of select="."/></xsl:if>
    </xsl:for-each>
</xsl:variable>

<xsl:template match="/">
    <html>
        <body>
            <h2>Titles of Books for which Most Copies are Available</h2>
            <table border="2">
                <tr bgcolor="#9acd32">
                    <th>Title</th>
                    <th>No of Copies</th>
                </tr>
                <xsl:apply-templates/>
            </table>
        </body>
    </html>
</xsl:template>
<xsl:template match="catalog">
    <xsl:for-each select="Book">
        <xsl:sort select="current_inventory" data-type="number" order="descending"/>

        <xsl:if test="current_inventory = $max">
                            <tr>
                <td>
                    <xsl:value-of select="title"/>
                </td>
                <td>
                    <xsl:value-of select="current_inventory"/>
                </td>
                            </tr>
        </xsl:if>

    </xsl:for-each>
</xsl:template>

Mads Hansen
You did the impossible ;-) ... one some correction: move the <tr> inside the <xsl:if>
Filburt
You are correct, I've corrected my answer to move the `<tr>` inside the `<xsl:if>`
Mads Hansen
See a solution in which you don't need to calculate and keep the maximum in a separate variable. Also, this way of finding the maximum may be more efficient than with sorting the whole node-set, because only the different-valued nodes are sorted. Enjoy. :)
Dimitre Novatchev
+2  A: 

Here is a solution using Muenchian grouping:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:key name="kBookByNums"
    match="Book" use="number(current_inventory)"/>

 <xsl:template match="/*">
   <xsl:for-each select=
    "Book
      [generate-id()
      =
       generate-id(key('kBookByNums',
                        number(current_inventory)
                       )[1]
                       )
       ]
    ">
     <xsl:sort select="current_inventory"
      data-type="number" order="descending"/>

      <xsl:if test="position()=1">
        <xsl:copy-of select=
          "key('kBookByNums',
                number(current_inventory)
               )"/>
      </xsl:if>
   </xsl:for-each>
 </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the provided XML document, the correct result is produced:

<Book>
<sku>12345</sku>
<title>Beauty Secrets</title>
<condition>New</condition>
<current_inventory>20</current_inventory>
<price>99.99</price>
</Book>
<Book>
<sku>54321</sku>
<title>Picturescapes</title>
<current_inventory>20</current_inventory>
<condition>New</condition>
<price>50.00</price>
</Book>

Update:

It may seem that this xsl-key's solution "plays its 'efficiency card' for large input documents only (i.e. thousands of books). " as someone put it. In fact, such a statement is quite imprecise.

The improvement in eficiency can be large even when there are not so many items, if the number of different values is very small, compared to the total number of values. So, even for fairly small sets of items (one-two hundred), there is significant effect if the number of different values is not more than 10-20 -- something that happens in a lot of real-world cases.

What matters is the small ratio of the number of different values to the total size.

Dimitre Novatchev
Nice solution. Unfortunately I can't vote on it. I voted up, and took it back again, now the system won't let me vote unless you edit it - just make a tiny change, so that voting is enabled again. Thank you.
Tomalak
@Tomalak, I'm glad you liked it. I used this update to clarify the applicability of the solution and your own statement :)
Dimitre Novatchev
Okay, maybe I should have said "hundreds" instead of "thousands". What I meant is that a processing time of, say, 50ms for a no-keys solution isn't really distinguishable (for a human) from 25ms that the keys solution might have, even tough it's nominally a 100% improvement. As always one must make own measurements, and there is no denying that a key based solution is the more efficient approach. ;)
Tomalak
+1  A: 

This XSLT 1.0 stylesheet

<xsl:stylesheet 
  version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
>
  <xsl:template match="catalog">
    <!-- find the maximum <current_inventory> of all books -->
    <xsl:variable name="MaxInventory">
      <xsl:for-each select="Book">
        <xsl:sort select="current_inventory" data-type="number" order="descending" />
        <xsl:if test="position() = 1">
          <xsl:value-of select="current_inventory" />
        </xsl:if>
      </xsl:for-each>
    </xsl:variable>

    <!-- output all matching <Book>s, sorted by title -->
    <table>
      <xsl:apply-templates select="Book[current_inventory = $MaxInventory]">
        <xsl:sort select="title" data-type="text" order="ascending" />
      </xsl:apply-templates>
    </table>
  </xsl:template>

  <xsl:template match="Book">
    <tr>
      <td><xsl:value-of select="title" /></td>
      <td><xsl:value-of select="current_inventory" /></td>
    </tr>
  </xsl:template>

</xsl:stylesheet>

produces

<table>
  <tr>
    <td>Beauty Secrets</td>
    <td>20</td>
  </tr>
  <tr>
    <td>Picturescapes</td>
    <td>20</td>
  </tr>
</table>

Note that a more efficient solution exists, it involves XSL keys (Dimitre Novatchev shows it). This one is straight-forward and easy to follow, the XSL key solution is advanced and plays its "efficiency card" for large input documents only (i.e. thousands of books). With small input documents, the performance difference will be negligible.

Tomalak
+1 Nice improvement.
Mads Hansen
@Tomalak, the statement that the xsl-key -s solution "plays its "efficiency card" for large input documents only (i.e. thousands of books). " is quite imprecise. The improvement in eficiency can be large even when there are not so many items, if the number of different values is very small, compared to the total number of values. So, even for fairly small sets of items ( one-two hundred), there is significant effect if the number of different values is not more than 10-20 -- something that happens in a lot of realworld cases.
Dimitre Novatchev