xslt简介_XSLT简介

xslt简介

在你开始前

本教程适用于希望使用可扩展样式表语言转换(XSLT)将XML数据转换为其他形式而无需使用Java™或其他语言进行编程的开发人员。

本教程假定您熟悉XML,但没有涉及该语言的任何深奥的方面,因此基本的了解就足够了。 除了关于扩展的小部分内容外,您不需要掌握任何特定的编程语言。 即使这样,只要处理器支持,这些概念就很简单并适用于任何编程语言。

本教程是关于什么的?

本教程是有关可扩展样式表语言转换(XSLT)的。 XSLT是与XML相关的基本规范之一,使您能够轻松地将XML数据从一种形式转换为另一种形式。

在本教程中,您将学到以下内容:

  • XSLT的基础
  • 使用简单的模板
  • 传播数据
  • 控制空间
  • XPath的基础
  • XPath函数
  • 循环和条件语句
  • 导入并包括其他样式表
  • 扩展XSLT
  • XSLT变量

入门

假设您有一组XML数据。 您可能选择以这种方式存储它的部分原因是因为XML的灵活性。 您知道您可以在几乎所有编程语言的多种平台上使用它。 但是有时,您需要更改该XML的形式。 例如,您可能需要将数据转换为另一种XML格式以存储在不同的系统中,或转换为另一种形式以进行展示或其他用途。

例如,您可能希望将XML数据显示在Web上,这意味着将其转换为HTML。 当然,您可以手动浏览文档并进行更改,或者可以考虑将XML加载到DOM中,然后手动构建输出文档。

幸运的是,您不必这样做。 有一种更简单的方法。

什么是XSLT?

可扩展样式表语言转换(XSLT)提供了一种自动将XML数据从一种格式转换为另一种格式的方法。 目标表单通常是另一个XML文档,但不一定如此。 您只需创建XSLT样式表并处理数据,即可将XML数据转换为几乎所有内容。 如果要更改输出,只需更改样式表并再次处理XML。 这具有向非程序员(例如设计师)提供权力的附加优势,他们可以编辑样式表并影响结果。

让我们来看一个例子。

你要完成的事情

在本教程中,您将获取一个XML文档并将其转换为可以显示为网页的XHTML文档。 输入数据是一个简单的配方文件(请参见清单1)。

清单1.基本数据
<recipes>
   <recipe>
       <name>Gush'gosh</name>
       <ingredients>
          
<ingredient><qty>1</qty><unit>pound</unit>
<food>hamburger</food></ingredient>
          
<ingredient><qty>1</qty><unit>pound</unit>
<food>elbow macaroni</food></ingredient>
          
<ingredient><qty>2</qty><unit>cups</unit>
<food>brown sugar</food></ingredient>
          <ingredient><qty>1</qty><unit>bag</unit>
<food>chopped onions</food></ingredient>
          
<ingredient><qty>1</qty><unit>teaspoon</unit>
<food>dried dill</food></ingredient>
       </ingredients>
       <instructions>
          <instruction>Brown the hamburger.</instruction>
          <instruction>Add onions and cook until
 transparent.</instruction>
          <instruction>Add brown sugar and dill.</instruction>
          <instruction>Cook and drain pasta.</instruction>
          <instruction>Combine meat and pasta.</instruction>
       </instructions>
   </recipe>
   
      <recipe>
       <name>A balanced breakfast</name>
       <ingredients>
          <ingredient><qty>1</qty><unit>cup</unit>
<food>cereal</food></ingredient>
          
<ingredient><qty>1</qty><unit>glass</unit>
<food>orange juice</food></ingredient>
          
<ingredient><qty>1</qty><unit>cup</unit>
<food>milk</food></ingredient>
          
<ingredient><qty>2</qty><unit>slices</unit>
<food>toast</food></ingredient>
       </ingredients>
       <instructions>
          <instruction>Combine cereal and milk in 
bowl.</instruction>
          <instruction>Add all ingredients to table.</instruction>
       </instructions>
   </recipe>

</recipes>

编者注 :这些食谱仅​​作为示例,经作者解释。 Gush'gosh的正确食谱(由他的妻子提供,实际上是由他做饭的)由1磅汉堡包,1磅肘通心粉,1/2杯红糖,1小袋(约10盎司)切碎的洋葱组成,1茶匙干莳萝和1小罐番茄酱,其中加入了红糖。

当然,这是一个非常简单的示例,因此您不会陷入数据细节的泥潭,但是您的XML数据可以是任何东西,从记录活动到财务。

目的是将这些数据转换成一个XHTML页面,该页面将单独显示配方并格式化其成分和说明(请参见清单2)。

清单2.输出
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/TR/xhtml1/strict">
<head><title>Recipe</title></head>
<body>
<h2>Gush'gosh</h2>
<h3>Ingredients:</h3>
<p>       1 pound hamburger<br/>
          1 pound elbow macaroni<br/>
          2 cups brown sugar<br/>
          1 bag chopped onions<br/>
          1 teaspoon dried dill<br/>
</p>
<h3>Directions:</h3>
<ol>
          <li>Brown the hamburger.</li>
          <li>Add onions and cook until transparent.</li>
          <li>Add brown sugar and dill.</li>
          <li>Cook and drain pasta.</li>
          <li>Combine meat and pasta.</li>
</ol>

<h2>A balanced breakfast</h2>
<h3>Ingredients:</h3>
<p>
          1 cup cereal<br/>
          1 glass orange juice<br/>
          1 cup milk<br/>
          2 slices toast<br/>
</p>
<h3>Directions:</h3>
<ol>
          <li>Combine cereal and milk in bowl.</li>
          <li>Add all ingredients to table.</li>
</ol>
</body>
</html>

然后,您可以在浏览器中显示此结果,如图1所示。

图1.结果显示在浏览器中
结果显示在浏览器中

如前所述,最终目的地可以是任何格式,而不仅仅是XHTML甚至XML。

让我们从基本转换开始。

