【问题标题】:More concise / efficient XSLT更简洁/高效的 XSLT
【发布时间】:2013-09-24 14:42:46
【问题描述】:

下面的 XSLT 转换如何更简洁?它可以工作,但我仍然无法以声明的方式看到问题(作为一个相对的 XSLT 新手),并且觉得这个解决方案是一个程序性的解决方案,而且相当冗长。我想看看对声明式方法有直观感觉的人如何解决/简化它?

XSLT 用于构造具有三个深度级别的垂直导航元素。导航元素根据选择的节点展开/折叠。 Css 类(活动)也会根据选择的级别而应用。

它有 2 个参数,id 和 query_string。

在它作用的 XML 中有一个特例节点“News” - 该节点的所有后代节点都具有相同的 id,因此使用 query_string 来区分它们。

这里是 XSLT

<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="html" omit-xml-declaration="yes" indent="yes" />
<xsl:param name="id" select="'id:144016'"></xsl:param>
<xsl:param name="query_string" select="'year=2012&amp;month=12'"/>

<xsl:template match="/">
<xsl:choose>
  <xsl:when test="//siteMapNode[@id=$id and @query_string=$query_string]"> 
    <xsl:apply-templates select="//siteMapNode[@id=$id and @query_string=$query_string]/ancestor-or-self::*[@depth=1]" />
  </xsl:when>
  <xsl:when test="//siteMapNode[@id=$id]/ancestor::*[@depth=1]">
    <xsl:apply-templates select="//siteMapNode[@id=$id]/ancestor-or-self::*[@depth=1]" />
  </xsl:when>
  <xsl:otherwise>
    <xsl:apply-templates select="//siteMapNode[@id=$id]" />
  </xsl:otherwise>
</xsl:choose>
</xsl:template>

<xsl:template match="siteMapNode/siteMapNode/siteMapNode">
<xsl:variable name="matchidandyearpartofquery" select="count(self::node()[@id=$id and @query_string=substring($query_string, 1, 9)])" />
<xsl:variable name="matchid" select="count(self::node()[@id=$id])" />    
<xsl:variable name="matchdescendentid" select="count(self::node()//*[@id=$id and @query_string=substring($query_string, 1, 9)])" />
<li>
  <xsl:if test="$matchidandyearpartofquery &gt; 0 or $matchdescendentid &gt; 0">
    <xsl:attribute name="class">
      <xsl:text>active</xsl:text>
    </xsl:attribute>
  </xsl:if>
  <a>
    <xsl:attribute name="href">
      <xsl:value-of select="@url" />
    </xsl:attribute>
    <xsl:value-of select="@title" />
  </a>
<xsl:choose>
  <xsl:when test="self::node()[@id=$id and @query_string=substring($query_string, 1, 9)]">
    <xsl:if test="./*">
      <ul>
        <xsl:apply-templates select="siteMapNode">
          <xsl:sort select="@month" data-type="number" order="descending"/>
        </xsl:apply-templates>
      </ul>
    </xsl:if>
  </xsl:when>
  <xsl:when test="self::node()//*[@id=$id and @query_string=substring($query_string, 1, 9)]">
      <ul>
        <xsl:apply-templates select="siteMapNode">
          <xsl:sort select="@month" data-type="number" order="descending"/>
        </xsl:apply-templates>
      </ul>
  </xsl:when>
  <xsl:when test="self::node()[@id=$id]">
    <xsl:if test="./*">
      <xsl:if test="./*[not(@id=$id)]">
      <ul>
        <xsl:apply-templates select="siteMapNode">
          <xsl:sort select="@month" data-type="number" order="descending"/>
        </xsl:apply-templates>
      </ul>
      </xsl:if>
    </xsl:if>
  </xsl:when>
  <xsl:when test="self::node()//*[@id=$id]">
      <ul>
        <xsl:apply-templates select="siteMapNode">
          <xsl:sort select="@month" data-type="number" order="descending"/>
        </xsl:apply-templates>
      </ul>
  </xsl:when>
  <xsl:otherwise>      
  </xsl:otherwise>
</xsl:choose>
</li>
</xsl:template>

<xsl:template match="siteMapNode/siteMapNode/siteMapNode/siteMapNode">
<xsl:variable name="matchidandyearpartofquery" select="count(self::node()[@id=$id and @query_string=$query_string])" />
<xsl:variable name="matchid" select="count(self::node()[@id=$id])" />
<xsl:variable name="matchdescendentid" select="count(self::node()//*[@id=$id and @query_string=substring($query_string, 1, 9)])" />
<li>
  <xsl:if test="$matchidandyearpartofquery &gt; 0 or $matchdescendentid &gt; 0">
    <xsl:attribute name="class">
      <xsl:text>active icon-nav-left</xsl:text>
    </xsl:attribute>
  </xsl:if>
  <a>
    <xsl:attribute name="href">
      <xsl:value-of select="@url" />
    </xsl:attribute>
    <xsl:value-of select="@title" />
  </a>
