Cesar Daniel
Cesar Daniel

Reputation: 23

How to use XSL to get first child after sort

I have an XML with that estruture:

<?xml version="1.0" encoding="utf-8" ?>
    <products>
      <product>
        <code>XXX</code>
        <name>XXXXXX</name>
        <variants>
          <variant>
            <code>XXXY</code>
            <stock>5</stock>
          </variant>
          <variant>
            <code>XXXZ</code>
            <stock>8</stock>
          </variant>
          <variant>
            <code>XXXW</code>
            <stock>7</stock>
          </variant>
        </variants>
      </product>
      <product>
        <code>ZZZ</code>
        <name>ZZZZZZ</name>
        <variants>
          <variant>
            <code>ZZZY</code>
            <stock>5</stock>
          </variant>
          <variant>
            <code>ZZZX</code>
            <stock>8</stock>
          </variant>
          <variant>
            <code>ZZZW</code>
            <stock>7</stock>
          </variant>
        </variants>
      </product>
    </products>

I need to sort by stock and get the first "variant". The output is something like that:

<variant>
  <code>XXXZ</code>
  <stock>8</stock>
</variant>

<variant>
  <code>ZZZX</code>
  <stock>8</stock>
</variant>

Is it possible? How can I solve this?

Upvotes: 1

Views: 1521

Answers (2)

Fabricio Koch
Fabricio Koch

Reputation: 1435

The code bellow should do the trick:

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

<xsl:template match="/">
  <xsl:for-each select="products/product">   
    <xsl:for-each select="variants/variant">
      <xsl:sort order="descending" select="stock" data-type="number"/>
      <xsl:if test="position() = 1">
              <xsl:apply-templates select="."/>
      </xsl:if>
    </xsl:for-each>
  </xsl:for-each>
</xsl:template>

<xsl:template match="variant">
    <xsl:apply-templates select="code"/>  
    <xsl:apply-templates select="stock"/>
</xsl:template>

</xsl:stylesheet>

What we're doing here:

  • Getting a product from your list of products;
  • For this product, we get and sort the list of variants;
  • In this list of variants, we get the first element;
  • we pass this first variant (select=".") to your external template;
  • then, you can use your variant in your template as you wish;

Upvotes: 1

Martin Honnen
Martin Honnen

Reputation: 167716

You can use xsl:perform-sort to sort the variant elements and then output or select the first in descending sort order:

  <xsl:template match="variants">
      <xsl:variable name="sorted-variants" as="element(variant)*">
          <xsl:perform-sort select="variant">
              <xsl:sort select="xs:integer(stock)" order="descending"/>
          </xsl:perform-sort>
      </xsl:variable>
      <xsl:copy-of select="$sorted-variants[1]"/>
  </xsl:template>

https://xsltfiddle.liberty-development.net/3NzcBsU

The same is possible using apply-templates if the other templates (e.g. identity transformation) copy the processed variants:

  <xsl:template match="variants">
      <xsl:variable name="sorted-variants" as="element(variant)*">
          <xsl:apply-templates select="variant">
              <xsl:sort select="xs:integer(stock)" order="descending"/>
          </xsl:apply-templates>
      </xsl:variable>
      <xsl:copy-of select="$sorted-variants[1]"/>
  </xsl:template>

https://xsltfiddle.liberty-development.net/3NzcBsU/1

With for-each you can directly nest an if test="position() = 1":

  <xsl:template match="variants">
      <xsl:for-each select="variant">
          <xsl:sort select="xs:integer(stock)" order="descending"/>
          <xsl:if test="position() = 1">
              <xsl:copy-of select="."/>
          </xsl:if>
      </xsl:for-each>
  </xsl:template>

https://xsltfiddle.liberty-development.net/3NzcBsU/2

Upvotes: 1

Related Questions