【问题标题】:XSLT for flat to nested/hierarchical, with level interpolation?XSLT 用于平面到嵌套/分层,具有级别插值?
【发布时间】:2016-10-27 00:06:38
【问题描述】:

鉴于此源 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 模板中处理这个问题,因为递归是“展开”的,但它还不太正确;在我的输出中,当它返回到 1 级时,正在插入一个新列表,但如果我纠正了这一点,我会删除最后 3 个内容项......如果有人能解决这个问题,我会印象深刻:-)

是的,我知道我的代码要复杂得多,所以如果对上面的 for-each-group 方法有任何简单的解决方法,最好有建议。

<?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 和 2.0 的解决方案,它们有很大的不同。如果您受限于 XSLT 1.0,您需要这样说,因为这类问题在 2.0 中要容易得多。
  • 不限于 1.0(除了我的技能!);事实上,使用 Saxon 9.7.0.7,非常感谢 :-)
  • 在标题“使用级别插值”和正文中,“[OP 的尝试] 不会为任何缺失的级别创建结构(例如,在 1 和 3 之间缺失的级别 2)”。这对我来说很清楚。我会提供一个解决方案,但您确实需要一个 XSLT2 解决方案,我对 XSLT2 的了解不如 1。
  • 正如弗林所说,需求一直存在。抱歉,您没有注意到 Dimitre,无论如何感谢您的努力!

标签: xslt


【解决方案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>

【讨论】:

  • 干得好@dimitre,尽管输入不清楚,但感谢您的坚持:-)
  • @JasonPlutext,我很高兴这次我猜对了。请在未来的问题中不要用“等”这个短语隐藏想要的结果的重要部分。 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-06-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-29
  • 1970-01-01
相关资源
最近更新 更多