【问题标题】:Merging and rearranging xml using xslt使用 xslt 合并和重新排列 xml
【发布时间】:2012-05-29 13:55:26
【问题描述】:

我正在使用 XSLT 转换从 web 派生的 XML,并将其即时转换为表示为 output 的目标 xml 文件。 即使尝试了很多,我仍然无法这样做,任何人都可以帮助我完成这个转换。

源 XML

<allocelement>
    <hd1>12</hd1>
    <hd2>14</hd2>
    <hd3>87</hd3>
        <alc>1</alc>
    <amount>4587</amount>
    <code>1111</code>
</allocelement>
<alloclement>
        <hd1>12</hd1>
    <hd2>14</hd2>
    <hd3>87</hd3>
    <alc>2</alc>
    <amount>80000</amount>
    <code>1111</code>
</alloclement>
<alloclement>
    <hd1>875</hd1>
    <hd2>455</hd2>
    <hd3>455</hd3>
    <alc>2</alc>
    <amount>80000</amount>
    <code>1112</code>
 </alloclement>

所需的输出

<allocelement>
    <Codeheader>
    <code>1111</code>
        <hd1>12</hd1>
        <hd2>14</hd2>
        <hd3>87</hd3>
                <alc>1</alc>
                    <amount>4587</amount>
                <alc>2</alc>
                    <amount>80000</amount>
    </codeHeader>
        <CodeHeader>
    <code>1112</code>
        <hd1>875</hd1>
        <hd2>455</hd2>
        <hd3>455</hd3>
            <alc>2</alc>
              <amount>80000</amount>
    </CodeHeader>
</allocelement>

分组是基于 Code,[hd1,hd2,hd3] 使得具有相同 Code 和 [hd1,hd2,hd3] 的不同元素将被合并,并且只显示不同的字段,即。和。 我也在使用 xslt 1.0 。

【问题讨论】:

  • 请发布您迄今为止在 XSLT 中所做的工作。

标签: xml xslt xslt-grouping


【解决方案1】:

一个更短更简单的 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="kCodeByVal" match="code" use="."/>

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

<xsl:template match="allocelement"/>

 <xsl:template match=
  "allocelement
    [generate-id(code) = generate-id(key('kCodeByVal', code)[1])]">
  <code><xsl:value-of select="code"/>
    <xsl:copy-of select=
    "node()[not(self::code)]
    | key('kCodeByVal', code)/../*[self::alc or self::amount]"/>
  </code>
 </xsl:template>
</xsl:stylesheet>

当此转换应用于以下 XML 文档时(包装提供的 XML 片段的单个顶部元素):

<t>
    <allocelement>
        <hd1>12</hd1>
        <hd2>14</hd2>
        <hd3>87</hd3>
        <alc>1</alc>
        <amount>4587</amount>
        <code>1111</code>
    </allocelement>
    <allocelement>
        <hd1>12</hd1>
        <hd2>14</hd2>
        <hd3>87</hd3>
        <alc>2</alc>
        <amount>80000</amount>
        <code>1111</code>
    </allocelement>
    <allocelement>
        <hd1>875</hd1>
        <hd2>455</hd2>
        <hd3>455</hd3>
        <alc>2</alc>
        <amount>80000</amount>
        <code>1112</code>
    </allocelement>
</t>

产生想要的正确结果

<allocelement>
   <code>1111<hd1>12</hd1>
      <hd2>14</hd2>
      <hd3>87</hd3>
      <alc>1</alc>
      <amount>4587</amount>
      <alc>2</alc>
      <amount>80000</amount>
   </code>
   <code>1112<hd1>875</hd1>
      <hd2>455</hd2>
      <hd3>455</hd3>
      <alc>2</alc>
      <amount>80000</amount>
   </code>
</allocelement>

说明:正确使用Muenchian grouping method


二。 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="/*">
  <allocelement>
   <xsl:for-each-group select="*" group-by="code">
     <code><xsl:value-of select="code"/>
        <xsl:sequence select=
        "node()[not(self::code)]
        | current-group()/*[self::alc or self::amount]"/>
  </code>
   </xsl:for-each-group>
  </allocelement>
 </xsl:template>
</xsl:stylesheet>

更新:OP改变了对输出的要求。

下面是相应的修改后的 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="kCodeByVal" match="code" use="."/>

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

    <xsl:template match="allocelement"/>

     <xsl:template match=
      "allocelement
        [generate-id(code) = generate-id(key('kCodeByVal', code)[1])]">
      <codeHeader>
       <code><xsl:value-of select="code"/></code>
         <xsl:copy-of select=
         "node()[not(self::code)]
         | key('kCodeByVal', code)/../*[self::alc or self::amount]"/>
      </codeHeader>
     </xsl:template>
</xsl:stylesheet>

【讨论】:

  • 它更短也是因为您“硬编码”了不重复的节点名称。另外,你能告诉我我的解决方案在语法或语义上不正确的地方吗? :)
  • @PavelVeller:您的 XSLT 解决方案使用未定义的键并导致编译错误。需要提供完整的转换——这不仅消除了任何可能的混淆,而且还显示了代码的真实大小。
  • @PavelVeller:解决方案更短(应该如此),因为我正在使用current-group() 函数——就像每个人一样。此外,在这种情况下,可以使用更短的表达式选择节点的所有元素兄弟姐妹 - 只需 ../*[not self::itsName]
  • 点了。尽管如此,您的解决方案仍绑定到这些标签名称。只需将&lt;something&gt;a&lt;/something&gt; 添加到allocelement&lt;code&gt;1111&lt;/code&gt; 的“第二次”出现中,看看它是如何没有进入结果树的。我尝试构建一个更通用的算法,这就是为什么我也对这些节点使用分组。同意你的解决方案在处理团体时更加优雅,我将不得不更多地练习这些技术:)
  • @PavelVeller:很好。至于我的解决方案是具体的,我认为在许多情况下,一个特定的解决方案正是需要的,因为我们无法正确猜测 OP 的所有可能上下文,并且因为经常添加通用性会使 OP 和读者感到困惑,同时对于他们的具体问题,有一个简短易懂的具体解决方案。可能正确的方法是首先提供简单的具体解决方案,然后才开始进行概括。
【解决方案2】:

我假设您的输入 XML 有一个 root 节点,该节点将所有内容包装起来以形成格式良好的文档。我还假设所有allocelement 都拼写正确(您有一些拼写为alloclement)。我还假设您要删除由标签名称及其文本值标识的重复项。

使用 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="code" match="allocelement/code" use="."/>

    <xsl:key name="code-sibling" 
             match="allocelement/*[name() != 'code']" 
             use="concat(parent::*/code, '|', name(), '|', .)"/>

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

    <xsl:template match="/">
        <xsl:apply-templates select="root/allocelement[1]"/>
    </xsl:template>

    <xsl:template match="allocelement">
        <xsl:copy>
            <xsl:apply-templates select="@*"/>
            <xsl:apply-templates 
                    select="(. | following-sibling::allocelement)/code
                                [generate-id() = generate-id(key('code', .)[1])]"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="code">
        <xsl:copy>
            <xsl:apply-templates select="@*"/>
            <xsl:value-of select="."/>

            <xsl:apply-templates 
                    select="key('code', .)/(preceding-sibling::* | following-sibling::*)
                                [generate-id() = 
                                 generate-id(key('code-sibling', concat(parent::*/code, '|', name(), '|', .))[1])]"/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

