使用xslt 2进行多值分组

时间:2013-07-28 18:45:42

标签: xslt-2.0

对于初学者我对标题的道歉,但我不知道描述我的问题的好方法。因此,代码示例将使事情变得更加清晰。

假设我有以下xml树:

<root>
<node>
<value xml:lang="en">Some English Content</value>
<value xml:lang="fr">Some French Content</value>
<value xml:lang="de">Some German Content</value>
</node>
<node>
<value xml:lang="en">Some English Content</value>
<value xml:lang="de">Some German Content</value>
</node>
<node>
<value xml:lang="en">Some Other English Content</value>
<value xml:lang="fr">Some Other French Content</value>
<value xml:lang="de">Some Other German Content</value>
</node>
<node>
<value xml:lang="en">Some English Content</value>
<value xml:lang="fr">Some French Content</value>
<value xml:lang="de">Some German Content</value>
</node>
<node>
<value xml:lang="fr">Some French Content</value>
<value xml:lang="de">Some German Content</value>
</node>
</root>

所以基本上有各种节点集,有许多本地化字符串,我想根据内容对这些集合进行分组。节点1,2,4和5是关于相同的主题,但并非所有语言环境中都可以使用所有字符串,因此我无法真正使用引用字符串(例如英语,因为它在节点5中不可用)。节点3包含不同的内容,因此它应该是不同组的一部分。

声音可能相当复杂,但这是我想要得到的结果(使用xslt 2):

<values>
<group>
    <value xml:lang="en">Some English Content</value>
    <value xml:lang="fr">Some French Content</value>
    <value xml:lang="de">Some German Content</value>
</group>
<group>
    <value xml:lang="en">Some Other English Content</value>
    <value xml:lang="fr">Some Other French Content</value>
    <value xml:lang="de">Some Other German Content</value>
</group>
</values>

有关如何最好地处理此问题的任何想法?请注意,我在一个节点中最多可以有40种不同的语言,并且文件中可能有数百个节点,因此资源也会成为一个问题。

1 个答案:

答案 0 :(得分:0)

我不确定我是否完全理解您的要求,但我编写了一些代码,首先尝试使用node填充value个元素以查找缺少的语言,然后对填充的元素进行分组:< / p>

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="2.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:mf="http://example.com/mf"
  exclude-result-prefixes="xs mf">

<xsl:param name="sep" as="xs:string" select="'|'"/>

<xsl:output indent="yes"/>

<xsl:variable name="main-doc" select="/"/>

<xsl:variable name="languages" as="xs:string*">
  <xsl:perform-sort select="distinct-values(root/node/value/@xml:lang)">
    <xsl:sort select="."/>
  </xsl:perform-sort>
</xsl:variable>

<xsl:key name="k1" match="node/value" use="concat(@xml:lang, $sep, .)"/>

<xsl:template match="root">
  <values>
    <xsl:variable name="filled" as="element(node)*">
      <xsl:apply-templates select="node" mode="fill"/>
    </xsl:variable>
    <xsl:for-each-group select="$filled" group-by="string-join(value, $sep)">
      <group>
        <xsl:copy-of select="value"/>
      </group>
    </xsl:for-each-group>
  </values>
</xsl:template>

<xsl:template match="node" mode="fill">
  <xsl:copy>
    <xsl:variable name="this" as="element(node)" select="."/>
    <xsl:for-each select="$languages">

      <value xml:lang="{.}">
        <xsl:value-of
          select="if ($this/value[lang(current())]) 
                  then $this/value[lang(current())]
                  else (key('k1', 
                            concat($this/value[1]/@xml:lang, $sep, $this/value[1]),
                            $main-doc)/../value[lang(current())])[1]"/>
      </value>
    </xsl:for-each>
  </xsl:copy>
</xsl:template>

</xsl:stylesheet> 

正如你所看到的,我需要一些命令来填充元素,所以我按照不同的@xml:lang排序,我不确定你想要那个。使用这种方法,您使用Saxon 9.5发布的输入的输出是

<values>
   <group>
      <value xml:lang="de">Some German Content</value>
      <value xml:lang="en">Some English Content</value>
      <value xml:lang="fr">Some French Content</value>
   </group>
   <group>
      <value xml:lang="de">Some Other German Content</value>
      <value xml:lang="en">Some Other English Content</value>
      <value xml:lang="fr">Some Other French Content</value>
   </group>
</values>

我也不确定哪个策略是填充元素的目标(另请参阅我发布的评论)。最后,我决定在node/value及其内容的串联上键入@xml:lang元素,然后在填写缺少语言的元素时,我只需匹配第一个value子元素node有。这基本上意味着,如果某些node的内容为value xml:lang="foo"的第一个bar,则该匹配仅针对该语言foo和内容bar,我们会复制缺少语言的value内容。

如果您不想进行排序,并且您可以使用初始属性序列的顺序,那么您可以省略排序,那么样式表就是

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="2.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:mf="http://example.com/mf"
  exclude-result-prefixes="xs mf">

<xsl:param name="sep" as="xs:string" select="'|'"/>

<xsl:output indent="yes"/>

<xsl:variable name="main-doc" select="/"/>

<xsl:variable name="languages" as="xs:string*" select="distinct-values(root/node/value/@xml:lang)"/>

<xsl:key name="k1" match="node/value" use="concat(@xml:lang, $sep, .)"/>

<xsl:template match="root">
  <values>
    <xsl:variable name="filled" as="element(node)*">
      <xsl:apply-templates select="node" mode="fill"/>
    </xsl:variable>
    <xsl:for-each-group select="$filled" group-by="string-join(value, $sep)">
      <group>
        <xsl:copy-of select="value"/>
      </group>
    </xsl:for-each-group>
  </values>
</xsl:template>

<xsl:template match="node" mode="fill">
  <xsl:copy>
    <xsl:variable name="this" as="element(node)" select="."/>
    <xsl:for-each select="$languages">

      <value xml:lang="{.}">
        <xsl:value-of
          select="if ($this/value[lang(current())]) 
                  then $this/value[lang(current())]
                  else (key('k1', 
                            concat($this/value[1]/@xml:lang, $sep, $this/value[1]),
                            $main-doc)/../value[lang(current())])[1]"/>
      </value>
    </xsl:for-each>
  </xsl:copy>
</xsl:template>

</xsl:stylesheet> 

这样输出就像你要求的那样:

<values>
   <group>
      <value xml:lang="en">Some English Content</value>
      <value xml:lang="fr">Some French Content</value>
      <value xml:lang="de">Some German Content</value>
   </group>
   <group>
      <value xml:lang="en">Some Other English Content</value>
      <value xml:lang="fr">Some Other French Content</value>
      <value xml:lang="de">Some Other German Content</value>
   </group>
</values>