基本样式表

最基本的样式表就是一个包含XSLT输出的XML文档(参见清单3)。

清单3.最基本的样式表
<html xsl:version="1.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      xmlns="http://www.w3.org/TR/xhtml1/strict">
  <head>
    <title>Recipe</title>
  </head>
  <body>
    <h2><xsl:value-of 
select="/recipes/recipe/name"/></h2> 
    <h3>Ingredients:</h3>
    <p><xsl:value-of 
select="/recipes/recipe/ingredients"/></p>
    <h3>Directions:</h3>
    <p><xsl:value-of 
select="/recipes/recipe/instructions"/></p>
  </body>
</html>

注意xsl:名称空间的使用。 添加此名称空间可以告诉处理器哪些元素与处理有关,哪些元素应简单地输出。 value-of元素告诉处理器在该位置插入特定的数据。 哪个特定部分由select属性的内容确定。

select属性由XPath表达式组成。 XPath将在更高级的XPath中进行详细讨论,但是在这里您可以看到,逐步浏览文档的层次结构可以访问名称,成分和说明元素。 您从根元素/recipes ,然后从那里开始。

如何进行转换

执行XML转换的最简单方法是将xml-stylesheet指令添加到XML并将其显示在浏览器中(参见清单4)。

清单4.将xml-stylesheet处理指令添加到XML
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="basicstylesheet.xsl" version="1.0" 
?>
<recipes>
   <recipe>
       <name>Gush'gosh</name>
...

该处理指令告诉浏览器检索位于basicstylesheet.xsl的样式表,并使用其转换XML数据并输出结果。 如果然后在Microsoft®InternetExplorer®中打开XML文档,则会看到类似于图2的结果。

图2.检索样式表并转换XML数据
检索样式表并转换XML数据

现在,这并不是您想要的,但是如果您在浏览器中执行视图/源代码,您将看到的只是原始XML。 要查看实际转换的结果,您需要执行转换并创建一个输出文件。 您可以通过以下命令使用Java代码从命令行执行此操作(请参见清单5)。

清单5.从命令行转换文档
java org.apache.xalan.xslt.Process -IN recipes.xml -XSL basicstylesheet.xsl -out 
result.html

如果收到ClassNotFoundException ,则可能需要下载Apache Xalan并将包含的JAR文件添加到您的类路径中。

执行清单5中所示的转换后,您将看到result.html文件包含清单6中所示的以下代码。

清单6.结果
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/TR/xhtml1/strict">
<head><title>Recipe</title></head>
<body>
<h2>Gush'gosh</h2>
<h3>Ingredients:</h3>
<p>
          1poundhamburger
          1poundelbow macaroni
          2cupsbrown sugar
          1bagchopped onions
          1teaspoondried dill
       </p>
<h3>Directions:</h3>
<p>
          Brown the hamburger.
          Add onions and cook until transparent.
          Add brown sugar and dill.
          Cook and drain pasta.
          Combine meat and pasta.
       </p>
</body></html>

我为可读性添加了一些间距,但是这里有两点需要注意。 首先,清单6仅显示一个配方的信息。 其次,各成分被挤在一起,没有任何空间。 这些都不是您想要的。 幸运的是,您可以创建更多特定的模板,以所需的形式输出数据。

添加模板

除非将数据保留在所需的格式中,否则转换不会对您有任何好处。 为此,您需要知道如何使用模板,这将在本节中学习。

创建模板

大多数样式表都不使用您在上一节中看到的非常简单的形式。 相反,它们分为一系列模板,每个模板都应用于特定类型的数据。 让我们首先将样式表转换为这种形式(参见清单7)。

清单7.重做的样式表
<xsl:stylesheet 
      version="1.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      xmlns="http://www.w3.org/TR/xhtml1/strict">

<xsl:template match="/">
<html>
  <head>
    <title>Recipe</title>
  </head>
  <body>
    <h2><xsl:value-of select="/recipes/recipe/name"/></h2> 
    <h3>Ingredients:</h3>
    <p><xsl:value-of 
select="/recipes/recipe/ingredients"/></p>
    <h3>Directions:</h3>
    <p><xsl:value-of 
select="/recipes/recipe/instructions"/></p>
  </body>
</html>
</xsl:template></xsl:stylesheet>

这里的所有信息都与以前相同,除了处理器查看样式表并从此处设置为匹配文档根目录的模板开始(由match属性指定)。 然后,它像以前一样输出包含所有值的模板。 如果现在执行转换,则应该看到与清单6完全相同的结果。

但这不是您想要的。 相反,您希望能够格式化成分和说明。 为此,您可以为这些元素创建单独的模板,并告诉样式表包括它们(请参见清单8)。

清单8.创建其他模板
<xsl:stylesheet
      version="1.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      xmlns="http://www.w3.org/TR/xhtml1/strict">
   
<xsl:template match="/">
<html>
  <head>
    <title>Recipe</title>
  </head>
  <body>
    <h2><xsl:value-of select="/recipes/recipe/name"/></h2> 
    <h3>Ingredients:</h3>
    <p><xsl:apply-templates 
select="/recipes/recipe/ingredients"/></p>
    <h3>Directions:</h3>
    <p><xsl:apply-templates 
select="/recipes/recipe/instructions"/></p>
  </body>
</html>
</xsl:template>

<xsl:template match="ingredients">

   <h3>INGREDIENTS HERE</h3>

</xsl:template>


<xsl:template match="instructions">

   <h3>INSTRUCTIONS HERE</h3>

</xsl:template>

</xsl:stylesheet>

请注意,您现在要告诉样式表将所有适用的模板应用于成分和说明元素,而不是简单地输出value-of元素。 然后,您为这些元素创建了单独的模板,并在match属性中指定了它们。 处理器转到apply-templates元素,然后选择文档中的所有配料元素。 然后,它查找成分模板,找到它后,将输出该模板。 它对instructions元素的作用相同。 结果看起来如图3所示。

