【问题标题】:Efficient mapping of two large lists两个大列表的高效映射
【发布时间】:2009-01-19 19:58:25
【问题描述】:

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

我有两种解决方案:

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

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

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

【问题讨论】:

  • 在阅读了您的问题描述之后(甚至在阅读您的解决方案之前),我想我会通过映射文档来解决问题,就像您在解决方案 2 中描述的那样。
  • 您能否提供一个包含两个 xml 文件的小示例(尽可能小)以及从列表转换为图像的规则?
  • 如果您不提供示例,这几乎是不回答的。顺便说一句:使用 xsl:choose 绝对不是最好的方法……即使对于较小的列表也是如此。

标签: xml xslt xpath xquery


【解决方案1】:

这是一个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. 使用&lt;xsl:key/&gt; 和XSLT 2.0 key() 函数 来确定和访问每个对应的输出值。第三个参数指定必须访问和索引的 xml 文档。

【讨论】:

  • 整洁。 :-) 我的方法是否存在根本缺陷,或者是否有必要让它在 XSLT 1.0 中工作?相比之下,我的转变似乎很漫长。
  • @Tomalak:您可以通过以下方式消除大部分默认值处理: 但是,要使其正常工作(在 XSLT 1 中),“默认”元素必须按文档顺序跟在所有“条目”元素之后。如果你问这个问题,我会回答:)
  • Re: "default" element must be last: 我怀疑在 XSLT 1.0 中,文档顺序优先于 "node set concatenation" 顺序,因此 "(node[1]|node[2 ])" 和 "(node[2]|node[1])" 产生相同的节点集。
  • @Tomalak:节点集必须按文档顺序排列的规则在 XSLT 2 中也是相同的。但是,XPath 2 有一个新的数据类型:序列,根据定义,它以任何指定的顺序保存项目。因此,如果您使用“,”或“to”运算符组成一个序列,您将获得所需的顺序。
  • 回复:非平凡的 XSLT。是的,如果您对他们认为仅限 C# 的问题发布 XSLT 答案,他们甚至会否决您。无论您的答案是否以完全优于提出的所有 C# 解决方案的方式解决了问题。我现在很享受解决 Project Euler 的问题——当然是在 XSLT 中。
【解决方案2】:

这是一种按照您的意愿行事的方法,使用&lt;xsl:key&gt;,否则按照您的方法二。

示例输入文件(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,将生成的节点集保存到变量中
  • 保存默认值以供进一步参考
  • 准备一个&lt;xsl:key&gt; 来处理“地图”节点集
  • 不要将&lt;xsl:for-each&gt; 用作循环,而是将其用作在调用key() 函数之前切换执行上下文的一种方式 - 否则key() 将对“数据”文档起作用并且不返回任何内容
  • key()函数找到对应的节点,保存到变量中
  • 检查输出的变量值 - 如果为空,使用默认值
  • 重复(通过&lt;xsl:apply-templates&gt;

&lt;xsl:for-each&gt; 巧妙的技巧归功于 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 在 cmets 中的提示,我能够创建一个相当短且更可取的样式表:

<?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>

【讨论】:

  • 这是一个 XSLT 2.0 解决方案 :)
  • @Tomalak:您可以通过以下方式消除大部分默认值处理: 但是,要使其正常工作(在 XSLT 1 中),“默认”元素必须按文档顺序跟在所有“条目”元素之后。如果你问这个问题,我会回答:)
  • @Tomalak:除了这种可能的重构之外,您的解决方案是一个非常好的解决方案!
  • 感谢您的提示。 :) 考虑在内 - 现在它看起来与您的解决方案非常相似。
  • @Tomalak:节点集必须按文档顺序排列的规则在 XSLT 2 中也是相同的。但是,XPath 2 有一个新的数据类型:序列,根据定义,它以任何指定的顺序保存项目。因此,如果您使用“,”或“to”运算符组成一个序列,您将获得所需的顺序
猜你喜欢
  • 1970-01-01
  • 2011-02-13
  • 2012-06-19
  • 1970-01-01
  • 1970-01-01
  • 2021-09-28
  • 2022-07-27
  • 2019-04-20
  • 2019-09-21
相关资源
最近更新 更多