如何通过内容将元素分组(XSLT 2.0)?

3

-- 修改后的问题 --

感谢所有提供潜在解决方案的人,但这些都与我已经尝试过的相同,所以我认为我应该更清楚。我扩展了XML,以使问题更加透明。

XML实际上是各种文件的编译,包含翻译内容,目的是获得一个统一的文档,只包含唯一的英语字符串,以及(经手动审核和清理后)每个字符串的单个翻译版本,因此它可以用于翻译记忆。这就是为什么它现在是一个带有大量冗余信息的大文件。

每个段落行都包含英语主控(可以在文件中重复几十次)和翻译变体。在很多情况下,这很容易,因为所有翻译版本都相等,所以我最终会得到一行,但在其他情况下可能会更加复杂。

因此,假设今天我有10个段落行,其中包含相同的英语内容(#1),2个不同的德语变体,3个不同的法语变体,以及其余语言环境只有一个变体,我需要获得:

1个段落包含:1个英语/ 2个德语(v1和v2)/ 3个法语(v1,v2和v3)/ ...

并且对于我的列表中的每个分组唯一英语值都要重复此过程。

修改后的XML:

<Books>
<!--First English String (#1) with number of potential translations -->
<Para>
    <EN>English Content #1</EN>
    <DE>German Trans of #1 v1</DE>
    <FR>French Trans of #1 v1</FR>
    <!-- More locales here -->
</Para>
<Para>
    <EN>English Content #1</EN>
    <DE>German Trans of #1 v2</DE>
    <FR>French Trans of #1 v1</FR>
    <!-- More locales here -->
</Para>
<Para>
    <EN>English Content #1</EN>
    <DE>German Trans of #1 v1</DE>
    <FR>French Trans of #1 v2</FR>
    <!-- More locales here -->
</Para>
<!--Second English String (#2) with number of potential translations -->
<Para>
    <EN>English Content #2</EN>
    <DE>German Trans of #2 v1</DE>
    <FR>French Trans of #2 v1</FR>
    <!-- More locales here -->
</Para>
<Para>
    <EN>English Content #2</EN>
    <DE>German Trans of #2 v3</DE>
    <FR>French Trans of #2 v1</FR>
    <!-- More locales here -->
</Para>
<Para>
    <EN>English Content #2</EN>
    <DE>German Trans of #2 v2</DE>
    <FR>French Trans of #2 v1</FR>
    <!-- More locales here -->
</Para>
<!--Loads of additional English Strings (#3 ~ #n) with number of potential    translations -->

当前的解决方案为我提供以下输出

<Books>
<Para>
    <EN>English Content #1</EN>
    <DE>German Trans of #1 v1</DE>
    <DE>German Trans of #1 v2</DE>
    <DE>German Trans of #2 v1</DE>
    <DE>German Trans of #2 v3</DE>
    <DE>German Trans of #2 v2</DE>
    <FR>French Trans of #1 v1</FR>
    <FR>French Trans of #1 v1</FR>
    <FR>French Trans of #1 v2</FR>
    <FR>French Trans of #2 v1</FR>
</Para>
</Books>

因此,只需拿取第一个EN标签,然后将所有其他标签分组,无论英语主字符串之间的差异如何。而我的目的是获得以下结果:
<Books>
<!-- First Grouped EN string and linked grouped translations -->
<Para>
    <EN>English Content #1</EN>
    <DE>German Trans of #1 v1</DE>
    <DE>German Trans of #1 v2</DE>
    <FR>French Trans of #1 v1</FR>
    <FR>French Trans of #1 v2</FR>
</Para>
<!-- Second Grouped EN string and linked grouped translations -->
<Para>
    <EN>English Content #2</EN>
    <DE>German Trans of #2 v1</DE>
    <DE>German Trans of #2 v3</DE>
    <DE>German Trans of #2 v2</DE>
    <FR>French Trans of #2 v1</FR>
</Para>
<!-- 3d to n Grouped EN string and linked grouped translations -->
</Books>

关于按其内容分组元素的好问题,点赞。 - Emiliano Poggi
好问题,+1。请看我的答案,它提供了一个正确工作的解决方案,即使是具有完全相同翻译的近似语言也可以。 - Dimitre Novatchev
当你询问关于XSLT分组的问题时,答案将完全不同取决于你使用的是XSLT 1.0还是XSLT 2.0,因此你确实需要明确你的限制条件。 - Michael Kay
好的,我已经编辑了我的答案,并附上了适用于 XSLT 1 和 2 的解决方案。干杯 - Emiliano Poggi
将标题更改以匹配内容。如果您关心此事,请随意恢复原始标题。 - Emiliano Poggi
显示剩余2条评论
4个回答

2

扩展的XSLT 2.0答案以满足问题请求中的更新

<xsl:stylesheet version="2.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="Books">
        <xsl:copy>
            <xsl:for-each-group select="*" 
                group-by="EN">
                <xsl:copy>
                   <xsl:copy-of select="EN"/>
                   <xsl:for-each-group select="current-group()/*[not(local-name()='EN')]"
                        group-by=".">
                        <xsl:sort select="local-name()"/>
                        <xsl:copy-of select="."/>
                    </xsl:for-each-group>
                </xsl:copy>
            </xsl:for-each-group>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

扩展的 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:key name="main" match="Para" use="EN"/>
    <xsl:key name="locale" match="Para/*[not(self::EN)]" use="concat(../EN,.)"/>

    <xsl:template match="Books">
        <xsl:copy>
            <xsl:apply-templates select="Para[
                generate-id()
                = generate-id(key('main',EN)[1])]" mode="EN"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="*" mode="EN">
        <xsl:copy>
            <xsl:copy-of select="EN"/>
            <xsl:apply-templates select="../Para/*[
                generate-id()
                = generate-id(key('locale',concat(current()/EN,.))[1])]" mode="locale">
                <xsl:sort select="local-name()"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="*" mode="locale">
        <xsl:copy>
            <xsl:value-of select="."/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

When applied o

n the new provided input, produces:

<Books>
    <Para>
        <EN>English Content #1</EN>
        <DE>German Trans of #1 v1</DE>
        <DE>German Trans of #1 v2</DE>
        <FR>French Trans of #1 v1</FR>
        <FR>French Trans of #1 v2</FR>
    </Para>
    <Para>
        <EN>English Content #2</EN>
        <DE>German Trans of #2 v1</DE>
        <DE>German Trans of #2 v3</DE>
        <DE>German Trans of #2 v2</DE>
        <FR>French Trans of #2 v1</FR>
    </Para>
</Books>

这个 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:key name="locale" match="Para/*[not(local-name()='EN')]" use="text()"/>

    <xsl:template match="Books">
        <xsl:copy>
            <Para>
                <xsl:copy-of select="Para[1]/EN"/>
                <xsl:apply-templates select="Para/*[
                    generate-id()
                    = generate-id(key('locale',text())[1])]" mode="group">
                    <xsl:sort select="local-name()"/>
                </xsl:apply-templates>
            </Para>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="*" mode="group">
        <xsl:copy>
            <xsl:value-of select="."/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

说明:

  • xsl:key 用于按内容(但不包括 EN)对所有元素进行分组
  • 直接复制第一个 PARA/EN 节点
  • 使用 xsl:sortMeunchian 分组方法,按请求分组输出其他元素(具有相同内容的元素只报告一次)

当应用于问题中提供的输入时,结果树为:

<Books>
   <Para>
      <EN>Some English Content</EN>
      <DE>German Trans v1</DE>
      <DE>German Trans v2</DE>
      <FR>French Trans v1</FR>
      <FR>French Trans v2</FR>
   </Para>
</Books>

使用XSLT 2.0中的xsl:for-each-group可以得到相同的结果(且转换更短)。
<xsl:stylesheet version="2.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="Books">
        <xsl:copy>
            <Para>
                <xsl:copy-of select="Para[1]/EN"/>
                <xsl:for-each-group select="Para/*[not(local-name()='EN')]" 
                            group-by=".">
                    <xsl:sort select="local-name()"/>
                    <xsl:copy>
                        <xsl:value-of select="."/>
                    </xsl:copy>
                </xsl:for-each-group>
            </Para>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

很好的回答,但是可能会有相同翻译的接近语言(如EN-US、EN-AU),在这种情况下,除了第一个之外,所有这样的接近语言翻译都会丢失。我知道这是非常特殊的情况,但请注意 :) - Dimitre Novatchev