图3.将适用的模板应用于成分和说明元素
将适用的模板应用于成分和说明元素

好吧,这有点儿接近了,至少现在您知道有两个食谱,但是显然您不想合并所有食谱的成分和所有食谱的说明。 幸运的是,您可以通过更好地组织模板来解决此问题。

传播模板

为了更好地组织数据,请注意如何扩展和传播模板。 XSLT旨在使您能够以迭代方式处理信息。 例如,您可以按配方细分信息,然后格式化说明和成分(请参见清单9)。

清单9.分解食谱
<xsl:stylesheet
    version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns="http://www.w3.org/TR/xhtml1/strict">

      <xsl:template match="/">
      <html>
        <head>
          <title>Recipe</title>
        </head>
        <body>
         <xsl:apply-templates select="/recipes/recipe"/>
        </body>
      </html>
      </xsl:template>

    <xsl:template match="recipe">

          <h2><xsl:value-of select="./name"/></h2> 
          <h3>Ingredients:</h3>
          <p><xsl:apply-templates select="./ingredients"/></p>
          <h3>Directions:</h3>
          <p><xsl:apply-templates  
select="./instructions"/></p>

      </xsl:template>
      
      <xsl:template match="ingredients">

         <h3>INGREDIENTS HERE</h3>

      </xsl:template>


      <xsl:template match="instructions">

         <h3>INSTRUCTIONS HERE</h3>

      </xsl:template>

      </xsl:stylesheet>

在这种情况下,样式表将输出基本页面(HTML),然后循环浏览每个配方,并为每个配方输出名称,成分和说明。 同样,将在更高级的XPath中检查XPath,但是在这种情况下, recipe元素变为“上下文节点”,因此您的select属性是相对于该节点的,就像文件可能相对于文件中的特定目录一样系统。 结果看起来如图4所示。

图4.配方元素成为上下文节点
配方元素成为上下文节点

好的,格式更接近您想要的格式,但是您仍然需要显示实际信息。 为此,请修改说明和成分模板(请参见清单10)。

清单10.处理成分和说明
...
    <xsl:template match="recipe">

          <h2><xsl:value-of select="./name"/></h2> 
          <h3>Ingredients:</h3>
          <p><xsl:apply-templates select="./ingredients"/></p>
          <h3>Directions:</h3>
          <ol><xsl:apply-templates  
select="./instructions"/></ol>

      </xsl:template>

      <xsl:template match="ingredients/ingredient">

         <xsl:value-of select="./qty"/> <xsl:value-of 
select="./unit"/> <xsl:value-of select="./food"/><br />

      </xsl:template>


      <xsl:template match="instructions/instruction">

         <li><xsl:value-of select="."/></li>

      </xsl:template>

      </xsl:stylesheet>

传播模板时,此处需要注意的一点:您告诉处理器将所有适用的模板应用于ingredients元素,但是您没有专门针对该元素的模板。 如果将模板应用于元素而没有模板可应用,则数据将不会显示。 事实并非如此。

相反,你注意到,当你告诉处理器到任何适用的模板,适用于这样的事实优势ingredients元素,它会检查不仅对ingredients元素,而且对任何孩子ingredients元素。 这样便可以找到成分模板,在其中您可以输出数量,单位,食物和换行符。

您对说明进行了相同的操作,将它们格式化为列表项。 请注意,您已经在主配方模板中创建了实际的订购清单,然后将商品发送出去以进行单独处理。

结果看起来如图5所示。

图5.在主配方模板中创建订购列表
在主配方模板中创建订购清单

格式绝对接近您现在想要的格式。 但是,如果查看输出,则可以看到配料中的间距问题仍然存在(请参见清单11)。

清单11.原始输出
<?xml version="1.0" encoding="UTF-8"?>
<html 
xmlns="http://www.w3.org/TR/xhtml1/strict"><head><title>Recipe
</title></head><body><h2>Gush'gosh</h2><h3>
Ingredients:</h3><p>
          1poundhamburger<br/>
          1poundelbow macaroni<br/>
          2cupsbrown sugar<br/>
          1bagchopped onions<br/>
          1teaspoondried dill<br/>
       </p><h3>Directions:</h3><ol>
          <li>Brown the hamburger.</li>
          <li>Add onions and cook until transparent.</li>
...

添加空格

现在,当您在样式表中包含空格时,为什么会发生这种情况? 它们不应该出现在输出中吗? 实际上,不一定。 有一些方法可以告诉样式表保留空白(将在循环和导入中进行检查),但是在某些情况下,将文本显式添加到输出中更为简单(请参见清单12)。

清单12.添加文本
...
      <xsl:template match="ingredients/ingredient">

         <xsl:value-of select="./qty"/><xsl:text> 
</xsl:text>
         <xsl:value-of select="./unit"/><xsl:text> 
</xsl:text>
         <xsl:value-of select="./food"/><br />

      </xsl:template>
...

这样可以照顾到数据中缺少的空格。 您还可以使用text元素将任意文本添加到模板。 (请记住,那是文本,而不是诸如换行符之类的元素。)结果是您希望看到的输出(参见清单13)。

清单13.最终输出
<?xml version="1.0" encoding="UTF-8"?>
<html 
xmlns="http://www.w3.org/TR/xhtml1/strict"><head><title>Recipe
</title></head><body><h2>Gush'gosh</h2><h3>
Ingredients:</h3><p>
          1 pound hamburger<br/>
          1 pound elbow macaroni<br/>
          2 cups brown sugar<br/>
          1 bag chopped onions<br/>
          1 teaspoon dried dill<br/>
       </p><h3>Directions:</h3><ol>
          <li>Brown the hamburger.</li>
          <li>Add onions and cook until transparent.</li>
          <li>Add brown sugar and dill.</li>
          <li>Cook and drain pasta.</li>
          <li>Combine meat and pasta.</li>
       </ol><h2>A balanced
 breakfast</h2><h3>Ingredients:</h3><p>
          1 cup cereal<br/>
          1 glass orange juice<br/>
          1 cup milk<br/>
          2 slices toast<br/>
       </p><h3>Directions:</h3><ol>
          <li>Combine cereal and milk in bowl.</li>
          <li>Add all ingredients to table.</li>
       </ol></body></html>

