【问题标题】:Transforming repeated XML elements containing different values using XSLT使用 XSLT 转换包含不同值的重复 XML 元素
【发布时间】:2019-02-04 09:59:42
【问题描述】:

你好 Stackoverflowers --

这是我第一次发帖。我遇到了一个令人沮丧的 XSLT 问题,尽管有各种假设的解决方案,但都未能解决。该方案是将一个 XML 文档 (#1) 基本转换为另一个 (#2) 的格式,以实现两个系统之间的互操作性。

XML doc #1 包含许多重复的元素,每个元素都包含一个单独的值。这些重复的元素需要转换为 doc #2 的替代元素,并包含 doc #1 中的相应值。

尽管进行了很多实验,但我似乎无法让它发挥作用。 doc #1 中的相关元素如下:

 <mods:extension> 
  <rx:funder>Funder A</rx:funder> 
  <rx:projectid>Funder A code number</rx:projectid> 
  <rx:funder>Funder B</rx:funder> 
  <rx:projectid>Funder B code number</rx:projectid> 
 </mods:extension> 

需要将文档#2中的XML转换成如下:

<project_input>
  <item>
    <project>Funder A code number</project>
    <funder_name>Funder A</funder_name>
  </item>
  <item>
    <project>Funder B code number</project>
    <funder_name>Funder B</funder_name>
  </item>
</project_input>

但不幸的是,我转换的输出始终是这个的变体:

<project_input>
  <item>
    <project>Funder A code numberFunder B code number</project>
    <funder_name>Funder AFunder B</funder_name>
  </item>
</project_input>

...将值填充到单个元素中。

此刻的转变是这样的:

<xsl:if test="v3:extension">
<project_input>
<item>      
<xsl:for-each select="v3:extension/rx:projectid">
    <project>
        <xsl:value-of select="."/>   
    </project>
</xsl:for-each> 
<xsl:for-each select="v3:extension/rx:funder">
    <funder_name>
        <xsl:value-of select="."/>      
    </funder_name>
</xsl:for-each>
</item> 
</project_input>
</xsl:if>

所以问题是,尽管正确的值在 doc #1 中成功循环,但它们在 doc #2 中没有正确输出。

我尝试过使用包含不同值的变体,但这也没有成功。令人沮丧的是,我以前做过这样的事情,但我这辈子都不记得了!堆栈上也已经描述了类似的场景,但那里提出的解决方案似乎在这个用例中不起作用。

谁能指出我遗漏的一些明显的东西?谢谢

更新的问题 - 请参阅下面的 cmets。这需要在 XSLT 1.0 中工作。我设法拼凑了一些东西。只要正确的数据被提取、排序和分组,它就工作了一半,但是缺少指定的 XML 元素。我可能遗漏了一些非常明显的东西。

有人可以帮帮我吗?这是 XSLT 1.0:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
xmlns:mods="http://www.loc.gov/mods/v3" xmlns:rx="http://example.com/rx" 
version="1.0">
<xsl:output indent="yes"/>
<xsl:key use="rx:funder" match="mods:mods/mods:extension" name="groups" />
<xsl:template match="/">
<xsl:apply-templates select="mods:mods/mods:extension" />
</xsl:template>
<xsl:template match="mods:mods">
<rioxx2_project_output>
  <xsl:for-each select="/mods:extension[generate-id(.)=generate- 
  id(key('groups', rx:funder))]">
    <item>
      <funder_name>
        <xsl:value-of select="mods:extension/rx:funder/text()" />
      </funder_name>
      <project>
        <xsl:value-of select="mods:extension/rx:projectid/text()" />
      </project>
     </item>
     </xsl:for-each>
     </project_input>
   </xsl:template>
  </xsl:stylesheet>

在 XSLT Fiddle 中:https://xsltfiddle.liberty-development.net/bFN1y8S/1

【问题讨论】:

  • 你能说是否可以使用 XSLT 2.0 或更高版本吗?使用 XSLT 2.0,您可能会使用 xsl:for-each-groupgroup-starting-with。此外,是否总是存在 projectidfunder 元素?谢谢!
  • 谢谢蒂姆。应该包括那个。它是 XSLT 2.0。不会总是有projectidfunder element 存在。谢谢
  • 如果不总是有projectidfunder,那么您怎么知道在您的示例中“资助者A 代码”与“资助者A”而不是“资助者”一起使用乙”?谢谢!
  • &lt;xsl:if&gt; 应该检查 doc #1 中是否存在值,但事实上,在 doc #1 中建模 XML 的方式并不令人满意。它缺少一个额外的分层元素,以便projectidfunder 可以正确分组。这将有助于解决我遇到的问题,但不幸的是我必须用我所拥有的东西来解决问题! :-(

