XSLT将结果分为每组3个。

8
一个Web应用程序为我提供了一个XML提要,我无法更改。我想做的是将此XML提要拆分为多个无序列表。我正在尝试使用下面的XSLT来实现这一目标。
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >    
  <xsl:output method="xml" omit-xml-declaration="yes" indent="yes" encoding="utf-8" />
  <xsl:param name="html-content-type" />
  <xsl:template match="/NavigationTree">
    <xsl:if test="count(//Page) > 0">
      <ul>
        <xsl:apply-templates select="Page">
        </xsl:apply-templates>
      </ul>
    </xsl:if>
  </xsl:template>

  <xsl:template match="//Page">    
    <li class="{position() mod 3}">
      <xsl:text disable-output-escaping="yes"><![CDATA[»&nbsp;]]></xsl:text>
      <a>
        <xsl:attribute name="href">
          <xsl:value-of select="@FriendlyHref" disable-output-escaping="yes"/>
        </xsl:attribute>
        <xsl:value-of select="@MenuText" disable-output-escaping="no"/>
      </a>
    </li>

    <xsl:if test="position() mod 3 = 0">
      <xsl:if test="position() &lt; count(//Page)">
        <!--Don't know if this is the correct approach, but when the position is 3 and there are more items following
        I want to create an new unordered list-->
      </xsl:if>      
    </xsl:if>
  </xsl:template>
</xsl:stylesheet>

使用上述XSLT,我能够将XML转换为一个无序列表,其中包含6个项目(假设总是有6个项目)。类似于下面的示例;

<ul>
    <li>Item1</li>
    <li>Item2</li>
    <li>Item3</li>
    <li>Item4</li>
    <li>Item5</li>
    <li>Item6</li>
</ul>

目前我得到的结果如上所示。但期望的结果应该是这样的:

<ul>
    <li>Item1</li>
    <li>Item2</li>
    <li>Item3</li>
</ul>
<ul>
    <li>Item4</li>
    <li>Item5</li>
    <li>Item6</li>
</ul>

编辑 - 样例 XML 输入

<NavigationTree>
    <Settings>
        <!--Snipped data-->
    </Settings>
    <Page ID="5" AreaID="1" MenuText="Bestellen" MouseOver="" Href="Default.aspx?ID=5" FriendlyHref="/nl-nl/klantenservice/bestellen.aspx" Image="" ImageActive="" ImageMouseOver="" Title="" Allowclick="True" ShowInSitemap="True" ShowInLegend="True" AbsoluteLevel="2" RelativeLevel="2" Sort="1" LastInLevel="False" InPath="False" ChildCount="0" class="L2" Active="False" IsPagePasswordProtected="False" IsPageUserProtected="False" CanAccessPasswordProtectedPage="False" CanAccessUserProtectedPage="True"/>
    <Page ID="6" AreaID="1" MenuText="Betalen" MouseOver="" Href="Default.aspx?ID=6" FriendlyHref="/nl-nl/klantenservice/betalen.aspx" Image="" ImageActive="" ImageMouseOver="" Title="" Allowclick="True" ShowInSitemap="True" ShowInLegend="True" AbsoluteLevel="2" RelativeLevel="2" Sort="2" LastInLevel="False" InPath="True" ChildCount="0" class="L2_Active" Active="True" IsPagePasswordProtected="False" IsPageUserProtected="False" CanAccessPasswordProtectedPage="False" CanAccessUserProtectedPage="True"/>
    <Page ID="7" AreaID="1" MenuText="Retourneren" MouseOver="" Href="Default.aspx?ID=7" FriendlyHref="/nl-nl/klantenservice/retourneren.aspx" Image="" ImageActive="" ImageMouseOver="" Title="" Allowclick="True" ShowInSitemap="True" ShowInLegend="True" AbsoluteLevel="2" RelativeLevel="2" Sort="3" LastInLevel="False" InPath="False" ChildCount="0" class="L2" Active="False" IsPagePasswordProtected="False" IsPageUserProtected="False" CanAccessPasswordProtectedPage="False" CanAccessUserProtectedPage="True"/>
    <Page ID="8" AreaID="1" MenuText="Garantie" MouseOver="" Href="Default.aspx?ID=8" FriendlyHref="/nl-nl/klantenservice/garantie.aspx" Image="" ImageActive="" ImageMouseOver="" Title="" Allowclick="True" ShowInSitemap="True" ShowInLegend="True" AbsoluteLevel="2" RelativeLevel="2" Sort="4" LastInLevel="False" InPath="False" ChildCount="0" class="L2" Active="False" IsPagePasswordProtected="False" IsPageUserProtected="False" CanAccessPasswordProtectedPage="False" CanAccessUserProtectedPage="True"/>
    <Page ID="9" AreaID="1" MenuText="Faq" MouseOver="" Href="Default.aspx?ID=9" FriendlyHref="/nl-nl/klantenservice/veel-gestelde-vragen.aspx" Image="" ImageActive="" ImageMouseOver="" Title="" Allowclick="True" ShowInSitemap="True" ShowInLegend="True" AbsoluteLevel="2" RelativeLevel="2" Sort="5" LastInLevel="False" InPath="False" ChildCount="0" class="L2" Active="False" IsPagePasswordProtected="False" IsPageUserProtected="False" CanAccessPasswordProtectedPage="False" CanAccessUserProtectedPage="True"/>
    <Page ID="10" AreaID="1" MenuText="Contact" MouseOver="" Href="Default.aspx?ID=10" FriendlyHref="/nl-nl/klantenservice/contact.aspx" Image="" ImageActive="" ImageMouseOver="" Title="" Allowclick="True" ShowInSitemap="True" ShowInLegend="True" AbsoluteLevel="2" RelativeLevel="2" Sort="6" LastInLevel="True" InPath="False" ChildCount="0" class="L2" Active="False" IsPagePasswordProtected="False" IsPageUserProtected="False" CanAccessPasswordProtectedPage="False" CanAccessUserProtectedPage="True"/>
