与 JSP、PHP 和其他 Web 开发语言的比较

在本文中,Benoit Marchal 考察了 XSLT 处理程序的工作原理。为了说明他的观点,他编写了专门的样式表把处理中的某些方面凸显出来。他特别强调了 XSLT 编码的递归性。很好地理解 XSLT 处理程序可以帮助您成为效率更高的 XSLT 程序员。

教学是一种令人难以置信的学习体验。我在公司的培训课程和有关会议上为开发人员讲授 XML 和 XSLT,经常发现为了向学员澄清一个复杂的问题所作的努力使我自己加深了对问题的理解。我不仅仅是在教我的学员,也是在教我自己。

另外,学员也会带来自己独到的观点,常常迫使我重新思考问题的某些方面并得出新的结论。本文就源于这样的一次经历。我认识到接触过 JSP、PHP、ASP 或 ColdFusion 的学员经常对 XSLT 抱有不正确的设想,这种设想会造成错误的编码。在寻求如何澄清这一问题时,我开始思考 XSLT 处理程序(如 Xalan、Saxon 或 MSXML)到底是如何工作的。这种新的看问题的角度给了我帮助,相信对您也会有所助益。

相似性与区别

初看起来,各种 Web 开发语言之间有很多相似之处,如 JSP 或 PHP 以及 XSLT。最明显的,它们都允许开发人员在 HTML 标签中混合编码:JSP 使用 Java 编写代码,PHP 使用特别的脚本语言,而对于 XSLT 代码则是 XSLT 名称空间的 XML 标签。

这种相似性隐藏了本质的区别。对于 JSP(以及 PHP、ASP 和 ColdFusion),HTML 标签被作为文本处理。事实上,当 JASP 页面在 servlet 中编译时,所有的 HTML 标签都被转移到写语句中。基本上,标签与代码的混合仅仅是为了方便编写代码——这意味着您不需要编写大量的写语句。

而 XSLT 却不是这样。XSLT 处理程序把标签看成是上等公民。XSLT 中的“T”代表转换(Transformation)。转换什么呢?把 XML 文档转换成另一个 XML 文档(HTML 被认为是 XML 的一种变体),或者更准确地说,把树转换成其他的树。什么是        呢?想一想 W3C DOM(在 Java 技术中是 org.w3c.dom 包)。尽管出于性能的原因,现代 XSLT 处理程序内部并不使用 DOM(一个优化库会更有效),但这样有助于把 XSLT 看作是从一棵 DOM 树转换成另一棵 DOM 树的语言。

和 JSP 或 PHP 不同,XSLT 处理程序并不是把标签盲目地写入输出。相反,XSLT 处理程序的工作如下:

  1. 作为 DOM 树(在内部,处理程序优化了 DOM,但这不影响我们的讨论)加载输入文档。
  2. 对输入树进行深度优先的遍历,这和您在编程 101 中所学的深度优先算法没有什么不同。
  3. 遍历文档的过程中,为当前节点在样式表中选择一个模板。
  4. 应用该模板,模板描述了如何在输出树中创建 0 个、1 个或多个节点。
  5. 遍历完成时,按照输入树和模板中的规则生成一棵新树(输出树)。
  6. 根据 HTML 或者 XML 语法写入输出树。

注意,也可以选择深度优先遍历之外的其他算法。但这里我要强调的是 XSLT 处理程序把输入和输出都看作是树。这种处理方式带来了三种结果:

  • 处理程序可以改变语法。根据           xsl:output 语句的值,处理程序可以按照 XML 或者 HTML 语法写入结果。Web 开发语言不能这样做,因为它们把 HTML 标签看作是文本,盲目地复制到输出中。
  • 虽然偶尔可能出错,但处理程序尽量保证输出是结构良好的 XML 文档。
  • 开发人员必须按照树操作表达自己的问题。

下一节中我将说明这句话的含义。


深度优先的遍历

这一节将比较两个样式表。第一个是典型的 XSLT 样式表,第二个对第一个进行了改写,以便公开所采用的深度优先遍历算法。虽然您不会采用这种编码风格,但它有助于解释处理程序是如何工作的。

常规样式表

清单 1 是一个示例 XML 文档,        图1是相应的 DOM 树。        清单2是一个简单的样式表,它把清单 1 转换成 HTML。

