两个大型列表的高效映射

时间:2009-01-19 19:58:25

标签: xml xslt xpath xquery

我的任务是编写一些XSLT 2.0来将XML文档转换为另一个XML文档。我对XSLT比较陌生,但是在我做这个的时候我已经学到了很多东西。在此期间,我不得不映射简单的值,即002 - > TH等。这对于小于10个值的小列表来说很好,我使用了xsl:choose。但是,我需要将300个值从一个列表映射到另一个列表,反之亦然。每个列表都有一个值和文本描述。这两个列表值并不总是直接映射,因此我可能需要比较文本描述并在必要时使用默认值。

我有两个问题的解决方案:

  1. 使用xsl:choose:如果其中一个列表发生变化,我认为这可能很慢并且很难更新。

  2. 拥有一个XML文档,其中包含每个列表项之间的关系。我会使用XPath表达式来检索关联的值:这是我首选的解决方案,因为我相信它更易于维护且更易于更新。虽然我不确定它是否有效。

  3. 我应该使用什么解决方案,我的建议之一,还是有更好的方法来映射这些值?

2 个答案:

答案 0 :(得分:4)

这是 XSLT 2.0解决方案

源XML文件:

<input>
  <data>001</data>
  <data>002</data>
  <data>005</data>
</input>

“映射”xml文件:

<map>
  <default>?-?-?</default>
    <input value="001">RZ</input>
    <input value="002">TH</input>
    <input value="003">SC</input>
</map>

XSLT转换:

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

  <xsl:param name="pmapFile" 
       select="'C:/temp/deleteMap.xml'" />

  <xsl:variable name="vMap" 
       select="document($pmapFile)" />

  <xsl:variable name="vDefault" 
       select="$vMap/*/default/text()" />

  <xsl:key name="kInputByVal" match="input" 
   use="@value" />

  <xsl:template match="/*">
    <output>
      <xsl:apply-templates/>
    </output>
  </xsl:template>

  <xsl:template match="data">
    <data>
        <xsl:sequence select= 
         "(key('kInputByVal', ., $vMap)[1]/text(),
           $vDefault
           )[1]
         "/>
    </data> 
  </xsl:template>
</xsl:stylesheet>

输出:

<output>
  <data>RZ</data>
  <data>TH</data>
  <data>?-?-?</data>
</output>

请注意以下

  1. 使用document()函数访问“映射”xml文档,该文档存储在单独的XML文件中。

  2. 使用<xsl:key/>和XSLT 2.0 key()函数来确定和访问每个相应的输出值。第三个参数指定必须访问和索引的xml文档。

答案 1 :(得分:2)

以下是使用<xsl:key>并按照您的方法二执行操作的方法。

示例输入文件(data.xml):

<?xml version="1.0" encoding="utf-8"?>
<input>
  <data>001</data>
  <data>002</data>
  <data>005</data>
</input>

示例映射文件(map.xml):

<?xml version="1.0" encoding="utf-8"?>
<map default="??">
  <entry key="001">RZ</entry>
  <entry key="002">TH</entry>
  <entry key="003">SC</entry>
</map>

示例XSL样式表,解释如下:

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

  <xsl:param name="map-file" select="string('map.xml')" />
  <xsl:variable name="map-doc" select="document($map-file)" />
  <xsl:variable name="default-value" select="$map-doc/map/@default" />
  <xsl:key name="map" match="/map/entry" use="@key" />

  <xsl:template match="/input">
    <output>
      <xsl:apply-templates select="data" />
    </output>
  </xsl:template>

  <xsl:template match="data">
    <xsl:variable name="raw-value" select="." />
    <xsl:variable name="mapped-value">
      <xsl:for-each select="$map-doc">
        <xsl:value-of select="key('map', $raw-value)" />
      </xsl:for-each>
    </xsl:variable>
    <data>
      <xsl:choose>
        <xsl:when test="$mapped-value = ''">
          <xsl:value-of select="$default-value" />
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="$mapped-value" />
        </xsl:otherwise>
      </xsl:choose>
    </data>
  </xsl:template>
</xsl:stylesheet>

这是做什么的:

  • 使用document()打开map.xml,将生成的节点集保存为变量
  • 保存默认值以供进一步参考
  • 准备<xsl:key>以对抗“地图”节点集
  • 不要将<xsl:for-each>用作循环,而是在调用key()函数之前切换执行上下文的方法 - 否则key()将对“数据”文档起作用并且不返回任何内容
  • 找到具有key()函数的相应节点,将其保存在变量
  • 检查输出上的变量值 - 如果为空,则使用默认值
  • 重复(通过<xsl:apply-templates>

整齐的<xsl:for-each>技巧归功于Jeni Tennison,他在XSL邮件列表中描述了这项技术。一定要阅读帖子。

针对data.xml运行样式表的输出:

<?xml version="1.0" encoding="utf-8"?>
<output>
  <data>RZ</data>
  <data>TH</data>
  <data>??</data>
</output>

所有这些都是XSLT 1.0。我确信存在一个更好/更优雅的版本,它利用了XSLT 2.0提供的优势,但不幸的是我对XSLT 2.0并不十分熟悉。也许其他人发布了更好的解决方案。


修改

通过Dimitre Novatchev在评论中的暗示,我能够创建一个相当短且更优选的样式表:

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

  <xsl:param name="map-file" select="string('map.xml')" />
  <xsl:variable name="map-doc" select="document($map-file)" />
  <xsl:variable name="default" select="$map-doc/map/default[1]" />
  <xsl:key name="map" match="/map/entry" use="@key" />

  <xsl:template match="/input">
    <output>
      <xsl:apply-templates select="data" />
    </output>
  </xsl:template>

  <xsl:template match="data">
    <xsl:variable name="raw-value" select="." />
    <data>
      <xsl:for-each select="$map-doc">
        <xsl:value-of select="(key('map', $raw-value)|$default)[1]" />
      </xsl:for-each>
    </data>
  </xsl:template>
</xsl:stylesheet>

但是,这个需要稍微不同的映射文件才能在XSLT 1.0中使用:

<?xml version="1.0" encoding="utf-8"?>
<map>
  <entry key="001">RZ</entry>
  <entry key="002">TH</entry>
  <entry key="003">SC</entry>
  <!-- default entry must be last in document -->
  <default>??</default>
</map>