XSLT是否为平面到嵌套/分层,具有级别插值?

时间:2016-10-27 00:06:39

标签: xslt

鉴于此源XML文档: input.xml

<body>
  <p ilvl="1">content</p>
  <p ilvl="1">content</p>
  <p ilvl="2">content</p>
  <p ilvl="3">content</p>

  <p ilvl="1">content</p>
  <p ilvl="2">content</p>
  <p ilvl="2">content</p>
  <p ilvl="3">content</p>
  <p ilvl="1">content</p>
  <p ilvl="1">content</p>
  <p ilvl="3">content</p>
</body>

我想转换为output.xml:

<list>
  <item>
    <list>
      <item>
        <p ilvl="1">content</p>
      </item>
      <item>
        <p ilvl="1">content</p>
        <list>
          <item>
            <p ilvl="2">content</p>
            <list>
              <item>
                <p ilvl="3">content</p>
              </item>
            </list>
          </item>
        </list>
      </item>
    </list>
  </item>
  <item>
    <p ilvl="1">content</p>
    <list>
      <item>
        <p ilvl="2">content</p>

属性ilvl是列表级别;它是一个从零开始的指数。

我尝试调整https://stackoverflow.com/a/11117548/1031689并得到输出:

<rs>
   <p ilvl="1"/>
   <p ilvl="1">
      <p ilvl="2">
         <p ilvl="3"/>
      </p>
   </p>
   <p ilvl="1">
      <p ilvl="2"/>
      <p ilvl="2">
         <p ilvl="3"/>
      </p>
   </p>
   <p ilvl="1"/>
   <p ilvl="1">
      <p ilvl="3"/>
   </p>
</rs>

我有两个问题:

  • 它不会为任何缺失的级别创建结构(例如,在1和3之间缺少级别2),并且
  • 起始参数级别必须与第一个条目匹配(即此处为1)。如果传递0,则嵌套错误。

在我尝试这个之前,我使用的是我自己的XSLT 1.0代码,附在下面。

棘手的部分是如何处理嵌套的减少,例如3级到1级:

  <p ilvl="3">content</p>
  <p ilvl="1">content</p> 

已更新

我尝试在addList模板中处理这个问题,因为递归是&#34; unwound&#34;,但它还不是很正确;在我的输出中,当它返回到1级时,正在插入一个新列表,但如果我更正了,我会删除最后3个内容项......如果有人能解决这个问题,我会留下深刻的印象: - )< / p>

