XSLT:实现特定顺序的顺序Ifs的替代方案

时间:2013-09-13 17:24:59

标签: xml xslt

XML:

<root>
  <item>
    <key>mustBeSECONDKey</key>
    <value>MustBeSECONDValue</value>
  </item>
  <item>
    <key>mustBeFIRSTKey</key>
    <value>MustBeFIRSTValue</value>
  </item>
</root>

XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <file>
      <xsl:for-each select="root/item">
        <xsl:if test="key[text()='mustBeFIRSTKey']">
          <xsl:element name="someCustomTagName">
            <xsl:value-of select="value" />
          </xsl:element>
        </xsl:if>
      </xsl:for-each>
      <xsl:for-each select="root/item">
        <xsl:if test="key[text()='mustBeSECONDKey']">
          <xsl:element name="anotherNameOfATag">
            <xsl:value-of select="value" />
          </xsl:element>
        </xsl:if>
      </xsl:for-each>
    </file>
  </xsl:template>
</xsl:stylesheet>

输出:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<file>
  <someCustomTagName>MustBeFIRSTValue</someCustomTagName>
  <anotherNameOfATag>MustBeSECONDValue</anotherNameOfATag>
</file>

这里的想法是我想确保标签以我指定的顺序结束在输出文档中。所以我通过基本上说“透过一切并找到第一件事,然后查看所有内容并找到第二件事来做到这一点。”这显然有效。

然而 - 这将是这个问题的关键 - 我想必须有一种更有效的方法来实现这个目标(可能不止一种方式)。它是什么?

还有一个皱纹。假设mustBeFIRSTKey有两个可能的值,MustBeFIRSTValueUnoMustBeFIRSTValueNi。然后,我想将这两个值分别映射到另一组两个值GazpachoSushi。所以

<item>
  <key>mustBeFIRSTKey</key>
  <value>MustBeFIRSTValueNi</value>
</item>

变为

<mustBeFIRSTKey>Sushi</mustBeFIRSTKey>

编辑:我发现我的问题主要是一个概念性问题,看看Java方面的问题。在设置Transformer期间,我正在执行此操作:

StreamSource xsltSource = new StreamSource(ClassLoader.getSystemResourceAsStream(transformLocation));

我应该这样做:

StreamSource xsltSource = new StreamSource(ClassLoader.getSystemResource(transformLocation).toString());

StreamSource构造函数设置systemId,这允许我从下面使用@ Ian_Robert更聪明的解决方案,如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <!-- I discovered I could merge the $map and $inlineMap variables. -->
  <xsl:variable name="inlineMap" select="document('')//xsl:variable[@name = 'inlineMap']/map">
    <map>
      <key from="mustBeFIRSTKey" to="someCustomTagName" />
      <key from="mustBeSECONDKey" to="anotherNameOfATag">
        <value from="MustBeSECONDValueUno" to="Gazpacho" />
        <value from="MustBeSECONDValueNi" to="Sushi" />
      </key>
    </map>
  </xsl:variable>
  <xsl:key name="valueMap" match="value" use="concat(../@from, '|', @from)" />
  <xsl:template match="root">
    <xsl:variable name="items" select="item" />
    <file>
      <xsl:for-each select="$inlineMap">
        <xsl:for-each select="key">
          <xsl:apply-templates select="$items[key = current()/@from]">
            <xsl:with-param name="elementName" select="@to"/>
          </xsl:apply-templates>
        </xsl:for-each>
      </xsl:for-each>
    </file>
  </xsl:template>
  <xsl:template match="item">
    <xsl:param name="elementName" />
    <xsl:variable name="currentItem" select="." />
    <xsl:element name="{$elementName}">
      <xsl:for-each select="$inlineMap">
        <xsl:variable name="value" select="key('valueMap', concat($currentItem/key, '|', $currentItem/value))" />
        <xsl:choose>
          <xsl:when test="$value">
            <xsl:value-of select="$value/@to" />
          </xsl:when>
          <xsl:otherwise>
            <xsl:value-of select="$currentItem/value" />
          </xsl:otherwise>
        </xsl:choose>
      </xsl:for-each>
    </xsl:element>
  </xsl:template>