清单 1. XML 文档
<?xml version="1.0"?>
<db:article xmlns:db="http://ananas.org/2002/docbook/subset">
<db:title>XSLT, JSP and PHP</db:title>
<db:section>
<db:title>Is there a difference?</db:title>
<db:para>Yes there is! XSLT is a pure XML technology that
traces its roots to <db:emphasis>tree manipulation
algorithms</db:emphasis>. JSP and PHP offer an ingenious
solution to combine scripting languages with HTML/XML
tagging.</db:para>
<db:para>The difference may not be obvious when you're first
learning XSLT (after all, it offers tags and instructions),
but understanding the difference will make you a
<db:emphasis role="bold">stronger and better</db:emphasis>
developer.</db:para>
</db:section>
<db:section>
<db:title>How do I learn the difference?</db:title>
<db:para>Interestingly enough, you can code the XSLT algorithm
in XSLT... one cool way to experiment with the
difference.</db:para>
</db:section>
</db:article>
清单 2. 转换成 HTML 的简单样式表
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:db="http://ananas.org/2002/docbook/subset">
<xsl:output method="html"/>
<xsl:template match="db:article">
<html>
<head><title>
<xsl:value-of select="db:articleinfo/db:title"/>
</title></head>
<body>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="db:para">
<p><xsl:apply-templates/></p>
</xsl:template>
<xsl:template match="db:ulink">
<a href="{@url}"><xsl:apply-templates/></a>
</xsl:template>
<xsl:template match="db:article/db:title">
<h1><xsl:apply-templates/></h1>
</xsl:template>
<xsl:template match="db:title">
<h2><xsl:apply-templates/></h2>
</xsl:template>
<xsl:template match="db:emphasis[@role='bold']">
<b><xsl:apply-templates/></b>
</xsl:template>
<xsl:template match="db:emphasis">
<i><xsl:apply-templates/></i>
</xsl:template>
</xsl:stylesheet>
图 1. 处理程序所看到的 XML 文档

遍历算法

这一节的目标是改写        清单 2,更加清楚地显示深度优先遍历算法。为此您需要一个命名模板。如果不熟悉命名模板,它们就相当于 XSLT 中的方法调用:命名模板是带有         name 属性的模板。它通过         xsl:param 指令接受参数,像下面这样:

<xsl:template name="print">
<xsl:param name="message"/>
<!-- template content goes here -->
</xsl:template>

xsl:call-template 指令用于调用命名模板(而不是         xsl:apply-templates ),比如:

<xsl:call-template name="print">
<xsl:with-param name="message"
select="'See if it prints this message.'"/>
</xsl:call-template>

清单3是对        清单2 的改写,它使得树的遍历算法更明显。为了避免依靠处理程序操作树,这个样式表有一个命名模板         main 实现了树的遍历。         main是一个递归函数,它用         current 参数接受一个节点集并遍历该节点集。模板的主要部分是一条 choose 指令,为给定的节点寻找最适当的规则。在处理一个节点时,该模板递归地调用自身,以便处理该节点的孩子。

清单 3. 公开遍历算法的样式表
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:db="http://ananas.org/2002/docbook/subset">
<xsl:output method="html"/>
<xsl:template match="/">
<xsl:call-template name="main">
<xsl:with-param name="nodes" select="."/>
</xsl:call-template>
</xsl:template>
<xsl:template name="main">
<xsl:param name="nodes"/>
<xsl:for-each select="$nodes">
<xsl:choose>
<xsl:when test="self::db:article">
<html>
<head><title>
<xsl:value-of select="db:title"/>
</title></head>
<body>
<xsl:call-template name="main">
<xsl:with-param name="nodes" select="child::node()"/>
</xsl:call-template>
</body>
</html>
</xsl:when>
<xsl:when test="self::db:para">
<p>
<xsl:call-template name="main">
<xsl:with-param name="nodes" select="child::node()"/>
</xsl:call-template>
</p>
</xsl:when>
<xsl:when test="self::db:ulink">
<a href="{@url}">
<xsl:call-template name="main">
<xsl:with-param name="nodes" select="child::node()"/>
</xsl:call-template>
</a>
</xsl:when>
<xsl:when test="self::db:title[parent::db:article]">
<h1>
<xsl:call-template name="main">
<xsl:with-param name="nodes" select="child::node()"/>
</xsl:call-template>
</h1>
</xsl:when>
<xsl:when test="self::db:title">
<h2>
<xsl:call-template name="main">
<xsl:with-param name="nodes" select="child::node()"/>
</xsl:call-template>
</h2>
</xsl:when>
<xsl:when test="self::db:emphasis[@role='bold']">
<b>
<xsl:call-template name="main">
<xsl:with-param name="nodes" select="child::node()"/>
</xsl:call-template>
</b>
</xsl:when>
<xsl:when test="self::db:emphasis">
<i>
<xsl:call-template name="main">
<xsl:with-param name="nodes" select="child::node()"/>
</xsl:call-template>
</i>
</xsl:when>
<xsl:when test="self::text()">
<xsl:value-of select="."/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="main">
<xsl:with-param name="nodes" select="child::node()"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

