使用XSLT展平:我想将一种元素移动一级

时间:2019-01-02 18:18:18

标签: xslt

我有一个XML文件,其中元素B在元素A内,我想将它们向上移动。发件人:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <A>
    <C>Text</C>
    Text again

    More text
    <D>Other text</D>
    <B>Text again</B>
    <C>No</C>
    <D>May be</D>
    <B>What?</B>
  </A>
  <A>
    Something
    <B>Nothing</B>
    <D>Again</D>
    <B>Content</B>
    End
  </A>
</root>

我想要:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <A>
    <C>Text</C>
    Text again

    More text
    <D>Other text</D>
  </A>
  <B>Text again</B>
  <A>
    <C>No</C>
    <D>May be</D>
  </A>
  <B>What?</B>
  <A>
    Something
  </A>
  <B>Nothing</B>
  <A>
    <D>Again</D>
  </A>
  <B>Content</B>
  <A>
    End
  </A>
</root>

我拥有的最接近的XSLT程序是这样的:

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

    <xsl:output method="xml" indent="yes"/>

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

    <xsl:template match="A">
      <xsl:for-each select="*">
    <xsl:choose>
      <xsl:when test="name()='B'">
            <xsl:apply-templates select="."/>
      </xsl:when>
      <xsl:otherwise>
                  <xsl:element name="A">
                      <xsl:apply-templates select="."/>
          </xsl:element>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:for-each>
    </xsl:template>

</xsl:stylesheet>

它有两个问题:它忽略文本节点(这可能只是将| text()添加到select =“ *”的问题),但是更重要的是,它为每个节点创建了一个元素,而我希望它们在一起。例如,上面的样式表可以:

<A><C>No</C></A>
<A><D>May be</D></A>

我想要的地方:

<A><C>No</C>
   <D>May be</D></A>

在我的XML文件中,始终是的直接子项,没有或嵌套。

主要用例是生成HTML,其中UL和OL不能在P内。

该问题与xslt flattening out child elements in a DocBook para element相关,但不相同(也可能与Flatten xml hierarchy using XSLT相关 )

3 个答案:

答案 0 :(得分:2)

正如我在对您的问题的评论中所说的那样,这不是是关于将元素按层次结构向上移动。它是关于分组节点,并为每个由分隔A元素确定的组创建新的父B元素。

在XSLT 1.0中,这可以使用所谓的同级递归来实现:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:template match="/root">
    <xsl:copy>
        <xsl:apply-templates select="A"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="A">
    <xsl:copy>
        <xsl:apply-templates select="node()[1][not(self::B)]" mode="sibling"/>
    </xsl:copy>
    <xsl:apply-templates select="B[1]" mode="sibling"/>
</xsl:template>

<xsl:template match="node()" mode="sibling">
    <xsl:copy-of select="." />
    <xsl:apply-templates select="following-sibling::node()[1][not(self::B)]" mode="sibling"/>
</xsl:template>

<xsl:template match="B" mode="sibling">
    <xsl:copy-of select="." />
    <xsl:if test="following-sibling::node()[normalize-space()]">
        <A>
            <xsl:apply-templates select="following-sibling::node()[1][not(self::B)]" mode="sibling"/>
        </A>
        <xsl:apply-templates select="following-sibling::B[1]" mode="sibling"/>
    </xsl:if>
</xsl:template>

</xsl:stylesheet>

答案 1 :(得分:1)

以下是一个非常丑陋的XSLT-1.0解决方案。输出是所需的,但仅适用于此简单的MCVE。如评论中提到的@ michael.hor257k,一般的解决方案要复杂得多。没有更多数据,就不可能在XSLT-1.0中创建更好的解决方案。 XSLT-2.0及更高版本的解决方案可以简化此过程。

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:template match="/root">
        <xsl:copy>
            <xsl:for-each select="A">
                <xsl:if test="normalize-space(text()[1])">
                    <A>
                        <xsl:copy-of select="text()[1]" />
                    </A>
                </xsl:if>
                <xsl:if test="preceding::*">
                    <xsl:copy-of select="B[1]" />
                </xsl:if>
                <A>
                    <xsl:copy-of select="C[1] | C[1]/following-sibling::text()[1] | D[1]" />
                </A>
                <xsl:if test="not(preceding::*)">
                    <xsl:copy-of select="B[1]" />
                </xsl:if>
                <A>
                    <xsl:copy-of select="C[2] | C[2]/following-sibling::text()[1]" />
                    <xsl:if test="D[2]">
                        <xsl:copy-of select="D[2]" />
                    </xsl:if>
                </A>
                <xsl:copy-of select="B[2]" />
                <xsl:if test="normalize-space(text()[last()])">
                    <A>
                        <xsl:copy-of select="text()[last()]" />
                    </A>
                </xsl:if>
            </xsl:for-each>
        </xsl:copy>
    </xsl:template>         

</xsl:stylesheet>

关于情况

<A><C>No</C></A>
<A><D>May be</D></A>

在上面的代码中对其进行了适当的处理。所以它的输出是

<A>
    <C>No</C>
    <D>May be</D>
</A>

答案 2 :(得分:0)

使用group-adjacent=". instance of element(B)"group-adjacent="boolean(self::B)"在XSLT 2或3中很容易,这是一个XSLT 3示例(Java和.NET(https://sourceforge.net/projects/saxon/files/Saxon-HE/)上的Saxon 9.8或9.9支持XSLT 3。以及自2017年以来由Altova发布):

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

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

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

  <xsl:template match="A">
      <xsl:for-each-group select="node()" group-adjacent=". instance of element(B)">
          <xsl:choose>
              <xsl:when test="current-grouping-key()">
                  <xsl:apply-templates select="current-group()"/>
              </xsl:when>
              <xsl:otherwise>
                  <xsl:copy select="..">
                      <xsl:apply-templates select="current-group()"/>
                  </xsl:copy>
              </xsl:otherwise>
          </xsl:choose>
      </xsl:for-each-group>
  </xsl:template>

</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/gWmuiKv

在XSLT 2中,您需要拼写<xsl:mode on-no-match="shallow-copy"/>作为身份转换模板,并使用xsl:element代替xsl:copy

<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">

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

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

  <xsl:template match="A">
      <xsl:for-each-group select="node()" group-adjacent=". instance of element(B)">
          <xsl:choose>
              <xsl:when test="current-grouping-key()">
                  <xsl:apply-templates select="current-group()"/>
              </xsl:when>
              <xsl:otherwise>
                  <xsl:element name="{name(..)}" namespace="{namespace-uri(..)}">
                      <xsl:apply-templates select="current-group()"/>
                  </xsl:element>
              </xsl:otherwise>
          </xsl:choose>
      </xsl:for-each-group>
  </xsl:template>

</xsl:transform>

http://xsltransform.hikmatu.com/pPqsHT2