适用于:

<root>
    <allocelement>
        <hd1>12</hd1>
        <hd2>14</hd2>
        <hd3>87</hd3>
        <alc>1</alc>
        <amount>4587</amount>
        <code>1111</code>
    </allocelement>
    <allocelement>
        <hd1>12</hd1>
        <hd2>14</hd2>
        <hd3>87</hd3>
        <alc>2</alc>
        <amount>80000</amount>
        <code>1111</code>
    </allocelement>
    <allocelement>
        <hd1>875</hd1>
        <hd2>455</hd2>
        <hd3>455</hd3>
        <alc>2</alc>
        <amount>80000</amount>
        <code>1112</code>
     </allocelement>
</root>

产生:

<root>
   <allocelement>
      <code>1111<hd1>12</hd1>
         <hd2>14</hd2>
         <hd3>87</hd3>
         <alc>1</alc>
         <amount>4587</amount>
         <alc>2</alc>
         <amount>80000</amount>
      </code>
      <code>1112<hd1>875</hd1>
         <hd2>455</hd2>
         <hd3>455</hd3>
         <alc>2</alc>
         <amount>80000</amount>
      </code>
   </allocelement>
</root>

这是它的工作原理。默认路由是identity transform。我们首先中断allocelement 并将转换发送到不同的路线。我们让它复制唯一的code 元素(由元素的文本值标识的唯一性)及其值,然后在共享相同值的所有code 节点的所有前面/后面的兄弟姐妹上应用模板。这些现在将成为code 节点的子节点。然后我们为那些唯一的兄弟姐妹(属于相同值的code,具有相同的名称和相同的文本值)调用身份转换。

一个音符。 MIXED content 从来都不是一个好主意。看看是否真的需要在code下混合文本值和子节点。

使用 XSLT 2.0,您可以远离generate-id()s:

<xsl:template match="allocelement">
    <xsl:copy>
        <xsl:apply-templates select="@*"/>
        <xsl:for-each-group select="(. | following-sibling::allocelement)/code" group-by=".">
            <xsl:apply-templates select="."/>
        </xsl:for-each-group>
    </xsl:copy>
</xsl:template>

<xsl:template match="code">
    <xsl:copy>
        <xsl:apply-templates select="@*"/>
        <xsl:value-of select="."/>

        <xsl:for-each-group select="key('code', .)/(preceding-sibling::* | following-sibling::*)"
                            group-by="concat(parent::*/code, '|', name(), '|', .)">
            <xsl:apply-templates select="."/>
        </xsl:for-each-group> 
    </xsl:copy>
</xsl:template>

【讨论】:

  • 您为什么在 XSLT 2.0 解决方案中使用 key() 函数 -- 未定义?
  • @DimitreNovatchev,只是因为它在 1.0 版本中仍然存在。我只是想说明xsl:for-each-group
  • Pavel Veller:那么您可能想了解current-group() 函数。没有人将key()xsl:for-each-group 一起使用,后者的主要用例之一是避免使用密钥。
  • @DimitreNovaatchev 感谢您的提示,但我想我们正在谈论不同的事情。我使用key() 作为选择器的一部分,只是因为它给了我所有与当前节点共享相同值的code 节点。无论如何,这个论点对在这里提出问题的人没有任何好处:)
  • Pavel Veller:是的,选择所有这些 code 节点的一个很好的 XSLT 2.0 方法是使用 current-group() 函数——您可能希望习惯使用它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-01-23
  • 2023-03-31
  • 2021-11-24
  • 1970-01-01
  • 2019-08-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多