您可以在图6中看到结果。

图6.照顾数据中的缺失空间
照顾数据中缺少的空格

接下来,您将学习如何使用XPath向页面添加特定信息。

基本XPath

以您希望的方式转换数据的能力要求您了解XML路径语言(XPath)或XPath,它使您能够控制传播和/或显示哪些数据。 本节说明XPath背后的概念,并向您展示如何创建基本表达式。

什么是XPath?

现在,您可能会注意到,使样式表起作用的一个非常重要的部分是选择文档的特定部分的能力。 例如,如果要显示说明,则需要知道如何引用它们。 在XSLT中,您可以通过使用XPath表达式来引用此信息。

XPath表达式可以选择一个节点或一组节点,也可以基于文档中的一个或多个节点返回单个值。 到目前为止,您已经处理了相当简单的XPath表达式,该表达式通过遍历层次结构选择一个或多个节点。 XPath提供了几种根据关系(例如父子关系或后代关系)指定节点组的方法。 在本教程中,您将研究这些关系,称为轴 。

您还将查看XPath的一些更强大的功能,并查看谓词 ,它们实际上是可以添加到表达式中的条件语句。 例如,在“ 设置上下文”中,您了解了如何选择文档的所有配方元素。 谓词使您可以根据特定条件仅选择特定元素。

最后,您将看到函数,这些函数通过启用您可能会编写到程序程序中的许多相同类型的逻辑,将功能进一步提高。

让我们从查看表达式的上下文开始。

设置上下文

理解XPath的第一步是要了解,您获得的结果将在很大程度上取决于当前上下文节点。 您可以将上下文节点视为一种“您在这里”的符号,根据XPath表达式从该符号向各个方向移动。 例如,考虑一下这个简单的样式表(请参见清单14)。

清单14.显示上下文
<xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns="http://www.w3.org/TR/xhtml1/strict">

        <xsl:template match="/">

        <xsl:copy-of select="." />

        </xsl:template>


    </xsl:stylesheet>

这是一个新的XSL元素copy-of 。 其中value-of输出元素的文本内容,而copy-of则完全按照其内容进行输出,并输出select属性引用的实际节点。

在这种情况下, select属性可能是最简单的XPath表达式之一。 单个句点(。)指的是上下文节点,就像您要引用“当前目录,不管是什么”时在文件系统中一样。 因此,如果执行了此转换,则会得到的结果(请参见清单15)。

清单15.最简单的转换
<recipes>
        <recipe recipeId="1">
        <name>Gush'gosh</name>
        <ingredients>

        <ingredient><qty>1</qty><unit>pound</unit>
        <food>hamburger </food></ingredient>

        <ingredient><qty>1</qty><unit>pound</unit>
        <food>elbow macaroni</food></ingredient>

        <ingredient><qty>2</qty><unit>cups</unit>
        <food>brown sugar</food></ingredient>

        <ingredient><qty>1</qty><unit>bag</unit>
    <food>chopped onions</food></ingredient>

        <ingredient><qty>1</qty><unit>teaspoon</unit>
        <food>dried dill</food></ingredient>
        </ingredients>
        <instructions>
        <instruction>Brown the hamburger.</instruction>
        <instruction>Add onions and cook until transparent.</instruction>
        <instruction>Add brown sugar and dill.</instruction>
        <instruction>Cook and drain pasta.</instruction>
        <instruction>Combine meat and pasta.</instruction>
        </instructions>
        </recipe>
        ...
        </recipes>

您会注意到,这只是重复给您的文档; 这是因为模板的match属性指定的上下文节点是文档根目录,或/。 如果更改上下文节点,则可以更改输出。 例如,您可以将上下文节点设置为第一个配方(请参见清单16)。

清单16.移动上下文节点
<xsl:stylesheet version="1.0"
		xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
		xmlns="http://www.w3.org/TR/xhtml1/strict">

		<xsl:template match="/recipes/recipe">

		<xsl:copy-of select="." />

		</xsl:template>


		</xsl:stylesheet>

现在,如果您运行转换,则可以看到区别(请参见清单17)。

清单17.结果
<?xml version="1.0" encoding="UTF-8"?>

        <recipe recipeId="1">
        <name>Gush'gosh</name>
        <ingredients>

        <ingredient><qty>1</qty><unit>pound</unit>
        <food>hamburger</food></ingredient>

        <ingredient><qty>1</qty><unit>pound</unit>
        <food>elbow macaroni</food></ingredient>

        <ingredient><qty>2</qty><unit>cups</unit>
        <food>brown sugar</food></ingredient>

        <ingredient><qty>1</qty><unit>bag</unit>
        <food>chopped onions</food></ingredient>

        <ingredient><qty>1</qty><unit>teaspoon</unit>
        <food>dried dill</food></ingredient>
        </ingredients>
        <instructions>
        <instruction>Brown the hamburger.</instruction>
        <instruction>Add onions and cook until transparent.</instruction>
        <instruction>Add brown sugar and dill.</instruction>
        <instruction>Cook and drain pasta.</instruction>
        <instruction>Combine meat and pasta.</instruction>
        </instructions>
        </recipe>

        <recipe recipeId="2">
        <name>A balanced breakfast</name>
        <ingredients>

        <ingredient><qty>1</qty><unit>cup</unit>
        <food>cereal</food></ingredient>

        <ingredient><qty>1</qty><unit>glass</unit>
        <food>orange juice</food></ingredient>

        <ingredient><qty>1</qty><unit>cup</unit>
        <food>milk</food></ingredient>

        <ingredient><qty>2</qty><unit>slices</unit>
        <food>toast</food></ingredient>
        </ingredients>
        <instructions>
        <instruction>Combine cereal and milk in bowl.</instruction>
        <instruction>Add all ingredients to table.</instruction>
        </instructions>
        </recipe>

