文档

Java™教程
隐藏目录
使用XSLT转换XML数据
路径: Java XML处理API (JAXP)
课程: 可扩展样式表语言转换

使用XSLT转换XML数据

可扩展样式表语言转换(XSLT)API可用于许多目的。例如,使用一个足够智能的样式表,您可以从XML数据生成PDF或PostScript输出。但一般来说,XSLT用于生成格式化的HTML输出,或者创建数据的替代XML表示。

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


注意 - XSLT规范非常庞大而复杂,所以本教程只能涉及表面。它将为您提供一些背景,以便您可以理解简单的XSLT处理任务,但不会详细讨论如何编写XSLT转换,而是专注于如何使用JAXP的XSLT转换API。要深入了解XSLT,请查阅一本好的参考手册,例如Michael Kay的《XSLT 2.0和XPath 2.0:程序员参考》(Wrox,2008年)。


定义简单的文档类型

首先定义一个非常简单的文档类型,用于编写文章。我们的article文档将包含以下结构标签:

这种结构的稍微不寻常之处在于,我们不会为部分标题创建一个单独的元素标签。通常会创建这样的元素来区分标题文本(以及它包含的任何标签)与部分正文(也就是标题下面的任何结构元素)。

相反,我们将允许标题无缝地合并到部分正文中。这种安排增加了样式表的一些复杂性,但它将给我们一个机会去探索XSLT的模板选择机制。它也符合我们对文档结构的直观期望,其中标题的文本直接跟随着结构元素,这种安排可以简化面向大纲的编辑。


注意 - 这种类型的结构不容易进行验证,因为XML的混合内容模型允许在部分中的任何位置包含文本,而我们希望将文本和内联元素限制在仅出现在部分正文中的第一个结构元素之前。基于断言的验证器可以做到,但大多数其他模式机制不能。因此,我们将不定义文档类型的DTD。


在这种结构中,部分可以嵌套。嵌套的深度将决定用于部分标题的HTML格式(例如,h1h2)。使用纯粹的SECT标签(而不是编号的部分)也对面向大纲的编辑很有用,因为它允许您随意移动部分,而不必担心更改任何受影响部分的编号。

对于列表,我们将使用type属性来指定列表条目是无序的(用符号标记),还是按字母顺序编号(小写字母),按字母顺序编号(大写字母)或按数字编号。

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


注意 - 内联标签不会生成换行符,因此内联标签引起的样式更改不会影响页面上的文本流动(尽管它会影响该文本的外观)。另一方面,结构标签标示了一个新的文本段落,因此至少会生成一个换行符,除了其他的格式更改。


<DEF>标签将用于文本中定义的术语。这些术语将以斜体显示,就像在文档中通常的方式一样。但是使用XML中的特殊标签将允许索引程序找到这些定义,并将它们与标题中的关键词一起添加到索引中。例如,在前面的注意事项中,内联标签和结构标签的定义可以用<DEF>标签标记为将来的索引。

最后,LINK标签有两个用途。首先,它可以让我们创建一个链接到URL的链接,而无需将URL输入两次;因此,我们可以编码<link>http//...</link>而不是<a href="http//...">http//...</a>。当然,我们还希望允许一个类似<link target="...">...name...</link>的表单。这就引出了<link>标签的第二个原因。它将给我们一个机会来使用XSLT中的条件表达式。


注意 - 尽管这篇文章的结构非常简单(只有十一个标签),但它引发了足够有趣的问题,让我们对XSLT的基本功能有一个良好的了解。但是我们仍然会留下大部分规范未涉及的领域。在XSLT还能做什么?中,我们将指出我们跳过的主要功能。


创建一个测试文档

在这里,您将使用嵌套的<SECT>元素,几个<PARA>元素,一个<NOTE>元素,一个<LINK>元素和一个<LIST type="unordered">元素创建一个简单的测试文档。我们的目标是创建一个包含各种元素的文档,以便我们可以探索更有趣的转换机制。


注意 - 在本节中讨论的代码位于article1.xml中,在解压缩XSLT示例后,在install-dir/jaxp-1_4_2-release-date/samples目录中找到。


为了创建测试文档,请创建一个名为article.xml的文件,并输入以下XML数据。