<xsl:choose>
 <xsl:when test="self::node()[@id=$id and @query_string=$query_string]">
    <xsl:if test="./*">
      <ul>
        <xsl:apply-templates select="siteMapNode">
          <xsl:sort select="@month" data-type="number" order="descending"/>
        </xsl:apply-templates>
      </ul>
    </xsl:if>
  </xsl:when>
  <xsl:when test="self::node()//*[@id=$id and @query_string=substring($query_string, 1, 9)]">
      <ul>
        <xsl:apply-templates select="siteMapNode">
          <xsl:sort select="@month" data-type="number" order="descending"/>
        </xsl:apply-templates>
      </ul>
  </xsl:when>
  <xsl:when test="self::node()[@id=$id]">
    <xsl:if test="./*">
      <xsl:if test="./*[not(@id=$id)]">
      <ul>
        <xsl:apply-templates select="siteMapNode">
          <xsl:sort select="@month" data-type="number" order="descending"/>
        </xsl:apply-templates>
      </ul>
      </xsl:if>
    </xsl:if>
  </xsl:when>
  <xsl:otherwise>    
  </xsl:otherwise>
</xsl:choose>
</li>
</xsl:template>

 <xsl:template match="siteMapNode/siteMapNode/siteMapNode/siteMapNode/siteMapNode">
<xsl:variable name="matchidandyearpartofquery" select="count(self::node()[@id=$id and @query_string=$query_string])" />
<li>
  <xsl:if test="$matchidandyearpartofquery &gt; 0">
    <xsl:attribute name="class">
      <xsl:text>active icon-nav-left</xsl:text>
    </xsl:attribute>
  </xsl:if>
  <a>
    <xsl:attribute name="href">
      <xsl:value-of select="@url" />
    </xsl:attribute>
    <xsl:value-of select="@title" />
  </a>
</li>
</xsl:template>
</xsl:stylesheet>

还有一些示例 XML 输入

<siteMapNode id="id:144037" title="Home" url="index.jsp" depth="0" show_in_top="Yes" show_in_footer="No" use_as_default="No" query_string="" month="" year="">
  <siteMapNode id="id:144037" title="Home" url="index.jsp" depth="1" show_in_top="Yes" show_in_footer="No" use_as_default="No" query_string="" month="" year="" />
  <siteMapNode id="id:144513" title="Company" url="our-company/index.jsp" depth="1" show_in_top="Yes" show_in_footer="No" use_as_default="No" query_string="" month="" year="">
    <siteMapNode id="id:144615" title="At a glance" url="our-company/at-a-glance.jsp" depth="2" show_in_top="No" show_in_footer="No" use_as_default="No" query_string="" month="" year="" />
    <siteMapNode id="id:144005" title="Our Brands" url="our-company/our-brands.jsp" depth="2" show_in_top="No" show_in_footer="No" use_as_default="No" query_string="" month="" year="" />
    <siteMapNode id="id:144629" title="Our Products" url="our-company/our-products.jsp" depth="2" show_in_top="No" show_in_footer="No" use_as_default="No" query_string="" month="" year="" />
    <siteMapNode id="id:144638" title="Our Global Purpose" url="our-company/our-global-purpose.jsp" depth="2" show_in_top="No" show_in_footer="No" use_as_default="No" query_string="" month="" year="" />
    <siteMapNode id="id:144002" title="Company History" url="our-company/company-history.jsp" depth="2" show_in_top="No" show_in_footer="No" use_as_default="No" query_string="" month="" year="" />
    <siteMapNode id="id:144003" title="Leadership" url="our-company/leadership.jsp" depth="2" show_in_top="No" show_in_footer="No" use_as_default="No" query_string="" month="" year="" />
  </siteMapNode>
  <siteMapNode id="id:144016" title="News" url="news-press/newslisting.jsp" depth="1" show_in_top="Yes" show_in_footer="No" use_as_default="No" query_string="" month="" year="">
    <siteMapNode id="id:144016" title="2012 Archive" url="news-press/newslisting.jsp?year=2012" depth="2" show_in_top="No" show_in_footer="No" use_as_default="No" query_string="year=2012" month="" year="2012">
      <siteMapNode id="id:144016" title="December" url="news-press/newslisting.jsp?year=2012&amp;month=12" depth="3" show_in_top="No" show_in_footer="No" use_as_default="No" query_string="year=2012&amp;month=12" month="12" year="2012" />
      <siteMapNode id="id:144016" title="April" url="news-press/newslisting.jsp?year=2012&amp;month=4" depth="3" show_in_top="No" show_in_footer="No" use_as_default="No" query_string="year=2012&amp;month=4" month="4" year="2012" />
    </siteMapNode>
    <siteMapNode id="id:144016" title="2013 Archive" url="news-press/newslisting.jsp" depth="2" show_in_top="No" show_in_footer="No" use_as_default="No" query_string="year=2013" month="" year=""/>
  </siteMapNode>