因为您移动了上下文节点,所以输出发生了变化。 当您要选择相对于上下文节点的节点时,这一点很重要。 例如,您可能只想选择配方的标题(请参见清单18)。

清单18.仅选择标题
<xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns="http://www.w3.org/TR/xhtml1/strict">

        <xsl:template match="/recipes/recipe">

        <xsl:copy-of select="./name" />

        </xsl:template>


        </xsl:stylesheet>

在这里,您选择的name元素比当前上下文节点低一级,因此结果将是(参见清单19)。

清单19.结果
<?xml version="1.0" encoding="UTF-8"?>
        <name>Gush'gosh</name>
        <name>A balanced breakfast</name>

稍后将检查按编号指定节点,但请注意,您可以将上下文节点移至第二个配方(请参见清单20)。

清单20.再次移动上下文节点
<xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns="http://www.w3.org/TR/xhtml1/strict">

        <xsl:template match="/recipes/recipe">
        </xsl:template>

        <xsl:template match="/recipes/recipe[2]">

        <xsl:copy-of select="./name" />

        </xsl:template>


        </xsl:stylesheet>

(不必担心多余的模板;这是为了防止其他配方的文本出现。)

您可以看到生成的转换显示了上下文的这种变化(请参见清单21)。

清单21.结果
<?xml version="1.0" encoding="UTF-8"?>
        <name>A balanced breakfast</name>
了解轴

既然您知道从哪里开始,那么知道可以去哪里就很有用。 到目前为止,您已经使用了非常简单的XPath语句,这些语句非常类似于文件系统中的层次结构,但是XPath可以为您提供更多的功能。 例如,当您仅选择子代时,您还可以选择查找特定节点的父代以及后代或祖先。

首先,让我们谈谈符号。 您使用了要求孩子的简化形式。 要使用“长格式”版本,请指定诸如child::name./name表达式,而不要使用./name

在这两种情况下,您都可以指定作为上下文节点的子节点并且也是name元素的任何节点。 您也可以像以前一样将它们链接在一起,例如/child::recipes/child::recipe而不是/recipes/recipe

后代

现在,您可能想知道为什么当短格式要容易得多时,为什么还要打扰长格式。 好吧,如果您只能选择孩子,那您就不会。 但是,在发生这种情况时,您也可以使用此表示法选择其他关系。 例如,XPath表达式descendant::instruction选择作为上下文节点后代的所有指令元素,而不仅仅是子元素。 同样,您可以组合说明。 例如,您可以选择第二个配方的所有说明: /recipes/recipe[2]/descendant::instruction

在后代轴上的一种变体是后代或自身轴,它从所有上下文节点的后代中选择请求的节点,但同时查看上下文节点本身。 例如,表达式descendant-or-self::instructions选择上下文节点处或上下文节点下方的所有指令节点。 此轴的常见缩写是双斜杠//。 这意味着表达式: /recipes/recipe[2]//instructions//instructions选择第二个配方的所有指令和文档中的所有指令。 第二个示例非常常见,如果您只想选择文档中特定类型的所有元素,则非常方便。

属性

您将遇到的另一个常见任务是需要为特定元素选择一个属性。 例如,您可能要为特定配方选择配方recipeId 。 表达式/recipes/recipe/attribute::recipeId为所有recipe元素选择recipeId属性。 该轴也具有缩写形式,因此您可以将其编写为: /recipes/recipe/@recipeId

父母

到目前为止,你看一切在带给你下来分层树,但你也有通过选择特定节点的父节点上去的选项。 例如,假设上下文节点是指令之一,但是您想输出当前配方的recipeId 。 您可以按照以下步骤进行操作: ./parent::node()/parent::node()/@recipeId :: ./parent::node()/parent::node()/@recipeId :: ./parent::node()/parent::node()/@recipeId

该表达式从当前节点(指令)开始,然后移动到该节点的父节点,指令元素,然后移动到THAT节点的父节点,配方,再到适当的属性。 当然,您可能更熟悉缩写形式: ./../../@recipeId

现在让我们来看一下设置一些条件。

更高级的XPath

在大多数情况下,仅需使用已经介绍的技术就可以完成所需的工作。 但是,遇到需要您更加具体的情况并不少见。 本节说明如何使用谓词根据特定条件选择节点,并向您介绍XPath内置的一些功能。

使用谓词

通常,您不仅需要任何节点,还需要基于特定条件的特定节点。 使用表达式/recipes/recipe[2]//instructions时,您已经看到了一个示例。 这实际上是/recipes/recipe[position() = 2]//instructions的缩写版本,它的意思是您要让XPath处理器浏览每个配方元素(当然,其中只有一个),然后针对每个食谱元素遍历每个食谱元素。 对于每个配方元素,请检查表达式position() = 2是否为真。 (换句话说,这是列表中的第二个配方吗?)如果该语句(称为谓词)为true,则处理器使用该节点并继续前进,返回任何指令。

您可以使用谓词来做各种事情。 例如,您可能只返回名称为/recipes/recipe[name] 。 该表达式仅测试是否存在recipe元素的子元素name 。 您也可以查找特定值。 例如,您只能返回名为“平衡早餐”的//recipe[name="A balanced breakfast"]//recipe[name="A balanced breakfast"]

请注意,谓词仅告诉处理器是否返回实际节点,因此在这种情况下,返回的是配方元素而不是名称。 另一方面,您可以告诉处理器使用这两个表达式之一仅返回第一个配方的名称(请参见清单22)。

清单22.仅返回第一个配方的名称
//recipe[@recipeId='1']/name
        //name[parent::recipe[@recipeId='1']]

对于第一个表达式,首先选择所有recipe元素,然后仅返回一个拥有recipeId 。找到该节点后,将移至其名为name的子节点,并返回该节点。 对于第二个表达式,您将找到所有的name元素,然后仅选择一个具有parent且其recipeId属性为1的父recipeId 。在两种情况下,您都将获得相同的输出(参见清单23)。

