Java 中文官方教程 2022 版(三十九)(3)https://developer.aliyun.com/article/1488159
处理剩余的结构元素
在这一部分,您将处理 LIST
和 NOTE
元素,这些元素为文章添加了更多结构。
注意 - 本节描述的示例文档是 article2.xml
,用于操作它的样式表是 article2.xsl
。结果是 stylizer2.html
。解压缩 XSLT examples
到 install-dir/jaxp-1_4_2-
release-date/samples
目录后,这些文件可以在 xslt/data
目录中找到。
首先向示例文档添加一些测试数据:
<?xml version="1.0"?> <ARTICLE> <TITLE>A Sample Article</TITLE> <SECT>The First Major Section ... </SECT> <SECT>The Second Major Section <PARA>This section adds a LIST and a NOTE. <PARA>Here is the LIST: <LIST type="ordered"> <ITEM>Pears</ITEM> <ITEM>Grapes</ITEM> </LIST> </PARA> <PARA>And here is the NOTE: <NOTE>Don't forget to go to the hardware store on your way to the grocery! </NOTE> </PARA> </SECT> </ARTICLE>
注意 - 虽然 XML 文件中的 list
和 note
包含在各自的段落中,但它们是否包含并不重要;生成的 HTML 无论如何都是相同的。但是包含它们会使它们在面向大纲的编辑器中更容易处理。
修改
处理
接下来,修改 PARA
模板以考虑我们现在允许一些结构元素嵌入到段落中的事实:
<xsl:template match="PARA"> <p> <xsl:apply-templates select= "text()|B|I|U|DEF|LINK"/> </p> <xsl:apply-templates select= "PARA|LIST|NOTE"/> </xsl:template>
这种修改使用了你用于节标题的相同技术。唯一的区别是 SECT
元素不应该出现在段落中。(但是,一个段落很容易存在于另一个段落内,例如,作为引用材料)。
处理
和
元素
现在你已经准备好添加一个模板来处理 LIST
元素:
<xsl:template match="LIST"> <xsl:if test="@type='ordered'"> <ol> <xsl:apply-templates/> </ol> </xsl:if> <xsl:if test="@type='unordered'"> <ul> <xsl:apply-templates/> </ul> </xsl:if> </xsl:template> </xsl:stylesheet>
标签使用
test=""
属性来指定一个布尔条件。在这种情况下,测试值是 type 属性,并且生成的列表会根据值是有序还是无序而改变。
在这个例子中注意两个重要的事情:
- 没有 else 子句,也没有 return 或 exit 语句,因此需要两个
标签来覆盖两个选项。(或者可以使用
标签,它提供了 case 语句功能)。
- 在属性值周围需要使用单引号。否则,XSLT 处理器会尝试将 ordered 作为 XPath 函数来解释,而不是作为字符串。
现在通过处理 ITEM 元素来完成 LIST 处理:
<xsl:template match="ITEM"> <li><xsl:apply-templates/> </li> </xsl:template> </xsl:stylesheet>
在样式表中排序模板
到目前为止,你应该已经了解到模板是彼此独立的,所以它们通常出现在文件中的位置并不重要。因此,从这一点开始,我们只会展示你需要添加的模板。(为了比较,它们总是添加在示例样式表的末尾)。
当两个模板可以应用于同一节点时,顺序确实很重要。在这种情况下,最后定义的模板是被找到和处理的。例如,要将缩进列表的排序更改为使用小写字母表,可以指定一个看起来像这样的模板模式://LIST//LIST
。在该模板中,您将使用 HTML 选项生成字母枚举,而不是数字枚举。
但这样的元素也可以通过模式//LIST
来识别。为了确保正确处理,指定//LIST
的模板必须出现在指定//LIST//LIST
的模板之前。
处理
元素
唯一剩下的结构元素是NOTE
元素。添加以下模板来处理它。
<xsl:template match="NOTE"> <blockquote><b>Note:</b><br/> <xsl:apply-templates/> </p></blockquote> </xsl:template> </xsl:stylesheet>
这段代码引发了一个有趣的问题,这是由于包含
标签导致的。为了使文件成为格式良好的 XML,必须在样式表中指定该标签为
,但许多浏览器不识别该标签。虽然大多数浏览器识别序列
,但它们都将其视为段落换行而不是单个换行。
换句话说,转换必须生成一个
标签,但样式表必须指定
。这就是我们在样式表中早期添加的特殊输出标签的主要原因:
<xsl:stylesheet ... > <xsl:output method="html"/> [...] </xsl:stylesheet>
该输出规范将空标签(如
)转换为它们的 HTML 形式
,在输出时。这种转换很重要,因为大多数浏览器不识别空标签。以下是受影响的标签列表:
area frame isindex base hr link basefont img meta br input param col
总之,默认情况下,XSLT 在输出时生成格式良好的 XML。因为 XSL 样式表本身就是格式良好的 XML,所以你不能轻易地在其中间放置
这样的标签。标签解决了这个问题,这样你可以在样式表中编码
,但在输出中得到
。
指定的另一个主要原因是,与指定
一样,生成的文本不会被转义。例如,如果样式表包含
<
实体引用,它将出现为生成文本中的<
字符。另一方面,当生成 XML 时,样式表中的<
实体引用将保持不变,因此在生成的文本中会显示为<
。
注意 - 如果你希望<
实际上作为 HTML 输出的一部分生成,你需要将其编码为<
。这个序列在输出时变为<
,因为只有&
被转换为&
字符。
使用定义了LIST
和NOTE
元素的Stylizer
示例运行
- 导航到
samples
目录。
% cd *install-dir*/jaxp-1_4_2-*release-date*/samples.
- 点击此链接下载 XSLT 示例,并将其解压缩到install-dir
/jaxp-1_4_2-
release-date/samples
目录中。 - 导航到
xslt
目录。
cd xslt
- 编译
Stylizer
示例。
输入以下命令:
% javac Stylizer.java
- 使用样式表
article2.xsl
在article2.xml
上运行Stylizer
示例。
% java Stylizer data/article2.xsl data/article2.xml
- 这是当你现在运行程序时为第二部分生成的 HTML:
... <h2>The Second Major Section </h2> <p>This section adds a LIST and a NOTE. </p> <p>Here is the LIST: </p> <ol> <li>Pears</li> <li>Grapes</li> </ol> <p>And here is the NOTE: </p> <blockquote> <b>Note:</b> <br>Do not forget to go to the hardware store on your way to the grocery! </blockquote>
处理内联(内容)元素
ARTICLE
类型中唯一剩下的标签是内联标签-它们不会在输出中创建换行,而是被整合到它们所属的文本流中。
内联元素与结构元素不同,内联元素是标签内容的一部分。 如果将元素视为文档树中的节点,则每个节点都具有内容和结构。 内容由它包含的文本和内联标记组成。 结构由标签下的其他元素(结构元素)组成。
注意 - 本节描述的示例文档是article3.xml
,用于操作它的样式表是article3.xsl
。 结果是stylizer3.html
。
首先向示例文档添加一些测试数据:
<?xml version="1.0"?> <ARTICLE> <TITLE>A Sample Article</TITLE> <SECT>The First Major Section [...] </SECT> <SECT>The Second Major Section [...] </SECT> <SECT>The <i>Third</i> Major Section <PARA>In addition to the inline tag in the heading, this section defines the term <DEF>inline</DEF>, which literally means "no line break". It also adds a simple link to the main page for the Java platform (<LINK>http://java.sun.com</LINK>), as well as a link to the <LINK target="http://java.sun.com/xml"> XML </LINK> page. </PARA> </SECT> </ARTICLE>
现在处理段落中的内联元素,将它们重命名为 HTML 斜体标记:
<xsl:template match="DEF"> <i> <xsl:apply-templates/> </i> </xsl:template>
接下来,注释掉文本节点规范化。它已经达到了它的目的,现在你需要保留重要的空格:
<!-- <xsl:template match="text()"> <xsl:value-of select="normalize-space()"/> </xsl:template> -->
这个修改使我们不会丢失在和等标签之前的空格。 (尝试在没有此修改的情况下运行程序,看看结果)。
现在处理基本的内联 HTML 元素,如,和,用于加粗,斜体和下划线。
<xsl:template match="B|I|U"> <xsl:element name="{name()}"> <xsl:apply-templates/> </xsl:element> </xsl:template>
标签允许您计算要生成的元素。 在这里,您使用当前元素的名称生成适当的内联标记。 特别要注意在
name=".."
表达式中使用大括号({}
)。 这些大括号导致引号内的文本被处理为 XPath 表达式,而不是被解释为字面字符串。 在这里,它们导致 XPath name()
函数返回当前节点的名称。
大括号可以出现在属性值模板可以出现的任何地方。 (属性值模板在 XSLT 规范的第 7.6.2 节中定义,并且它们出现在模板定义的几个地方)。 在这种表达式中,大括号也可以用于引用属性的值,{@foo}
,或元素的内容{foo}
。
注意 - 您还可以使用生成属性。 有关更多信息,请参阅 XSLT 规范的第 7.1.3 节。
最后剩下的元素是LINK
标签。处理该标签的最简单方法是设置一个带有参数的命名模板:
<xsl:template name="htmLink"> <xsl:param name="dest" select="UNDEFINED"/> <xsl:element name="a"> <xsl:attribute name="href"> <xsl:value-of select=""/> </xsl:attribute> <xsl:apply-templates/> </xsl:element> </xsl:template>
这个模板的主要区别在于,不是指定匹配子句,而是使用name=""
子句为模板指定一个名称。 因此,只有在调用它时才会执行此模板。
在模板内部,还使用标签指定一个名为
dest
的参数。 为了进行一些错误检查,您使用 select 子句为该参数指定了一个默认值UNDEFINED
。 要在标签中引用变量,您指定
.
。
`* * *
注意 - 请记住,引号中的条目被解释为表达式,除非它进一步被单引号括起来。 这就是为什么之前需要在"@type='ordered'"
中使用单引号来确保 ordered 被解释为字符串。
标签生成一个元素。之前,你可以简单地通过编写类似的代码来指定我们想要的元素。但是在这里,你正在动态生成
标签的主体中 HTML 锚点(
)的内容。并且你正在使用
标签动态生成锚点的
href
属性。
模板的最后一个重要部分是标签,它插入
LINK
元素下文本节点中的文本。如果没有它,生成的 HTML 链接中将没有文本。
<xsl:template match="LINK"> <xsl:if test="@target"> <!--Target attribute specified.--> <xsl:call-template name="htmLink"> <xsl:with-param name="dest" select="@target"/> </xsl:call-template> </xsl:if> </xsl:template> <xsl:template name="htmLink"> [...]
test="@target"
子句在 LINK 标签中存在 target 属性时返回 true。因此,当链接的文本和为其定义的目标不同时,这个标签会生成 HTML 链接。
标签调用命名模板,而
使用
name
子句指定参数,并使用select
子句指定其值。
在样式表构建过程的最后一步中,添加标签来处理没有
target
属性的LINK
标签。
<xsl:template match="LINK"> <xsl:if test="@target"> [...] </xsl:if> <xsl:if test="not(@target)"> <xsl:call-template name="htmLink"> <xsl:with-param name="dest"> <xsl:apply-templates/> </xsl:with-param> </xsl:call-template> </xsl:if> </xsl:template>
not(...)
子句反转了先前的测试(记住,没有 else 子句)。因此,当未指定目标属性时,模板的这部分被解释。这次,参数值不是来自select
子句,而是来自元素的内容。
注意 - 仅仅是为了明确:参数和变量(稍后在 XSLT 还能做什么?中讨论)的值可以通过select
子句指定,让你可以使用 XPath 表达式,也可以通过元素的内容指定,让你可以使用 XSLT 标签。
在这种情况下,参数的内容是由标签生成的,它插入
LINK
元素下文本节点的内容。
运行带有内联元素定义的Stylizer
示例
- 导航到
samples
目录。
% cd *install-dir*/jaxp-1_4_2-*release-date*/samples.
- 点击此链接下载 XSLT 示例并将其解压缩到install-dir
/jaxp-1_4_2-
release-date/samples
目录。 - 导航到
xslt
目录。
cd xslt
- 编译
Stylizer
示例。
输入以下命令:
% javac Stylizer.java
- 使用样式表
article3.xsl
在article3.xml
上运行Stylizer
示例。
% java Stylizer data/article3.xsl data/article3.xml
- 现在运行程序,结果应该看起来像这样:
[...] <h2>The <i>Third</i> Major Section</h2> <p>In addition to the inline tag in the heading, this section defines the term <i>inline</i>, which literally means "no line break". It also adds a simple link to the main page for the Java platform (<a href="http://java.sun.com">http://java.sun.com</a>), as well as a link to the <a href="http://java.sun.com/xml">XML</a> page.</p>
- 干得好!你现在已经将一个相当复杂的 XML 文件转换为 HTML。(尽管一开始看起来很简单,但它确实提供了很多探索的机会)。
打印 HTML
您现在已将 XML 文件转换为 HTML。总有一天,会有人制作出一个了解 HTML 的打印引擎,您将能够通过 Java 打印服务 API 找到并使用它。到那时,您将能够通过生成 HTML 打印任意 XML 文件。您只需设置一个样式表并使用您的浏览器。
XSLT 还能做什么?
尽管本节内容很长,但只是触及了 XSLT 的能力表面。XSLT 规范中还有许多额外的可能性等待着您。以下是一些值得关注的事项:
import
(第 2.6.2 节)和 include
(第 2.6.1 节)
rt
(第 2.6.2 节)和 include(第 2.6.1 节)使用这些语句对 XSLT 样式表进行模块化和组合。include 语句只是插入所包含文件中的任何定义。import 语句允许您用自己样式表中的定义覆盖导入文件中的定义。
for-each
循环(第 8 节)
遍历一系列项目并依次处理每个项目。
choose
(case 语句)用于条件处理(第 9.2 节)
根据输入值分支到多个处理路径之一。
生成数字(第 7.7 节)
动态生成编号的章节、编号元素和数字文字。XSLT 提供三种编号模式:
- Single:将项目编号在单个标题下,就像 HTML 中的有序列表
- Multiple:生成多级编号,如"A.1.3"
- Any:无论项目出现在何处,都连续编号,就像课程中的脚注。
格式化数字(第 12.3 节)
控制枚举格式,以便获得数字(format="1"
)、大写字母(format="A"
)、小写字母(format="a"
)或复合数字,如"A.1",以及适合特定国际区域设置的数字和货币金额。
排序输出(第 10 节)
按照所需的排序顺序生成输出。
基于模式的模板(第 5.7 节)
多次处理一个元素,每次在不同的“模式”中。您可以向模板添加一个模式属性,然后指定来仅应用具有匹配模式的模板。结合
属性,将基于模式的处理应用于输入数据的子集。
变量(第 11 节)
变量类似于方法参数,可以控制模板的行为。但它们并不像你想象的那样有价值。变量的值仅在定义它的当前模板或标签(例如)的范围内才可知。你无法将一个值从一个模板传递到另一个模板,甚至无法将一个模板的封闭部分的值传递到同一模板的另一部分。
即使是“全局”变量,这些说法也是正确的。你可以在模板中更改它的值,但更改仅适用于该模板。当用于定义全局变量的表达式被评估时,该评估发生在结构的根节点的上下文中。换句话说,全局变量本质上是运行时常量。这些常量可以用于改变模板的行为,特别是与包含和导入语句结合使用时。但变量不是通用的数据管理机制。
变量的问题
创建一个单一模板并为链接的目标设置一个变量,而不是费力地设置一个带参数的模板并以两种不同的方式调用它,这是一个诱人的想法。这个想法是将变量设置为默认值(比如,LINK
标签的文本),然后,如果目标属性存在,将目的地变量设置为目标属性的值。
如果这个方法行得通就好了。但问题在于变量只在定义它们的范围内才被知晓。因此,当你编写一个标签来改变变量的值时,该值只在
标签的上下文中被知晓。一旦遇到,对变量设置的任何更改都会丢失。
一个同样诱人的想法是用一个变量()来替换
text()|B|I|U|DEF|LINK规范。但由于变量的值取决于它的定义位置,全局内联变量的值由文本节点、
`节点等组成,这些节点恰好存在于根级别。换句话说,在这种情况下,这样一个变量的值是空的。