<?xml version="1.0"?>
<ARTICLE>
   <TITLE>一个示例文章</TITLE>
   <SECT>第一大节
      <PARA>本节将介绍一个子节。</PARA>
      <SECT>子节标题
         <PARA>这是子节的文本。
         </PARA>
      </SECT>
   </SECT>
</ARTICLE>

请注意,在XML文件中,子节完全包含在主节内部。(在HTML中,标题不包含节的主体)。结果是一个更难以以纯文本形式编辑的大纲结构,但是在面向大纲的编辑器中更容易编辑。

将来,如果有一款理解内联标签(如<B><I>)的树形XML编辑器,就可以以大纲形式编辑这种类型的文章,而不需要复杂的样式表。(这样的编辑器将允许作者专注于文章的结构,将布局留到后面的流程中)。在这样的编辑器中,文章片段可能会像这样:

<ARTICLE> 
 <TITLE>一个示例文章 
  <SECT>第一大节 
   <PARA>本节将 
            介绍一个子节。
    <SECT>子节标题 
     <PARA>这是子节的文本。 
         请注意 ...

注意 - 目前存在树形结构的编辑器,但它们对待<B><I>等内联标签的方式与对待结构标签的方式相同,这可能使得"大纲"有点难以阅读。


编写XSLT转换

现在是时候开始编写一个XSLT转换,将XML文章转换为HTML并呈现出来。


注意 - 在本节讨论的代码中,article1a.xsl位于解压缩XSLT示例后的install-dir/jaxp-1_4_2-release-date/samples目录下。


首先创建一个普通的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之外的任何内容,那么您将需要一个像所示的<xsl:output>标记,指定texthtml。(默认值为xml)。


注意 - 当您指定XML输出时,您可以添加indent属性以生成漂亮缩进的XML输出。该规范看起来像这样:<xsl:output method="xml" indent="yes"/>


处理基本结构元素

您将通过处理用于创建目录的元素来填充样式表:根元素、标题元素和标题。您还将处理在测试文档中定义的PARA元素。


注意 - 如果您在第一次阅读时跳过了讨论XPath寻址机制的部分,请回头阅读该部分:XPath的工作原理,现在是回顾该部分的好时机。


首先,通过添加处理根元素的主指令开始:

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

</xsl:stylesheet>

新的XSL命令以粗体显示。(请注意它们在xsl命名空间中定义)。指令<xsl:apply-templates>处理当前节点的子节点。在这种情况下,当前节点是根节点。

尽管这个示例很简单,但它阐述了许多重要的概念,因此值得深入了解。第一个概念是样式表包含一些模板,这些模板使用<xsl:template>标记定义。每个模板都包含一个match属性,该属性使用XPath寻址机制(在XPath的工作原理中描述)选择将应用模板的元素。

在模板中,不以xsl: namespace前缀开头的标记只是被复制。随后的换行符和空格也被复制,这有助于使生成的输出可读。


注意 - 当不存在换行符时,通常会忽略空格。在这种情况下,要在输出中包含空格或其他文本,可以使用<xsl:text>标记。基本上,XSLT样式表期望处理标记。因此,它看到的每一样东西都必须是<xsl:..>标记、其他标记或空格。


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

处理<TITLE>元素

接下来,添加一个模板来处理文章标题:

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

</xsl:stylesheet>

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

更重要的是,apply-templates指令导致标题的文本被处理。像DOM数据模型一样,XSLT数据模型基于包含在元素节点中的文本节点的概念(这些元素节点又可以包含在其他元素节点中,依此类推)。这种层次结构构成了源树。还有一个结果树,其中包含了输出结果。

XSLT通过将源树转换为结果树来工作。为了可视化XSLT操作的结果,了解这些树的结构和内容是很有帮助的。(有关此主题的更多信息,请参阅XSLT/XPath数据模型)。

处理标题

继续处理基本结构元素,添加一个模板来处理顶级标题:

 <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属性可以将文本和内联元素放在<h2>...</h2>标签之间,同时确保处理该部分中的所有结构标签。换句话说,您确保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">
 错误:章节只能嵌套 2 层。
 </xsl:message>
 </xsl:template>

</xsl:stylesheet>

terminate="yes" 语句在生成消息后停止转换过程。如果没有它,处理仍然可以继续,但会忽略该章节中的所有内容。

作为一个额外的练习,你可以扩展样式表以处理嵌套到四层的章节,并生成 <h2>...<h5> 标签。在任何嵌套到五层的章节上生成错误。

