XSLT 将顺序 XML 转换为分层 XML

Posted

技术标签:

【中文标题】XSLT 将顺序 XML 转换为分层 XML【英文标题】:XSLT Transforming sequential XML to hierarchical XML 【发布时间】:2012-11-24 16:47:03 【问题描述】:

我需要将顺序 XML 节点列表转换为层次结构,但我遇到了一些 XSLT 特定的知识缺口。 输入 XML 包含文章、颜色和尺寸。在下面的示例中,“Record1”是一篇文章,“Record2”代表一种颜色,“Record3”是尺寸。颜色和大小(record2 和 record3)元素的数量可能会有所不同。

<root>
 <Record1>...</Record1>
 <Record2>...</Record2>
 <Record3>...</Record3>
 <Record3>...</Record3>
 <Record2>...</Record2>
 <Record3>...</Record3>
 <Record3>...</Record3>
 <Record3>...</Record3>
 <Record3>...</Record3>
 <Record1>...</Record1>
 <Record2>...</Record2>
 <Record3>...</Record3>
 <Record3>...</Record3>
 <Record2>...</Record2>
 <Record3>...</Record3>
 <Record3>...</Record3>
 <Record3>...</Record3>
 <Record3>...</Record3>
</root> 

所有字段都在同一层级上,但我仍然必须创建这个结构作为输出:

<root>
 <article>              -> Record1
  <color>               -> Record2
   <size>...</size>     -> Record3
   <size>...</size>     -> Record3
  </color>
  <color>               -> Record2
   <size>...</size>     -> Record3
   <size>...</size>     -> Record3
   <size>...</size>     -> Record3
   <size>...</size>     -> Record3
  </color>
 </article>
 <article>              -> Record1
  <color>               -> Record2
   <size>...</size>     -> Record3
   <size>...</size>     -> Record3
  </color>
  <color>               -> Record2
   <size>...</size>     -> Record3
   <size>...</size>     -> Record3
   <size>...</size>     -> Record3
   <size>...</size>     -> Record3
  </color>
 </article>
</root>

我尝试按顺序迭代节点,但例如,在处理“颜色”(=record2) 节点时,“文章”(=record1) 节点标记需要保持未关闭状态。处理未关闭“颜色”的“大小”(=record3) 的计数相同,但 XSLT 不允许这样做。 我的下一个想法是为每篇文章、颜色和大小级别调用一个模板,但我不知道如何选择当前“record2”和“record1”表示的下一篇文章之间的所有“record3”节点。

我对 XSLT 版本也有限制,因为我需要在仅支持 XSLT 1.0 的 BizTalk Server 中进行这种转换

有人能把我推向正确的方向吗?

【问题讨论】:

类似问题 --> ***.com/questions/9207795/… Record1 和 Record2 的文本值 (...) 会发生什么变化? Jean-Paul Smit,您可能对更简单且可能更有效的基于键的转换感兴趣。 【参考方案1】:

DevNull 和 Novatchev 的解决方案都是可行的。第三种解决方案,在我看来最优雅的一个,是使用“兄弟递归”。我不会给出完整的代码,但你可以从这样的根模板规则开始:

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

然后为不同的“级别”设置模板规则,如下所示:

<xsl:template match="record1">
  <section1>
    <xsl:apply-templates select="following-sibling::*[1][self::record2]"/>
  </section1>
  <xsl:apply-templates select="following-sibling::record1[1]"/>
</xsl:template>

每个级别都类似。这个解决方案可能比其他解决方案更快、更简洁,尽管在你习惯之前它可能有点“令人费解”。理解它的关键是,在每一层,你首先处理第一个“逻辑子”——即平面序列中的下一个元素,前提是它比当前元素更深;然后你处理下一个“逻辑兄弟”——平面序列中与当前元素处于同一级别的下一个元素。当然,该解决方案可以适用于可能缺少级别或级别编号由属性而不是元素名称指示的情况。它甚至可以适用于事先不知道级别数的情况:您只需使用一个规则,其中“第一个逻辑子”和“下一个逻辑兄弟”的选择被适当地参数化。

【讨论】:

【参考方案2】:

这是一个更简单且可能更有效的转换,使用键:

<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="kColors" match="Record2"
          use="generate-id(preceding-sibling::Record1[1])"/>

 <xsl:key name="kSizes" match="Record3"
          use="generate-id(preceding-sibling::Record2[1])"/>

 <xsl:template match="/*">
  <root>
    <xsl:apply-templates select="Record1"/>
  </root>
 </xsl:template>

 <xsl:template match="Record1">
   <article>
     <xsl:apply-templates select="key('kColors', generate-id())"/>
   </article>
 </xsl:template>

 <xsl:template match="Record2">
  <color>
     <xsl:apply-templates select="key('kSizes', generate-id())"/>
  </color>
 </xsl:template>

 <xsl:template match="Record3">
  <size><xsl:apply-templates/></size>
 </xsl:template>
