XSLT:按2个值中的较低者排序

时间:2012-09-05 21:10:43

标签: xslt xpath xslt-1.0

我有一些格式如下的XML:

<products>
  <product>
    <name>Product 1</name>
    <price>
      <orig>15</orig>
      <offer>10</offer>
    </price>
  </product>
  <product>
    <name>Product 2</name>
    <price>
      <orig>13</orig>
      <offer>12</offer>
    </price>
  </product>
  <product>
    <name>Product 3</name>
    <price>
      <orig>11</orig>
    </price>
  </product>
</products>

我需要根据当前价格使用XSLT 1.0(按升序或降序)对产品进行排序。我的困难在于,我需要对两个可能的价格值<orig><offer> 中的较低者进行排序,如果它们都存在。

对于上面的例子,正确的顺序是:

  • 产品1(最低值= 10)
  • 产品3(最低值= 11)
  • 产品2(最低值= 12)

任何帮助都会非常感激,因为我似乎无法通过搜索找到类似的问题。

3 个答案:

答案 0 :(得分:6)

(答案已更新,包括对XSLT 1.0和2.0的想法)

<强>予。 XSLT 1.0:

请注意,XSLT 1.0没有内置等效于min()的内容;假设您的解析器支持EXSLT,您可以使用其math:min()函数来实现与下面的XSLT 2.0变体非常类似的解决方案。


<强> II。 XSLT 2.0:

这是一个使用XPath 2.0聚合函数min()的解决方案。

当这个XSLT 2.0解决方案:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
  <xsl:output omit-xml-declaration="no" indent="yes"/>
  <xsl:strip-space elements="*"/>

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

  <xsl:template match="products">
    <products>
      <xsl:apply-templates select="product">
        <xsl:sort select="min(price/offer|price/orig)"
          data-type="number" order="ascending" />
      </xsl:apply-templates> 
    </products>
  </xsl:template>

</xsl:stylesheet>

..适用于提供的XML:

<products>
  <product>
    <name>Product 1</name>
    <price>
      <orig>15</orig>
      <offer>10</offer>
    </price>
  </product>
  <product>
    <name>Product 2</name>
    <price>
      <orig>13</orig>
      <offer>12</offer>
    </price>
  </product>
  <product>
    <name>Product 3</name>
    <price>
      <orig>11</orig>
    </price>
  </product>
</products>

..产生了想要的结果:

<?xml version="1.0" encoding="UTF-8"?>
<products>
   <product>
      <name>Product 1</name>
      <price>
         <orig>15</orig>
         <offer>10</offer>
      </price>
   </product>
   <product>
      <name>Product 3</name>
      <price>
         <orig>11</orig>
      </price>
   </product>
   <product>
      <name>Product 2</name>
      <price>
         <orig>13</orig>
         <offer>12</offer>
      </price>
   </product>
</products>

答案 1 :(得分:5)

不需要EXSLT的XSLT 1.0解决方案:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output omit-xml-declaration="no" indent="yes"/>
    <xsl:strip-space elements="*"/>

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

    <xsl:template match="products">
        <products>
            <xsl:apply-templates select="product">
                <xsl:sort select="(price/*[not(. > ../*)])[1]"
                    data-type="number" order="ascending" />
            </xsl:apply-templates> 
        </products>
    </xsl:template>

</xsl:stylesheet>

答案 2 :(得分:5)

<强>予。有一个通用的纯XSLT 1.0解决方案 - 就像这个简单:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

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

 <xsl:template match="/*">
  <products>
   <xsl:apply-templates select="*">
    <xsl:sort data-type="number" select=
    "price/*[not(../* &lt; .)]"/>
   </xsl:apply-templates>
  </products>
 </xsl:template>
</xsl:stylesheet>

<强> II。如果price除了offerorig 之外还有其他孩子 - 在这种情况下,通用解决方案。上面(以及此问题的其他两个答案)无法正常工作。

以下是针对此案例的正确解决方案

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

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

 <xsl:template match="/*">
  <products>
   <xsl:apply-templates select="*">
    <xsl:sort data-type="number" select=
    "sum(price/orig[not(../offer &lt;= .)])
   +
     sum(price/offer[not(../orig &lt; .)])
    "/>
   </xsl:apply-templates>
  </products>
 </xsl:template>
</xsl:stylesheet>

<强> III。如果我们知道offer永远不会超过orig

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

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

 <xsl:template match="/*">
  <products>
   <xsl:apply-templates select="*">
    <xsl:sort data-type="number" 
         select="price/offer | price/orig[not(../offer)]"/>
   </xsl:apply-templates>
  </products>
 </xsl:template>
</xsl:stylesheet>

<强> IV。验证

上面的所有三个转换,当应用于提供的XML文档时:

<products>
  <product>
    <name>Product 1</name>
    <price>
      <orig>15</orig>
      <offer>10</offer>
    </price>
  </product>
  <product>
    <name>Product 2</name>
    <price>
      <orig>13</orig>
      <offer>12</offer>
    </price>
  </product>
  <product>
    <name>Product 3</name>
    <price>
      <orig>11</orig>
    </price>
  </product>
</products>

生成想要的正确结果

<products>
   <product>
      <name>Product 1</name>
      <price>
         <orig>15</orig>
         <offer>10</offer>
      </price>
   </product>
   <product>
      <name>Product 3</name>
      <price>
         <orig>11</orig>
      </price>
   </product>
   <product>
      <name>Product 2</name>
      <price>
         <orig>13</orig>
         <offer>12</offer>
      </price>
   </product>
</products>

解决方案II是在应用于此XML文档时仍然产生正确结果的三者中的唯一(将minAcceptable子项添加到price):

<products>
  <product>
    <name>Product 1</name>
    <price>
      <orig>15</orig>
      <offer>10</offer>
      <minAcceptable>8</minAcceptable>
    </price>
  </product>
  <product>
    <name>Product 2</name>
    <price>
      <orig>13</orig>
      <offer>12</offer>
      <minAcceptable>6</minAcceptable>
    </price>
  </product>
  <product>
    <name>Product 3</name>
    <price>
      <orig>11</orig>
      <minAcceptable>7</minAcceptable>
    </price>
  </product>
</products>

请注意没有其他答案正确处理此XML文档。