# 使用 XSLT 转换 XML 数据

> 原文： [https://docs.oracle.com/javase/tutorial/jaxp/xslt/transformingXML.html](https://docs.oracle.com/javase/tutorial/jaxp/xslt/transformingXML.html)

可扩展样式表语言转换（XSLT）API 可用于多种用途。例如，使用足够智能的样式表，您可以从 XML 数据生成 PDF 或 PostScript 输出。但通常，XSLT 用于生成格式化的 HTML 输出，或用于创建数据的替代 XML 表示。

在本节中，XSLT 转换用于将 XML 输入数据转换为 HTML 输出。

* * *

**注 -** [XSLT 规范](http://www.w3.org/TR/xslt20/)庞大而复杂，因此本教程只能划伤表面。它将为您提供一些背景知识，以便您可以理解简单的 XSLT 处理任务，但它没有详细研究如何编写 XSLT 转换，而是专注于如何使用 JAXP 的 XSLT 转换 API。有关 XSLT 的更全面基础，请参考一本好的参考手册，例如 Michael Kay 的 _XSLT 2.0 和 XPath 2.0：程序员参考 _（Wrox，2008）。

* * *

## 定义简单文档类型

首先定义一个非常简单的文档类型，可用于编写文章。我们的`文章`文档将包含以下结构标记：

*   `&lt; TITLE&gt;` ：文章的标题

*   `&lt; SECT&gt;` ：由标题和正文组成的部分

*   `&lt; PARA&gt;` ：一段

*   `&lt; LIST&gt;` ：一个清单

*   `&lt; ITEM&gt;` ：列表中的条目

*   `&lt;注意&gt;` ：除此之外，它偏离正文

这种结构稍微不同寻常的一点是我们不会为节标题创建单独的元素标记。通常创建这样的元素以将标题文本（及其包含的任何标签）与该部分的主体（即标题下方的任何结构元素）区分开。

相反，我们将允许标题无缝地合并到一个部分的主体中。这种安排为样式表增加了一些复杂性，但它将使我们有机会探索 XSLT 的模板选择机制。它还符合我们对文档结构的直观期望，其中标题的文本直接遵循结构元素，这种安排可以简化面向轮廓的编辑。

* * *

**注 -** 这种结构不容易验证，因为 XML 的混合内容模型允许文本中任何地方的文本，而我们想要限制文本和内联元素，使它们只出现在第一个结构元素之前部分的主体。基于断言的验证器可以做到这一点，但大多数其他架构机制都不能。因此，我们将不再为文档类型定义 DTD。

* * *

在此结构中，可以嵌套节。嵌套的深度将决定用于节标题的 HTML 格式（例如， `h1` 或 `h2` ）。使用普通的 `SECT` 标签（而不是编号的部分）对于面向轮廓的编辑也很有用，因为它可以让您随意移动部分，而不必担心更改任何受影响部分的编号。

对于列表，我们将使用 type 属性来指定列表条目是无序（项目符号），alpha（使用小写字母枚举），ALPHA（使用大写字母枚举）还是编号。

我们还将允许一些改变文本外观的内联标签。

*   `&lt; B&gt;` ：大胆

*   `&lt; I&gt;` ：斜体

*   `&lt; U&gt;` ：下划线

*   `&lt; DEF&gt;` ：定义

*   `&lt; LINK&gt;` ：链接到 URL

* * *

**注意 -** 内联标记不会生成换行符，因此内联标记引起的样式更改不会影响页面上的文本流（尽管它会影响该文本的外观）。另一方面，结构标记划分新的文本段，因此除了其他格式更改外，它总是至少生成换行符。

* * *

`&lt; DEF&gt;` 标签将用于文本中定义的术语。这些术语将以斜体显​​示，即它们通常在文档中的方式。但是在 XML 中使用特殊标记将允许索引程序找到这样的定义并将它们与标题中的关键字一起添加到索引中。例如，在前面的注释中，内联标签和结构标签的定义可以用`&lt; DEF&gt;标记。` 标签用于将来索引。

最后， `LINK` 标签有两个用途。首先，它将让我们创建一个 URL 的链接，而不必将 URL 放入两次;所以我们可以编码`&lt; link&gt; http // ...。&lt; / link&gt;` 代替`&lt; a href =“http //...”&gt; http // ...。&lt; / a&gt;` 。当然，我们还希望允许一个看起来像`&lt; link target =“...”&gt; ... name ...&lt; / link&gt;的表单。` 。这导致了`&lt; link&gt;的第二个原因。` 标签。它将为我们提供在 XSLT 中使用条件表达式的机会。

* * *

**注 -** 虽然文章结构非常简单（仅包含 11 个标签），但它提出了足够有趣的问题，让我们能够很好地了解 XSLT 的基本功能。但是我们仍然会保持规范的大部分区域不受影响。在 [XSLT 还能做什么？](#ggyut) ，我们将指出我们跳过的主要功能。

* * *

## 创建测试文档

在这里，您将使用嵌套的`&lt; SECT&gt;创建一个简单的测试文档。` 元素，少数&lt; PARA&gt;元素，`&lt;注意&gt;` 元素，`&lt; LINK&gt;` 和`&lt; LIST type =“unordered”&gt;` 。我们的想法是创建一个包含所有内容的文档，以便我们可以探索更有趣的翻译机制。

* * *

**注意 -** 本节讨论的代码在 `article1.xml` 中，解压后 [`XSLT examples`，在 `xslt / data` 目录中找到](../examples/xslt_samples.zip)进入 _install-dir_ `/ jaxp-1_4_2-` _ 发布日期 _ `/样本`目录。

* * *

要制作测试文档，请创建一个名为 `article.xml` 的文件，然后输入以下 XML 数据。

```
<?xml version="1.0"?>
<ARTICLE>
   <TITLE>A Sample Article</TITLE>
   <SECT>The First Major Section
      <PARA>This section will introduce a subsection.</PARA>
      <SECT>The Subsection Heading
         <PARA>This is the text of the subsection.
         </PARA>
      </SECT>
   </SECT>
</ARTICLE>

```

请注意，在 XML 文件中，子节完全包含在主要部分中。 （另一方面，在 HTML 中，标题不包含节的主体）。结果是一个更难以纯文本形式编辑的大纲结构，就像这样，但使用面向轮廓的编辑器更容易编辑。

有一天，给定一个面向树的 XML 编辑器，它能理解内联标签，例如`&lt; B&gt;。` 和`&lt; I&gt;` ，应该可以以大纲形式编辑此类文章，而无需复杂的样式表。 （这样的编辑器将允许作者专注于文章的结构，将布局留到过程的后期）。在这样的编辑器中，文章片段看起来像这样：

```
<ARTICLE> 
 <TITLE>A Sample Article 
  <SECT>The First Major Section 
   <PARA>This section will 
            introduce a subsection.
    <SECT>The Subheading 
     <PARA>This is the text of the subsection. 
         Note that ...

```

* * *

**注 -** 目前，存在树状结构编辑器，但它们处理内联标签，例如`&lt; B&gt;。` 和`&lt; I&gt;` 与处理结构标签的方式相同，这可能使“轮廓”有点难以阅读。

* * *

## 编写 XSLT 转换

现在是时候开始编写一个 XSLT 转换，它将转换 XML 文章并以 HTML 格式呈现它。

* * *

**注 -** 本节讨论的代码在 `article1a.xsl` 中，在解压缩 [`XSLT examples`后可在 `xslt / data` 目录中找到](../examples/xslt_samples.zip)进入 _install-dir_ `/ jaxp-1_4_2-` _ 发布日期 _ `/样本`目录。

* * *

首先创建一个普通的 XML 文档：

```
<?xml version="1.0" encoding="ISO-8859-1"?>

```

然后添加以下突出显示的行以创建 XSL 样式表：

```
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet 
 xmlns:xsl=
    "http://www.w3.org/1999/XSL/Transform" 
 version="1.0"
 >

</xsl:stylesheet>

```

现在将其设置为生成与 HTML 兼容的输出。

```
<xsl:stylesheet 
[...]

   >
<xsl:output method="html"/>

[...]

</xsl:stylesheet>

```

我们将在本节后面详细介绍该条目。现在，请注意，如果要输出格式良好的 XML 以外的任何内容，则需要`&lt; xsl：output&gt;` 标签如图所示，指定`文本`或 `html` 。 （默认值为 `xml` ）。

* * *

**注 -** 指定 XML 输出时，可以添加 indent 属性以生成良好缩进的 XML 输出。规范如下所示：`&lt; xsl：output method =“xml”indent =“yes”/&gt;` 。

* * *

## 处理基本结构元素

您将通过处理创建目录的元素开始填充样式表：根元素，标题元素和标题。您还将处理测试文档中定义的 `PARA` 元素。

* * *

**注 -** 如果您在第一次阅读时跳过了讨论 XPath 寻址机制的部分， [XPath 如何工作](xpath.html)，现在是回顾和审查该部分的好时机。

* * *

首先添加处理根元素的主要指令：

```
 <xsl:template match="/">
      <html><body>
         <xsl:apply-templates/>
      </body></html>
   </xsl:template>

</xsl:stylesheet>

```

新的 XSL 命令以粗体显示。 （请注意，它们在 `xsl` 命名空间中定义）。指令`&lt; xsl：apply-templates&gt;` 处理当前节点的子节点。在这种情况下，当前节点是根节点。

尽管它很简单，但这个例子说明了许多重要的想法，因此值得深入了解。第一个概念是样式表包含许多模板，使用`&lt; xsl：template&gt;定义。` 标签。每个模板都包含一个匹配属性，该属性使用 [XPath Works](xpath.html) 中描述的 XPath 寻址机制来选择模板将应用于的元素。

在模板中，只复制不以 `xsl：namespace` 前缀开头的标记。它们之后的换行符和空格也被复制，这有助于使得结果输出可读。

* * *

**注 -** 当不存在换行符时，通常会忽略空格。要在这种情况下在输出中包含空格，或者要包含其他文本，可以使用`&lt; xsl：text&gt;` 标签。基本上，XSLT 样式表需要处理标记。所以它看到的一切都需要是`&lt; xsl：..&gt;` 标签，其他一些标签或空格。

* * *

在这种情况下，非 XSL 标记是 HTML 标记。因此，当根标记匹配时，XSLT 输出 HTML 开始标记，处理适用于根的子项的任何模板，然后输出 HTML 结束标记。

## 处理`&lt; TITLE&gt;` 元素

接下来，添加模板以处理文章标题：

```
 <xsl:template match="/ARTICLE/TITLE">
 <h1 align="center"> 
    <xsl:apply-templates/> </h1>
 </xsl:template>

</xsl:stylesheet>

```

在这种情况下，您指定 TITLE 元素的完整路径并输出一些 HTML 以使标题文本成为一个大的居中标题。在这种情况下， `apply-templates` 标签确保如果标题包含任何内联标记（如斜体，链接或下划线），它们也将被处理。

更重要的是， `apply-templates` 指令会导致标题文本被处理。与 DOM 数据模型一样，XSLT 数据模型基于元素节点中包含的文本节点的概念（反过来，它可以包含在其他元素节点中，依此类推）。该分层结构构成源树。还有一个结果树，其中包含输出。

XSLT 的工作原理是将源树转换为结果树。为了可视化 XSLT 操作的结果，了解这些树的结构及其内容是有帮助的。 （有关此主题的更多信息，请参阅 [XSLT / XPath 数据模型](xpath.html#gchlm)）。

## 流程标题

要继续处理基本结构元素，请添加模板以处理顶级标题：

```
 <xsl:template match=
    "/ARTICLE/SECT">
 <h2> <xsl:apply-templates
 select="text()|B|I|U|DEF|LINK"/> 
</h2>
 <xsl:apply-templates select=
    "SECT|PARA|LIST|NOTE"/>
 </xsl:template>

</xsl:stylesheet>

```

在这里，指定最顶层 `SECT` 元素的路径。但这一次，您使用 `select` 属性分两个阶段应用模板。对于第一阶段，使用 XPath `text（）`函数选择文本节点以及粗体和斜体等内联标记。 （垂直管道（ `|` ）用于匹配多个项目：文本或粗体标记或斜体标记等）。在第二阶段，您可以选择文件中包含的其他结构元素，包括部分，段落，列表和注释。

使用 select 属性可以将文本和内联元素放在`&lt; h2&gt; ...&lt; / h2&gt;之间。` 标签，同时确保之后处理该部分中的所有结构标签。换句话说，您确保 XML 文档中标题的嵌套不会反映在 HTML 格式中，这对 HTML 输出很重要。

通常，使用 select 子句可以将所有模板应用于当前上下文中可用信息的子集。另一个例子，该模板选择当前节点的所有属性：

```
<xsl:apply-templates select="@*"/></attributes>

```

接下来，添加几乎相同的模板来处理嵌套在一层更深层次的子标题：

```
 <xsl:template match=
    "/ARTICLE/SECT/SECT">
 <h3> <xsl:apply-templates
 select="text()|B|I|U|DEF|LINK"/> 
</h3>
 <xsl:apply-templates select=
    "SECT|PARA|LIST|NOTE"/>
 </xsl:template>

</xsl:stylesheet>

```

## 生成运行时消息

您也可以添加更深层标题的模板，但是在某些时候您必须停止，只是因为 HTML 只降低到五个级别。对于此示例，您将停在两个级别的章节标题。但是，如果 XML 输入恰好包含第三级，您将需要向用户传递错误消息。本节介绍如何执行此操作。

* * *

**注 -** 我们可以通过用表达式 `/ SECT / SECT // SECT` 选择它们来继续处理进一步向下的 `SECT` 元素。 `//` 选择任何深度的任何 `SECT` 元素，如 XPath 寻址机制所定义。但相反，我们将借此机会发布消息。

* * *

添加以下模板以在遇到嵌套太深的部分时生成错误：

```
 <xsl:template match=
    "/ARTICLE/SECT/SECT/SECT">
 <xsl:message terminate="yes">
 Error: Sections can only be nested 2 deep.
 </xsl:message>
 </xsl:template>

</xsl:stylesheet>

```

`terminate =“yes”`子句导致转换过程在生成消息后停止。没有它，处理仍然可以继续，该部分中的所有内容都将被忽略。

作为附加练习，您可以扩展样式表以处理最多嵌套四个部分的部分，生成`&lt; h2&gt; ...&lt; h5&gt;` 标签。在嵌套五层深的任何部分生成错误。

最后，通过添加模板来处理 `PARA` 标记来完成样式表：

```
 <xsl:template match="PARA">
 <p><xsl:apply-templates/></p>
 </xsl:template>
</xsl:stylesheet>

```

## 编写基本程序

现在，您将修改使用 XSLT 的程序，以不更改地回显 XML 文件，更改它以使其使用样式表。

* * *

**注意 -** 本节讨论的代码在 `Stylizer.java` 中，解压 [`XSLT examples`](../examples/xslt_samples.zip) 后可在 `xslt` 目录中找到]进入 _install-dir_ `/ jaxp-1_4_2-` _ 发布日期 _ `/样本`目录。结果是 `stylizer1a.html` ，见于 `xslt / data` 。

* * *

`Stylizer` 示例改编自 `TransformationApp02` ，它解析 XML 文件并写入 `System.out` 。两个程序之间的主要区别如下所述。

首先， `Stylizer` 在创建 `Transformer` 对象时使用样式表。

```
// ...
import javax.xml.transform.dom.DOMSource; 
import javax.xml.transform.stream.StreamSource; 
import javax.xml.transform.stream.StreamResult; 
// ... 

public class Stylizer {
    // ...
    public static void main (String argv[]) {
        // ...
        try {
            File stylesheet = new File(argv[0]);
            File datafile = new File(argv[1]);

            DocumentBuilder builder = factory.newDocumentBuilder();
            document = builder.parse(datafile);
            // ...
            StreamSource stylesource = new StreamSource(stylesheet); 
            Transformer transformer = Factory.newTransformer(stylesource);
        }
    }
}

```

此代码使用该文件创建 `StreamSource` 对象，然后将源对象传递给工厂类以获取转换器。

* * *

**注 -** 你可以通过消除 `DOMSource` 类来简化代码。而不是为 XML 文件创建 `DOMSource` 对象，为它创建 `StreamSource` 对象，以及样式表。

* * *

### 运行 `Stylizer` Sample

1.  **导航至`样本`目录。**

    ```
    ％cd  install-dir   / jaxp-1_4_2-  释放日期  /样品。 
    ```

2.  **[`Download the XSLT examples by clicking this link`](../examples/xslt_samples.zip) 并将它们解压缩到 _install-dir_ `/ jaxp-1_4_2-` _ 释放日期 _ `/样品`目录。**
3.  **导航到 `xslt` 目录。**

    ```
    cd xslt

    ```

4.  **Compile the `Stylizer` sample.**

    键入以下命令：

    ```
    % javac Stylizer.java

    ```

5.  **Run the `Stylizer` sample on `article1.xml` using the stylesheet `article1a.xsl`.**

    ```
    % java Stylizer data/article1a.xsl  data/article1.xml

    ```

    您将看到以下输出：

    ```
    &lt;html&gt;
    &lt;body&gt;

    &lt;h1 align="center"&gt;A Sample Article&lt;/h1&gt;
    &lt;h2&gt;The First Major Section

    &lt;/h2&gt;
    &lt;p&gt;This section will introduce a subsection.&lt;/p&gt;
    &lt;h3&gt;The Subsection Heading

    &lt;/h3&gt;
    &lt;p&gt;This is the text of the subsection.
    &lt;/p&gt;

    &lt;/body&gt;
    &lt;/html&gt;

    ```

    此时，输出中存在相当多的空白。在下一节中，您将了解如何消除大部分内容。

## 修剪空白

回想一下，当你看到 DOM 的结构时，有许多文本节点只包含可忽略的空格。输出中的大多数空白都来自这些节点。幸运的是，XSL 为您提供了消除它们的方法。 （有关节点结构的更多信息，请参见 [XSLT / XPath 数据模型](xpath.html#gchlm)）。

* * *

**注 -** 本节讨论的样式表在 `article1b.xsl` 中，在解压缩 [`XSLT examples`后可在 `xslt / data` 目录中找到](../examples/xslt_samples.zip)进入 _install-dir_ `/ jaxp-1_4_2-` _ 发布日期 _ `/样本`目录。结果是 `stylizer1b.html` ，见于 `xslt / data` 。

* * *

要删除一些多余的空格，请将以下突出显示的行添加到样式表中。

```
<xsl:stylesheet ...
   >
<xsl:output method="html"/> 
<xsl:strip-space elements="SECT"/>

[...]

```

该指令告诉 XSL 删除 `SECT` 元素下只包含空格的任何文本节点。包含除空格之外的文本的节点不会受到影响，其他类型的节点也不会受到影响。

### 使用 Trimmed Whitespace 运行 `Stylizer` Sample

1.  **导航至`样本`目录。**

    ```
    ％cd  install-dir   / jaxp-1_4_2-  释放日期  /样品。 
    ```

2.  **[`Download the XSLT examples by clicking this link`](../examples/xslt_samples.zip) 并将它们解压缩到 _install-dir_ `/ jaxp-1_4_2-` _ 释放日期 _ `/样品`目录。**
3.  **导航到 `xslt` 目录。**

    ```
    cd xslt

    ```

4.  **Compile the `Stylizer` sample.**

    键入以下命令：

    ```
    % javac Stylizer.java

    ```

5.  **Run the `Stylizer` sample on `article1.xml` using the stylesheet `article1b.xsl`.**

    ```
    % java Stylizer 
      data/article1b.xsl  
      data/article1.xml

    ```

    您将看到以下输出：

    ```
    &lt;html&gt;
    &lt;body&gt;

    &lt;h1 align="center"&gt;A Sample Article&lt;/h1&gt;

    &lt;h2&gt;The First Major Section
       &lt;/h2&gt;
    &lt;p&gt;This section will introduce a subsection.&lt;/p&gt;
    &lt;h3&gt;The Subsection Heading
          &lt;/h3&gt;
    &lt;p&gt;This is the text of the subsection.
          &lt;/p&gt;

    &lt;/body&gt;
    &lt;/html&gt;

    ```

    这是一个很大的改进。标题后面仍然有换行符和空格，但这些都来自 XML 的编写方式：

    ```
    &lt;SECT&gt;The First Major Section
    ____&lt;PARA&gt;This section will introduce a subsection.&lt;/PARA&gt;
    ^^^^

    ```

    在这里，您可以看到在 PARA 条目开始之前，节标题以换行符和缩进空格结尾。这不是一个很大的担心，因为处理 HTML 压缩的浏览器会定期忽略多余的空间。但是我们还有一种格式化工具可供使用。

## 删除最后一个空格

* * *

**注 -** 本节讨论的样式表在 `article1c.xsl` 中，在解压缩 [`XSLT examples`后可在 `xslt / data` 目录中找到](../examples/xslt_samples.zip)进入 _install-dir_ `/ jaxp-1_4_2-` _ 发布日期 _ `/样本`目录。结果是 `stylizer1c.html` ，见于 `xslt / data` 。

* * *

通过在样式表中添加以下内容来处理最后一点空格：

```
   <xsl:template match="text()">
 <xsl:value-of select="normalize-space()"/>
 </xsl:template>

</xsl:stylesheet>

```

使用此样式表运行 `Stylizer` 将删除所有剩余的空格。

### 使用 All Whitespace Trimmed 运行 `Stylizer` Sample

1.  **导航至`样本`目录。**

    ```
    ％cd  install-dir   / jaxp-1_4_2-  释放日期  /样品。 
    ```

2.  **[`Download the XSLT examples by clicking this link`](../examples/xslt_samples.zip) 并将它们解压缩到 _install-dir_ `/ jaxp-1_4_2-` _ 释放日期 _ `/样品`目录。**
3.  **导航到 `xslt` 目录。**

    ```
    cd xslt

    ```

4.  **Compile the `Stylizer` sample.**

    键入以下命令：

    ```
    % javac Stylizer.java

    ```

5.  **Run the `Stylizer` sample on `article1.xml` using the stylesheet `article1c.xsl`.**

    ```
    % java Stylizer 
      data/article1c.xsl  
      data/article1.xml

    ```

    输出现在看起来像这样：

    ```
    &lt;html&gt;
    &lt;body&gt;
    &lt;h1 align="center"&gt;A Sample Article
    &lt;/h1&gt;
    &lt;h2&gt;The First Major Section&lt;/h2&gt;
    &lt;p&gt;This section will introduce a subsection.
    &lt;/p&gt;
    &lt;h3&gt;The Subsection Heading&lt;/h3&gt;
    &lt;p&gt;This is the text of the subsection.
    &lt;/p&gt;
    &lt;/body&gt;
    &lt;/html&gt;

    ```

    那要好一点。当然，如果它缩进会更好，但事实证明它比预期的要困难一些。以下是一些可能的攻击途径以及困难：

    Indent option

    不幸的是，可以应用于 XML 输出的 `indent =“yes”`选项不适用于 HTML 输出。即使该选项可用，也无济于事，因为 HTML 元素很少嵌套！尽管 HTML 源经常缩进以显示隐含的结构，但 HTML 标记本身并不以创建真实结构的方式嵌套。

    Indent variables

    `&lt; xsl：text&gt;` 功能允许您添加所需的任何文本，包括空格。因此可以想象它可以用于输出缩进空间。问题是改变缩进空间的数量。 XSLT 变量似乎是一个好主意，但它们在这里不起作用。原因是当您为模板中的变量赋值时，该值仅在该模板中是已知的（静态地，在编译时）。即使变量是全局定义的，分配的值也不会以允许其他模板在运行时动态识别的方式存储。当`&lt; apply-templates /&gt;` 调用其他模板，这些模板不知道在其他地方进行的任何变量设置。

    Parameterized templates

    使用参数化模板是修改模板行为的另一种方法。但确定要传递的缩进空间量仍然是问题的症结所在。

    那么，目前似乎没有任何好方法来控制 HTML 格式输出的缩进。如果您需要以纯文本格式显示或编辑 HTML，那将会很不方便。但是，如果您在 XML 表单上进行编辑，使用 HTML 版本仅在浏览器中显示，则不会出现问题。 （例如，当您查看 `stylizer1c.html` 时，您会看到预期的结果）。

## 处理剩余的结构元素

在本节中，您将处理 `LIST` 和 `NOTE` 元素，这些元素为文章添加了更多结构。

* * *

**注 -** 本节中描述的示例文档是 `article2.xml` ，用于操作它的样式表是 `article2.xsl` 。结果是 `stylizer2.html` 。将 [`XSLT examples`](../examples/xslt_samples.zip) 解压缩到 _install-dir_ `/ jaxp-1_4_2-` 后，可以在 `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 文件中的`列表`和`注意`包含在它们各自的段落中，但它们是否包含在内并没有区别;生成的 HTML 将以相同的方式相同。但是将它们包含在内会使它们更容易在面向轮廓的编辑器中处理。

* * *

## 修改`&lt; PARA&gt;` 处理

接下来，修改 `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` 元素。 （但是，一个段落很容易存在于另一个段落中 - 例如，作为引用的材料）。

## 过程`&lt; LIST&gt;` 和`&lt; ITEM&gt;` 元素

现在您已准备好添加模板来处理 `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>

```

`&lt; xsl：if&gt;` 标签使用 `test =“”`属性指定布尔条件。在这种情况下，将测试 type 属性的值，并且生成的列表会根据值是有序还是无序而更改。

请注意此示例中的两个重要事项：

*   没有 else 子句，也没有返回或退出语句，因此需要两个`&lt; xsl：if&gt;` 标签涵盖了两个选项。 （或者可以使用`&lt; xsl：choose&gt;` 标签，它提供了 case 语句功能）。

*   属性值周围需要单引号。否则，XSLT 处理器会尝试将有序字解释为 XPath 函数而不是字符串。

现在通过处理 ITEM 元素完成 LIST 处理：

```
 <xsl:template match="ITEM">
 <li><xsl:apply-templates/>
 </li>
 </xsl:template>

</xsl:stylesheet>

```

## 样式表中的订购模板

到目前为止，您应该知道模板彼此独立，因此它们在文件中出现的位置通常并不重要。因此，从现在开始，我们将仅显示您需要添加的模板。 （为了便于比较，它们总是添加在示例样式表的末尾）。

当两个模板可以应用于同一节点时，顺序确实有所不同。在这种情况下，最后定义的那个是找到并处理的那个。例如，要将缩进列表的顺序更改为使用小写字母，可以指定如下所示的模板模式： `// LIST // LIST` 。在该模板中，您将使用 HTML 选项生成字母枚举，而不是数字枚举。

但是这样的元素也可以通过模式 `// LIST` 来识别。为了确保正确处理，指定 `// LIST` 的模板必须出现在指定 `// LIST // LIST` 的模板之前。

## 过程`&lt;注意&gt;` 元素

最后剩下的结构元素是 `NOTE` 元素。添加以下模板来处理它。

```
 <xsl:template match="NOTE">
 <blockquote><b>Note:</b><br/>
 <xsl:apply-templates/>
 </p></blockquote>
 </xsl:template>

</xsl:stylesheet>

```

该代码提出了一个有趣的问题，该问题是由于包含`&lt; br /&gt;而产生的。` 标签。要使文件格式良好的 XML，必须在样式表中将标记指定为`&lt; br /&gt;` ，但很多浏览器都无法识别该标签。虽然大多数浏览器都能识别序列`&lt; br&gt;&lt; / br&gt;` ，他们都把它视为一个段落而不是单个换行符。

换句话说，转换必须产生`&lt; br&gt;。` 标签，但样式表必须指定`&lt; br /&gt;` 。这就是我们在样式表的早期添加的特殊输出标记的主要原因：

```
<xsl:stylesheet ... >
   <xsl:output method="html"/>
   [...]
</xsl:stylesheet>

```

该输出规范转换空标签，例如`&lt; br /&gt;。` 到他们的 HTML 表格，`&lt; br&gt;` ，关于输出。这种转换非常重要，因为大多数浏览器都无法识别空标签。以下是受影响标签的列表：

```
area      frame   isindex
base      hr      link
basefont  img     meta
br        input   param
col

```

总而言之，默认情况下，XSLT 在输出上生成格式良好的 XML。并且因为 XSL 样式表是格式良好的 XML，所以你不能轻易地放置诸如`&lt; br&gt;之类的标签。` 在它的中间。 `&lt; xsl：output method =“html”/&gt;` 标签解决了这个问题，因此您可以编码`&lt; br /&gt;样式表中的`但得到`&lt; br&gt;` 在输出中。

指定`&lt; xsl：output method =“html”/&gt;的另一个主要原因`就像规范`&lt; xsl：output method =“text”/&gt;一样`，生成的文本未被转义。例如，如果样式表包括`&lt;` 实体参考，它将显示为`&lt;生成的文本中的`字符。另一方面，当生成 XML 时，`&lt;样式表中的`实体引用将保持不变，因此它将显示为`&lt;生成的文本中的`。

* * *

**注 -** 如果你真的想要`&lt;` 作为 HTML 输出的一部分生成，您需要将其编码为`&amp; lt;` 。该序列变为`&lt;` 关于输出，因为只有`&amp;` 转换为`&amp;` 字符。

* * *

### 使用 `LIST` 和`注`元素定义运行 `Stylizer` 样本

1.  **导航至`样本`目录。**

    ```
    ％cd  install-dir   / jaxp-1_4_2-  释放日期  /样品。 
    ```

2.  **[`Download the XSLT examples by clicking this link`](../examples/xslt_samples.zip) 并将它们解压缩到 _install-dir_ `/ jaxp-1_4_2-` _ 释放日期 _ `/样品`目录。**
3.  **导航到 `xslt` 目录。**

    ```
    cd xslt

    ```

4.  **Compile the `Stylizer` sample.**

    键入以下命令：

    ```
    % javac Stylizer.java

    ```

5.  **Run the `Stylizer` sample on `article2.xml` using the stylesheet `article2.xsl`.**

    ```
    % java Stylizer data/article2.xsl  data/article2.xml

    ```

    以下是您现在运行程序时为第二部分生成的 HTML：

    ```
    ...
    &lt;h2&gt;The Second Major Section
    &lt;/h2&gt;
    &lt;p&gt;This section adds a LIST and a NOTE.
    &lt;/p&gt;
    &lt;p&gt;Here is the LIST:
    &lt;/p&gt;
    &lt;ol&gt;
    &lt;li&gt;Pears&lt;/li&gt;
    &lt;li&gt;Grapes&lt;/li&gt;
    &lt;/ol&gt;
    &lt;p&gt;And here is the NOTE:
    &lt;/p&gt;
    &lt;blockquote&gt;
    &lt;b&gt;Note:&lt;/b&gt;
    &lt;br&gt;Do not forget to go to the hardware store on your way to the grocery!
    &lt;/blockquote&gt;

    ```

## 处理内联（内容）元素

`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>

```

现在处理内联`&lt; DEF&gt;段落中的`元素，将它们重命名为 HTML 斜体标记：

```
<xsl:template match="DEF">
 <i> <xsl:apply-templates/> </i> 
</xsl:template>

```

接下来，注释掉文本节点规范化。它已经达到了它的目的，现在你需要保留重要的空间：

```
<!--  
<xsl:template match="text()">
  <xsl:value-of select="normalize-space()"/>
   </xsl:template>
-->

```

这种修改使我们在诸如`&lt; I&gt;之类的标签之前不会丢失空格。` 和`&lt; DEF&gt;` 。 （尝试没有此修改的程序以查看结果）。

现在处理基本的内联 HTML 元素，例如`&lt; B&gt;` ，`&lt; I&gt;` 和`&lt; U&gt;` 用于粗体，斜体和下划线。

```
<xsl:template match="B|I|U">
 <xsl:element name="{name()}">
 <xsl:apply-templates/>
 </xsl:element> 
</xsl:template>

```

`&lt; xsl：element&gt;` 标签可让您计算要生成的元素。在这里，您使用当前元素的名称生成适当的内联标记。特别要注意在 `name =“..”`表达式中使用花括号（ `{}` ）。这些花括号使得引号内的文本被处理为 XPath 表达式，而不是被解释为文字字符串。这里，它们使 XPath `name（）`函数返回当前节点的名称。

可以在属性值模板可以出现的任何位置识别大括号。 （属性值模板在 XSLT 规范的 7.6.2 节中定义，它们出现在模板定义中的几个位置）。在这样的表达式中，花括号也可以用来指代属性的值 `{@foo}` ，或者指向元素 `{foo}` 的内容。

* * *

**注 -** 您还可以使用`&lt; xsl：attribute&gt;生成属性。` 。有关更多信息，请参阅 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 =“”`子句为模板命名，而不是指定匹配子句。因此，只有在您调用此模板时才会执行此模板。

在模板中，您还可以使用`&lt; xsl：param&gt;指定名为 `dest` 的参数。` 标签。对于一些错误检查，使用 select 子句为该参数提供默认值 `UNDEFINED` 。引用`&lt; xsl：value-of&gt;中的变量`标签，指定。

* * *

**注 -** 回想一下引号中的条目被解释为表达式，除非它进一步用单引号括起来。这就是为什么在`“@ type ='ordered'”`中需要单引号以确保有序被解释为字符串。

* * *

`&lt; xsl：element&gt;` 标签生成一个元素。以前，您可以通过编码类似`&lt; html&gt;的内容来简单地指定我们想要的元素。` 。但是在这里，您在`&lt; xsl：element&gt;的主体中动态生成 HTML 锚点（`&lt; a&gt;` ）的内容。` 标签。并且您使用`&lt; xsl：attribute&gt;动态生成锚点的 `href` 属性。` 标签。

模板的最后一个重要部分是`&lt; apply-templates&gt;` 标签，用于插入 `LINK` 元素下文本节点的文本。没有它，生成的 HTML 链接中将没有文本。

接下来，添加 `LINK` 标签的模板，并从中调用命名模板：

```
<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">

[...]

```

如果 LINK 标记中存在目标属性，则 `test =“@ target”`子句返回 true。所以这`&lt; xsl-if&gt;当链接的文本和为其定义的目标不同时，`标记会生成 HTML 链接。

`&lt; xsl：call-template&gt;` 标签调用命名模板，而`&lt; xsl：with-param&gt;` 使用 name 子句指定参数，并使用 select 子句指定其值。

作为样式表构建过程的最后一步，添加`&lt; xsl-if&gt;` 标签处理没有目标属性的 `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 子句）。因此，当未指定 target 属性时，将解释模板的这一部分。这次，参数值不是来自 select 子句，而是来自`&lt; xsl：with-param&gt;的内容。` 元素。

* * *

**注 -** 只是为了明确：参数和变量（在[中稍后讨论过 XSLT 可以做什么？](#ggyut)可以通过 select 子句指定它们的值，允许您使用 XPath 表达式，或者使用元素的内容，这样可以使用 XSLT 标记。

* * *

在这种情况下，参数的内容由`&lt; xsl：apply-templates /&gt;生成。` 标签，用于在 `LINK` 元素下插入文本节点的内容。

### 运行带有内联元素的`样式器`样本

1.  **导航至`样本`目录。**

    ```
    ％cd  install-dir   / jaxp-1_4_2-  释放日期  /样品。 
    ```

2.  **[`Download the XSLT examples by clicking this link`](../examples/xslt_samples.zip) 并将它们解压缩到 _install-dir_ `/ jaxp-1_4_2-` _ 释放日期 _ `/样品`目录。**
3.  **导航到 `xslt` 目录。** &lt;

    ```
    cd xslt

    ```

4.  **Compile the `Stylizer` sample.**

    键入以下命令：

    ```
    % javac Stylizer.java

    ```

5.  **Run the `Stylizer` sample on `article3.xml` using the stylesheet `article3.xsl`.**

    ```
    % java Stylizer data/article3.xsl  data/article3.xml

    ```

    现在运行程序时，结果应如下所示：

    ```
    [...]
    &lt;h2&gt;The &lt;i&gt;Third&lt;/i&gt; Major Section&lt;/h2&gt;
    &lt;p&gt;In addition to the inline tag in the heading, this section  defines the term &lt;i&gt;inline&lt;/i&gt;, which literally means "no line break". It also adds a simple link to the main page for the Java platform (&lt;a href="http://java.sun.com"&gt;http://java.sun.com&lt;/a&gt;),  as well as a link to the &lt;a href="http://java.sun.com/xml"&gt;XML&lt;/a&gt; page.&lt;/p&gt;

    ```

    干得好！您现在已经将一个相当复杂的 XML 文件转换为 HTML。 （就像它最初看起来一样简单，它确实为探索提供了很多机会）。

## 打印 HTML

您现在已将 XML 文件转换为 HTML。有一天，有人会生成一个 HTML 识别的打印引擎，您可以通过 Java Printing Service API 找到并使用它。此时，您将能够通过生成 HTML 来打印任意 XML 文件。您所要做的就是设置样式表并使用浏览器。

## XSLT 还能做什么？

与本节一样冗长，它只是触及了 XSLT 功能的表面。 XSLT 规范中有许多其他可能性等着您。以下是一些需要注意的事项：

`import` (Section 2.6.2) and `include` (section 2.6.1)

`rt` （第 2.6.2 节）和 include（第 2.6.1 节）使用这些语句来模块化和组合 XSLT 样式表。 include 语句只是插入包含文件中的任何定义。 import 语句允许您使用自己的样式表中的定义覆盖导入文件中的定义。

`for-each` loops (section 8)

循环遍历项目集合并依次处理每个项目。

`choose` (case statement) for conditional processing (section 9.2)

根据输入值分支到多个处理路径之一。

Generating numbers (section 7.7)

动态生成编号的部分，编号的元素和数字文字。 XSLT 提供三种编号模式：

*   单个：单个标题下的数字项，如 HTML 中的有序列表

*   多个：生成多级编号，例如“A.1.3”

*   任何：连续编号项目出现在任何地方，如课程中的脚注。

Formatting numbers (section 12.3)

控制枚举格式，以便您获得数字（ `format =“1”`），大写字母（ `format =“A”`），小写字母（ `format =“a”`）或复合数字，如“A.1”，以及适合特定国际区域的数字和货币金额。

Sorting output (section 10)

以所需的排序顺序生成输出。

Mode-based templates (section 5.7)

多次处理元素，每次都处于不同的“模式”。您将模式属性添加到模板，然后指定`&lt; apply-templates mode =“...”&gt;` 仅应用具有匹配模式的模板。结合`&lt; apply-templates select =“...”&gt;` 属性将基于模式的处理应用于输入数据的子集。

Variables (section 11)

变量类似于方法参数，因为它们可以让您控制模板的行为。但它们没有您想象的那么有价值。变量的值仅在当前模板的范围内已知或`&lt; xsl：if&gt;` 标签（例如）中定义的标签。您不能将值从一个模板传递到另一个模板，甚至不能从模板的封闭部分传递到同一模板的另一部分。

即使对于“全局”变量，这些陈述也是正确的。您可以在模板中更改其值，但更改仅适用于该模板。当评估用于定义全局变量的表达式时，该评估将在结构的根节点的上下文中进行。换句话说，全局变量本质上是运行时常量。这些常量可用于更改模板的行为，尤其是在与 include 和 import 语句结合使用时。但变量不是通用的数据管理机制。

## 变量的麻烦

创建单个模板并为链接的目标设置变量很有诱惑力，而不是设置参数化模板并以两种不同的方式调用它。我们的想法是将变量设置为默认值（例如， `LINK` 标签的文本），然后，如果目标属性存在，则将目标变量设置为目标属性的值。

这将是一个好主意 - 如果它奏效。但同样，问题是变量只在定义它们的范围内才知道。所以当你编码`&lt; xsl：if&gt;` 标签用于更改变量的值，该值仅在`&lt; xsl：if&gt;的上下文中已知。` 标签。一旦`&lt; / xsl：if&gt;遇到`，变量设置的任何更改都将丢失。

同样诱人的想法是用变量（）替换 `text（）| B | I | U | DEF | LINK` 规范的可能性。但是因为变量的值由定义的位置确定，所以全局内联变量的值由文本节点，`&lt; B&gt;组成。` 节点，等等，恰好存在于根级别。换句话说，在这种情况下，这种变量的值为空。