1
这个转换:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:key name="kLangByValAndText"
  match="Para/*[not(self::EN)]"
  use="concat(name(), '+++', .)"/>

 <xsl:template match="/">
  <Books>
   <Para>
    <xsl:copy-of select="/*/Para[1]/EN"/>
    <xsl:for-each select=
    "/*/*/*[generate-id()
           =
            generate-id(key('kLangByValAndText',
                            concat(name(), '+++', .)
                            )
                            [1]
                       )
           ]
    ">
     <xsl:sort select="name()"/>
     <xsl:copy-of select="."/>
    </xsl:for-each>
   </Para>
  </Books>
 </xsl:template>
</xsl:stylesheet>

当应用于此XML文档(提供的扩展版本,使其更有趣):

<Books>
    <Para>
        <EN>Some English Content</EN>
        <DE>German Trans v1</DE>
        <FR>French Trans v1</FR>
        <!-- More locales here -->
    </Para>
    <Para>
        <EN>Some English Content</EN>
        <EN-US>Some English Content</EN-US>
        <DE>German Trans v1</DE>
        <FR>French Trans v1</FR>
        <!-- More locales here -->
    </Para>
    <Para>
        <EN>Some English Content</EN>
        <Australian>Some English Content</Australian>
        <DE>German Trans v1</DE>
        <FR>French Trans v2</FR>
        <!-- More locales here -->
    </Para>
    <!-- Much more para's hereafter containing variety of <EN> Content -->