清单23.输出
<?xml version="1.0" encoding="UTF-8"?>
        <name>Gush'gosh</name>

功能

XPath还提供了许多不同的功能。 它们中的一些与节点本身有关,例如那些看位置的节点,其中一些操纵字符串,一些与数字有关,例如和,还有一些与布尔值有关。

节点集功能

与节点集相关的功能可帮助您执行诸如根据位置选择特定节点的操作。 例如,您可以专门请求最后一个配方: //recipe[last()] 。 该表达式选择所有recipe元素,然后仅返回最后一个元素。 您也可以单独使用函数,而不是作为谓词的一部分。 例如,您可以专门请求计数配方元素: count(//recipe)

您已经看过position()函数及其工作原理。 其他与节点集相关的功能包括id()local-name()namespace-uri()name()

字符串函数

contains()函数外,大多数字符串函数都是用于操作字符串而不是对其进行测试。 contains()函数可以告诉您特定字符串是否是较大整体的一部分。 例如,您只能返回包含特定字符串的节点,例如: //recipe[contains(name, 'breakfast')]

该表达式返回其名称元素中具有字符串“ breakfast”的配方元素。

substring()函数使您可以从字符串中选择特定范围的字符。 例如,表达式: substring(//recipe[2]/name, 1, 5)返回A bal

第一个参数是完整的字符串,第二个参数是第一个字符的位置,第三个参数是字符串的长度。

其他字符串函数包括concat()substring-before()substring-after()starts-with()string-length()

数值函数

数值函数包括number()函数,该函数将值转换为数值,以便其他函数可以对其执行操作。 数字函数还包括sum()floor()ceiling()round() 。 例如,您可以使用表达式sum(//recipe/@recipeId)找到所有食谱ID值的sum(//recipe/@recipeId)

的确,没有太多理由进行这种计算,但这是示例文档中唯一的数值。

floor()函数查找小于或等于提供的值的最大整数,而ceiling()函数则沿另一个方向查找。 round()以传统方式执行(请参见清单24)。

清单24.数值函数的结果
floor(42.7) = 42
        ceiling(42.7) = 43
        round(42.7) = 43

布尔函数

当您进入条件表达式时,布尔函数最有用,您将在条件处理中查看它。 也许最有用的是not()函数,该函数可用于判断特定节点是否不存在。 例如,表达式//recipe[contains(name, 'breakfast')]返回其name元素中包含字符串“ breakfast”的每个配方。 但是,如果您想要所有非早餐的食谱该怎么办? 您可以使用以下表达式: //recipe[not(contains(name, 'breakfast'))]

其他布尔函数包括true()false() (它们返回常量)和boolean() (它们将值转换为布尔值,以便可以用作测试值)。

循环和导入

现在来看一下使用XSLT样式表的两个重要方面:创建循环和导入外部样式表。

循环播放

XSLT可能需要一点适应,因为它是一种功能语言,而不是过程语言。 换句话说,您通常不会明确控制其执行给定指令的方式。 但是,确实存在例外。 例如,您具有执行循环和条件操作的能力。 让我们从循环开始。

到目前为止,在示例中,您已经使用XSLT的内置模板传播将样式应用于特定元素。 在某些情况下,这很好。 但是,在某些情况下,如果您具有复杂的XML文件或复杂的需求,则可能会发现显式应用信息更容易(请参见清单25)。

清单25.使用循环直接应用样式
<xsl:template match="recipe">

        <h2><xsl:value-of select="./name"/></h2>
        <h3>Ingredients:</h3>
        <p>
        
            <xsl:for-each select="./ingredients/ingredient">
            <xsl:value-of select="./qty"/><xsl:text> </xsl:text>
            <xsl:value-of select="./unit"/><xsl:text> </xsl:text>
            <xsl:value-of select="./food"/><br />
            </xsl:for-each>

        </p>

        <h3>Directions:</h3>
        <ol>

            <xsl:for-each select="./instructions/instruction">
            <li><xsl:value-of select="."/></li>
            </xsl:for-each>

        </ol>

        </xsl:template>

        </xsl:stylesheet>

这种循环结构与它命名的for-each结构非常相似。 就像它的名字一样,循环的每个实例都带有列表中的下一个值。 在Java编程中,它可能是数组中的下一个值; 在这里,它是select属性中XPath语句返回的节点集中的下一个节点。 这意味着您第一次执行第一个循环时,上下文节点是第一个ingredient元素。 这使您能够选择该元素的数量,单位和食物子代,并将它们添加到文档中,就像之前使用模板一样。 指令与这些指令相同,只是它们只是直接输出。

结果几乎与通过模板传播所获得的结果相同。 每个模板都作为单独的行添加到文档中,但是由于您实际上是将这些信息作为一个模板进行处理,因此您失去了很多以前看到的间距(请参见图7)。

图7.结果
结果

这在XML的某些应用程序中很重要,但是因为您使用的是HTML,所以实际上并不重要。 但是在决定使用哪种方法时,您需要牢记这一点。

包括和导入样式表

样式表的另一个变体涉及其结构。 到目前为止,您的所有信息都位于一个样式表中。 但是,在某些情况下,您可能需要将其分解为多个部分。 这种模块化可以提高可维护性,并提供灵活性以将不同的样式表用于不同的目的。 例如,您可以创建两个单独的样式表,一个用于样式表(参见清单26)。

清单26. Ingredients.xsl文件
<xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns="http://www.w3.org/TR/xhtml1/strict">

        <xsl:template match="ingredients/ingredient">

        <xsl:value-of select="./qty"/><xsl:text> </xsl:text>
        <xsl:value-of select="./unit"/><xsl:text> </xsl:text>
        <xsl:value-of select="./food"/><br />

        </xsl:template>

        </xsl:stylesheet>

您还可以为说明创建样式表(请参见清单27)。

清单27. Instructions.xsl文件
<xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns="http://www.w3.org/TR/xhtml1/strict">

        <xsl:template match="instructions/instruction">

        <li><xsl:value-of select="."/></li>

        </xsl:template>

        </xsl:stylesheet>

这些模板与实际样式表中的模板相同。 您可以通过包含它们来将它们添加到样式表中(参见清单28)。

清单28.包括样式表
<xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns="http://www.w3.org/TR/xhtml1/strict">

        
            <xsl:include href="ingredients.xsl" />
            <xsl:include href="instructions.xsl" />
        

        <xsl:template match="/">
        ...
        </xsl:template>

        <xsl:template match="recipe">

        <h2><xsl:value-of select="./name"/></h2>
        <h3>Ingredients:</h3>
        <p><xsl:apply-templates select="./ingredients"/></p>
        <h3>Directions:</h3>
        <ol><xsl:apply-templates  
select="./instructions"/></ol>

        </xsl:template>

        </xsl:stylesheet>

将样式表添加到与主要样式表相同的目录中时,不必这样做; href属性可以包含任何可访问的URL。 请注意,您发送处理器以在模板中搜索“ ingredients和“ instructions元素,在此文件中看不到任何内容。 但是,如果您处理样式表,则结果就是直接在其他模板中而不是通过include元素(如图8所示)在其他模板中看到的结果。

图8.结果
结果

include元素提供的效果与此时将内容实际添加到样式表的效果相同。 从语义上讲,它们是相同的。 另一方面,您还有第二种选择来包括信息:导入。

XSLT使您可以在文件顶部导入样式表。 为什么在顶部? 因为导入样式表的目的是让您自己选择覆盖导入中包含的所有模板的选项。 例如,您可以导入配料模板并将其覆盖(参见清单29)。

清单29.导入样式表
<xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns="http://www.w3.org/TR/xhtml1/strict">

        <xsl:import href="ingredients.xsl" />
        <xsl:import href="instructions.xsl" />

        <xsl:template match="/">
        ...
        </xsl:template>

        <xsl:template match="recipe">
        ...
        </xsl:template>

        
            <xsl:template match="ingredients">

            <ul><xsl:apply-templates select="./ingredient" /></ul>

            </xsl:template>

            <xsl:template match="ingredient">

            <li><xsl:value-of select="./qty"/><xsl:text> 
</xsl:text>
            <xsl:value-of select="./unit"/>
            <xsl:text> </xsl:text><xsl:value-of
            select="./food"/></li>

            </xsl:template>
        

        </xsl:stylesheet>

在这里,您实际上是用两个模板替换了一个模板,这两个模板将覆盖导入的样式表中的模板(请参见图9)。

图9.结果
结果

请注意,由于所有的居住规则,您都可以通过包含来实现相同的目的。 但是,使用XSLT,您可以使用priority属性来确定在处理导入时哪个模板具有更大的权重。

扩展XSLT

您已经研究了使XSLT更像编程的方法。 如何添加实际的编程呢? 让我们看看将Java功能添加到XSLT样式表中。

首先请注意,虽然扩展机制是XSLT建议书的一部分,但是您在此处看到的实际实现特定于Xalan XSLT处理器。 其他处理器的概念可能相似,但是您必须检查文档以了解具体信息。

接下来,您将创建一个扩展,使您可以缩放一份以上一份的食谱。

扩展元素

扩展XSLT需要不同的技术。 首先是extension元素的使用。 扩展元素是命名空间中专门指定为与Java类相对应的元素。 例如,考虑这个扩展元素(参见清单30)。

清单30.使用extension元素
<xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns="http://www.w3.org/TR/xhtml1/strict"
        
            xmlns:scaler = "com.backstop.RecipeScaler"
            extension-element-prefixes="scaler">
        

        <xsl:template match="/">
        <html>
        <head>
        <title>Recipes</title>
        </head>
        <body>

        <scaler:scaleMessage servings="2" />

        <xsl:apply-templates select="/recipes/recipe"/>
        </body>
        </html>
        </xsl:template>
        ...

您已经创建了一个与comp.backstop.RecipeScaler类相对应的名称空间,其中包括一个名为scaleMessage的静态方法(请参见清单31)。

清单31. RecipeScaler
package com.backstop;

        public class RecipeScaler {

        public static String scaleMessage (
        org.apache.xalan.extensions.XSLProcessorContext context,
        org.w3c.dom.Element thisElement){

        return "This recipe has been scaled by a factor of " +
        thisElement.getAttribute("servings") + ".";

        }

        }

当处理器到达元素时,它会看到scaler:名称空间前缀,并且知道它被指定为扩展元素前缀,因此它知道在名称空间定义中指定的类。 它调用的方法响应元素的本地名称scaleMessage 。 该方法本身接收两个参数,您实际使用其中一个。 context参数是指处理器上下文,它使您能够查看extension元素上的元素,但是您将只关注第二个参数,即extension元素本身。 因为您收到该元素作为方法的参数,所以在这种情况下,您可以提取添加到该元素的任何属性的值,例如servings 。 该方法返回的文本将代替extension元素添加到输出中。

这意味着,如果处理样式表,则会得到如图10所示的结果。

图10.扩展元素的结果
扩展元素的结果

如果很难使用,扩展元素可能会非常有用。

扩展功能

通过样式表添加功能的另一种方法是使用扩展功能,该功能比扩展元素更易于实现。 例如,您可以创建一个将配料数量和份量相乘的函数(请参见清单32)。

清单32. scaleIngredient()方法
package com.backstop;

        public class RecipeScaler {

        public static String scaleMessage (
        org.apache.xalan.extensions.XSLProcessorContext context,
        org.w3c.dom.Element thisElement){

        return "This recipe has been scaled by a factor of " +
        thisElement.getAttribute("servings") + ".";

        }

        public static int scaleIngredient(int servings, int original){

        return (servings * original);

        }

        }

Adding this function to the stylesheet is similar to adding an extension element, in that you already have a namespace map to the class (see Listing 33).

Listing 33. Adding the extension function
<xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns="http://www.w3.org/TR/xhtml1/strict"
        xmlns:scaler = "com.backstop.RecipeScaler"
        extension-element-prefixes="scaler">
        ...
        <xsl:template match="ingredients/ingredient">

        <xsl:value-of select="scaler:scaleIngredient(2, ./qty)"/>
        <xsl:text> </xsl:text><xsl:value-of select="./unit"/>
        <xsl:text> </xsl:text><xsl:value-of select="./food"/><br
        />

        </xsl:template>
        ...

Notice that the function call includes the namespace prefix. As before, the processor sees the prefix and knows it needs to execute a call to the RecipeScaler class. The results are that the ingredients are multiplied by two (see Figure 11).

Figure 11. Using the extension function
using the extension function

But while this works, it's not very maintainable. Now look at a way to make that easier.

Programming XSLT

Before I wrap things up, you need to know about two aspects of XSLT that give you some of the capabilities you expect from a regular program language.

XSLT variables

It's nice that you have a way to execute a function, but you wound up with a constant buried in the midst of the stylesheet. Wouldn't it be better if you could set a variable at the top of the page? Of course it would (see Listing 34).

Listing 34. Setting a variable
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns="http://www.w3.org/TR/xhtml1/strict"
    xmlns:scaler = "com.backstop.RecipeScaler"
    extension-element-prefixes="scaler">

    <xsl:variable name="numberOfServings" select="3" />

    <xsl:template match="/">
    <html>
    <head>
    <title>Recipes</title>
    </head>
    <body>

    <scaler:scaleMessage servings="$numberOfServings" />

    <xsl:apply-templates select="/recipes/recipe"/>
    </body>
    </html>
    </xsl:template>

    <xsl:template match="recipe">
    ...
    </xsl:template>

    <xsl:template match="ingredients/ingredient">

    <xsl:value-of select="scaler:scaleIngredient($numberOfServings,
    ./qty)"/>
    <xsl:text> </xsl:text><xsl:value-of select="./unit"/>
    <xsl:text> </xsl:text><xsl:value-of select="./food"/><br
    />

    </xsl:template>
    ...

XSLT enables you to create a variable, and that reference uses the "dollar sign" ($) notation you see in the listing. If you process the stylesheet, you'll see two effects (see Figure 12).

Figure 12. Using a variable
using a variable

Notice that the ingredients were multiplied by the number of servings, as expected. However, if you look more carefully, you will see that the extension element did not process properly, taking a variable as a string rather than as the value of the variable itself. This is not a bug; the specification does not require the processor to do anything at all to attribute values before processing the extension element. So you'll have to find another way around this problem.

Conditional processing

The first thing that you can do is to use conditional processing so that you only display the message if it's needed in the first place. For example, see Listing 35.

Listing 35. Using an if element
<xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns="http://www.w3.org/TR/xhtml1/strict"
        xmlns:scaler = "com.backstop.RecipeScaler"
        extension-element-prefixes="scaler">

        <xsl:variable name="numberOfServings" select="1" />

        <xsl:template match="/">

        <html>
        <head>
        <title>Recipes</title>
        </head>
        <body>

        <xsl:if test="$numberOfServings > 1">

        <scaler:scaleMessage servings="3" />

        </xsl:if>

        <xsl:apply-templates select="/recipes/recipe"/>
        </body>
        </html>
        </xsl:template>
        ...

The contents of the if element specified by the test attribute evaluates to true. If not, as in this case, the output does not appear at all (see Figure 13).

Figure 13. The results of the if statement
results of the if statement

The way it is written, it doesn't make much sense; if the value is greater than one, the extension element will display with a value of "3." You are better off with a variety of choices (see Listing 36).

Listing 36. The choose element
<xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns="http://www.w3.org/TR/xhtml1/strict"
        xmlns:scaler = "com.backstop.RecipeScaler"
        extension-element-prefixes="scaler">

        <xsl:variable name="numberOfServings" select="3" />

        <xsl:template match="/">

        <html>
        <head>
        <title>Recipes</title>
        </head>
        <body>

        
            <xsl:choose>
            <xsl:when test="$numberOfServings < 1">
            Recipes have been scaled by an invalid number.
            </xsl:when>
            <xsl:when test="$numberOfServings = 1">
            </xsl:when>
            <xsl:when test="$numberOfServings = 2">
            <scaler:scaleMessage servings="2" />
            </xsl:when>
            <xsl:when test="$numberOfServings = 3">
        
        <scaler:scaleMessage servings="3" />
        
            </xsl:when>
            <xsl:otherwise>
            Recipes have been scaled for multiple portions.
            </xsl:otherwise>
            </xsl:choose>
        

        <xsl:apply-templates select="/recipes/recipe"/>
        </body>
        </html>
        </xsl:template>
        ...

In this case, you have a combination of the if-then-else and the case statement from traditional programming languages. The choose element acts as a container, but the when element displays its contents only if its test attribute evaluates to true. Finally, if none of the when elements evaluate to true, the processor displays the contents of the otherwise element.

The result is as you might expect (see Figure 14).

Figure 14. The result
the result

And now you have the flexibility to specifically display more quantities, or to adapt for other cases.

摘要

结语

This tutorial took you from minimal knowledge about XSL Transformations to the creation of fairly complex stylesheets. First you learned about the basics of stylesheets, and then about XPath expressions, one of the foundations of XSLT. The last part of this tutorial examined some of the more complicated aspects of using XSLT stylesheets, that is, their variables, conditional processing, and extensions. As a result, you should have enough knowledge to do virtually anything with XSLT stylesheets -- or at least to know what you need to find out if you get stuck.


翻译自: https://www.ibm.com/developerworks/xml/tutorials/x-introxslt/x-introxslt.html

xslt简介

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值