如果比较清单 2 和清单 3,就会发现它们在结构上是一致的。在        清单2 中,巨大的 choose 指令是通过模板在幕后实现的。不需要显式地写上 choose 指令,但这确实是处理程序工作的方式。比较清单2 中的模板和         清单3中的测试条件,就会发现存在一一对应的关系。

即使清单 3 中的最后两个测试,也通过默认模板与清单 2 对应。虽然得益于默认模板,清单 2 中不需要明确地写出这两个模板,处理程序确实有两个模板,一个用于文本内容和另一个“catch-all”模板。

清单 2 中,         xsl:apply-templates 指令代替了递归调用。在很多方面,可以认为         xsl:apply-templates是对样式表自身的递归调用!它告诉处理程序移到当前节点的孩子,并尝试寻找另一个适用的模板。清单 3 中的循环和测试非常明显,而在清单 2 中是由处理程序隐含完成的。在清单3 中, 模板使用了一个额外的参数表示当前节点,而在清单 2 中,该参数是隐式的。         xsl:apply-templates自动改变当前节点。

最后但同样重要的一点是模板参数。在        清单3,模板使用参数表示要处理的节点。在        清单2,模板不需要参数,因为处理程序负责管理当前节点。当前节点总是指向模板所应用的节点。当前节点就像是一个隐式参数。


事实上,没有人会编写         清单3这样的样式表。这个例子仅用于教学,但确实可以说明处理程序在幕后是如何工作的。通过比较清单        2和清单         3,可以看出处理程序处理了很多基本代码(比如循环和传递参数),以便实现深度优先的搜索。编写下一个样式表时要把这些记在脑子里,也许您会发现它改变了您编码的方式。

比如,不再像 XSLT 新手常做的那样编写这种代码:

<xsl:template match="db:emphasis">
<xsl:choose>
<xsl:when test="@role='bold'">
<b><xsl:apply-templates/></b>
</xsl:when>
<xsl:otherwise><i><xsl:apply-templates/></i></xsl:otherwise>
</xsl:choose>
</xsl:template>

您可以改写成下面这样,如果考虑到处理程序的工作原理,它们是完全等价的:

<xsl:template match="db:emphasis[@role='bold']">
<b><xsl:apply-templates/></b>
</xsl:template>
<xsl:template match="db:emphasis">
<i><xsl:apply-templates/></i>
</xsl:template>

我希望已经说明了 XSLT 处理程序的内部工作原理。很好地理解这一点对于改进您的样式表编码非常重要。

参考资料

  • 您可以参阅本文在 developerWorks 全球站点上的          英文原文.
  • 请参与 Marchal “使用 XML”专栏的          讨论论坛
  • 通过 Michael Kay 的“          XSLT 是什么类型的语言?”(          developerWorks,2001 年 2 月)进一步了解 XSLT 及其与其他语言的比较。
  • 阅读“          递归,而非拆分,以便得胜”(          developerWorks,2001 年 7 月),Benoit Marchal 讨论如何利用 XSLT 递归满足特殊的需要。
  • 看一看 Benoit Marchal 的“          把文件映射成 SOAP 请求,第 2 部分”(          developerWorks,2004 年 1 月),这是在 XSLT 中使用递归转换特定数据的又一个例子。
  • 通过 Uche Ogbuji 的文章“          实时调试 XSLT”(          developerWorks,2002 年 11 月)学习能够洞察样式表工作原理的调试技术。
  • 在          developerWorksXML 专区可以找到数以百计的 XML 资源,包括          专栏汇总页面中 Benoit Marchal 的“使用 XML”专栏。
  • 下载          IBM WebSphere Studio Application Developer,这是一个支持使用 XML、JSPTM、servlet、HTML、Web服务、数据库和 EJB 等不同技术构建各种应用程序的开发工具。
  • 在          developerWorksDeveloper Bookstore可以找到各种关于 XML 的书籍。
  • 了解如何才能成为一名          IBM认证的 XML 及相关技术的开发人员

