如何使用XSLT动态嵌套元素?

时间:2019-01-28 05:58:09

标签: xslt

我正在尝试进行身份转换。这是我的源xml的示例:

<?xml version="1.0" encoding="UTF-8"?>
<text>
    <p id="542">This is a parapgraph</p>
    <p id="561">This is a first level bullet</p>
    <p id="561">This is a first level bullet</p>
    <p id="561">This is a first level bullet</p>
    <p id="561">This is a first level bullet</p>
    <p id="561">This is a first level bullet</p>
    <p id="542">This is a parapgraph</p>
    <p id="561">This is a first level bullet</p>
    <p id="562">This is a second level bullet</p>
    <p id="562">This is a second level bullet</p>
    <p id="561">This is a first level bullet</p>
    <p id="561">This is a first level bullet</p>
    <p id="542">This is a parapgraph</p>
    <p id="542">This is a parapgraph</p>
    <p id="560">This is a first ordered list</p>
    <p id="560">This is a first ordered list</p>
    <p id="560">This is a first ordered list</p>
    <p id="562">This is a second level bullet</p>
    <p id="562">This is a second level bullet</p>

</text>enter code here

我正在寻找类似于以下内容的输出:

<?xml version="1.0" encoding="UTF-8"?>
<text>
    <p id="">This is a parapgraph</p>
    <ul>
    <li>This is a first level bullet</li>
    <li>This is a first level bullet</li>
    <li>This is a first level bullet</li>
    <li>This is a first level bullet</li>
    <li>This is a first level bullet</li>
    </ul>
    <p id="">This is a parapgraph</p>
    <ul>
    <li>This is a first level bullet
        <ul>
            <li>This is a second level bullet</li>
            <li>This is a second level bullet</li>
            <li>This is a second level bullet</li>
        </ul></li>
    <li>This is a first level bullet</li>
    <li>This is a first level bullet</li>
    </ul>
    <p id="">This is a parapgraph</p>
    <p id="">This is a parapgraph</p>
    <ol>
    <li>This is a first ordered list</li>
    <li>This is a first ordered list</li>
    <li>This is a first ordered list</li>
    <li>This is a first level bullet
        <ul>
            <li>This is a second level bullet</li>
            <li>This is a second level bullet</li>
        </ul>
    </li>
    <li>This is a first level bullet</li>
    </ol>
</text>

能否请您帮我了解该怎么做?我是XSLT的新手。我正在寻找一种执行类似操作但找不到的教程。 我当前的XSLT如下所示:

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

    <xsl:output method="xml" standalone="no"/>

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


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

    <xsl:template match="topic">
        <book>
        <xsl:attribute name="id">
            <xsl:value-of select="generate-id(.)"/>
        </xsl:attribute>
        <xsl:apply-templates select="@*|node()"/>

        </book>
    </xsl:template>

    <xsl:template match="body">
        <text>

                <xsl:apply-templates select="p[@id='542']"/>
                <xsl:call-template name="f-ul"/>
                <xsl:call-template name="s-ul"/>
                <xsl:call-template name="o-l"/>

        </text>
    </xsl:template>

    <!-- all body text style to p tag> -->
    <xsl:template match="p[@id='542']">
        <p><xsl:apply-templates select="@*|node()" /></p>
    </xsl:template>

    <xsl:template name="f-ul">
    <xsl:if test="p[@id='561']">
        <ul>
            <xsl:attribute name="id">
                <xsl:text></xsl:text>
            </xsl:attribute>
            <xsl:apply-templates select="p[@id='561']"/>
        </ul>
    </xsl:if>
    </xsl:template>

    <xsl:template name="s-ul">
    <xsl:if test="p[@id='562']">
        <ul>
            <xsl:attribute name="id">
                <xsl:text></xsl:text>
            </xsl:attribute>
            <xsl:apply-templates select="p[@id='562']"/>
        </ul>
    </xsl:if>
    </xsl:template>

    <xsl:template name="o-l">
    <xsl:if test="p[@id='560']">
        <ol>
            <xsl:attribute name="id">
                <xsl:text></xsl:text>
            </xsl:attribute>
            <xsl:apply-templates select="p[@id='560']"/>
        </ol>
    </xsl:if>
    </xsl:template>



    <!-- all list number style to step tag> -->

    <xsl:template match="p[@id='560']">
        <li><xsl:apply-templates select="@*|node()" /></li>
    </xsl:template>


    <!-- all list bullet style to li tag> -->

    <xsl:template match="p[@id='561']">
        <li><xsl:apply-templates select="node()" /></li>
    </xsl:template>

    <!-- all list bullet 2 style to li tag> -->
    <xsl:template match="p[@id='562']">
        <li><xsl:apply-templates select="node()" /></li>
    </xsl:template>