</xsl:stylesheet>

当此转换应用于提供的 XML 文档时:

<root>
    <Record1>article1</Record1>
    <Record2>color1</Record2>
    <Record3>size1</Record3>
    <Record3>size2</Record3>
    <Record2>color2</Record2>
    <Record3>size3</Record3>
    <Record3>size4</Record3>
    <Record3>size5</Record3>
    <Record3>size6</Record3>
    <Record1>article2</Record1>
    <Record2>color3</Record2>
    <Record3>size7</Record3>
    <Record3>size8</Record3>
    <Record2>color4</Record2>
    <Record3>size9</Record3>
    <Record3>size10</Record3>
    <Record3>size11</Record3>
    <Record3>size12</Record3>
</root>

产生了想要的正确结果:

<root>
   <article>
      <color>
         <size>size1</size>
         <size>size2</size>
      </color>
      <color>
         <size>size3</size>
         <size>size4</size>
         <size>size5</size>
         <size>size6</size>
      </color>
   </article>
   <article>
      <color>
         <size>size7</size>
         <size>size8</size>
      </color>
      <color>
         <size>size9</size>
         <size>size10</size>
         <size>size11</size>
         <size>size12</size>
      </color>
   </article>
</root>

解释

    名为kColors 的键将Record2 (color) 元素的集合表示为其Record1 (article) 逻辑父元素的函数。

    同样,名为kSizes 的键将Record3 (size) 元素的集合表示为它们的Record2 (color) 逻辑父元素的函数。

【讨论】:

感谢您的回复。虽然我没有尝试过,但这似乎是一个可行的解决方案。 @Jean-PaulSmit,不客气。我鼓励您尝试所有三种解决方案,以便您能够理解它们并尽可能多地学习。【参考方案3】:

这是一个 XSLT 1.0 选项。我不确定您想对 Record1 和 Record2 的值做什么,所以我将它们放在 val 属性中。

XML 输入

<root>
    <Record1>article1</Record1>
    <Record2>color1</Record2>
    <Record3>size1</Record3>
    <Record3>size2</Record3>
    <Record2>color2</Record2>
    <Record3>size3</Record3>
    <Record3>size4</Record3>
    <Record3>size5</Record3>
    <Record3>size6</Record3>
    <Record1>article2</Record1>
    <Record2>color3</Record2>
    <Record3>size7</Record3>
    <Record3>size8</Record3>
    <Record2>color4</Record2>
    <Record3>size9</Record3>
    <Record3>size10</Record3>
    <Record3>size11</Record3>
    <Record3>size12</Record3>
</root>

XSLT 1.0

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="/*">
        <xsl:copy>
            <xsl:apply-templates select="Record1"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="Record1">
        <article val=".">
            <xsl:apply-templates select="following-sibling::Record2[generate-id(preceding-sibling::Record1[1])=generate-id(current())]"/>
        </article>
    </xsl:template>

    <xsl:template match="Record2">
        <color val=".">
            <xsl:apply-templates select="following-sibling::Record3[generate-id(preceding-sibling::Record2[1])=generate-id(current())]"/>
        </color>
    </xsl:template>

    <xsl:template match="Record3">
        <size>
            <xsl:value-of select="."/>
        </size>
    </xsl:template>

</xsl:stylesheet>

XML 输出

<root>
   <article val="article1">
      <color val="color1">
         <size>size1</size>
         <size>size2</size>
      </color>
      <color val="color2">
         <size>size3</size>
         <size>size4</size>
         <size>size5</size>
         <size>size6</size>
      </color>
   </article>
   <article val="article2">
      <color val="color3">
         <size>size7</size>
         <size>size8</size>
      </color>
      <color val="color4">
         <size>size9</size>
         <size>size10</size>
         <size>size11</size>
         <size>size12</size>
      </color>
   </article>
</root>

【讨论】:

你是 XSLT 的他妈的魔术师!非常感谢。我已经尝试过了,它有效!关于这些东西,我有很多东西要学。 @Jean-PaulSmit - 哈哈。没问题。可能有更有效的方法,但我不经常在 1.0 中进行这种类型的转换。

以上是关于XSLT 将顺序 XML 转换为分层 XML的主要内容,如果未能解决你的问题,请参考以下文章

使用 Python 或 XSLT 将复杂的 XML 转换为 CSV

XSLT 通用解决方案,用于从 XML 中获取分层 html 表

在 Java 中通过 XSLT 进行 XML 粉碎

使用 Xslt 将 XML 转换为 XML

XSLT 转换 XML:选择 uuid 并按顺序排序

使用带有条件的 XSLT 将 XML 转换为 XML