是的,我知道我的代码更复杂,所以如果对上面的for-each-groups方法有任何简单的解决方案,那么建议会很棒。

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
  <xsl:output method="xml" indent="yes"/>

  <!-- works, except makes new list for siblings -->

  <xsl:template name="listSection">
    <xsl:param name="last-level">-1</xsl:param>
    <xsl:param name="items"/>

    <xsl:variable name="currentItem" select="$items[1]"/>
    <xsl:variable name="currentLevel">
      <xsl:value-of select="number($currentItem/@ilvl)"/>
    </xsl:variable>

    <xsl:variable name="nextItems" select="$items[position() > 1]"/>


    <xsl:choose>

      <xsl:when test="$currentLevel = $last-level">
        <!-- just add an item -->
        <xsl:call-template name="addItem">
          <xsl:with-param name="currentItem"  select="$currentItem"/>
          <xsl:with-param name="nextItems"  select="$nextItems"/>
        </xsl:call-template>
        <!-- that handles next level higher case, and level same case-->

        <!-- level lower is handled is addList template-->

      </xsl:when>

      <xsl:when test="$currentLevel &gt; $last-level">

        <xsl:call-template name="addList">
          <xsl:with-param name="currentLevel"  select="$last-level"/>
          <xsl:with-param name="nextItems"  select="$items"/> <!-- since haven't handled current item yet -->
        </xsl:call-template>

      </xsl:when>

      <xsl:otherwise> this level &lt; last level: should not happen?</xsl:otherwise>

    </xsl:choose>

  </xsl:template>




  <xsl:template name="addItem">

    <xsl:param name="currentItem"/>
    <xsl:param name="nextItems"/>


    <xsl:variable name="currentLevel">
      <xsl:value-of select="number($currentItem/@ilvl)"/>
    </xsl:variable>

    <item>
      <xsl:apply-templates select="$currentItem"/>

      <!-- is the next level higher?-->

      <xsl:if test="(count($nextItems) &gt; 0) and
                                        (number($nextItems[1]/@ilvl) &gt; $currentLevel)">
        <!-- insert list/item to the necessary depth-->
        <xsl:call-template name="addList">
          <xsl:with-param name="currentLevel"  select="$currentLevel"/>
          <xsl:with-param name="nextItems"  select="$nextItems"/>
        </xsl:call-template>
      </xsl:if>
    </item>

    <!-- next level same-->
    <xsl:if test="(count($nextItems) &gt; 0) and
                                        (number($nextItems[1]/@ilvl) = $currentLevel)">

      <xsl:call-template name="addItem">
        <xsl:with-param name="currentItem"  select="$nextItems[1]"/>
        <xsl:with-param name="nextItems"  select="$nextItems[position() > 1]"/>
      </xsl:call-template>

    </xsl:if>

  </xsl:template>



  <xsl:template name="addList">

    <xsl:param name="currentLevel">-1</xsl:param>
    <xsl:param name="nextItems"/>

    <xsl:variable name="targetLevel">
      <xsl:value-of select="number($nextItems[1]/@ilvl)"/>
    </xsl:variable>

    <xsl:choose>

      <xsl:when test="$targetLevel - $currentLevel &gt; 1">
        <!-- interpolate -->
        <list>
          <xsl:variable name="stuff">
          <item>
            <xsl:call-template name="addList">
              <xsl:with-param name="currentLevel"  select="$currentLevel+1"/>
              <xsl:with-param name="nextItems"  select="$nextItems"/>
            </xsl:call-template>
          </item>
          </xsl:variable>

          <xsl:copy-of select="$stuff"/>

          <xsl:variable name="currentPos" select="count(msxsl:node-set($stuff)//p)" />

          <xsl:variable name="ascentLevel">
            <xsl:value-of select="number($nextItems[$currentPos]/@ilvl)"/>
          </xsl:variable>

          <xsl:variable name="ascentItems" select="$nextItems[position() > $currentPos]"/>

          <xsl:variable name="aftertargetLevel">
            <xsl:value-of select="number($ascentItems[1]/@ilvl)"/>
          </xsl:variable>

      <xsl:if test="(count($ascentItems) &gt; 1) and
                                    ($aftertargetLevel - $currentLevel = 1)">    
            <xsl:call-template name="listSection">
              <xsl:with-param name="last-level"  select="$currentLevel"/>
              <xsl:with-param name="items"  select="$ascentItems"/> 
            </xsl:call-template>

          </xsl:if>
        </list>


      </xsl:when>

      <xsl:when test="$targetLevel - $currentLevel = 1">
        <!-- insert real item -->

        <xsl:variable name="stuff">
          <list>
          <xsl:call-template name="addItem">
            <xsl:with-param name="currentItem"  select="$nextItems[1]"/>
            <xsl:with-param name="nextItems"  select="$nextItems[position() > 1]"/>
          </xsl:call-template>

        </list>
        </xsl:variable>

        <!-- might be items on the way out -->
        <xsl:copy-of select="$stuff"/>

        <xsl:variable name="currentPos" select="count(msxsl:node-set($stuff)//p)" />

        <xsl:variable name="ascentLevel">
          <xsl:value-of select="number($nextItems[$currentPos]/@ilvl)"/>
        </xsl:variable>

        <xsl:variable name="ascentItems" select="$nextItems[position() > $currentPos]"/>

        <xsl:variable name="aftertargetLevel">
          <xsl:value-of select="number($ascentItems[1]/@ilvl)"/>
        </xsl:variable>

      <xsl:if test="(count($ascentItems) &gt; 1) and
                                    ($aftertargetLevel - $currentLevel = 1)">    
          <xsl:call-template name="listSection">
            <xsl:with-param name="last-level"  select="$currentLevel"/> 
            <xsl:with-param name="items"  select="$ascentItems"/>
          </xsl:call-template>

        </xsl:if>


      </xsl:when>

      <xsl:otherwise>
        <!--should not happen!-->
      </xsl:otherwise>

    </xsl:choose>

  </xsl:template>


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

  <xsl:template match="body">
    <xsl:call-template  name="listSection">
      <xsl:with-param name="items"  select="*"/>
    </xsl:call-template>


  </xsl:template>