</Books>

能够产生想要的、正确的结果

<Books>
   <Para>
      <EN>Some English Content</EN>
      <Australian>Some English Content</Australian>
      <DE>German Trans v1</DE>
      <EN-US>Some English Content</EN-US>
      <FR>French Trans v1</FR>
      <FR>French Trans v2</FR>
   </Para>
</Books>

说明: 在由两部分组成的键上进行Muenchian分组。

请注意: 仅在翻译上进行分组(如在此问题的另一个答案中所做)会丢失<Australian>翻译--将@empo的解决方案应用于同一文档,结果是(<Australian>被遗漏了!):

<Books>
   <Para>
      <EN>Some English Content</EN>
      <DE>German Trans v1</DE>
      <EN-US>Some English Content</EN-US>
      <FR>French Trans v1</FR>
      <FR>French Trans v2</FR>
   </Para>
</Books>

0

另一种 Muenchian 分组,使用子级复合键:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes" />
  <xsl:key name="english" match="EN" use="." />
  <xsl:key name="others" match="Para/*[not(self::EN)]" use="concat(../EN, '&#160;', ., '&#160;', name())" />
  <xsl:template match="/Books">
    <Books>
      <xsl:for-each select="Para/EN[generate-id() = generate-id(key('english', .)[1])]">
        <Para>
          <xsl:copy-of select=".|key('english', .)/../*[not(self::EN)][generate-id() = generate-id(key('others', concat(current(), '&#160;', ., '&#160;', name()))[1])]" />
        </Para>
      </xsl:for-each>
    </Books>
  </xsl:template>
</xsl:stylesheet>

抱歉修改了很多次,我一直在寻找更简洁的代码... 我想我已经无法再减少它了。 :) - Erlock

0
使用Saxon 9时,当我应用样式表时。
<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="2.0">

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

  <xsl:template match="Books">
    <xsl:copy>
      <xsl:for-each-group select="Para" group-by="EN">
        <xsl:apply-templates select="."/>
      </xsl:for-each-group>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="Para">
    <xsl:copy>
      <xsl:copy-of select="EN"/>
      <xsl:for-each-group select="current-group()/(* except EN)" group-by="node-name(.)">
        <xsl:for-each-group select="current-group()" group-by=".">
          <xsl:copy-of select="."/>
        </xsl:for-each-group>
      </xsl:for-each-group>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

到输入

<Books>
<!--First English String (#1) with number of potential translations -->
<Para>
    <EN>English Content #1</EN>
    <DE>German Trans of #1 v1</DE>
    <FR>French Trans of #1 v1</FR>
    <!-- More locales here -->
</Para>
<Para>
    <EN>English Content #1</EN>
    <DE>German Trans of #1 v2</DE>
    <FR>French Trans of #1 v1</FR>
    <!-- More locales here -->
</Para>
<Para>
    <EN>English Content #1</EN>
    <DE>German Trans of #1 v1</DE>
    <FR>French Trans of #1 v2</FR>
    <!-- More locales here -->
</Para>
<!--Second English String (#2) with number of potential translations -->
<Para>
    <EN>English Content #2</EN>
    <DE>German Trans of #2 v1</DE>
    <FR>French Trans of #2 v1</FR>
    <!-- More locales here -->
</Para>
<Para>
    <EN>English Content #2</EN>
    <DE>German Trans of #2 v3</DE>
    <FR>French Trans of #2 v1</FR>
    <!-- More locales here -->
</Para>
<Para>
    <EN>English Content #2</EN>
    <DE>German Trans of #2 v2</DE>
    <FR>French Trans of #2 v1</FR>
    <!-- More locales here -->
</Para>
</Books>

我得到了结果

<Books>
   <Para>
      <EN>English Content #1</EN>
      <DE>German Trans of #1 v1</DE>
      <DE>German Trans of #1 v2</DE>
      <FR>French Trans of #1 v1</FR>
      <FR>French Trans of #1 v2</FR>
   </Para>
   <Para>
      <EN>English Content #2</EN>
      <DE>German Trans of #2 v1</DE>
      <DE>German Trans of #2 v3</DE>
      <DE>German Trans of #2 v2</DE>
      <FR>French Trans of #2 v1</FR>
   </Para>
</Books>

@Martin Honnen 请查看一下这个SO - Abhishek Nayak

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接