最后,通过添加一个处理 PARA 标签的模板来完成样式表:

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

编写基本程序

现在,您将修改使用XSLT来原样回显XML文件的程序,更改为使用您的样式表。


注意 - 本节讨论的代码位于 Stylizer.java 文件中,在解压缩 XSLT examples 后,可以在 install-dir/jaxp-1_4_2-release-date/samples 目录下找到。结果是 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示例

  1. 导航到samples目录。
    % cd install-dir/jaxp-1_4_2-release-date/samples.
    
  2. 通过点击此链接下载XSLT示例并将其解压缩到install-dir/jaxp-1_4_2-release-date/samples目录中。
  3. 导航到xslt目录。
    cd xslt
    
  4. 编译Stylizer示例。

    输入以下命令:

    % javac Stylizer.java
    
  5. 使用样式表article1a.xslarticle1.xml运行Stylizer示例。
    % java Stylizer data/article1a.xsl  data/article1.xml
    

    您将看到以下输出:

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

    此时,输出中有相当多的多余空白。在下一节中,您将看到如何消除大部分空白。

修剪空白

回想一下,当你查看DOM的结构时,会发现很多只包含可忽略空白的文本节点。输出中大部分多余的空白来自这些节点。幸运的是,XSL提供了一种消除它们的方法。(有关节点结构的更多信息,请参阅XSLT/XPath数据模型)。


注意 -本节讨论的样式表在article1b.xsl中,你可以在解压缩XSLT示例后,在install-dir/jaxp-1_4_2-release-date/samples目录中找到。结果在xslt/data中的stylizer1b.html


为了删除一些多余的空白,将以下突出显示的行添加到样式表中。

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

[...]

这个指令告诉XSL删除SECT元素下只包含空白的文本节点。包含非空白文本的节点不会受到影响,其他类型的节点也不会受到影响。

使用修剪空白的Stylizer示例运行

  1. 导航到samples目录。
    % cd install-dir/jaxp-1_4_2-release-date/samples.
    
  2. 点击此链接下载XSLT示例并将其解压缩到install-dir/jaxp-1_4_2-release-date/samples目录中。
  3. 导航到xslt目录。
    cd xslt
    
  4. 编译Stylizer示例。

    输入以下命令:

    % javac Stylizer.java
    
  5. 使用样式表article1b.xslarticle1.xml上运行Stylizer示例。
    % java Stylizer 
      data/article1b.xsl  
      data/article1.xml
    

    你将看到以下输出:

    <html>
    <body>
    
    <h1 align="center">一个示例文章</h1>
    
    <h2>第一大节
       </h2>
    <p>本节将介绍一个子节。</p>
    <h3>子节标题
          </h3>
    <p>这是子节的文本。
          </p>
    
    </body>
    </html>
    

    这是相当大的改进。标题后面仍然有换行符和空白字符,但这些是XML编写方式造成的:

    <SECT>第一大节
    ____<PARA>本节将介绍一个子节。</PARA>
    ^^^^
    

    在这里,你可以看到节标题以换行符和缩进空格结尾,然后是PARA条目的开头。这并不是什么大问题,因为处理HTML的浏览器会自动压缩和忽略多余的空白。但我们还有一个格式化工具可供使用。

删除最后的空白


注意 - 本节中讨论的样式表位于 article1c.xsl,在将 XSLT示例 解压到 install-dir/jaxp-1_4_2-release-date/samples 目录后,可以在 xslt/data 目录中找到。结果是在 xslt/data 中找到的 stylizer1c.html


通过将以下内容添加到样式表中,可以去除最后一小部分空白:

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

</xsl:stylesheet>

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