XSLT 处理程序是如何工作的的更多相关文章

  1. httpHandlers与Http处理程序

    ASP.NET HTTP 处理程序是响应对 ASP.NET Web 应用程序的请求而运行的过程(通常称为"终结点").最常用的处理程序是处理 .aspx 文件的 ASP.NET 页 ...

  2. 使用 EPUB 制作数字图书

    基于 XML 的开放式 eBook 格式 是否需要分发文档.创建电子图书或者把喜欢的博客文章存档?EPUB 是一种开放式的数字图书规范,以常用的技术如 XML.CSS 和 XHTML 为基础,EPUB ...

  3. 15.linux按键驱动程序(二)

    linux按键驱动程序 包含内容定时器延时去抖动,阻塞型设备驱动设计 一.定时器延时去抖 按键所用开关为机械弹性开关,当机械触点断开.闭合时,由于机械触点的弹性作用,开关不会马上稳定地接通或断开.因而 ...

  4. 淘宝购物车页面 智能搜索框Ajax异步加载数据

    如果有朋友对本篇文章的一些知识点不了解的话,可以先阅读此篇文章.在这篇文章中,我大概介绍了一下构建淘宝购物车页面需要的基础知识. 这篇文章主要探讨的是智能搜索框Ajax异步加载数据.jQuery的社区 ...

  5. 转载MSDN 在ASP.NET 中执行 URL 重写

    转载文章原网址 http://msdn.microsoft.com/zh-cn/library/ms972974.aspx 摘要:介绍如何使用 Microsoft ASP.NET 执行动态 URL 重 ...

  6. Linux内核:关于中断你须要知道的

    1.中断处理程序与其它内核函数真正的差别在于,中断处理程序是被内核调用来对应中断的,而它们执行于中断上下文(原子上下文)中,在该上下文中执行的代码不可堵塞. 中断就是由硬件打断操作系统. 2.异常与中 ...

  7. Linux的时钟管理

    本文转自博客园zhenwenxian的Linux时间管理,很详细,写得很不错,对初学者还是有很大帮助的. 时间管理在内核中占有非常重要的地位.相对于事件驱动,内核中有大量的函数都是基于时间驱动的.内核 ...

  8. Linux内核——定时器和时间管理

    定时器和时间管理 系统定时器是一种可编程硬件芯片.它能以固定频率产生中断.该中断就是所谓的定时器中断.它所相应的中断处理程序负责更新系统时间,还负责执行须要周期性执行的任务. 系统定时器和时钟中断处理 ...

  9. NET那点不为人知的事

    ASP.NET那点不为人知的事(一)   我们上网时,在浏览器地址输入网址:Http://www.cnblogs.com,按下回车,一张网页就呈现在我们眼前.这究竟发生了什么?对于一名优秀的Progr ...

随机推荐

  1. [DevExpress]GridControl 同步列头checkbox与列中checkbox状态

    关键代码: /// <summary> /// 同步列头checkbox与列中checkbox状态 /// </summary> /// <param name=&quo ...

  2. JS中的!=、== 、!==、===的用法和区别。

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 var num = 1;   var str = '1';   var test = 1;   t ...

  3. 1062 Talent and Virtue (25)

    /* L (>=60), the lower bound of the qualified grades -- that is, only the ones whose grades of ta ...

  4. 1061. Dating (20)

    #include <stdio.h> #include <map> #include <string.h> #include <ctype.h> usi ...

  5. SQL Server数据库备份(异机)

    简单的远程异机备份数据库功能,通过这个存储过程,讲远程其他机器上的数据库备份到本地.其主要原理为: 1.通过XP_CMDSHELL执行Windows命令,将本机的共享目录映射为远程机器的网络驱动器. ...

  6. JSON操作,转载

    http://www.cnblogs.com/mcgrady/archive/2013/06/08/3127781.html#_label0

  7. MVC使用的MetaModel代码生成器模板

    代码生成器能使从一些重复的工作中缓解下来 在最近开发MVC项目中使用到了MetaModel用来设定Model的显示名称,数据限制的代码生成模板,自己第一做代码生成模板还有很多缺陷. 下面是模板代码: ...

  8. ASIHTTPRequest的使用(转)

    转载自:http://fushengfei.iteye.com/blog/1147112 博客分类: IOS   原文地址:http://wiki.magiche.net/pages/viewpage ...

  9. To get TaskID's Integer ID value from the GUID in SharePoint workflow

    list.GetItemByUniqueId(guid).ID int itemID = spList.Items[new Guid("")].ID;

  10. Web前端框架学习成本比较及学习方法

    就项目中自己用过的前端框架的学习成本比较与学习心得分享 刚工作时间不长只用过这几个框架下面是难易程度比较: 不论哪个web前端框架, 究其本质都是把页面的数据传递给后台服务器语言(如java)进行处理 ...