利用类型和模式改进您的 XSLT 2.0 样式表

通过 XSLT 2.0 指定模式类型、参数类型和返回值以简化调试和维护

在您调试样式表时,XSLT 2.0 的类型感知和模式感知特性能够为您提供极大的帮助,并且在处理所有输入数据时,帮助您改进样式表的质量和健壮性 (robustness)。了解如何在调试和测试流程中使用类型感知和模式感知的 XSLT 2.0,以避免出现有关数据类型和基数的无效路径、错误假设等常见问题。此外,本文还提供了一些包含错误的 XSLT 样式表示例,如果未使用模式感知特性,就无法捕获这些错误。您将了解如何在有用的错误消息中显式指定类型结果。

Priscilla Walmsley, 常务董事, Datypic

Priscilla Walmsley 的照片Priscilla Walmsley 是 Datypic 公司的常务董事和高级顾问。她擅长 XML 技术、架构和实现。最近,她一直忙于通过 Trusted Federal Systems 公司和美国司法部一起开发 LEXS —— 一个基于 NIEM 的 IEPD 框架。她是 Definitive XML Schema(Prentice Hall,2001)和 XQuery(O'Reilly Media,2007)的作者,还是 Web Service Contract Design and Versioning for SOA(Prentice Hall,2008)的合著者。



2012 年 7 月 02 日

XML 内容可能是复杂的且难以预测。如果使用 XSLT 1.0 处理 XML 内容,在定义正确的表达式、处理所有可能出现的内容结构时,需要经历大量试错过程。举例来说,XPath 1.0 表达式中拼写错误的名称会导致不返回任何内容,而不是提供有用的错误消息,因而难以调试。

常用的首字母缩略词

  • HTML:超文本标记语言
  • XHTML:可扩展 HTML
  • XML:可扩展标记语言
  • XSLT:可扩展样式表语言转换

与 1.0 版本相比,XSLT 2.0 取得了很大的进展,其中新增的强类型和模式感知都是非常有用的特性。在 XSLT 样式表中声明值类型使处理器能够告诉您:何时做出了有关 XML 内容数据类型、特定元素或属性的出现次数的错误假设。这项功能将提供更有用的错误消息。导入 XML 模式进一步加强了这项功能,为处理器提供充足的信息,使之能够了解输入文档结构,以便在出现无效的 XPath 时通知您,而非不返回任何内容。它们还提供了有关内容中数据类型的信息,避免对相应数据类型执行无意义的操作。

在 XSLT 中使用类型

大多数编程语言都提供了指定变量或参数类型的方法。在 XSLT 2.0 中,您可以使用 as 属性声明表达式类型,该属性可出现在多个位置:

  • xsl:variablexsl:param 元素中,指明变量或参数的类型
  • xsl:templatexsl:function 元素中,指明该模板或函数的返回类型
  • xsl:sequence 元素中,指明序列类型
  • xsl:with-param 元素中,指明传递给一个模板的值类型

as 属性的值也称为序列类型

使用 XML 模式的内置类型

as 属性中常用的一种序列类型就是特定数据类型(例如字符串或者整数)的名称。在 XSLT 中,您可以使用 XML 模式规范内置的数据类型之一。为此,举例来说,您可使用 xs: 作为类型名称的前缀,并在样式表顶端声明 xs: 名称空间。表 1 列举了最常用的 XML 模式数据类型。

表 1. 常用的 XML 架构数据类型
数据类型名称描述示例
string任意文本字符串abc 就是一个字符串
integer任意大小的整数1, 2
decimal小数1.2, 5.0
double双精度浮点数1.2, 5.0
dateYYYY-MM-DD 格式的日期2009-12-25
timeHH:MM:SS 格式的时间12:05:04
boolean真/假值真、假
anyAtomicType任意简单类型的值字符串、123、假、2009-12-25

举例来说,如果希望将变量值声明为日期,我可以使用:

 <variable name="effDate" select="bill/effDate" as="xs:date"/>

字符串和日期等简单值也称为原子值。请注意,如果 effDate 是一个元素,其内容将转换为 xs:date 类型的原子值(假设没有与此输入文档关联的模式)。

表示 XML 节点的序列类型

您也可以使用 表 2 中的更为通用的序列类型,表示 XML 树中的节点类型。无需使用 xs: 作为这些序列类型的前缀,因为它们不属于 XML 模式类型。

表 2. 表示 XML 节点的序列类型
序列类型描述
element()任意元素
element(book)名为 book 的任意元素
attribute()任意属性
attribute(isbn)名为 isbn 的任意属性
text()任意文本节点
node()任意种类的节点(元素、属性、文本节点等)
item()任意种类的节点,或者任意种类的原子值(如字符串、整数)

举例来说,如果我想表示与一个变量绑定的值是一个元素,则可以说:

 <variable name="effDate" select="//effDate" as="element()"/>

与上一个示例不同,此值不会转换为原子值;变量将包含元素本身。

使用出现次数指示符 (indicator)

您还可以使用 表 3 所列的出现次数指示符,表示特定项目可能出现了多少次。这些出现次数指示符将出现在序列类型的末尾处,紧临 表 1 或者 表 2 的表达式之后。

表 3. 使用出现次数指示符
出现次数指示符描述
*零次到多次
?零次到一次
+一次到多次
(无出现次数指示符)一次且仅一次

举例来说,如果我想表示与一个变量绑定的值是零到多个元素,则可以说:

 <variable name="effDate" select="//effDate" as="element()*"/>

零个元素(或者任意种类的零个项目)也称为空序列


利用类型使您的样式表更加健壮

您可能有兴趣了解如何利用 as 属性改进样式表。下面来看几个例子。本文以 .zip 文件的形式提供了所有这些代码示例的下载。(请参阅 下载)。

场景 1:使用类型执行基数

清单 1 展示了一个包含两本书的输入文档。

清单 1. 图书输入样例 (books.xml)
 <books xmlns="http://www.datypic.com/books/ns">
   <book>
     <title>The East-West House: Noguchi's Childhood in Japan</title>
     <author>
       <person>         
         <first>Christy</first>         
         <last>Hale</last>       
       </person>
     </author>
     <price>9.95</price>     
     <discount>1.95</discount>
   </book>   
   <book>     
     <title>Buckminster Fuller and Isamu Noguchi: Best of Friends</title>     
     <author>   
       <person>       
         <first>Shoji</first>         
         <last>Sadao</last>   
       </person>   
     </author>     
     <price>65.00</price>     
     <discount>5.00</discount> 
   </book>
 </books>

清单 2 展示了将图书输入转为 HTML 的样式表。

清单 2. 场景 1 的 XSLT 原始版本 (xslt_before.xsl)
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"   
  xmlns:dtyf="http://www.datypic.com/functions"   
  xpath-default-namespace="http://www.datypic.com/books/ns">    
   
  <xsl:output method="html"/>    
  
  <xsl:template match="books">     
    <html>       
      <body>         
        <table border="1">           
          <xsl:apply-templates/>         
        </table>       
      </body>     
    </html>   
  </xsl:template>    
  
  <xsl:template match="book">     
    <tr>       
      <td><xsl:value-of select="title"/></td>       
      <td><xsl:value-of select="dtyf:format-person-name(author/person)"/></td>       
      <td><xsl:value-of select="price"/></td>     
    </tr>   
  </xsl:template>    
  
  <xsl:function name="dtyf:format-person-name">     
    <xsl:param name="person"/>     
    <xsl:value-of select="$person/last"/>     
    <xsl:text>, </xsl:text>     
    <xsl:value-of select="$person/first"/>   
  </xsl:function> 
</xsl:stylesheet>

利用所提供的输入文档,XSLT 将如期运行,提供如 图 1 所示的结果。

图 1. 场景 1 的原始 XSLT 在使用 books.xml 时的结果
该图显示了场景 1 的原始 XSLT 在使用 books.xml 时的结果

假设我试着使用更多变的测试数据运行它,例如 morebooks.xml(清单 3)。

清单 3. 更多变的输入文档 (morebooks.xml)
<books xmlns="http://www.datypic.com/books/ns">   
  <book>     
    <title>Isamu Noguchi: Sculptural Design</title>     
    <author>       
      <institution>Vitra Design Museum</institution>     
    </author>     
    <price>125.00</price>   
  </book>   
  <book>     
    <title>The East-West House: Noguchi's Childhood in Japan</title>     
    <author>       
      <person>         
        <first>Christy</first>         
        <last>Hale</last>      
      </person>     
    </author>     
    <price>9.95</price>     
    <discount>1.95</discount>   
  </book>   
  <book>     
    <title>Buckminster Fuller and Isamu Noguchi: Best of Friends</title>     
    <author>       
      <person>         
        <first>Shoji</first>         
        <last>Sadao</last>       
      </person>     
    </author>     
    <price>65.00</price>     
    <discount>5.00</discount>   
  </book>   
  <book>     
    <title>Isamu Noguchi and Modern Japanese Ceramics: A Close Embrace                
               of the Earth</title>     
    <author>       
      <person>         
        <first>Louise</first>         
        <middle>Allison</middle>         
        <last>Cort</last>       
      </person>     
    </author>     
    <author>       
      <person>         
        <first>Bert</first>         
        <last>Winther-Tamaki</last>       
      </person>     
    </author>     
    <author>       
      <person>         
        <first>Bruce</first>         
        <middle>J.</middle>         
        <last>Altshuler</last>       
      </person>     
    </author>     
    <author>       
      <person>         
        <first>Ryu</first>         
        <last>Niimi</last>       
      </person>     
    </author>     
    <price>29.95</price>     
    <discount>5.95</discount>   
  </book> 
</books>

图 2 表明,我得到的是不完整的结果。由机构创作的图书未列入结果。对于拥有多名作者的图书,结果将所有作者的名字放在逗号之前,将所有作者的姓氏放在逗号之后。这个样式表并未提供错误消息,而是以静默方式提供了错误的输出。

图 2. 场景 1 的原始 XSLT 在使用 morebooks.xml 时的结果
该图显示了场景 1 的原始 XSLT 在使用 morebooks.xml 时的结果

为了创建更加健壮的样式表,我可以修改如 清单 4 所示的函数,为参数和函数(作为函数的返回类型)添加 as 属性,在编写函数时作出显式的假设。

清单 4. 场景 1 的 XSLT 中经过修订的函数
<xsl:function name="dtyf:format-person-name" as="xs:string*">   
  <xsl:param name="person" as="element()"/>   
  <xsl:value-of select="$person/last"/>   
  <xsl:text>, </xsl:text>   
  <xsl:value-of select="$person/first"/> 
</xsl:function>

序列类型 element() 表示允许一个且仅一个元素。运行修订后的 XSLT 时,我就得到了适当的错误消息:

  • 不允许使用空序列作为 dtyf:format-person-name() 的第一个参数(针对机构作者)
  • 不允许将具有一个以上项目的序列作为 dtyf:format-person-name() 的第一个参数(针对多名作者)

根据这些错误消息,我了解到我需要对 XSLT 做出如 清单 5 所示的更改,以便同时应对机构作者和一名以上的作者。

清单 5. 场景 1 的 XSLT 最终版本
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"   
  xmlns:xs="http://www.w3.org/2001/XMLSchema"   
  xmlns:dtyf="http://www.datypic.com/functions"   
  xpath-default-namespace="http://www.datypic.com/books/ns">    
  
  <xsl:output method="html"/>    
  
  <xsl:template match="books">     
    <html>       
      <body>         
        <table border="1">           
          <xsl:apply-templates/>         
        </table>       
      </body>     
    </html>   
  </xsl:template>    
  
  <xsl:template match="book">     
    <tr>       
      <td><xsl:value-of select="title"/></td>       
      <td>         
        <xsl:for-each select="author">           
          <xsl:choose>             
            <xsl:when test="person">               
              <xsl:value-of select="dtyf:format-person-name(person)"/>             
            </xsl:when>             
            <xsl:when test="institution">               
              <xsl:value-of select="institution"/>             
            </xsl:when>           
          </xsl:choose>           
          <xsl:if test="position() != last()">             
            <br/>           
          </xsl:if>         
        </xsl:for-each>       
      </td>       
      <td><xsl:value-of select="price"/></td>     
    </tr>   
  </xsl:template>    
  
  <xsl:function name="dtyf:format-person-name" as="xs:string*">     
    <xsl:param name="person" as="element()"/>     
    <xsl:value-of select="$person/last"/>     
    <xsl:text>, </xsl:text>     
    <xsl:value-of select="$person/first"/>   
  </xsl:function> 
</xsl:stylesheet>

图 3 所示,现在的结果得到了改进,正确显示了机构作者和多名作者。

图 3. 场景 1 的最终 XSLT 在使用 morebooks.xml 时的结果
该图显示了场景 1 的最终 XSLT 在使用 morebooks.xml 时的结果的

本例中函数的返回类型并不重要,但不管怎样最好的实践是明确所返回的是什么。在其他情况下,函数的结果将用于需要特定类型结果的运算。

场景 2:利用类型来确保正确解释运算符

继续图书的示例,清单 6 展示了一个 XSLT,这个 XSLT 在计算价格时考虑到了 discount 元素。

清单 6. 场景 2 的 XSLT 原始版本
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"  
 xmlns:xs="http://www.w3.org/2001/XMLSchema"  
 xmlns:dtyf="http://www.datypic.com/functions"  
 xpath-default-namespace="http://www.datypic.com/books/ns">   
 
 <xsl:output method="html"/>   
 
 <xsl:template match="books">   
  <html>    
   <body>     
    <table border="1">      
     <xsl:apply-templates/>     
    </table>    
   </body>   
  </html>  
 </xsl:template>   
 
 <xsl:template match="book">   
  <tr>    
   <td><xsl:value-of select="title"/></td>    
   <td><xsl:value-of select="author"/></td>    
   <td><xsl:value-of select="dtyf:calculate-price(price, discount)"/></td>   
  </tr>  
 </xsl:template>   
 
 <xsl:function name="dtyf:calculate-price">   
  <xsl:param name="price"/>   
  <xsl:param name="discount"/>   
  <xsl:sequence select="$price - $discount"/>  
 </xsl:function>  
 
</xsl:stylesheet>

同样,这个 XSLT 对于 books.xml 表现良好(除了一个舍入误差之外),但在对 morebooks.xml 运行这个 XSLT 时,它无法处理无折扣的图书,如 图 4 所示,因为 $discount 的值是一个空序列,对空序列的任何算术运算都将返回空序列。

图 4. 场景 2 的原始 XSLT 在使用 morebooks.xml 时的结果
该图显示了场景 2 的原始 XSLT 在使用 morebooks.xml 时的结果

我最初只是考虑将 $discount 与 0 对比,以便修复这个问题,但随后我确定,如果能确定折扣低于价格,则函数将更加健壮。我的表格中不应该出现任何负值的价格。因此我还包含了对比 $discount < $price,如 清单 7 所示。

清单 7. 场景 2 的 XSLT 修订版
 <xsl:function name="dtyf:calculate-price">   
  <xsl:param name="price"/>   
  <xsl:param name="discount"/>   
  <xsl:choose>    
   <xsl:when test="$discount > 0 and $discount &lt; $price">     
    <xsl:sequence select="$price - $discount"/>    
   </xsl:when>    
   <xsl:otherwise>    
    <xsl:sequence select="$price"/>    
   </xsl:otherwise>   
  </xsl:choose>  
 </xsl:function>

图 5 中的结果初看起来非常理想。没有折扣的第一本图书显示正确。但仔细观察结果之后,我注意到最后一本书没有减掉折扣。

图 5. 场景 2 的修订版 XSLT 在使用 morebooks.xml 时的结果
该图显示了场景 2 的修订版 XSLT 在使用 morebooks.xml 时的结果

这个缺陷 (glitch) 的原因在于 XML 将价格与折扣和字符串进行对比:这是操作数属于非类型化时,比较运算符的默认行为。字符串 5.95 大于字符串 29.95,因此对比返回的是 “假”,而非 “真”。如 清单 8 所示,将类型添加到我的模式中之后,问题即可解决。

清单 8. 场景 2 的 XSLT 最终版
<xsl:function name="dtyf:calculate-price" as="xs:decimal">   
  <xsl:param name="price" as="xs:decimal"/>   
  <xsl:param name="discount" as="xs:decimal?"/>   
  <xsl:choose>     
    <xsl:when test="$discount > 0 and $discount &lt; $price">        
       <xsl:sequence select="$price - $discount"/>     
    </xsl:when>     
    <xsl:otherwise>        
       <xsl:sequence select="$price"/>     
    </xsl:otherwise>   
  </xsl:choose> 
</xsl:function>

我将两个参数同时指定为小数,并使用出现次数指示符 ?,允许折扣使用空序列(因为某些图书可能无折扣)。在修订后的 XSLT 中,调用函数时,价格和折扣元素的值将转为小数。(只要输入数据是无类型的(即无模式),这样的转换就会自动进行。)现在,XML 可将价格和折扣与数字进行对比,我将获得正确的结果,如 图 6 所示。

图 6. 场景 2 的最终 XSLT 在使用 morebooks.xml 时的结果
该图显示了场景 2 的最终 XSLT 在使用 morebooks.xml 时的结果

舍入误差不复存在。因为默认情况下,减法运算符将数字视为 xs:double 值处理,这种浮点数的精度低于 xs:decimal 值。


利用模式改进您的样式表

之前的几个示例使用 XML 模式内置的类型,但不要求为输入文档使用模式。下面我们将介绍如何利用 XML 模式提高 XSLT 的健壮性。模式感知是 XSLT 处理器的一项可选特性,本节中的样式表必须使用模式感知的 XSLT 处理器运行。本文中的示例经过 Saxon-EE 测试(参考资料 部分提供了获得更多信息的链接)。

导入和使用模式

假设您有一个用于 books.xml 文档的模式,名为 books.xsd,如 清单 9 所示。它定义元素的全部数据类型和基数。XML 模式的完整说明已经超出了本文的讨论范围:建议您阅读 XML Primer (请参见 参考资料)以了解基本介绍。

清单 9. 图书模式
<?xml version="1.0" encoding="UTF-8"?> 
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"   
  targetNamespace="http://www.datypic.com/books/ns"   
  xmlns="http://www.datypic.com/books/ns" elementFormDefault="qualified">   
  <xs:element name="books" type="BooksType"/>    
  
  <xs:complexType name="BooksType">     
    <xs:sequence>       
      <xs:element ref="book" maxOccurs="unbounded"/>     
    </xs:sequence>   
  </xs:complexType>    
  
  <xs:element name="book" type="BookType"/>   
  <xs:complexType name="BookType">     
     <xs:sequence>       
       <xs:element ref="title"/>       
       <xs:element ref="author" maxOccurs="unbounded"/>       
       <xs:element ref="price"/>       
       <xs:element ref="discount"  minOccurs="0"/>     
     </xs:sequence>   
   </xs:complexType>    
   
   <xs:element name="title" type="xs:string"/>   
   <xs:element name="author" type="AuthorType"/>   
   <xs:element name="price" type="xs:decimal"/>   
   <xs:element name="discount" type="xs:decimal"/>   
   <xs:complexType name="AuthorType">     
     <xs:choice>       
       <xs:element ref="person"/>       
       <xs:element ref="institution"/>     
     </xs:choice>   
   </xs:complexType>    
   
   <xs:element name="person" type="PersonType"/>   
   <xs:element name="institution" type="xs:string"/>   
   <xs:complexType name="PersonType">     
     <xs:sequence>       
       <xs:element ref="first"/>       
       <xs:element ref="middle" minOccurs="0" maxOccurs="unbounded"/>       
       <xs:element ref="last"/>     
     </xs:sequence>   
   </xs:complexType>   
   <xs:element name="first" type="xs:string"/>   
   <xs:element name="middle" type="xs:string"/>   
   <xs:element name="last" type="xs:string"/>  
   
 </xs:schema>

为样式表使用模式时,您可以使用 表 4 中列出的其他序列类型。

表 4. 模式感知的序列类型
序列类型示例含义
element(*,BookType)类型为 BookType 的任意元素
element(book,BookType)名为 book、类型为 BookType 的任意元素
schema-element(book)针对模式中的 book 全局声明进行了验证的任意元素,或者其置换组的成员
attribute(*,ISBNType)类型为 ISBNType 的任意属性
attribute(isbn,ISBNType)名为 isbn、类型为 ISBNType 的任意属性
schema-attribute(isbn)针对模式中的 isbn 全局声明进行了验证的任意属性,或者其置换组的成员

场景 3:检测无效路径

使用架构时,一种非常有用的特性就是它能告诉您 XPath 表达式中使用了无效名称或者路径。清单 10 展示了包含多个细微错误的 XSLT。

清单 10. 场景 3 的 XSLT 原始版
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"   
  xpath-default-namespace="http://www.datypic.com/books/ns">    
  
  <xsl:output method="html"/>   
  <xsl:template match="books">     
    <html>       
      <body>         
        <table border="1">           
          <xsl:apply-templates/>         
        </table>       
      </body>     
    </html>   
  </xsl:template>    
  
  <xsl:template match="book">     
    <tr>       
      <td><xsl:value-of select="title"/></td>       
      <td><xsl:value-of select="author/last"/></td>       
      <td><xsl:value-of select="author/first"/></td>       
      <td><xsl:value-of select="prce"/></td>     
    </tr>   
  </xsl:template>  
  
</xsl:stylesheet>

图 7 所示,我的结果中既缺少作者名,也缺少价格。如果没有模式,我必须通过试错法判断出问题在哪里。

图 7. 场景 3 的原始 XSLT 在使用 books.xml 时的结果
该图显示了场景 3 的原始 XSLT 在使用 books.xml 时的结果

使样式表具有模式感知将有所帮助。首先,我必须导入模式,如 清单 11 所示,为其提供模式的文件名(绝对或者相对),以及该模式的目标名称空间。此外,为了实现我需要的错误检查,我必须将 match 属性中使用的序列类型更改为使用 schema-element()

清单 11. 场景 3 的 XSLT 修订版
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"   
  xpath-default-namespace="http://www.datypic.com/books/ns">    
  
  <xsl:output method="html"/> 
  <xsl:import-schema namespace="http://www.datypic.com/books/ns"                      
                     schema-location="books.xsd"/>   
 <xsl:template match="schema-element(books)">     
   <html>       
     <body>         
       <table border="1">           
         <xsl:apply-templates/>         
       </table>       
     </body>     
   </html>   
 </xsl:template>    
 
 <xsl:template match="schema-element(book)">     
   <tr>       
     <td><xsl:value-of select="title"/></td>       
     <td><xsl:value-of select="author/last"/></td>       
     <td><xsl:value-of select="author/first"/></td>       
     <td><xsl:value-of select="prce"/></td>     
   </tr>   
 </xsl:template>  
 
</xsl:stylesheet>

现在我会得到三条错误消息:

  • 复杂类型 AuthorType 不允许使用名为 last 的子元素
  • 复杂类型 AuthorType 不允许使用名为 first 的子元素
  • 复杂类型 BookType 不允许使用名为 prce 的子元素

前两条错误消息告诉我,路径发生了错误;我忘记了 person 元素,它是 author 的一个子元素。第三条错误消息使我认识到我错误地拼写了 price。经过修改的 清单 12 使用正确的路径,提供了完整的输出。

清单 12. 场景 3 的 XSLT 最终版
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"  
 xpath-default-namespace="http://www.datypic.com/books/ns">   
 
 <xsl:output method="html"/>  
 <xsl:import-schema namespace="http://www.datypic.com/books/ns"
                    schema-location="books.xsd"/>  
 <xsl:template match="books">   
  <html>    
   <body>     
    <table border="1">      
     <xsl:apply-templates/>     
    </table>    
   </body>   
  </html>  
 </xsl:template>   
 
 <xsl:template match="schema-element(book)">   
  <tr>    
   <td><xsl:value-of select="title"/></td>    
   <td><xsl:value-of select="author/person/last"/></td>    
   <td><xsl:value-of select="author/person/first"/></td>    
   <td><xsl:value-of select="price"/></td>   
  </tr>  
 </xsl:template>  
 
</xsl:stylesheet>

场景 4:验证输出

在某些情况下,您可能需要确保生成有效输出。在结构化数据转换中更是如此,在生成 XHTML 时也是如此。为了输出 XHTML,我必须使用 清单 12 中的代码,将输出方法更改为 xhtml。在本例中,结果将正确显示在浏览器中。但它或许还需要是有效的 XHTML,可能是因为它会输入到某个需要有效输入的其他样式表或进程中;也可能是因为我坚持遵循标准(而事实上我就是这样的)!

为了检查输出的有效性,首先必须导出模式,就像对输入文档模式所做的那样。我还告诉处理器,我希望向 root 元素添加 xsl:validation="strict" 属性,以便验证输出。这样的处理将对 html 的全部内容进行验证。

我所做的另外一项更改就是使 XHTML 名称空间成为默认名称空间,原因在于,为了保持有效,输出必须处于正确的名称空间内。清单 13 展示了这些更改。

清单 13. 场景 4 的 XSLT,修订自场景 3
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
 xmlns="http://www.w3.org/1999/xhtml"  
 xpath-default-namespace="http://www.datypic.com/books/ns">   
 
 <xsl:output method="xhtml"/>  
 <xsl:import-schema namespace="http://www.datypic.com/books/ns" 
                    schema-location="books.xsd"/> 
 <xsl:import-schema namespace="http://www.w3.org/1999/xhtml" 
                    schema-location="xhtml1-strict.xsd"/>   
                    
 <xsl:template match="books">   
  <html xsl:validation="strict">    
   <body>     
    <table border="1">      
     <xsl:apply-templates/>     
    </table>    
   </body>   
  </html>  
 </xsl:template>   
 
 <xsl:template match="schema-element(book)">   
  <tr>    
   <td><xsl:value-of select="title"/></td>    
   <td><xsl:value-of select="author/person/last"/></td>    
   <td><xsl:value-of select="author/person/first"/></td>    
   <td><xsl:value-of select="price"/></td>   
  </tr>  
 </xsl:template>  
 
</xsl:stylesheet>

运行时,我将得到以下错误消息:

  • 在元素 <html> 的内容中:内容模型不允许元素 <body> 出现在这里。预期:{http://www.w3.org/1999/xhtml}head

这条错误消息告诉我,我缺少一个 head 元素。经过多次迭代后,我得到了如 清单 14 所示的最终版 XSLT,它将严格生成有效的 XHTML。

清单 14. 场景 4 的 XSLT 最终版
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"  
 xmlns="http://www.w3.org/1999/xhtml"  
 xpath-default-namespace="http://www.datypic.com/books/ns">   
 
 <xsl:output method="xhtml"/>  
 <xsl:import-schema namespace="http://www.datypic.com/books/ns"
                    schema-location="books.xsd"/>
 <xsl:import-schema namespace="http://www.w3.org/1999/xhtml" 
                    schema-location="xhtml1-strict.xsd"/>   
 
 <xsl:template match="books">   
  <html xsl:validation="strict">    
   <head><title>Books</title></head>    
   <body>     
    <table border="1">      
     <xsl:apply-templates/>     
    </table>    
   </body>   
  </html>  
 </xsl:template>   
 
 <xsl:template match="schema-element(book)">   
  <tr>    
   <td><xsl:value-of select="title"/></td>    
   <td><xsl:value-of select="author/person/last"/></td>    
   <td><xsl:value-of select="author/person/first"/></td>    
   <td><xsl:value-of select="price"/></td>   
  </tr>  
 </xsl:template>  
</xsl:stylesheet>

结束语

正如您在本文示例中所看到的那样,XSLT 样式表有时可能会以静默方式发生故障,或者提供不正确的结果。在这些简单的示例中,您可以轻松判断出问题,也能轻易修复这些问题。然而,输入文档和 XSLT 样式表通常更为复杂。如果没有显式类型和导入模式,XSLT 将很难调试。

除此之外,测试数据文档并非总是包含可能会在实际输入数据中遇到的全部排列。对于测试过程中完全未遇到过的数据,显式指定模式类型可能会造成问题。指定参数和返回值的类型也能带来更出色的归档函数和模板,使您的代码更易于维护,更易于被他人理解。

如果您仍然心存疑虑,我建议您在现有 XSLT 样式表中添加类型和架构。结果将令您倍感震惊。


下载

描述名字大小
本文中的 XSLT 样式表样例examples.zip10KB

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML, Web development
ArticleID=823756
ArticleTitle=利用类型和模式改进您的 XSLT 2.0 样式表
publish-date=07022012