如何使用组相邻(XSLT)重新格式化XML

时间:2010-01-14 11:02:39

标签: xml xslt transformation

我是这个XSLT的新手,我无法弄清楚如何:

这是我从xml开始的片段:

<Article>    
<Bullettext>10,00 </Bullettext>  
<Bullettext>8,00 </Bullettext>    
</Article>  
<Article>  
<something>some text</something>  
</Article>  
<Article>  
<Corpsdetexte>Bulgaria</Corpsdetexte>  
<Bullettext>15,0 </Bullettext>  
<Bullettext>10,0 </Bullettext>  
</Article> ` 

这就是我想要的输出:

<LIST>  
<ITEM>12,00 </ITEM>  
<ITEM>10,00 </ITEM>  
<ITEM>8,00 </ITEM>  
</LIST>  
<P>  
<something>some text</something>  
</P>  

<P>  
<Corpsdetexte>Bulgaria</Corpsdetexte>  
</P>  
<LIST>  
<ITEM>15,0 </ITEM>  
<ITEM>10,0 </ITEM>  
</LIST>  

任何想法??

4 个答案:

答案 0 :(得分:5)

根据您对Rubens Farias的回答的评论(实际上,您应该编辑要包含的问题),似乎您需要一种通用的方法将任何一组相邻的BulletText元素转换为一个列表。这让我们得到两个问题:我们如何找到这些群体,并找到它们,我们如何将它们转换为列表?

要查找组,我们需要查找其前一个兄弟不是 BulletText元素的所有BulletText个元素。其中每一个都开始一个组,这些是我们要转换成列表的元素。所以我们要做的第一件事就是创建一个可以找到它们的XPath表达式:

BulletText[not(preceding-sibling::*[1][name()='BulletText'])]

如果你看一下XPath表达式中的谓词,那就是我所说的我们需要做的事情:它匹配一个BulletText元素,如果不是它的第一个前一个兄弟(preceding-sibling::*[1] )名称为BulletText。请注意,如果元素 没有先前的兄弟,则此表达式仍将匹配它。

现在我们可以创建一个匹配这些start-of-group元素的模板。我们在这个模板中放了什么?我们要将这些元素转换为LIST元素,因此模板开始看起来像:

<LIST>
   ...
</LIST>

够容易。但是,我们如何找到将填充该列表的元素?我们有两种情况需要处理。

第一个很简单:如果以下所有兄弟节点都是BulletText元素,我们希望使用此元素及其所有后续兄弟节点填充列表。

第二个更难。如果有一个不是BulletText元素的兄弟,我们希望我们的列表是当前元素父元素的所有子元素,从当前元素开始,到stop元素之前结束。下面是我们需要使用count()函数计算起始和结束索引的情况,以及position()函数来查找每个元素的位置。

完成的模板如下所示:

<xsl:template match="BulletText[not(preceding-sibling::*[1][name()='BulletText'])]">
  <!-- find the element that we want to stop at -->
  <xsl:variable name="stop" select="./following-sibling::*[name() != 'BulletText'][1]"/>
  <LIST>
    <xsl:choose>
      <!-- first, the simple case:  there's no element we have to stop at -->
      <xsl:when test="not($stop)">
        <xsl:apply-templates select="." mode="item"/>
        <xsl:apply-templates select="./following-sibling::BulletText" mode="item"/>
      </xsl:when>
      <!-- transform all elements between the start and stop index into items -->
      <xsl:otherwise>
        <xsl:variable name="start_index" select="count(preceding-sibling::*) + 1"/>
        <xsl:variable name="stop_index" select="count($stop/preceding-sibling::*)"/>
        <xsl:apply-templates select="../*[position() &gt;= $start_index 
                                      and position() &lt;= $stop_index]"
                             mode="item"/>
      </xsl:otherwise>
    </xsl:choose>
  </LIST>
</xsl:template>

您还需要两个其他模板。一个将BulletText个元素转换为项目 - 我们在此处使用mode,以便我们可以将其应用于BulletText元素,而无需调用我们当前使用的模板:

<xsl:template match="BulletText" mode="item">
   <ITEM>
      <xsl:value-of select="."/>
   </ITEM>
</xsl:template>

然后你还需要一个模板来保存我们的第一个模板匹配的BulletText个元素来生成任何输出(因为如果我们使用身份变换,他们会如果我们不这样做就被复制):

<xsl:template match='BulletText'/>

由于XSLT模板优先级规则的神奇之处,两个模板匹配的任何BulletText元素都将被第一个元素转换,而这个元素将会捕获其余元素。

只需将这三个模板添加到身份转换中,就可以了。

答案 1 :(得分:1)

我认为您正在寻找conditional deep-copy

以下是根据您的情况重写的上述链接中的代码:

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

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

    <!-- nodes with Bullettext children -->
    <xsl:template match="*[Bullettext]">
        <!-- for every child -->
        <xsl:copy>
            <xsl:for-each select="*">
                <!-- if child is a Bullettext and it has a Bullettext before it, don't copy it (it has already been copied) -->
                <xsl:if test="not(local-name(.) = 'Bullettext' and local-name(./preceding-sibling::*[1]) = 'Bullettext')">
                    <xsl:choose>
                        <xsl:when test="local-name(.) = 'Bullettext'">
                            <!-- copy all Bullettext children adjacent to this one and each other -->
                            <LIST>
                                <xsl:call-template name="get-all-adjacent-siblings">
                                    <xsl:with-param name="sibling-before" select="." />
                                </xsl:call-template>
                            </LIST>
                        </xsl:when>
                        <xsl:otherwise>
                            <!-- copy non-Bullettext child -->
                            <xsl:apply-templates select="." />
                        </xsl:otherwise>
                    </xsl:choose>
                </xsl:if>
            </xsl:for-each>
        </xsl:copy>
    </xsl:template>

    <xsl:template name="get-all-adjacent-siblings">
        <xsl:param name="sibling-before" />
        <!-- return me -->
        <xsl:copy>
            <xsl:value-of select="$sibling-before" />
        </xsl:copy>
        <!-- return my adjacent Bullettext siblings below me -->
        <xsl:if test="local-name($sibling-before/following-sibling::*[1]) = 'Bullettext'">
            <xsl:call-template name="get-all-adjacent-siblings">
                <xsl:with-param name="sibling-before" select="$sibling-before/following-sibling::*[1]" />
            </xsl:call-template>
        </xsl:if>
    </xsl:template>

</xsl:stylesheet>

我使用的输入是:

<?xml version="1.0" encoding="utf-8"?>
<Articles>
    <Article>
        <Bullettext>10,00 </Bullettext>
        <Bullettext>8,00 </Bullettext>
    </Article>
    <Article>
        <something>some text</something>
    </Article>
    <Article>
        <Corpsdetexte>Bulgaria</Corpsdetexte>
        <deeper>
            <before>dogs</before>
            <Bullettext>15,0 </Bullettext>
            <Bullettext>10,0 </Bullettext>
            <middle>cats</middle>
            <Bullettext>25,0 </Bullettext>
            <Bullettext>20,0 </Bullettext>
            <after>cows</after>
        </deeper>
    </Article>
</Articles>

它给了我:

<?xml version="1.0" encoding="UTF-8"?>
<Articles>
    <Article>
        <LIST>
            <Bullettext>10,00 </Bullettext>
            <Bullettext>8,00 </Bullettext>
        </LIST>
    </Article>
    <Article>
        <something>some text</something>
    </Article>
    <Article>
        <Corpsdetexte>Bulgaria</Corpsdetexte>
        <deeper>
            <before>dogs</before>
            <LIST>
                <Bullettext>15,0 </Bullettext>
                <Bullettext>10,0 </Bullettext>
            </LIST>
            <middle>cats</middle>
            <LIST>
                <Bullettext>25,0 </Bullettext>
                <Bullettext>20,0 </Bullettext>
            </LIST>
            <after>cows</after>
        </deeper>
    </Article>
</Articles>

如果你想进行其他转换,例如在同一个样式表中添加<p></p>,但是如果你使用两个样式表进行两步转换,第一个执行上面的条件深层复制,然后是第二,使用第一个结果进行主要转换,你应该好好去。

答案 2 :(得分:1)

使用三个模板对兄弟姐妹进行分组

虽然目前这个问题有几个可行的答案,但实际上你可以用三个模板和身份很容易地对兄弟姐妹进行分组。

首先,您需要一个只删除所有节点并设置其优先级的模板,以便我们可以使用另一个模板覆盖它。

<xsl:template match="Bullettext" priority="1"/>

然后,定义一个模板,该模板匹配任何不在其自身之前的节点,为其分配更高的优先级。此模板将注入组,然后开始以不同的模式复制节点。

<xsl:template match="Bullettext[not(preceding-sibling::*[1][self::Bullettext])]" priority="2">
  <LIST>
    <xsl:apply-templates select="." mode="bullet-list"/>
  </LIST>
</xsl:template>

最后,定义一个尾递归模板来处理被分组的项目。

<xsl:template match="Bullettext" mode="bullet-list">
  <ITEM>
    <xsl:apply-templates select="@*|node()"/>
  </ITEM>
  <xsl:apply-templates select="following-sibling::*[1][self::Bullettext]" mode="bullet-list"/>
</xsl:template>

以下是一个完整的样式表,它将对示例中的Bullettext元素进行分组:

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

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

  <!-- Removes the Bullettext elements in the default mode. -->
  <xsl:template match="Bullettext" priority="1" />

  <!-- Creates the LIST elements around the removed Bullettext elements. -->
  <xsl:template match="Bullettext[not(preceding-sibling::*[1][self::Bullettext])]" priority="2">
    <LIST>
      <xsl:apply-templates select="." mode="bullet-list" />
    </LIST>
  </xsl:template>

  <!-- Converts sequential Bullettext elements into ITEM elements. -->
  <xsl:template match="Bullettext" mode="bullet-list">
    <ITEM>
      <xsl:apply-templates select="@*|node()" />
    </ITEM>
    <xsl:apply-templates select="following-sibling::*[1][self::Bullettext]" mode="bullet-list" />
  </xsl:template>

</xsl:stylesheet>

答案 3 :(得分:0)

尝试这样的事情:

<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/Articles">
    <LIST>
      <xsl:for-each select="Article[1]/Bullettext">
        <ITEM>
          <xsl:value-of select="." />
        </ITEM>
      </xsl:for-each>
    </LIST>

    <p>
      <something>
        <xsl:value-of select="Article[2]/something" />
      </something>
    </p>

    <p>
      <Corpsdetexte>
        <xsl:value-of select="Article[3]/Corpsdetexte" />
      </Corpsdetexte>
    </p>

    <LIST>
      <xsl:for-each select="Article[4]/Bullettext">
        <ITEM>
          <xsl:value-of select="." />
        </ITEM>
      </xsl:for-each>
    </LIST>
  </xsl:template>
</xsl:stylesheet>