</siteMapNode>

这是预期输出 HTML 的示例(此处,从新闻部分中选择了 2012 年 12 月)

<li class="active"><a href="news-press/newslisting.jsp?year=2012">2012 Archive</a>
  <ul>
    <li class="active icon-nav-left"><a href="news-press/newslisting.jsp?year=2012&amp;month=12">December</a></li>
    <li><a href="news-press/newslisting.jsp?year=2012&amp;month=4">April</a></li>
  </ul>
</li>
<li><a href="news-press/newslisting.jsp">2013 Archive</a></li>

【问题讨论】:

  • 如果您能提供输入 XML 的示例,那将有所帮助。
  • ... 以及预期的输出。
  • 我已经添加了两者的示例 - 更好吗?
  • XSLT 旨在用作声明性语言。通常,重构样式表的一个好方法是用apply-templatestemplate 中的xpath 条件替换条件(ifchoose)和for-each 循环(但你已经没有)。跨度>
  • 更好,但是:(1) 您的输入 XML 无效,因为并非每个 siteMapNode 都有一个结束元素,(2) 您的 XSLT 样式表无效,因为它缺少开始 xsl:stylesheet 元素和 xsl:variable 元素和 (3) 预期的输出 HTML 令人困惑,因为它有一个文本元素 2013 Archive 根本没有出现在输入 XML 中。请发布有效 XML 输入、完整 XSLT 样式表和您从中获得的精确 输出。

标签: xslt


【解决方案1】:

这是我的解决方案,希望对 cme​​ts 有所帮助。它适用于您的示例,但您可能有更具体的要求,这没有考虑到。不过,我希望它对你有用。

以下 XSLT 样式表:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="html"/>

  <xsl:param name="id" select="'id:144016'"></xsl:param>
  <xsl:param name="query_string" select="'year=2012&amp;month=12'"/>

  <!-- Match the siteMapNode element at depth 1 with the specified id. Just apply 
       templates to its children. -->
  <xsl:template match="siteMapNode[@id=$id and @depth='1']"
        priority="5">
    <xsl:apply-templates select="node()|@*"/>
  </xsl:template>

  <!-- Match the siteMapNode element with the specified id that partially match the 
       query string. These will have child elements so apply templates and keep
       going. -->
  <xsl:template match="siteMapNode[@id=$id and contains($query_string, @query_string)]"
        priority="1">
    <li class="active">
      <xsl:call-template name="anchor"/>
      <ul>
    <xsl:apply-templates select="node()|@*"/>
      </ul>
    </li>
  </xsl:template>

  <!-- Match the siteMapNode element with the specified id that completely match the 
       query string. -->
  <xsl:template match="siteMapNode[@id=$id and $query_string=@query_string]"
        priority="10">
    <li class="active icon-nav-left">
      <xsl:call-template name="anchor"/>
    </li>
  </xsl:template>

  <!-- Match the remaining siteMapNode elements with the specified id.
       This template has the default priority of 0.5. -->
  <xsl:template match="siteMapNode[@id=$id]">
    <li>
      <xsl:call-template name="anchor"/>
    </li>
  </xsl:template>

  <!-- The anchor element we output is common, so put it into a template we can call. -->
  <xsl:template name="anchor">
      <a href="{@url}">
    <xsl:value-of select="@title"/>
      </a>
  </xsl:template>

  <!-- The built-in template for elements and the root node is already doing what we want, 
       so we don't need to include it explicitly. It's below, commented out, for 
       reference. -->
<!--  <xsl:template match="*|/">
    <xsl:apply-templates/>
  </xsl:template>-->

  <!-- Don't want to output text or attribute nodes unless we explicitly say so, so we 
       need to override the built-in templates for these. -->
  <xsl:template match="text()|@*"/>

</xsl:stylesheet>

在应用于您的示例输入 XML 时提供以下输出:

<li class="active"><a href="news-press/newslisting.jsp?year=2012">2012 Archive</a><ul>
      <li class="active icon-nav-left"><a href="news-press/newslisting.jsp?year=2012&amp;month=12">December</a></li>
      <li><a href="news-press/newslisting.jsp?year=2012&amp;month=4">April</a></li>
   </ul>
</li>
<li><a href="news-press/newslisting.jsp">2013 Archive</a></li>

【讨论】:

  • 当我尝试这个时转换对我来说失败了 - 说'变量不能在表达式中使用'......我认为你不能在模板匹配中使用变量???
  • 对不起,你是对的(尽管这对我使用 Saxon HE 9.0.4.4N 有效——我将不得不更多地研究原因)。我很快会再看一遍并(希望)让它发挥作用。
猜你喜欢
  • 2021-12-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-04
  • 2021-05-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多