</NavigationTree>

正如您所看到的,我总是希望以3个项目为一组输出。使用XSLT能否实现这种HTML输出?如果可以,我该如何做?欢迎任何帮助!


1
好问题,+1。请看我的答案,其中包括两个解决方案:一个是不使用任何条件XSLT指令的XSLT 1.0解决方案;另一个是使用xsl:for-each-group的更短的XSLT 2.0解决方案。如果您需要对一组兄弟节点进行分组的通用XSLT 1.0位置分组解决方案,请参阅我在此问题中的答案:http://stackoverflow.com/questions/7299594/grouping-and-sorting-xslt-by-key-and-position/7302747#7302747 - Dimitre Novatchev
3个回答

12

I. XSLT 1.0

下面是一个简短的参数化解决方案,展示了如何按预定义大小将兄弟元素分组。没有使用显式条件XSLT指令:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:param name="pGroupSize" select="3"/>

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

 <xsl:template match="/*">
  <nums>
   <xsl:apply-templates select=
       "num[position() mod $pGroupSize = 1]"/>
  </nums>
 </xsl:template>

 <xsl:template match="num">
  <group>
   <xsl:copy-of select=
    ".|following-sibling::*
              [not(position() > $pGroupSize -1)]"/>
  </group>
 </xsl:template>
</xsl:stylesheet>

当在以下 XML 文档上应用此转换时

<nums>
  <num>01</num>
  <num>02</num>
  <num>03</num>
  <num>04</num>
  <num>05</num>
  <num>06</num>
  <num>07</num>
  <num>08</num>
  <num>09</num>
  <num>10</num>
</nums>

得到的结果是想要且正确的:

<nums>
   <group>
      <num>01</num>
      <num>02</num>
      <num>03</num>
   </group>
   <group>
      <num>04</num>
      <num>05</num>
      <num>06</num>
   </group>
   <group>
      <num>07</num>
      <num>08</num>
      <num>09</num>
   </group>
   <group>
      <num>10</num>
   </group>
</nums>

II. XSLT 2.0

<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:param name="pGroupSize" select="3"/>

 <xsl:template match="/*">
     <nums>
       <xsl:for-each-group select="*"
            group-by="(position() -1) idiv $pGroupSize">
         <group>
          <xsl:sequence select="current-group()"/>
         </group>
       </xsl:for-each-group>
     </nums>
 </xsl:template>
</xsl:stylesheet>

当对同一XML文档(上述文档)应用此转换时,将产生相同的正确结果

<nums>
   <group>
      <num>01</num>
      <num>02</num>
      <num>03</num>
   </group>
   <group>
      <num>04</num>
      <num>05</num>
      <num>06</num>
   </group>
   <group>
      <num>07</num>
      <num>08</num>
      <num>09</num>
   </group>
   <group>
      <num>10</num>
   </group>
</nums>

说明:

  1. 使用<xsl:for-each-group>,对所选节点进行分组,其中分组的顺序是它们所属组的连续编号。

  2. 使用标准的XSLT 2.0函数current-group()


8
为了做到这一点,您需要匹配位于位置1、4、7等的页面元素...换句话说,当position() mod 3等于1时。
<xsl:if test="position() mod 3 = 1">