标签: xml xslt transformation


【解决方案1】:

这似乎是使用 for-each-group group-starting-withgroup-ending-with 的经典示例,因此在您的情况下使用例如

  <xsl:template match="mods:extension">
      <project_input>
          <xsl:for-each-group select="*" group-starting-with="rx:funder">
              <item>
                  <project>
                      <xsl:value-of select="current-group()[2]"/>
                  </project>
                  <funder_name>
                      <xsl:value-of select="."/>
                  </funder_name>
              </item>
          </xsl:for-each-group>
      </project_input>
  </xsl:template>

这假设 mods:extension 的子元素序列有一定的规律性,rx:funder 能够识别一个组。

使用 XSLT 3 的完整示例 https://xsltfiddle.liberty-development.net/bFN1y8S(对于 XSLT 2 处理器,您需要拼出身份转换模板而不是使用 xsl:mode

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:mods="http://example.com/mods"
    xmlns:rx="http://example.com/rx"
    exclude-result-prefixes="#all"
    version="3.0">

  <xsl:output indent="yes"/>

  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:template match="mods:extension">
      <project_input>
          <xsl:for-each-group select="*" group-starting-with="rx:funder">
              <item>
                  <project>
                      <xsl:value-of select="current-group()[2]"/>
                  </project>
                  <funder_name>
                      <xsl:value-of select="."/>
                  </funder_name>
              </item>
          </xsl:for-each-group>
      </project_input>
  </xsl:template>

</xsl:stylesheet>

要在 XSLT 1 中实现类似的方法,一种方法是使用一个键,该键在前面的兄弟 rx:funder 的 id 上键入任何非 rx:funder 元素:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:mods="http://example.com/mods"
    xmlns:rx="http://example.com/rx"
    exclude-result-prefixes="mods rx"
    version="1.0">

  <xsl:output indent="yes"/>
  <xsl:strip-space elements="*"/>

  <xsl:key name="group" match="mods:extension/*[not(self::rx:funder)]" 
     use="generate-id(preceding-sibling::rx:funder[1])"/>

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

  <xsl:template match="mods:extension">
      <project_input>
          <xsl:for-each select="rx:funder">
              <item>
                  <project>
                      <xsl:value-of select="key('group', generate-id())[self::rx:projectid]"/>
                  </project>
                  <funder_name>
                      <xsl:value-of select="."/>
                  </funder_name>
              </item>
          </xsl:for-each>
      </project_input>
  </xsl:template>

</xsl:stylesheet>

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

【讨论】:

  • 谢谢 Martin - 我很快就会尝试一下。我会用结果更新这个问题。再次感谢。
  • 这在 XSLT 小提琴中完美运行 - 我在踢自己,因为我刚刚意识到我使用的系统中使用的引擎实际上使用 XSLT 1.0!这行不通 - 这有多烦人?
  • @GeorgeMacgregor,嗯,存在各种 XSLT 1 方法来解决这些问题,我相信大多数关于 XSLT 的教科书(甚至还有像 cranesoftwrights.github.io/books/ptux/index.htm 这样的免费教科书)都会有所帮助。不确定您使用哪种“系统”,如果它是基于 Java 的,那么通常将 Saxon 9.8 或 9.9 HE 放在类路径中即可获得 XSLT 3 支持。
  • 我已经设法在 XSLT 1.0 中拼凑一些东西,只要正确检索、排序和分组正确的数据,它就可以工作,但是缺少指定的元素标签。有任何想法吗?我可能在这里忽略了一些非常明显的东西。
  • 您也可以不编写任何代码,如xsltfiddle.liberty-development.net/bFN1y8S/2,以获得该结果,目前内置模板只是按文档顺序写出现有文本。 XSLT 2/3 中的 group-starting-with 没有在 XSLT 1 中“实现”,并使用元素值上的键,而是使用同级的 id。
【解决方案2】:
    <xsl:strip-space elements="*"/>
        <xsl:template match="node()">
            <xsl:copy>
                <xsl:apply-templates />
            </xsl:copy>
        </xsl:template>
        <xsl:template match="mods:extension">
            <xsl:element name="project_input">
                <xsl:for-each-group select="node()" group-starting-with="rx:funder">

                       <item>
                           <project>
                               <xsl:value-of select="./following-sibling::*[1][self::rx:projectid]"/>
                       </project>
                           <funder_name>
                               <xsl:value-of select="."/>
                           </funder_name>

                       </item>

                </xsl:for-each-group>
            </xsl:element>  
        </xsl:template>
Kindly chek it.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-10-24
    • 1970-01-01
    • 1970-01-01
    • 2013-04-06
    • 1970-01-01
    • 1970-01-01
    • 2010-10-13
    • 2020-11-03
    相关资源
    最近更新 更多