</xsl:stylesheet>

这个例子删除了处理值的逻辑而没有映射,因为对于我的问题,没有映射。

3 个答案:

答案 0 :(得分:2)

您可以使用<xsl:sort>来实现此目的

<xsl:variable name="sortOrder" select="'|MustBeFIRSTKey|MustBeSECONDKey|--' />

<xsl:template match="/">
  <file>
    <xsl:apply-templates select="root/item">
      <xsl:sort select="string-length(substring-after($sortOrder,
                                                      concat('|', key, '|')))"
                data-type="number"
                order="descending" />
    </xsl:apply-templates>
  </file>
</xsl:template>

<xsl:template match="item">
  <xsl:element name="{key}">
    <xsl:value-of select="value" />
  </xsl:element>
</xsl:template>

这将首先放入MustBeFIRSTKey,将MustBeSECONDKey放在第二位,然后按原始顺序放置此后的任何其他键值。它的工作原理是根据每个键在sortOrder变量中的位置为其生成一个数字排序值。对于MustBeFIRSTKey,值为string-length('MustBeSECONDKey|--')(18),对于MustBeSECONDKey,它将为string-length('--')(2),对于任何其他内容,它将为string-length('')(0)。

要更改标记名称,您可以扩展此方法以使用变量对映射进行编码:

<xsl:variable name="sortOrder" select="concat(
          '|MustBeFIRSTKey+someCustomTagName',
          '|MustBeSECONDKey+anotherNameOfATag',
          '|--' />

<xsl:template match="/">
  <file>
    <xsl:apply-templates select="root/item">
      <xsl:sort select="string-length(substring-after($sortOrder,
                                                      concat('|', key, '+')))"
                data-type="number"
                order="descending" />
    </xsl:apply-templates>
  </file>
</xsl:template>

<!-- for keys that have a mapping -->
<xsl:template match="item[substring-after($sortOrder, concat('|', key, '+'))]">
  <xsl:element name="{substring-before(
       substring-after($sortOrder, concat('|', key, '+')), '|')}">
    <xsl:value-of select="value" />
  </xsl:element>
</xsl:template>

<!-- for keys that don't -->
<xsl:template match="item">
  <xsl:element name="{key}">
    <xsl:value-of select="value" />
  </xsl:element>
</xsl:template>

编辑:对于你的第三个问题,如果你想重新映射值和键,那么我认为一个单独的映射文件可能是最清晰的方法。

<强> mapping.xml

<map>
  <key from="MustBeFIRSTKey" to="someCustomTagName">
    <value from="MustBeFIRSTValueUno" to="Gazpacho" />
    <value from="MustBeFIRSTValueNi" to="Sushi" />
  </key>
  <key from="MustBeSECONDKey" to="anotherNameOfATag" />
</map>