</xsl:stylesheet>

如果您可以帮助我找到解决方案,或者至少将我定向到正确的资源,将不胜感激。谢谢大家。

1 个答案:

答案 0 :(得分:1)

总是很难从示例中进行操作,并且我不确定所显示的所需结果是否一致,我建议进行两步转换,第一个确定li项并设置ol/ul,第二个确定任何内部列表都包裹在前面的li中。

这是第一步的XSLT 3方法(适用于Java和.NET的Saxon 9.8或9.9,以及oXygen或Stylus Studio等XML IDE中也由Altova Raptor在其产品线中实现的自2017年发行以来))第一步:

<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="#all"
    version="3.0">

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

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

  <xsl:param name="list-levels" as="map(xs:integer, xs:integer+)"
    select="map { 1 : (560, 561), 2: (562) }"/>

  <xsl:param name="list-map" as="map(xs:integer, xs:QName)"
    select="map { 560 : QName('', 'ol'), 561 : QName('', 'ul'), 562 : QName('', 'ul') }"/>

  <xsl:function name="mf:group" as="node()*">
      <xsl:param name="nodes" as="node()*"/>
      <xsl:param name="level" as="xs:integer"/>
      <xsl:for-each-group select="$nodes" group-adjacent="boolean(self::p[@id = $list-levels?($level to 6)])">
          <xsl:choose>
              <xsl:when test="current-grouping-key()">
                  <xsl:element name="{$list-map(xs:integer(@id))}">
                      <xsl:sequence select="mf:group(current-group(), $level + 1)"/>
                  </xsl:element>                  
              </xsl:when>
              <xsl:otherwise>
                  <xsl:apply-templates select="current-group()"/>
              </xsl:otherwise>
          </xsl:choose>
      </xsl:for-each-group>
  </xsl:function>

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

  <xsl:template match="p[@id = 542]">
      <xsl:copy>
          <xsl:apply-templates/>
      </xsl:copy>
  </xsl:template>

  <xsl:template match="p[@id = $list-levels?*]">
      <li>
          <xsl:apply-templates/>
      </li>
  </xsl:template>

</xsl:stylesheet>

您可以将其设置为在https://xsltfiddle.liberty-development.net/pPzifoZ上运行,结果如下:

<text>
   <p>This is a parapgraph</p>
   <ul>
      <li>This is a first level bullet</li>
      <li>This is a first level bullet</li>
      <li>This is a first level bullet</li>
      <li>This is a first level bullet</li>
      <li>This is a first level bullet</li>
   </ul>
   <p>This is a parapgraph</p>
   <ul>
      <li>This is a first level bullet</li>
      <ul>
         <li>This is a second level bullet</li>
         <li>This is a second level bullet</li>
      </ul>
      <li>This is a first level bullet</li>
      <li>This is a first level bullet</li>
   </ul>
   <p>This is a parapgraph</p>
   <p>This is a parapgraph</p>
   <ol>
      <li>This is a first ordered list</li>
      <li>This is a first ordered list</li>
      <li>This is a first ordered list</li>
      <ul>
         <li>This is a second level bullet</li>
         <li>This is a second level bullet</li>
      </ul>
   </ol>
</text>

