【问题标题】:XSLT Layouts With Dynamic Content Region具有动态内容区域的 XSLT 布局
【发布时间】:2010-08-15 05:16:02
【问题描述】:

我正在重建我们网站的 UI 部分,该部分完全基于 javascript/ajax(没有充分的理由并且以相当低效的方式),以便后端现在可以完成大部分内容生成。它是一个 C# .net 应用程序。

几乎我们所有的页面(可能有 40-50 页)都具有相同的基本布局。我是 XSLT 的新手,但我已经使用 MVC 框架完成了很多工作,例如 Spring(java,使用 Sitemesh 进行布局)、Symfony (PHP)、一些 rails 以及其他一些框架。我喜欢拥有一个或多个通用模板的能力,然后拥有一个特定的“内容”部分,页面特定的东西可以放在其中。我无法弄清楚这是如何使用 XSLT 完成的。在这个应用程序的情况下,我在支持 xslt 页面的 xml 中有一个可用的值,我们称之为 ContentXSL,它的值是 xsl 文件的名称 我想用于页面的内容部分。我知道这是不可能的,但它会很好用:

 <xsl:call-template name="{$ContentXSL}" />

然后我可以简单地把它放在内容部分。但是这是不可能的,所以我需要一个基于 ContentPage 变量调用正确模板的大量选择语句。这也意味着在我的布局中。 xsl 文件我必须包含所有 40-50 个 xsl 文档。我认为开销会很大,但我不确定。如果网站获得大量流量,这样做是否合理?

还有哪些其他常见的方法?似乎大多数现代框架都允许您使用这种模式来装饰内容。在 Symfony 的情况下,它运行得非常好并且非常灵活(有插槽等等)。

我知道另一种可能的解决方案是拥有 40 个独立文件,这些文件都具有相似的标记并包括页眉和页脚等特殊部分。这意味着如果我想更改网站布局的整体结构,我必须编辑所有 40-50 个页面(非常烦人)。

更新 -- 更多解释

我想进一步解释这一点,因为我有一些需要大量工程才能改变的要求。 首先,后端将传递给我一些 xml,它会让我知道查询 args 在网站的 URL 中。此外,它将传递给我构建页面所需的数据(数据在形式的业务数据,没有 html 或类似的东西)。 数据看起来类似于这样:

<xml>
 <section>Blogs</section>
 <page>showAll</section>
  <data>
   <blogs>
     <blog>
       <author>somebody</author>
       <title></title>
       <content>..</content>
    </blog>
    </blog>..</blog>
   </blogs>    
  </data>
</xml>

现在想要有一个这样的页面模板:

<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform' xmlns:msxsl='urn:schemas-microsoft-com:xslt'>  
 <xsl:output omit-xml-declaration='yes' method='html' media-type='text/html' indent='yes' />
 <xsl:include href="Header.xsl"/>
 <xsl:include href="Nav.xsl"/> 
 <xsl:template name='MainLayout' match='*'>
 <html>
  <head>
   <title></title>
  </head>
  <body>
    <div id="header"><xsl:call-template name="Header" /></div>
    <div id="nav"><xsl:call-template name="Nav" /></div>
    <div id="content">
      [here is where i want to use the xsl from {/xml/section}/{/xml/page}.xsl]
    </div>
  </body>
</html>
</xsl:template>    
</xsl:stylesheet>

现在,对于此页面的内容,我将拥有以下文件: 博客/showAll.xsl

<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform' xmlns:msxsl='urn:schemas-microsoft-com:xslt'>  
      <xsl:output omit-xml-declaration='yes' method='html' media-type='text/html' indent='yes' />
<xsl:template name='Blogs_ShowAll'>
  <div id="blogs-showAll">
   ..iterate over /xml/data/blogs converting to html
  </div>
</xsl:template>
</xsl:stylesheet>

到目前为止的解决方案都很好,但只有一个我能够完全消化(其中提到包括所有 xsl 文件并使用 xsl:choose 来选择正确的那个)。 我不确定如何将 FXSL 方法应用于手头的问题。 请注意,我不会反对使用 sitemesh 类型的方法,我指定了 html/body 标记以及子项中的所有内容,并让它将我在子项的 body 部分中的内容替换为布局的内容 div(另外,如果子中有一个标题标签替换布局中的标题标签——类似的东西)。

【问题讨论】:

  • 好问题 (+1)。有关完整且广泛支持的解决方案,请参阅我的答案。 :)