使用修剪所有空白的 Stylizer 示例运行

  1. 导航到samples目录。
    % cd install-dir/jaxp-1_4_2-release-date/samples.
    
  2. 通过点击此链接下载XSLT示例并将其解压到install-dir/jaxp-1_4_2-release-date/samples目录。
  3. 导航到xslt目录。
    cd xslt
    
  4. 编译Stylizer示例。

    输入以下命令:

    % javac Stylizer.java
    
  5. 使用样式表article1c.xsl运行Stylizer示例,对article1.xml进行处理。
    % java Stylizer 
      data/article1c.xsl  
      data/article1.xml
    

    输出结果如下:

    <html>
    <body>
    <h1 align="center">一篇示例文章
    </h1>
    <h2>第一大节</h2>
    <p>此节将介绍一个子节。
    </p>
    <h3>子节标题</h3>
    <p>这是子节的文本。
    </p>
    </body>
    </html>
    

    这样看起来好多了。当然,如果能够缩进就更好了,但这比预期的要困难一些。以下是一些可能的解决方案,以及相应的困难:

    缩进选项

    遗憾的是,适用于XML输出的indent="yes"选项在HTML输出中不可用。即使该选项可用,也没有帮助,因为HTML元素很少嵌套!尽管HTML源代码经常缩进以显示隐含的结构,但HTML标记本身并没有以创建真正结构的方式嵌套。

    缩进变量

    <xsl:text>函数允许您添加任何文本,包括空格。因此,它可能被用于输出缩进空格。问题是如何变化缩进空格的数量。XSLT变量似乎是一个好主意,但在这里不起作用。原因是当您在模板中为变量赋值时,该值仅在该模板内部(在编译时静态地)已知。即使变量在全局范围内定义,所赋的值也不会以一种能够让其他模板在运行时动态知道的方式存储。当<apply-templates/>调用其他模板时,这些模板对其他地方的任何变量设置都不知情。

    带参数的模板

    使用带参数的模板是修改模板行为的另一种方法。但确定传递作为参数的缩进空格数量仍然是问题的关键。

    目前看来,控制HTML格式输出的缩进没有什么好办法。如果您需要将HTML显示或编辑为纯文本,这可能不方便。但如果您使用XML形式进行编辑,并仅将HTML版本用于在浏览器中显示,那么这不是问题。(例如,当您查看stylizer1c.html时,您会看到期望的结果)。

处理剩余的结构元素

在本节中,您将处理LISTNOTE元素,这些元素为文章添加了更多的结构。


注意 - 本节中描述的示例文档是article2.xml,用于处理它的样式表是article2.xsl。结果是stylizer2.html。这些文件可以在您解压缩XSLT示例install-dir/jaxp-1_4_2-release-date/samples目录后,在xslt/data目录中找到。


首先向示例文档添加一些测试数据:

<?xml version="1.0"?>
<ARTICLE>
<TITLE>一个示例文章</TITLE>
 <SECT>第一大节
    ...
  </SECT>
  <SECT>第二大节
  <PARA>这个节添加了一个LIST和一个NOTE。
    <PARA>这是LIST:
      <LIST type="ordered">
        <ITEM>梨子</ITEM>
        <ITEM>葡萄</ITEM>
      </LIST>
  </PARA>
  <PARA>这是NOTE:
  <NOTE>不要忘记去杂货店之前去五金店!
  </NOTE>
  </PARA>
 </SECT> 
</ARTICLE>

注意 - 尽管XML文件中的listnote包含在各自的段落中,但它们是否包含并没有影响;生成的HTML无论如何都是一样的。但是,将它们包含在段落中将使它们在面向大纲的编辑器中更容易处理。


修改<PARA>处理

接下来,修改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><ITEM>元素

现在,您可以添加一个处理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>

<xsl:if> 标签使用 test="" 属性来指定一个布尔条件。在这个例子中,测试 type 属性的值,并且生成的列表会根据值是有序还是无序而改变。

在这个例子中有两个重要的事情需要注意:

现在通过处理 ITEM 元素来完成 LIST 的处理:

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

</xsl:stylesheet>

对样式表中的模板进行排序

到目前为止,你应该已经知道模板是相互独立的,所以它们出现在文件中的位置通常并不重要。因此,从现在开始,我们只会显示需要添加的模板。(为了比较,它们总是添加在示例样式表的末尾)。

当两个模板可以应用到同一个节点时,顺序确实很重要。在这种情况下,最后定义的模板是被找到和处理的模板。例如,要将缩进列表的排序更改为使用小写字母表,你可以指定一个模板模式,如 //LIST//LIST。在该模板中,你将使用 HTML 选项来生成字母顺序,而不是数字顺序。

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

处理 <NOTE> 元素

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

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

</xsl:stylesheet>