第二步,我添加了处理嵌套ulol的包装的模式:

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

  <xsl:template match="ul | ol" mode="wrap">
      <xsl:copy>
          <xsl:for-each-group select="*" group-starting-with="li">
              <xsl:copy>
                  <xsl:apply-templates select="node(), current-group() except ." mode="#current"/>
              </xsl:copy>
          </xsl:for-each-group>
      </xsl:copy>
  </xsl:template>

  <xsl:template match="text">
      <xsl:copy>
          <xsl:apply-templates select="mf:group(*, 1)" mode="wrap"/>
      </xsl:copy>
  </xsl:template>

https://xsltfiddle.liberty-development.net/pPzifoZ/1处的示例产生输出

<text>
   <p>This is a parapgraph</p>
   <ul>
      <li>This is a first level bullet</li>
      <li>This is a first level bullet</li>
      <li>This is a first level bullet</li>
      <li>This is a first level bullet</li>
      <li>This is a first level bullet</li>
   </ul>
   <p>This is a parapgraph</p>
   <ul>
      <li>This is a first level bullet<ul>
            <li>This is a second level bullet</li>
            <li>This is a second level bullet</li>
         </ul>
      </li>
      <li>This is a first level bullet</li>
      <li>This is a first level bullet</li>
   </ul>
   <p>This is a parapgraph</p>
   <p>This is a parapgraph</p>
   <ol>
      <li>This is a first ordered list</li>
      <li>This is a first ordered list</li>
      <li>This is a first ordered list<ul>
            <li>This is a second level bullet</li>
            <li>This is a second level bullet</li>
         </ul>
      </li>
   </ol>
</text>

完整代码是

<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="#all"
    version="3.0">

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

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

  <xsl:param name="list-levels" as="map(xs:integer, xs:integer+)"
    select="map { 1 : (560, 561), 2: (562) }"/>

  <xsl:param name="list-map" as="map(xs:integer, xs:QName)"
    select="map { 560 : QName('', 'ol'), 561 : QName('', 'ul'), 562 : QName('', 'ul') }"/>

  <xsl:function name="mf:group" as="node()*">
      <xsl:param name="nodes" as="node()*"/>
      <xsl:param name="level" as="xs:integer"/>
      <xsl:for-each-group select="$nodes" group-adjacent="boolean(self::p[@id = $list-levels?($level to 6)])">
          <xsl:choose>
              <xsl:when test="current-grouping-key()">
                  <xsl:element name="{$list-map(xs:integer(@id))}">
                      <xsl:sequence select="mf:group(current-group(), $level + 1)"/>
                  </xsl:element>                  
              </xsl:when>
              <xsl:otherwise>
                  <xsl:apply-templates select="current-group()"/>
              </xsl:otherwise>
          </xsl:choose>
      </xsl:for-each-group>
  </xsl:function>

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

  <xsl:template match="ul | ol" mode="wrap">
      <xsl:copy>
          <xsl:for-each-group select="*" group-starting-with="li">
              <xsl:copy>
                  <xsl:apply-templates select="node(), current-group() except ." mode="#current"/>
              </xsl:copy>
          </xsl:for-each-group>
      </xsl:copy>
  </xsl:template>

  <xsl:template match="text">
      <xsl:copy>
          <xsl:apply-templates select="mf:group(*, 1)" mode="wrap"/>
      </xsl:copy>
  </xsl:template>

  <xsl:template match="p[@id = 542]">
      <xsl:copy>
          <xsl:apply-templates/>
      </xsl:copy>
  </xsl:template>

  <xsl:template match="p[@id = $list-levels?*]">
      <li>
          <xsl:apply-templates/>
      </li>
  </xsl:template>

</xsl:stylesheet>

示例使用XSLT 3,但是分组在XSLT 2中将是相同的,只需要将p id到元素和列表级别的映射存储在某种XML结构而不是XPath {{1 }} s我在上面使用过。

限制:在map中,我选择了最大嵌套级别6,但是可以很容易地对其进行调整。

但是,选择一些更智能的表示形式(如数组)将使我们避免定义最高级别,因此,像https://xsltfiddle.liberty-development.net/pPzifoZ/2一样,先使用$level to 6然后使用<xsl:param name="list-levels" as="array(xs:integer+)" select="[ (560, 561), (562) ]"/>,就没有了需要定义一个硬编码到XSLT中的最高级别。