XSLT-XML到HTML的转换-从一级元素创建嵌套的<div>

时间:2018-08-13 05:18:25

标签: xml xslt xslt-2.0

我有一个如下的xml文件,

<doc>
    <para>111111111111</para>
    <!--start div1-->
    <para>2222222</para>
    <para>3333333</para>
    <!--start div2-->
    <para>4444444</para>
    <para>5555555</para>
    <para>6666666</para>
    <!--start div3-->
    <para>7777777</para>
    <para>8888888</para>
    <!--end div3-->
    <para>9999999</para>
    <para>1010110</para>
    <!--end div2-->
    <para>11,11,11</para>
    <para>12,12,12,</para>
    <!--end div1-->
    <para>13,13,13</para>
</doc>

我正在从此输入进行XSLT转换,我需要将这些元素放在嵌套的<div>元素上。在上一阶段添加了注释,以标识<div>元素应该去的位置。在给定的示例中,存在三个嵌套的div元素,但是可以更改。我的预期输出是

<doc>
    <para>111111111111</para>
    <div>
        <!--start div1-->
        <para>2222222</para>
        <para>3333333</para>
        <div>
            <!--start div2-->
            <para>4444444</para>
            <para>5555555</para>
            <para>6666666</para>
            <div>
                <!--start div3-->
                <para>7777777</para>
                <para>8888888</para>
                <!--end div3-->
            </div>
            <para>9999999</para>
            <para>1010110</para>
            <!--end div2-->
        </div>
        <para>11,11,11</para>
        <para>12,12,12,</para>
        <!--end div1-->
    </div>
    <para>13,13,13</para>
</doc>

我无法编写三个嵌套的<for-each>组,因为这些嵌套的<div>元素可以在其他输入中进行更改。 (可以转到嵌套的第四或第五级,等等。)

我尝试过以下方法,但是仅添加了第一个<div>元素,

<xsl:template match="doc">
        <doc>
            <xsl:for-each-group select="*|comment()" group-adjacent="
                if (self::comment())
                then
                substring-after(self::comment(),' ')
                else
                local-name()">

                <xsl:for-each-group select="current-group()" group-starting-with="*">
                    <div id="{local-name()}-{position()}">   
                        <xsl:copy-of select="current-group()" />      
                    </div>    
                </xsl:for-each-group>  

            </xsl:for-each-group>
        </doc>

    </xsl:template>

有人可以建议我实现这些嵌套的<div>元素的方法吗?或如何在XSLT中递归调用<for-each>

2 个答案:

答案 0 :(得分:2)

您可以在模板或函数中实现递归,以下是一个函数,因为它允许更紧凑的语法:

  <xsl:function name="mf:group" as="node()*">
      <xsl:param name="input" as="node()*"/>
      <xsl:param name="level" as="xs:integer"/>
      <xsl:for-each-group select="$input" group-starting-with="comment()[. = 'start div' || $level]">
          <xsl:choose>
              <xsl:when test="self::comment()[. = 'start div' || $level]">
                  <xsl:for-each-group select="current-group()" group-ending-with="comment()[. = 'end div' || $level]">
                      <xsl:choose>
                          <xsl:when test="current-group()[last()][self::comment()[. = 'end div' || $level]]">
                              <div>
                                  <xsl:apply-templates select="."/>
                                  <xsl:sequence select="mf:group(current-group()[not(position() = (1, last()))], $level + 1)"/>
                                  <xsl:apply-templates select="current-group()[last()]"/>
                              </div>                              
                          </xsl:when>
                          <xsl:otherwise>
                              <xsl:sequence select="current-group()"/>
                          </xsl:otherwise>
                      </xsl:choose>
                  </xsl:for-each-group>
              </xsl:when>
              <xsl:otherwise>
                  <xsl:apply-templates select="current-group()"/>
              </xsl:otherwise>
          </xsl:choose>
      </xsl:for-each-group>
  </xsl:function>

完整的示例是(XSLT 3,但是对于XSLT 2,您只需要使用||调用来代替对concat运算符的使用):

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:mf="http://example.com/mf"
    exclude-result-prefixes="xs mf"
    version="3.0">

  <xsl:function name="mf:group" as="node()*">
      <xsl:param name="input" as="node()*"/>
      <xsl:param name="level" as="xs:integer"/>
      <xsl:for-each-group select="$input" group-starting-with="comment()[. = 'start div' || $level]">
          <xsl:choose>
              <xsl:when test="self::comment()[. = 'start div' || $level]">
                  <xsl:for-each-group select="current-group()" group-ending-with="comment()[. = 'end div' || $level]">
                      <xsl:choose>
                          <xsl:when test="current-group()[last()][self::comment()[. = 'end div' || $level]]">
                              <div>
                                  <xsl:apply-templates select="."/>
                                  <xsl:sequence select="mf:group(current-group()[not(position() = (1, last()))], $level + 1)"/>
                                  <xsl:apply-templates select="current-group()[last()]"/>
                              </div>                              
                          </xsl:when>
                          <xsl:otherwise>
                              <xsl:sequence select="current-group()"/>
                          </xsl:otherwise>
                      </xsl:choose>
                  </xsl:for-each-group>
              </xsl:when>
              <xsl:otherwise>
                  <xsl:apply-templates select="current-group()"/>
              </xsl:otherwise>
          </xsl:choose>
      </xsl:for-each-group>
  </xsl:function>

  <xsl:output indent="yes"/>

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

  <xsl:template match="doc">
      <xsl:copy>
          <xsl:sequence select="mf:group(node(), 1)"/>
      </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/94hvTzt

答案 1 :(得分:0)

<xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="comment()[.='start div1' or .='start div2' or .='start div3' or .='end div3' or  .='end div2' or .='end div1']">
        <xsl:choose>
            <xsl:when test=". ='start div1'">
                <xsl:text disable-output-escaping="yes">&lt;div&gt;</xsl:text>
                <xsl:text disable-output-escaping="yes">&lt;!--start div1--&gt;</xsl:text>
            </xsl:when>
            <xsl:when test=". ='start div2'">
                <xsl:text disable-output-escaping="yes">&lt;div&gt;</xsl:text>
                <xsl:text disable-output-escaping="yes">&lt;!--start div2--&gt;</xsl:text>
            </xsl:when>
            <xsl:when test=". ='start div3'">
                <xsl:text disable-output-escaping="yes">&lt;div&gt;</xsl:text>
                <xsl:text disable-output-escaping="yes">&lt;!--start div3--&gt;</xsl:text>
            </xsl:when>
            <xsl:when test=". ='end div3'">
                <xsl:text disable-output-escaping="yes">&lt;!--end div3--&gt;</xsl:text>
                <xsl:text disable-output-escaping="yes">&lt;/div&gt;</xsl:text>
            </xsl:when>
            <xsl:when test=". ='end div2'">
                <xsl:text disable-output-escaping="yes">&lt;!--end div2--&gt;</xsl:text>
                <xsl:text disable-output-escaping="yes">&lt;/div&gt;</xsl:text>
            </xsl:when>
            <xsl:when test=". ='end div1'">
                <xsl:text disable-output-escaping="yes">&lt;!--end div1--&gt;</xsl:text>
                <xsl:text disable-output-escaping="yes">&lt;/div&gt;</xsl:text>
            </xsl:when>
        </xsl:choose>
    </xsl:template>
You may try like this