标签: .net xml xslt


【解决方案1】:
 <xsl:call-template name="{$ContentXSL}" />

虽然这在所有 XSLT 版本中在语法上都是非法的,但 自十年前以来,FXSL library 就已实现并使用 XSLT 模板作为高阶函数 .

这里有一个关于如何实现这一点的简化想法:

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

 <xsl:param name="pFunction1">
   <fun name="increment"/>
 </xsl:param>

 <xsl:param name="pFunction2">
   <fun name="double"/>
 </xsl:param>

 <xsl:variable name="vFunIncrement" select=
 "document('')/*/xsl:param[@name='pFunction1']/*"/>

 <xsl:variable name="vFunDouble" select=
 "document('')/*/xsl:param[@name='pFunction2']/*"/>

 <xsl:variable name="vInput" select="."/>

 <xsl:template match="/">
  increment(<xsl:value-of select="$vInput"/>) = <xsl:text/>

  <xsl:apply-templates select="$vFunIncrement">
    <xsl:with-param name="parg1" select="$vInput"/>
  </xsl:apply-templates>

  double(<xsl:value-of select="$vInput"/>) = <xsl:text/>

  <xsl:apply-templates select="$vFunDouble">
    <xsl:with-param name="parg1" select="$vInput"/>
  </xsl:apply-templates>
 </xsl:template>

 <xsl:template match="fun[@name='double']">
  <xsl:param name="parg1"/>

  <xsl:value-of select="2*$parg1"/>
 </xsl:template>

 <xsl:template match="fun[@name='increment']">
  <xsl:param name="parg1"/>

  <xsl:value-of select="$parg1+1"/>
 </xsl:template>
</xsl:stylesheet>

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

<num>2</num>

结果是

  increment(2) = 3

  double(2) = 4

请注意

  1. &lt;fun&gt; 元素可以通过全局级参数从外部传递给转换。这意味着转换不知道将执行哪些函数。

  2. 功能由模板模拟匹配 fun 元素的name 属性具有特定值。

如果您想阅读和理解 FXSL,这是两个最好的材料:

  1. FXSL 1.x(适用于 XSLT 1.0)
  2. FXSL 2.0(适用于 XSLT 2.0)

【讨论】:

  • 这很有趣,但我仍然不确定如何使用它来动态包含另一个 xslt 样式表。我会创建一个包含另一个模板的函数并调用它吗?此外,将 FXSL 与 XSLT 一起使用有多容易?我只是在我的项目中包含一个 dll 并且它会工作吗?
  • @Matt-Wolfe:我完全不建议尝试动态包含样式表模块。我简要展示的功能要强大得多——这允许您将模板用作函数,并将函数/模板作为参数动态传递。此外,动态返回函数/模板作为其他函数/模板的结果。这正是您试图通过&lt;xsl:call-template name="{$ContentXSL}" /&gt; 实现的目标。
  • @Matt-Wolfe:[将 FXSL 与 XSLT 结合使用有多容易?]:您只需导入所需的 FXSL 样式表模块,然后调用其中一个模板——或者使用相同的模板FXSL 使用的实现思路。如果您下载 FXSL 1.x(用于 EXSLT),您可以使用 XslCompiledTransform 或直接在 Visual Studio 中运行任何测试文件。
  • @Dimitre:哇! FXSL 10 年! +1
  • 我已经更新了我原来的帖子。你能否给我一些澄清,这将如何适用于我正在尝试做的事情,我仍然不明白。此外,我看不到您将 FXSL 样式表导入文档的位置,如果是这种情况,我什至需要这些样式表。我不想用这是基本布局/模板的东西来做数学..
【解决方案2】:

OP 提供了有关他的问题的更多详细信息,此答案提供了现在要求的其他解决方案。

我。想法:

这种转变