这段代码提出了一个有趣的问题,这是由于包含 <br/> 标签引起的。为了使文件成为良好格式的 XML,标签必须在样式表中指定为 <br/>,但是这个标签并不被许多浏览器认可。虽然大多数浏览器都会识别序列 <br></br>,但它们都会将其视为段落分隔而不是单个换行符。

换句话说,转换必须生成一个<br>标签,但样式表必须指定<br/>。这就是为什么我们在样式表中早期添加了特殊输出标签的主要原因:

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

该输出规范将空标签(如<br/>)转换为它们的HTML形式<br>。这种转换很重要,因为大多数浏览器不识别空标签。以下是受影响的标签列表:

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

总之,默认情况下,XSLT在输出时生成符合格式的XML。由于XSL样式表本身就是符合格式的XML,所以你不能在其中间插入<br>等标签。<xsl:output method="html"/>标签解决了这个问题,这样你就可以在样式表中编码<br/>并在输出中获得<br>

指定<xsl:output method="html"/>的另一个主要原因是,与指定<xsl:output method="text"/>类似,生成的文本不会被转义。例如,如果样式表中包含<实体引用,它将出现为生成文本中的<字符。另一方面,当生成XML时,样式表中的<实体引用不会改变,所以它在生成文本中仍然是<


注意 - 如果你实际上想要在HTML输出中生成<,你需要将其编码为&lt;。该序列在输出时变为<,因为只有&被转换为&字符。


使用定义了LISTNOTE元素的Stylizer示例运行

  1. 导航到samples目录。
    % cd install-dir/jaxp-1_4_2-release-date/samples.
    
  2. 点击此链接下载XSLT示例并将其解压缩到install-dir/jaxp-1_4_2-release-date/samples目录中。
  3. 导航到xslt目录。
    cd xslt
    
  4. 编译Stylizer示例。

    输入以下命令:

    % javac Stylizer.java
    
  5. 使用样式表article2.xslarticle2.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>一个示例文章</TITLE>
  <SECT>第一大节
      [...]
  </SECT>
  <SECT>第二大节
      [...]
  </SECT> 
<SECT>第三个</i> 
    大节
 <PARA>除了标题中的内联标签外,
 这个部分定义了术语 
 <DEF>inline</DEF>,
 这个术语字面上的意思是“没有换行”。 
 它还添加了一个链接到Java平台的主页 
