从 XSL 参数获取值

 

库尔特·卡格尔
2000 年 6 月 22 日

目录

了解 XSLT 参数和变量 A Select Vintage 设置参数 摘要

如果你曾经编写过 ASP 页,你可能参与了两项活动:从一些外部资源读取信息并将其他 (通常修改) 信息保存到其他资源,这对发送到客户端的最终输出只有偶然的重要性。 其中一些是状态信息(例如存储可能来自查询字符串请求的信息),而在其他情况下,你正在从表单帖子更新数据库,或者可能存储文件。

当我开始使用基于 Microsoft 1998 年 12 月提交的 XSL 和 XML 模式) 的较旧的 XSL (时,我很快意识到它必须具备多少强大功能才能从根本上简化输出代码的生产。 在调用样式表时,XSL 筛选器 () 可以将数据库调用转换为表,可以安全地合并锅炉板 HTML 代码,甚至可以执行简单的处理任务。

然而,XSL 对于严重、重型起重的提升有一些限制。 最大的问题是参数:如果没有熟悉样式表中的基础代码,就没有一种干净的方法来更改 XSL 结构。 通常,将信息传递到 XSLT 转换的唯一方法是在 XML 输入上放置参数。 另一个问题是在内部保留有关处理器当前状态的信息。 如果有要发送到输出流的 XML 标记,则无法暂时保存它并在多个位置使用它。 最后,没有真正的方法来保存 XSL 环境本身中的状态;必须从 XSL 转换中获取结果,使用文档对象模型 (DOM) 调用筛选出来,并实际上会丢失筛选器中可能发生的任何中间状态。

技术预览 XML 分析程序支持位于 http://www.w3.org/TR/xslt) 的较新的XSL-Transformation规范 (。 这个较新的规格包含许多创新功能,使 XSLT 比 Microsoft 1998 年 12 月版本的 XSL 更强大。 该规范还使 XSLT 成为脚本语言作为服务器工具的严重竞争对手。 除此之外,Microsoft 还引入了一种将脚本代码 (符合 W3C 标准的新方法) 集成,最终可能会彻底改变我们对服务器端编程的看法。

了解 XSLT 参数和变量

在理想情况下,XSLT 筛选器是一个“黑匣子”,它接受一个或多个输入并创建一个或多个输出。 你不应该知道那个盒子里发生了什么。 在函数中,通过参数将信息传递到黑盒中完成。 在 XSL 文档中,该过程也是使用参数(特别是 <xsl:param> 元素)完成的。 这些 XSLT 元素在声明性世界中提供一些相同的用途, (数据流进入、转换,然后流出) 作为函数参数在过程世界中 (日常代码占据最高) 。 但是,这两者并不完全相同。

XSLT 还包括变量。 变量和参数之间的主要区别在于,参数旨在将某个源的内容分配给其当前模板,而变量在内部分配。 这在传统函数中再次有一个类比:在函数主体中声明的变量通常是私有的,调用函数的代码不应知道或关心存在此类变量。 在这方面,XSLT 变量同样是私有的。

考虑 xsl:param 或 xsl:variable 元素的最佳方法是,一旦将参数与名称和内容相关联,该元素是不可变的;你还可能已将其写入一次读取多 CD-ROM。 这一点本质上是采用的,如果尝试创建另一个具有相同名称的参数,至少 (在同一范围内;稍后) ,分析程序将当场拒绝你的尝试。 这与大多数过程语言中的参数大相径径,这些参数可在内部轻松修改为函数。

此外,xsl:param 和 xsl:variable 元素实际上有两种不同的赋值方式。 可以在 W3C 规范中 xsl:param 对象的正式说明中看到这些内容:

<xsl:param
   name = qname
   select = expression>
   <!-- Content: template -->
</xsl:param>

<xsl:variable
   name = qname
   select = expression>
   <!-- Content: template -->
</xsl:variable>

设置 xsl:param 元素内容的最明显方法是将元素分配给其内部内容, (xsl:param 和 </xsl:param>> 标记之间的<材料) 。 简单而言,只需将文本分配给 参数即可。 例如,如果要将某人的姓氏传递给参数,只需将其作为内部内容包括:

<xsl:param name="last_name">Cagle</xsl:param>

以这种方式定义参数后,稍后可以在 XSLT 中引用它,方法是在名称前面加上美元符号 ($) (顺便说一句,此参数也适用于变量。) 。 例如,若要将其 <插入模板中的 last_name> 元素,请使用 xsl:value-of 元素:

<last_name><xsl:value-of select="$last_name"/></last_name>

但是,也可以将 XML 标记分配给 参数。 例如,假设你想要创建人员的完整记录。 可以通过 param 语句执行此操作:

<xsl:param name="person">
   <record>
       <first_name>Kurt</first_name>
       <last_name>Cagle</last_name>
       <vocation>Writer</vocation>
   </record>
</xsl:param>

以这种格式处理结构稍微复杂一点,但并不多。 如果尝试使用表达式 <xsl:value-of select=“$person”>,则将写入输出流的所有内容如下:

KurtCagleWriter

这可能不是你打算的。 另一 <方面,xsl:copy-of select=“$person> 语句允许输出整个 XML 结构:

<record>
      <first_name>Kurt</first_name>
      <last_name>Cagle</last_name>
      <vocation>Writer</vocation>
</record>

在这里,事情变得有趣。 关于参数 (及其同级变量) ,需要记住的一个重要点是,分析程序在遇到参数或变量时会自动解释参数或变量,而不是存储匹配模式供以后解释。 如果分析程序在变量或参数中遇到 XSL 表达式,则此时会自动计算该表达式,并将其写入与参数名称关联的空间中。 这个奇怪的小事实使参数和变量 非常有用

例如,请考虑如下事项:

<xsl:param name="first_name">Kurt</xsl:param>
<xsl:param name="last_name">Cagle</xsl:param>
<xsl:param name="vocation">Writer</xsl:param>
<xsl:param name="record">
   <first_name><xsl:value-of select="$first_name"/></first_name>
   <last_name><xsl:value-of select="$last_name"/></last_name>
   <vocation><xsl:value-of select="$vocation"/></vocation>
</xsl:param>

记录参数计算前三个参数的值,以便无论参数设置为什么,记录都将反映参数的更新值。 现在,在 XSL 文档中输入简单参数以生成更复杂的对象变得非常简单。

结果树片段

但是,这有一个主要限制。 W3C 的 XSLT 委员会内争论较激烈的辩论之一涉及如何处理变量内容的问题。 内容是位于元素标记内的元素部分, (换句话说,元素的“文本”) 。 为 XSLT 变量执行 select 语句时,select 语句会将 node-set 分配给变量,该变量可以像文档中的任何其他节点集一样进行操作。

另一方面,W3C 决定,即使节点内的内容格式良好,内容格式错误的危险足以指定内容是其自身的唯一类型,称为 结果树片段。 此类片段在 XSLT 中被视为二等公民。 虽然可以 (执行一些操作,例如获取最顶层节点的名称) ,但无法对其应用大多数 XPath 导航运算符。

因此,对于上述记录,表达式:

<xsl:value-of select="$record/first_name"/>

根据 XSLT 规范,即使已从对现有输入流执行操作的选定值分配了记录,也是非法的:

<xsl:variable name="record select="//record[1]"/>

那么,同样的声明是完全合法的。

请注意,MSXML 技术预览版分析程序当前行为无法识别此限制。 从变量元素的内容创建的片段被视为具有与 select 语句中的片段相同的特权。 这是一项重要功能-在很大程度上,因为它提供了一种从命名模板操作的结果访问元素的方法, (可以调用的模板(如函数),而不是通过搜索模式进行匹配;这将在即将发布的专栏) 中讨论。 例如,如果模板对元素进行排序和筛选,则这样就可以循环访问排序的元素,而不是循环访问未筛选集,这是创建“分页”输出的重要功能。

为了遵守 W3C 在这方面的行为,Microsoft 最终可能会在最终的 MSXML Web 版本中弃用此关键功能,但可能会通过扩展函数在最终的 MSXML Web 版本中使用它,如下所示:

<xsl:value-of select="msxsl:node set($record)/first_name"/>

此外,Microsoft 正在推动将此功能包含在正在讨论的 XSLT 2.0 规范中,因为将内容视为完整状态节点集的功能大大扩展了 XSLT 可以执行的操作。

精选年份

select 属性提供了另一种填充参数或变量的方法。 具有参数的 select 语句的工作方式与 select 语句在其他位置的工作方式完全相同:如果属性中的表达式可以解释为 XPath 表达式,则分析程序将尝试查找满足给定表达式的所有节点,然后将其放入变量中。 例如,假设输入流包含大量记录,其中显示了大量人员的姓名和职业。 可以设置变量 (此处不需要参数,) 检索初始流中的所有编写者:

<xsl:variable name="writers" select="//record[vocation='Writer']">

有了此变量后,基本上就会有一个节点片段,其中$writers作为其根,并且只有那些作为子级编写者的人。 如果要循环访问此集,只要记得$writers是根节点,就可以使用 <xsl:apply-templates> 或 <xsl:for-each> 元素轻松将上下文设置为每个节点:

<xsl:template match="/">
<ul>
<xsl:for-each select="$writers/*">
       <li><xsl:value-of select="first_name"/><xsl:text> </xsl:text></xsl:value-of select="last_name"/></li>
</xsl:for-each>
</ul>
</xsl:template>

这会创建初始流中所有编写器的 HTML 项目符号列表。 请注意,这样做的一个效果是文档中) (上下文不再依赖于模板的直接上下文。 顺便说一句,表达式 <xsl:text></xsl:text> 将空格字符放入输出中。 这有点难看,但请务必记住,XSLT 针对生成的 XML(而不是原始文本)进行优化。

select 语句将自动替代变量或参数标记的内容,但从技术上讲,使用这两者被视为 XSLT 冲突。 幸运的是,它不会在 MSXML 分析器中引发错误,因为有时两者都很方便。

还可以使用 select 语句来计算表达式或包含文本,但在后一种情况下,必须小心。 XSLT 分析程序会自动将 select 属性中找到的任何文本解释为 XPath 表达式的一部分,除非该文本是特意双引号 (,双引号内包含单引号,反之亦然,) 。 因此,可以将职业字段设置为“writer”,如下所示:

<xsl:param name="vocation" select="'writer'"/>

计算表达式稍微复杂一点,但并不多。 根据 元素的当前上下文,将 select 属性视为任何 XPath 表达式的窗口非常有用。 除了能够导航主 DOM 之外,XPath 还包含许多可用于帮助计算表达式的函数,这些表达式可以包括可显著扩展 XSLT 功能的算术、字符串或其他运算。 例如,假设你有一个 XML 结构,该结构提供了发票中的项目列表,包括任何给定项的编号以及其中任一项的成本 (,如下所示) 。

<lineItems>
   <lineItem>
      <code>42AC5</code>
      <title>Loopy Fruit Cereal</title>
      <amount>12</amount>
      <cost>4.25</cost>
   </lineItem>
   <lineItem>
      <code>H343A</code>
      <title>MicroSecond Rice</title>
      <amount>14</amount>
      <cost>2.35</cost>
   </lineItem>
   <lineItem>
      <code>EA198</code>
      <title>Crescent Toothpaste</title>
      <amount>18</amount>
      <cost>1.95</cost>
   </lineItem>
</lineItems>

然后,可以使用 XPath 固有的一些内置函数,将整个行项集的总成本分配给变量:

<xsl:variable name="lineItemSubTotals">
   <xsl:for-each select="//lineItem">
      <subTotal><xsl:value-of select="number(amount)*number(cost)"/></subTotal>
   </xsl:for-each>
</xsl:variable>
<xsl:variable name="lineItemsTotal">
   <xsl:value-of select="sum($lineItemSubTotals/subTotal)"/>
</xsl:variable>

在这种情况下, 变量 $lineItemSubTotals 创建一个由 subTotal> 元素组成的<从属 XML 结构,并将其分配给 lineItemTotals 变量。 对于上面的示例,它实质上与:

<xsl:variable name="lineItemTotals">
   <subTotal>51</subTotal>
   <subTotal>32.9</subTotal>
   <subTotal>35.1</subTotal>
</xsl:variable>

第二个变量 $lineItemsTotal,使用 sum () XPath 函数一起添加项总计,然后将生成的值 119 分配给 lineItemsTotal 变量。 这与过程方法不同,这与算法 (() 非常简单)不同,而是因为事务中不使用包含每个行项项之和的变量 $lineItemsTotal。 还可以更改记录的输出,使其在行项中读取,然后使用添加的字段创建新的行项,但保留小计信息以拉取总和:

<xsl:variable name="newLineItems">
   <xsl:for-each select="//lineItem"">
      <lineItem>
         <xsl:copy-of select="*"/>
         <subTotal><xsl:value-of select="number(amount)*number(cost)"/></subTotal>
      </lineItem>
   </xsl:for-each>
</xsl:variable>
<xsl:variable name="lineItemsTotal">
   <xsl:value-of select="sum($newLineItems/subTotal)"/>
</xsl:variable>

此代码集创建一组新的行项并执行求和,说明 XSL 筛选器基本上可以放大 (或减少) 现有 XML 文档以合并中间处理信息的方式。

设置参数

若要使用参数,需要对其进行设置。 鉴于此处所述的 XSLT 旨在在 ASP 环境中运行,因此必须在 ASP 中设置参数。 有多种方法可以执行此操作,具体取决于首先将信息传输到服务器的方式,尽管在大多数情况下,它们涉及 XML DOM。

在几乎所有情况下,设置实际参数都涉及设置参数的 select 属性或为该属性分配文本。 例如,可以设置属性以在 Visual Basic Scripting Edition (VBScript) 中使用某些 DOM 调用为 addRecord.xsl 筛选器创建新记录,假设first_name、last_name和职业属性作为查询字符串发送:

<%
firstName=request.queryString("first_name")
lastName=request.queryString("last_name")
vocation=request.queryString("vocation")
set addRecordFilter=createObject("MSXML2.DOMDocument")
addRecordFilter.async=false
addRecordFilter.load server.mapPath("addRecord.xsl")
set
firstNameNode=addRecordFilter.selectSingleNode("//xsl:param[@name=
'first_name']")
firstNameNode.setAttribute "select","'"+firstName+"'"
set
lastNameNode=addRecordFilter.selectSingleNode("//xsl:param[@name=
'last_name']")
lastNameNode.setAttribute "select","'"+lastName+"'"
set
vocationNode=addRecordFilter.selectSingleNode("//xsl:param[@name=
'vocation']")
vocationNode.setAttribute "select","'"+vocation+"'"
set stubDoc=createObject("MSXML.DOMDocument")
stubDoc.loadXML "<stub/>"
response.write stubDoc.transformNode(addRecordFilter)
%>

例如,表达式“//xsl:param[@name='first_name']”访问名称为first_name的参数,并将 select 属性的内容替换为从查询字符串检索到的值(用单引号包装),以强制将其计算为字符串而不是 XPath 表达式。

还可以使用 XSLTemplate(XML 技术预览版分析程序的一部分)在一定程度上简化此过程。 派生自模板的处理器 () 提供了许多用于更新参数和对象的接口:

<%
firstName=request.queryString("first_name")
lastName=request.queryString("last_name")
vocation=request.queryString("vocation")
set addRecordFilter=createObject("MSXML2.DOMDocument")
addRecordFilter.async=false
addRecordFilter.load
server.mapPath("addRecord.xsl")
set template=createObject("MSXML2.XSLTemplate")
set template.stylesheet=addRecordFilter
set processor=template.createProcessor
processor.AddParameter "first_name",firstName
processor.AddParameter "last_name",lastName
processor.AddParameter "covation",vocation
set stubDoc=createObject("MSXML.DOMDocument")
stubDoc.loadXML "<stub/>"
processor.input=stubDoc
processor.transform
processor.output=response
%>

请注意,XSLT 文档实际上并不关心它正在转换的 XML 文档。 文档的唯一用途是强制输出。 这是为了证明在使用 XSLT 时不需要专门使用主输入流。 同样,这对于命名模板比使用变量更有意义,变量是未来列的主题。 实际工作是使用 document () persistDocument () 函数完成的,这些函数加载到记录的结构中,将节点 (排序) 添加到记录中,然后保存这些结构。

此处的目的是尽可能减少特定于应用程序的代码,以便可以避免大量代码返工。 执行此操作的一种方法是通过 queryStringForm 集合枚举以检索可能的参数值,然后为参数赋值(如果存在此类参数)。

这也提供了一个有趣的可能性:你可以以相同的方式将 XSL 筛选器的名称作为参数传递,包含在变量“filter”中。这样,同一 ASP 文件就可以以更通用的方式处理任意数量的 XSLT 筛选器。

<%
' XSLServer.asp
function main()
   xslFilter=request("filter")
   if right(xslFilter,4,1)<>"." Then
      xslFilter=xslFilter+".xsl"
   end if
   set xslDoc=createObject("MSXML2.DOMDocument")
   xslDoc.async=false
   xslDoc.load server.mapPath(xslFilter+".xsl")
   for each key in request.form
            set paramNode=xslDoc.selectSingleNode("//xsl:param[name='"+key+"']")
            if not (paramNode is nothing) then
                  paramNode.setAttribute "select","'"+request(key)+"'"
            end if
      next
      for each key in request.queryString
            set paramNode=xslDoc.selectSingleNode("//xsl:param[name='"+key+"']")
            if not (paramNode is nothing) then
                  paramNode.setAttribute "select","'"+request(key)+"'"
            end if
      next
      set stubDoc=createObject("MSXML.DOMDocument")
      stubDoc.loadXML "<stub/>"
      response.write stubDoc.transformNode(xslDoc)
end main
 
main
%>

请注意,仅当 XSLT 参数已存在时,查询字符串或表单参数才会映射到 XSLT 参数。 这还提供了一种将 XSL 文件的名称传递给筛选代码的方法。 只需设置一个名为 filter 的参数。 请注意,如果未提供其他扩展名,则代码还会将字符串 .xsl 追加到筛选器名称。 这种触摸只是使筛选器看起来更像是一个命令,而不是文件名。

因此,查询字符串:

http://www.myserver.com/xslserver.asp?filter=addrecord &first_name=Kurt&last_name=Cagle&vocation=Writer

会将该信息传递到 addRecord.xsl 筛选器。 这为基于 URL 的合理强大 API 约定提供了基础 -- 包含要使用的方法名称的筛选器,以及包含关联参数的所有其他查询字符串参数。

还可以通过选择接口) 传递 XML 节点、XML 节点集 (,甚至将整个 DOM 文档作为参数传递。 在这种情况下,只需将 元素作为子元素追加到 参数并删除 select 语句:

<%
' XSLServer.asp
function main()
      set newUserDoc=createObject("MSXML2.DOMDocument")
      newUserDoc.load  "newUser.xml"
      set xslDoc=createObject("MSXML2.DOMDocument")
      xslDoc.async=false
      xslDoc.load server.mapPath("addRecord.xsl")
      set newRecordNode=xslDoc.selectSingleNode("//xsl:param[name='record']")
      newRecordNode.appendChild newUserDoc.documentElement
      newRecordNode.removeAttribute "select"
      set stubDoc=createObject("MSXML.DOMDocument")
      stubDoc.loadXML "<stub/>"
      response.write stubDoc.transformNode(xslDoc)
end main
 
main
%>

这是另一种情况,即使用 XSL 处理器与使用 transformNode 函数一样多或更多。 XSLT 的主要问题是,每次需要执行转换时分析样式表的成本都非常高,尤其是在许多情况下,你处理的是每种情况中使用的相同 XSLT 脚本。 使用 处理器 对象呈现的相同功能 (保存在会话变量) 中,也可以按如下所示编写:

<%
' XSLServer.asp
function main()

   if typename(session("addRecord"))="IXSLProcessor" then
      set addRecord=session("xslProc")
      set stubDoc=session("stubDoc")
   else
      set xslDoc=createObject("MSXML2.DOMDocument")
      xslDoc.async=false
      xslDoc.load server.mapPath("addRecord.xsl")
      set xslTemplate=createObject("MSXML2.XSLTemplate")
      set xslTemplate.stylesheet=xslDoc
      set addRecord=xslTemplate.createProcessor
      session("addRecord")=addRecord
      set stubDoc=createObject("MSXML.DOMDocument")
      stubDoc.async=false
      stubDoc.loadXML "<stub/>"
      session("stubDoc")=stubDoc
   end if
   set newUserDoc=createObject("MSXML2.DOMDocument")
      newUserDoc.load "newUser.xml"
   addRecord.addParameter "record",newUserDoc
   addRecord.input=stubDoc
   addRecord.output=response
   addRecord.transform
end main
 
main
%>

这与前两个示例大致相同,但会将 DOM 树传递到 XSL 样式表,而不是单个字符串。 如果要将已处理的节点传递回 XSLT 筛选器,这会很方便。

有关 XSLTemplateXSLProcess 对象的详细信息,请参阅 Chris Lovett 内部 MSXML3 性能

总结

变量和参数可以通过提供保存临时信息的机制、为未传递的属性提供默认值、充当外部文档的根节点以及公开使样式表更具动态性的方法,显著扩展 XSLT 的功能。 还可以特别使用参数作为工具,用于处理其他组件,特别是通过提供对组件的引用或允许计算设备位置的方法,尤其是与脚本结合使用。 参数应被视为任何 XML 开发人员的剧目的关键部分。

Kurt Cagle 是一位作家和开发人员,专门从事 XML 和相关 Internet 技术。 他与妻子和两个女儿住在华盛顿奥林匹亚,可以通过电子邮件 cagle@olywa.net 或共享网站* VB XML 联系。