这将给出列表的第一个元素。然后您可以像这样获得剩余的2个元素

<xsl:apply-templates select=".|following-sibling::Page[position() &lt; 3]" mode="list"/>

将所有内容综合起来,得到以下XSLT。
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:output method="xml" omit-xml-declaration="yes" indent="yes" encoding="utf-8"/>
   <xsl:param name="html-content-type"/>
   <xsl:param name="group-size" select="3"/>
   <xsl:template match="/NavigationTree">
      <xsl:if test="count(//Page) &gt; 0">
         <xsl:apply-templates select="Page"/>
      </xsl:if>
   </xsl:template>
   <xsl:template match="Page">
      <xsl:if test="position() mod $group-size = 1">
         <ul>
            <xsl:apply-templates select=".|following-sibling::Page[position() &lt; $group-size]" mode="list"/>
         </ul>
      </xsl:if>
   </xsl:template>
   <xsl:template match="Page" mode="list">
      <li class="{position()}">
         <xsl:text disable-output-escaping="yes"><![CDATA[»&nbsp;]]></xsl:text>
         <a>
            <xsl:attribute name="href">
               <xsl:value-of select="@FriendlyHref" disable-output-escaping="yes"/>
            </xsl:attribute>
            <xsl:value-of select="@MenuText" disable-output-escaping="no"/>
         </a>
      </li>
   </xsl:template>
</xsl:stylesheet>

当在您的输入XML上运行时,它应该生成以下输出。
<ul>
  <li class="1">»&nbsp;<a href="/nl-nl/klantenservice/bestellen.aspx">Bestellen</a></li>
  <li class="2">»&nbsp;<a href="/nl-nl/klantenservice/betalen.aspx">Betalen</a></li>
  <li class="3">»&nbsp;<a href="/nl-nl/klantenservice/retourneren.aspx">Retourneren</a></li>
</ul>
<ul>
  <li class="1">»&nbsp;<a href="/nl-nl/klantenservice/garantie.aspx">Garantie</a></li>
  <li class="2">»&nbsp;<a href="/nl-nl/klantenservice/veel-gestelde-vragen.aspx">Faq</a></li>
   <li class="3">»&nbsp;<a href="/nl-nl/klantenservice/contact.aspx">Contact</a></li>
</ul>

请注意,我已经将组大小参数化,这样您就可以轻松地将列表更改为每个4或5个元素,例如。


+1 很棒的解决方案。喜欢带参数的组大小解决方案。非常灵活!感谢您快速而出色的解决方案 :) - Rob
实际上,在我的解决方案中有一个不必要的 xsl:if!对于 position() mod $group-size = 1 的测试需要放在最初的 xsl:apply-templates select="Page" 中。我认为这样会更有效率。 - Tim C

3
<xsl:stylesheet version="1.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
    <xsl:output method="xml" omit-xml-declaration="yes" indent="yes" encoding="utf-8" />

    <xsl:template match="/NavigationTree">
        <xsl:apply-templates select="Page[position() mod 3 = 1]" mode="ul"/>
    </xsl:template>

    <xsl:template match="Page" mode="li">
        <li class="{position() mod 3}">
            <a href="{@FriendlyHref}">
                <xsl:value-of select="@MenuText" disable-output-escaping="no"/>
            </a>
        </li>
    </xsl:template>

    <xsl:template match="Page" mode="ul">
        <ul>
            <xsl:apply-templates select=". | following-sibling::Page[position() &lt; 3]" mode="li"/>
        </ul>
    </xsl:template>

</xsl:stylesheet>

输出:

<ul>
  <li class="1">
    <a href="/nl-nl/klantenservice/bestellen.aspx">Bestellen</a>
  </li>
  <li class="2">
    <a href="/nl-nl/klantenservice/betalen.aspx">Betalen</a>
  </li>
  <li class="0">
    <a href="/nl-nl/klantenservice/retourneren.aspx">Retourneren</a>
  </li>
</ul>
<ul>
  <li class="1">
    <a href="/nl-nl/klantenservice/garantie.aspx">Garantie</a>
  </li>
  <li class="2">
    <a href="/nl-nl/klantenservice/veel-gestelde-vragen.aspx">Faq</a>
  </li>
  <li class="0">
    <a href="/nl-nl/klantenservice/contact.aspx">Contact</a>
  </li>
</ul>

+1个好的工作解决方案。但是我更喜欢Tim C的方案,因为当我想要更改显示的项目数量时,它更加灵活。 - Rob

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接