(<LINK>http://java.sun.com</LINK>),
 以及一个链接到 
 <LINK target="http://java.sun.com/xml">
   XML </LINK> 
 页面。
 </PARA>
 </SECT> 
</ARTICLE>

现在处理段落中的内联 <DEF> 元素,将它们重命名为HTML的斜体标签:

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

接下来,注释掉文本节点的规范化。它已经完成了它的任务,现在你需要保留重要的空格:

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

这个修改使我们不会丢失在 <I><DEF> 等标签之前的空格。(尝试不带此修改的程序,看看结果如何)。

现在处理基本的内联HTML元素,如加粗、斜体和下划线的 <B><I><U>

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

<xsl:element> 标签让你可以计算要生成的元素。这里,你使用当前元素的名称生成适当的内联标签。特别要注意在 name=".." 表达式中使用花括号({})。这些花括号使引号内的文本被处理为XPath表达式,而不是被解释为字面字符串。在这里,它们导致XPath的 name() 函数返回当前节点的名称。

花括号可以在任何属性值模板可以出现的地方识别。 (属性值模板在XSLT规范的第7.6.2节中定义,并且它们在模板定义的几个位置出现)。 在这种表达式中,花括号还可以用于引用属性的值,{@foo},或者元素的内容{foo}


注意 -您还可以使用<xsl:attribute>生成属性。有关更多信息,请参阅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=""子句给模板命名。因此,只有在调用它时才会执行此模板。

在模板中,您还使用<xsl:param>标签指定一个名为dest的参数。为了进行一些错误检查,您使用select子句为该参数指定了默认值UNDEFINED。要在<xsl:value-of>标签中引用变量,您需要指定


注意 -请记住,引号中的条目被解释为表达式,除非它进一步用单引号括起来。这就是为什么之前需要使用单引号在"@type='ordered'"中,以确保ordered被解释为字符串。


<xsl:element>标签生成一个元素。之前,您可以通过编码<html>之类的内容来指定所需的元素。但是在这里,您正在动态生成HTML锚点(<a>)的内容,并且正在使用<xsl:attribute>标签动态生成锚点的href属性。

模板的最后一个重要部分是<apply-templates>标签,它插入LINK元素下的文本节点中的文本。如果没有它,生成的HTML链接中将没有文本。

接下来,添加LINK标记的模板,并在其中调用命名模板:

<xsl:template match="LINK">
 <xsl:if test="@target">
 <!--指定了Target属性。-->
 <xsl:call-template 
    name="htmLink">
 <xsl:with-param name="dest" 
    select="@target"/> 
 </xsl:call-template>
 </xsl:if>
</xsl:template>
<xsl:template name="htmLink">

[...]

如果LINK标签中存在target属性,则test="@target"子句返回true。因此,当链接的文本和定义的目标不同时,<xsl-if>标签生成HTML链接。

<xsl:call-template>标签调用命名模板,而<xsl:with-param>使用name子句指定参数,并使用select子句指定其值。

在样式表构建过程的最后一步中,添加<xsl-if>标签来处理没有目标属性的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子句,而是来自<xsl:with-param>元素的内容。


注意 - 只是为了明确:参数和变量(稍后在XSLT还能做什么?中讨论)可以通过select子句指定其值,从而可以使用XPath表达式,或者通过元素的内容指定其值,从而可以使用XSLT标签。


在这种情况下,参数的内容是由<xsl:apply-templates/>标签生成的,它插入LINK元素下的文本节点的内容。

使用内联元素运行Stylizer示例

  1. 导航到samples目录。
    % cd install-dir/jaxp-1_4_2-release-date/samples.
    
  2. 点击此链接下载XSLT示例并将其解压到install-dir/jaxp-1_4_2-release-date/samples目录中。
  3. 导航到xslt目录。<
    cd xslt
    
  4. 编译Stylizer示例。

    输入以下命令:

    % javac Stylizer.java
    
  5. 使用样式表article3.xslStylizer示例运行在article3.xml上。
    % java Stylizer data/article3.xsl  data/article3.xml
    

    现在运行程序,结果应该如下所示:

    [...]
    <h2>第三个主要部分</h2>
    <p>除了标题中的内联标记外,此部分还定义了术语“内联”,字面上意思是“无换行”。还为Java平台的主页(<a href="http://java.sun.com">http://java.sun.com</a>)添加了一个简单的链接,以及指向<a href="http://java.sun.com/xml">XML</a>页面的链接。</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节)

多次处理一个元素,每次在不同的“模式”中进行。您可以在模板中添加一个mode属性,然后使用<apply-templates mode="...">来只应用具有匹配模式的模板。结合<apply-templates select="...">属性,将基于模式的处理应用于输入数据的子集。

变量 (第11节)

变量类似于方法参数,它们允许您控制模板的行为。但是它们并不像您可能认为的那样有价值。变量的值仅在当前模板或<xsl:if>标签(例如)的范围内是已知的。您不能将一个值从一个模板传递到另一个模板,甚至不能将一个模板的一个封闭部分的值传递到同一模板的另一部分。

即使对于“全局”变量,这些说法也是正确的。您可以在一个模板中更改它的值,但该更改仅适用于该模板。当计算用于定义全局变量的表达式时,该计算是在结构的根节点的上下文中进行的。换句话说,全局变量实际上是运行时常量。这些常量可以用于改变模板的行为,特别是与include和import语句结合使用时。但是变量不是通用的数据管理机制。

变量的问题

诱人的做法是创建一个单一模板,并设置一个变量作为链接的目标,而不是为参数化模板设置参数并以两种不同的方式调用它。这个想法是将变量设置为默认值(比如,LINK标签的文本),然后如果存在目标属性,将目标变量设置为目标属性的值。

如果这个方法可行就好了。但问题是,变量只在定义它们的作用域中可见。所以当你编写一个<xsl:if>标签来改变变量的值时,该值仅在<xsl:if>标签的上下文中可见。一旦遇到</xsl:if>,对变量设置的任何更改都会丢失。

另一个诱人的想法是用变量()替换text()|B|I|U|DEF|LINK的规范。但由于变量的值是由其定义的位置决定的,全局内联变量的值由文本节点、<B>节点等组成,这些节点恰好存在于根级别。换句话说,在这种情况下,这样一个变量的值是空的。


上一页:从任意数据结构生成XML
下一页:XML的流API