</xsl:stylesheet>

1 个答案:

答案 0 :(得分:1)

这是我第二次尝试为这个问题提供解决方案,在目前的状态下迫使人们(至少是我)想出想要的东西:

此转化

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:param name="pStartLevel" select="1"/>

 <xsl:key name="kChildren" match="p"
  use="generate-id(preceding-sibling::p
                            [not(@ilvl >= current()/@ilvl)][1])"/>

 <xsl:template match="/*">
  <list>
    <item>
      <xsl:apply-templates select="key('kChildren', '')[1]" mode="start">
        <xsl:with-param name="pParentLevel" select="$pStartLevel"/>
        <xsl:with-param name="pSiblings" select="key('kChildren', '')"/>
      </xsl:apply-templates>
    </item>
  </list>
 </xsl:template>

 <xsl:template match="p" mode="start">
   <xsl:param name="pParentLevel"/>
   <xsl:param name="pSiblings"/>
   <list>
    <xsl:apply-templates select="$pSiblings">
      <xsl:with-param name="pParentLevel" select="$pParentLevel"/>
    </xsl:apply-templates>
  </list>
 </xsl:template>

 <xsl:template match="p">
   <xsl:param name="pParentLevel"/>
   <xsl:apply-templates select="self::*[@ilvl - $pParentLevel > 1]" 
                        mode="buildMissingLevels">
     <xsl:with-param name="pParentLevel" select="$pParentLevel"/>
   </xsl:apply-templates>
   <xsl:apply-templates select="self::*[not(@ilvl - $pParentLevel > 1)]" mode="normal">
     <xsl:with-param name="pParentLevel" select="$pParentLevel"/>
   </xsl:apply-templates>
 </xsl:template>

 <xsl:template match="p" mode="normal">
   <xsl:param name="pParentLevel"/>
   <item>
     <xsl:copy-of select="."/>
     <xsl:apply-templates mode="start"
             select="key('kChildren',generate-id())[1]">
        <xsl:with-param name="pParentLevel" select="@ilvl"/>
        <xsl:with-param name="pSiblings" 
             select="key('kChildren',generate-id())"/>
     </xsl:apply-templates>
   </item>
 </xsl:template>

 <xsl:template match="p" mode="buildMissingLevels">
   <xsl:param name="pParentLevel"/>
       <item>
         <p ilvl="{$pParentLevel +1}"/>
         <list>
           <xsl:apply-templates select=".">
             <xsl:with-param name="pParentLevel" select="$pParentLevel +1"/>
           </xsl:apply-templates>
         </list>
       </item>   
 </xsl:template>
</xsl:stylesheet>

应用于提供的XML文档

<body>
  <p ilvl="1">content</p>
  <p ilvl="1">content</p>
  <p ilvl="2">content</p>
  <p ilvl="3">content</p>

  <p ilvl="1">content</p>
  <p ilvl="2">content</p>
  <p ilvl="2">content</p>
  <p ilvl="3">content</p>
  <p ilvl="1">content</p>
  <p ilvl="1">content</p>
  <p ilvl="3">content</p>
</body>

产生我认为需要的东西

<list>
   <item>
      <list>
         <item>
            <p ilvl="1">content</p>
         </item>
         <item>
            <p ilvl="1">content</p>
            <list>
               <item>
                  <p ilvl="2">content</p>
                  <list>
                     <item>
                        <p ilvl="3">content</p>
                     </item>
                  </list>
               </item>
            </list>
         </item>
         <item>
            <p ilvl="1">content</p>
            <list>
               <item>
                  <p ilvl="2">content</p>
               </item>
               <item>
                  <p ilvl="2">content</p>
                  <list>
                     <item>
                        <p ilvl="3">content</p>
                     </item>
                  </list>
               </item>
            </list>
         </item>
         <item>
            <p ilvl="1">content</p>
         </item>
         <item>
            <p ilvl="1">content</p>
            <list>
               <item>
                  <p ilvl="2"/>
                  <list>
                     <item>
                        <p ilvl="3">content</p>
                     </item>
                  </list>
               </item>
            </list>
         </item>
      </list>
   </item>
</list>