<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:template match="/">
  <html>
   <xsl:apply-templates select="*/page"/>
  </html>
 </xsl:template>

 <xsl:template match="page[. = 'showAll']">
   <!-- Transform all data to html -->
   <xsl:apply-templates select="../*/blogs" mode="showAll"/>
 </xsl:template>

 <xsl:template match="page[. = 'showBrief']">
   <!-- Transform the data to Summary html -->
      <xsl:apply-templates select="../*/blogs" mode="showBrief"/>
 </xsl:template>

 <xsl:template match="blogs" mode="showAll">
  <h1>All Blogs: </h1>
  <table border="1">
    <xsl:apply-templates mode="showAll"/>
  </table>
 </xsl:template>

 <xsl:template match="blog" mode="showAll">
  <tr>
    <td>Blog of <xsl:value-of select="author"/></td>
    <td><xsl:value-of select="title"/></td>
  </tr>
  <tr>
    <td colspan="2"><xsl:apply-templates select="content/node()" mode="showAll"/></td>
  </tr>
  <xsl:if test="not(position()=last())">
   <tr><td colspan="2">&#xA0;</td></tr>
  </xsl:if>
 </xsl:template>

  <xsl:template match="blogs" mode="showBrief">
  <h1>Blogs Summary: </h1>
  <table border="1">
    <xsl:apply-templates mode="showBrief"/>
  </table>
  </xsl:template>

   <xsl:template match="blog" mode="showBrief">
     <tr>
      <td>
        <xsl:value-of select="concat(author, ': ', title)"/>
      </td>
     </tr>
   </xsl:template>

</xsl:stylesheet>

应用于此 XML 文档时(基于提供的 XML 文本,但使其格式良好且更充实):

<xml>
 <section>Blogs</section>
 <page>showAll</page>
 <data>
   <blogs>
     <blog>
       <author>John Smith</author>
       <title>All about golden fish</title>
       <content>
       Here I publish my latest achievements
       in raising golden fish.
       </content>
    </blog>
     <blog>
       <author>Mary Jones</author>
       <title>Knitting, Knitting, Knitting</title>
       <content>
       How to knit a sharf.
       </content>
    </blog>
   </blogs>
 </data>
</xml>

产生所需的“显示全部”类型的输出

<html>
   <h1>All Blogs: </h1>
   <table border="1">
      <tr>
         <td>Blog of John Smith</td>
         <td>All about golden fish</td>
      </tr>
      <tr>
         <td colspan="2">
            Here I publish my latest achievements
                   in raising golden fish.

         </td>
      </tr>
      <tr>
         <td colspan="2">&nbsp;</td>
      </tr>
      <tr>
         <td>Blog of Mary Jones</td>
         <td>Knitting, Knitting, Knitting</td>
      </tr>
      <tr>
         <td colspan="2">
                   How to knit a sharf.

         </td>
      </tr>
   </table>
</html>

现在我们更改 XML 文档并将 page 元素替换为这个

 <page>showBrief</page>

相同的转换应用于更新的 XML 文档时,它现在会生成所需的摘要输出

<html>
   <h1>Blogs Summary: </h1>
   <table border="1">
      <tr>
         <td>John Smith: All about golden fish</td>
      </tr>
      <tr>
         <td>Mary Jones: Knitting, Knitting, Knitting</td>
      </tr>
   </table>
</html>

二。下一步

实际上,给定模式下的所有模板都将位于其单独的 xsl 文件中,并将由主样式表导入:

转换(主要样式表)因此变为

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:import href="showAll.xsl"/>
 <xsl:import href="showBrief.xsl"/>

 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:template match="/">
  <html>
   <xsl:apply-templates select="*/page"/>
  </html>
 </xsl:template>
</xsl:stylesheet>

请注意

  1. 转换事先不知道将应用哪些模板 -- 转换完全由数据驱动。

  2. 现在不存在的模板可以在未来编写并应用,而无需更改主样式表。

  3. 没有条件逻辑&lt;xsl:choose&gt; 指令等。这就是 xsl 模板的真正威力

  4. 此转换基于 FXSL 所基于的相同理念

【讨论】:

  • 问题, 在上面的代码中做了什么。我认为这基本上打印出内容但是在 xs:apply-templates 而不是 xsl:value 中使用它有什么不同的行为吗?
  • @Matt-Wolfe:这个想法是&lt;content&gt; 节点的主体中可能有标记,并且可能有其他模板匹配这些标记节点并以某种方式转换它们,我们不是甚至知道。这与&lt;xsl:value-of select="content"/&gt;非常不同——这只会输出&lt;content&gt; 的所有文本节点后代的串联。
【解决方案3】:

Dimitre 的例子很好..

这也是一种方法......一个有点丑陋的解决方案,但可以解决问题

primary.xsl

<xsl:variable name="ContentXSL" select="/your/xml/settings/@content" />

<!-- Reference templates -->
<xsl:include href="template1.xsl" />
<xsl:include href="template2.xsl" />
<xsl:include href="template3.xsl" />
<xsl:include href="template4.xsl" />