<强>样式表

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:variable name="map" select="document('mapping.xml')/map" />

  <!-- define some keys used for looking up entries in the mapping -->
  <xsl:key name="keyMap" match="key" use="@from" />
  <xsl:key name="valMap" match="value" use="concat(../@from, '|', @from)" />

  <xsl:template match="/">
    <xsl:variable name="items" select="root/item" />
    <file>
      <!-- the key function looks up nodes in the "current document", so
           we need this for-each to switch the context to the mapping file
           for the key lookups -->
      <xsl:for-each select="$map">
        <!-- go through the keys in order -->
        <xsl:for-each select="key">
          <!-- process all items with that key... -->
          <xsl:apply-templates select="$items[key = current()/@from]">
            <!-- ... using the mapped tag name -->
            <xsl:with-param name="tagName" select="@to"/>
          </xsl:apply-templates>
        </xsl:for-each>
        <!-- now process any remaining keys that don't have a mapping -->
        <xsl:apply-templates select="$items[not(key('keyMap', key))]" />
      </xsl:for-each>
    </file>
  </xsl:template>

  <xsl:template match="item">
    <!-- tag name defaults to the key text if another name is not passed in -->
    <xsl:param name="tagName" select="key" />
    <xsl:variable name="curItem" select="." />
    <xsl:element name="{$tagName}">
      <!-- again, switch focus to the mapping file for key lookups -->
      <xsl:for-each select="$map">
        <!-- do we have a remapping for the item's *value*? -->
        <xsl:variable name="value"
            select="key('valMap', concat($curItem/key, '|', $curItem/value))" />
        <xsl:choose>
          <xsl:when test="$value"><xsl:value-of select="$value/@to" /></xsl:when>
          <xsl:otherwise><xsl:value-of select="$curItem/value" /></xsl:otherwise>
        </xsl:choose>
      </xsl:for-each>
    </xsl:element>
  </xsl:template>

</xsl:stylesheet>

如果映射文件足够小,您可以通过替换

将其包含在样式表中
  <xsl:variable name="map" select="document('mapping.xml')/map" />

  <xsl:variable name="inlineMap">
    <map>
      <key from="..." to="..."/>
      <!-- ... -->
    </map>
  </xsl:variable>

  <xsl:variable name="map"
                select="document('')//xsl:variable[@name='inlineMap']/map" />

这使用了document('')使您可以访问样式表本身的XML树的技巧。

最后,请注意keyMap仅用于查找的键在映射文件中有条目的项目。如果您拥有所有可能键的映射,则可以保留此键(以及使用它的<xsl:apply-templates select="$items[not(key('keyMap', key))]" />)。

答案 1 :(得分:1)

我首先要写一个模板

<xsl:template match="item">
          <xsl:element name="{key}">
            <xsl:value-of select="value" />
          </xsl:element>
</xsl:template>

现在你可以使用

  <xsl:template match="/root">
    <file>
      <xsl:apply-templates select="item[key ='MustBeFIRSTKey']"/>
      <xsl:apply-templates select="item[key = 'MustBeSECONDKey']"/>
    </file>
  </xsl:template>

使用XSLT 2.0,您只需要

  <xsl:template match="/root">
    <file>
      <xsl:apply-templates select="item[key ='MustBeFIRSTKey'], item[key = 'MustBeSECONDKey']"/>
    </file>
  </xsl:template>

当然,无论是XSLT 1.0还是2.0,您都可以定义一个密钥

<xsl:key name="k1" match="item" use="key"/>

然后将代码缩短为

  <xsl:template match="/root">
    <file>
      <xsl:apply-templates select="key('k1', 'MustBeFIRSTKey')"/>
      <xsl:apply-templates select="key('k1', 'MustBeSECONDKey')"/>
    </file>
  </xsl:template>
分别

  <xsl:template match="/root">
    <file>
      <xsl:apply-templates select="key('k1', 'MustBeFIRSTKey'), key('k1', 'MustBeSECONDKey')"/>
    </file>
  </xsl:template>

当然,根据您的预期排序,您可以使用xsl:sort而不是拼写订单。

答案 2 :(得分:1)

我会让这个数据驱动。在单独的XML文件中定义项目的顺序,可以是单独的文档,也可以是样式表的一部分:

<order>
  <item>mustbefirst</item>
  <item>mustbesecond</item>
</order>

并使用它来驱动样式表处理:

<xsl:template match="order">
  <xsl:for-each select="item">
    <xsl:variable name="target" select="$root/item[key=current()]">
    <xsl:if test="$target">
      <xsl:element name="$target/key"><xsl:value-of select="$target/value"/></xsl:element>
    </xsl:if>
  </xsl:for-each>
</xsl:template>

其中$ root绑定到你的&#34; root&#34;元件。