<xsl:template match="/">
  <html>
    <head>
      <title>..</title>
    </head>
  </html>
  <body>
    <xsl:call-template name="getcontent" />
  </body>
</xsl:template>

<xsl:template name="getcontent">
  <xsl:choose>
    <xsl:when test="$ContentXSL = 'template1'">
      <xsl:apply-templates match="/your/xml/structure" mode="template1" />
    </xsl:when>
    <xsl:when test="$ContentXSL = 'template2'">
      <xsl:apply-templates match="/your/xml/structure" mode="template2" />
    </xsl:when>
    <xsl:when test="$ContentXSL = 'template3'">
      <xsl:apply-templates match="/your/xml/structure" mode="template3" />
    </xsl:when>
    <xsl:when test="$ContentXSL = 'template4'">
      <xsl:apply-templates match="/your/xml/structure" mode="template4" />
    </xsl:when>
    <xsl:otherwise>
      <!-- Default template? -->
      <xsl:apply-templates match="/your/xml/structure" mode="template1" />
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

template1.xsl

<xsl:template match="/your/xml/structure" mode="template1">
  Template 1<br />
</xsl:template>

template2.xsl

<xsl:template match="/your/xml/structure" mode="template2">
  Template 2<br />
</xsl:template>

template3.xsl

<xsl:template match="/your/xml/structure" mode="template3">
  Template 3<br />
</xsl:template>

template4.xsl

<xsl:template match="/your/xml/structure" mode="template4">
  Template 4<br />
</xsl:template>

【讨论】:

  • 这正是我现在正在做的事情,因为这是我弄清楚如何去做的唯一方法。我的主要问题是可扩展性..如果我说 100 个子页面将有多少开销执行 xsl:include 对所有这些是..添加的标记很烦人,但不是一个破坏者,性能可能是一个破坏者,但如果它没有'不缩放
  • 实际上,这与我正在做的事情略有不同,但实现了相同的目标。我只是使用 xsl:call-template 按名称调用模板,而不是使用 xsl:apply-templates。这些模板被命名并且没有匹配元素,所以它实现了同样的事情..不确定上述解决方案是否有好处
【解决方案4】:

除了 Dimitre 的优秀回答推荐了一种实现高阶函数的方法外,您还可以使用母版页和子页结合某种代码的方法,如下所示:

MasterContent.xml:

<title>Test for XSLT</title>

MasterLayout.xml:

<html>
    <head>
        <title></title>
    </head>
    <body>
        <p>This is master page</p>
    </body>
</html>

Master.xsl:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:param name="pMasterLayout" select="document('MasterLayout.xml')"/>
    <xsl:param name="pMasterContent" select="document('MasterContent.xml')"/>
    <xsl:output method="xml"/>
    <xsl:template match="/">
        <xsl:apply-templates select="$pMasterLayout/*"/>
    </xsl:template>
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="title">
        <xsl:copy>
            <xsl:value-of select="$pMasterContent/title"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

子布局:

<html>
    <head>
        <title></title>
    </head>
    <body>
        <h1></h1>
    </body>
</html>

所以,这个转换(“Child.xsl”):

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:include href="Master.xsl"/>
    <xsl:param name="pChildLayout" select="document('ChildLayout.xml')"/>
    <xsl:param name="pChildContent" select="/"/>
    <xsl:template match="body">
        <xsl:copy>
            <xsl:apply-templates select="$pChildLayout/html/body/*"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="h1">
        <xsl:copy>
            <xsl:value-of select="$pChildContent/header"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

使用此输入(“ChildContent”):

<header>Child Content</header>

输出:

<html>
    <head>
        <title>Test for XSLT</title>
    </head>
    <body>
        <h1>Child Content</h1>
    </body>
</html>

注意

在 aranedabienesraices.com.ar 上查看更好的生活示例 我建议使用@id 作为锚点来使用内容填充布局(您可以使用空模板去除那些)。此方法不会将您绑定到任何供应商 IDE(带有 XSLT 概念)来构建您的布局页面。

【讨论】:

  • 嗯,这很有趣。让我想起了一些sitemesh。 Sitemesh 虽然负责以最少的设置进行替换,这需要我弄清楚我将如何/在哪里允许替换.. 我当然喜欢这个想法,它可能比我的原始方法灵活得多,因为我的主模板可以更灵活。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-04
  • 1970-01-01
  • 1970-01-01
  • 2015-02-03
  • 1970-01-01
相关资源
最近更新 更多