XPath 如何工作
XPath 规范是各种规范的基础,包括 XSLT 和链接/寻址规范,如XPointer
。因此,对 XPath 的理解对许多高级 XML 用法至关重要。本节介绍了 XPath 在 XSLT 上下文中的基本知识。
XPath 表达式
一般来说,XPath 表达式指定了选择一组 XML 节点的模式。然后 XSLT 模板在应用转换时使用这些模式。(另一方面,XPointer
添加了定义点或范围的机制,以便可以使用 XPath 表达式进行寻址)。
XPath 表达式中的节点不仅指代元素,还指代文本和属性等其他内容。事实上,XPath 规范定义了一个抽象文档模型,定义了七种节点类型:
- 根
- 元素
- 文本
- 属性
- 注释
- 处理指令
- 命名空间
XML 数据的根元素由一个元素节点建模。XPath 根节点包含文档的根元素以及与文档相关的其他信息。
XSLT/XPath 数据模型
类似于文档对象模型(DOM),XSLT/XPath 数据模型由包含各种节点的树组成。在任何给定元素节点下,都有文本节点、属性节点、元素节点、注释节点和处理指令节点。
在这个抽象模型中,语法区别消失了,你只剩下了数据的规范化视图。例如,在文本节点中,无论文本是在 CDATA 部分中定义的还是包含实体引用,都没有区别。文本节点将包含规范化的数据,即在所有解析完成后存在的数据。因此,文本将包含一个<
字符,无论是否使用实体引用(如<
)或 CDATA 部分来包含它。(类似地,文本将包含一个&
字符,无论是使用&
传递的还是在 CDATA 部分中的)。
在这一部分,我们将主要处理元素节点和文本节点。有关其他寻址机制,请参阅 XPath 规范。
模板和上下文
XSLT 模板是一组应用于 XPath 表达式选择的节点的格式化指令。在样式表中,XSLT 模板看起来像这样:
<xsl:template match="//LIST"> ... </xsl:template>
表达式//LIST
从输入流中选择LIST
节点集。模板中的附加指令告诉系统如何处理它们。
由这样一个表达式选择的节点集定义了模板中其他表达式评估的上下文。该上下文可以被视为整个集合 - 例如,在确定它包含的节点数时。
上下文也可以被视为集合中的单个成员,因为每个成员都会逐个处理。例如,在LIST
处理模板内部,表达式@type
指的是当前LIST
节点的类型属性。(类似地,表达式@*
指的是当前 LIST 元素的所有属性)。
基本的 XPath 地址定位
XML 文档是一个树形结构(分层)的节点集合。与分层目录结构一样,指定指向层次结构中特定节点的路径是很有用的(因此规范的名称是 XPath)。事实上,许多目录路径的表示法完全保留不变:
- 正斜杠(/)用作路径分隔符。
- 从文档根开始的绝对路径以
/
开头。 - 从给定位置开始的相对路径以其他任何字符开头。
- 双点(…)表示当前节点的父节点。
- 单点(.)表示当前节点。
例如,在一个可扩展的 HTML(XHTML)文档(一个看起来像 HTML 但符合 XML 规则的 XML 文档)中,路径/h1/h2/
表示h1
下的h2
元素。(回想一下,在 XML 中,元素名称是区分大小写的,因此这种规范在 XHTML 中比在普通 HTML 中更有效,因为 HTML 是不区分大小写的)。
在诸如 XPath 的模式匹配规范中,规范/h1/h2
选择所有位于h1
元素下的h2
元素。要选择特定的h2
元素,您可以使用方括号[]
进行索引(就像用于数组的那样)。因此,路径/h1[4]/h2[5]
将选择第四个h1
元素下的第五个h2
元素。
注意 - 在 XHTML 中,所有元素名称都是小写的。这是 XML 文档的一个相当普遍的约定。但是,在像本教程这样的教程中,大写名称更容易阅读。因此,在剩下的 XSLT 课程中,所有 XML 元素名称将以大写形式呈现。(另一方面,属性名称将保持小写)。
XPath 表达式中指定的名称指的是一个元素。例如,在/h1/h2
中,h1
指的是h1
元素。要引用属性,您需要在属性名称前加上@
符号。例如,@type
指的是元素的类型属性。假设您有一个带有 LIST 元素的 XML 文档,那么表达式LIST/@type
将选择LIST
元素的类型属性。
注意 - 因为表达式不以/
开头,所以引用指定了相对于当前上下文的列表节点-无论文档中的位置是什么。
基本的 XPath 表达式
XPath 表达式的完整范围利用了 XPath 定义的通配符、运算符和函数。您很快将了解更多相关内容。在这里,我们简单介绍了一些最常见的 XPath 表达式。
表达式 @type="unordered"
指定了一个名为 type 的属性,其值为 unordered。诸如 LIST/@type
这样的表达式指定了 LIST
元素的 type 属性。
你可以将这两种表示法结合起来,得到一些有趣的东西。在 XPath 中,通常与索引相关联的方括号表示法([]
)被扩展为指定选择条件。因此,表达式 LIST[@type="unordered"]
选择所有类型值为 unordered 的 LIST
元素。
元素也存在类似的表达式。每个元素都有一个关联的字符串值,该值由连接在元素下的所有文本段组成。(有关该过程如何工作的更详细解释,请参见 元素的字符串值。)
假设您使用由 PROJECT
元素和具有项目名称文本字符串、多个列出参与者的 PERSON
元素以及可选记录项目状态的 STATUS
元素组成的 XML 结构来对组织中发生的事情进行建模。以下是使用扩展方括号表示法的其他示例:
/PROJECT[.="MyProject"]
:选择名为 “MyProject” 的PROJECT
。/PROJECT[STATUS]
:选择所有具有STATUS
子元素的项目。/PROJECT[STATUS="Critical"]
:选择所有具有字符串值为 Critical 的STATUS
子元素的项目。
结合索引地址
XPath 规范定义了相当多的寻址机制,它们可以以许多不同的方式组合。因此,XPath 为相对简单的规范提供了很多表达能力。本节展示了其他有趣的组合:
LIST[@type="ordered"][3]
:选择所有类型为 ordered 的LIST
元素,并返回第三个。LIST[3][@type="ordered"]
:选择第三个LIST
元素,但仅当它是 ordered 类型时。
注意 - 在 XPath 规范 的第 2.5 节中列出了更多的地址操作符组合。这可能是规范中最有用的部分,用于定义 XSLT 转换。
通配符
根据定义,未经限定的 XPath 表达式选择与指定模式匹配的一组 XML 节点。例如,/HEAD
匹配所有顶级 HEAD
条目,而 /HEAD[1]
仅匹配第一个。表 4-1 列出了可用于 XPath 表达式中的通配符,以扩大模式匹配的范围。
表 4-1 XPath 通配符
通配符 | 含义 |
* |
匹配任何元素节点(不包括属性或文本)。 |
node() |
匹配任何类型的任何节点:元素节点、文本节点、属性节点、处理指令节点、命名空间节点或注释节点。 |
@* |
匹配任何属性节点。 |
在项目数据库示例中,/*/PERSON[.="Fred"]
匹配任何命名为 Fred 的 PROJECT
或 ACTIVITY
元素。
扩展路径寻址
到目前为止,您看到的所有模式都指定了层次结构中的确切级别。例如,/HEAD
指定了层次结构中第一级的任何HEAD
元素,而/*/*
指定了层次结构中第二级的任何元素。要指定层次结构中的不确定级别,请使用双斜杠(//
)。例如,XPath 表达式//PARA
选择文档中的所有段落元素,无论它们在哪里找到。
//
模式也可以在路径中使用。因此,表达式/HEAD/LIST//PARA
表示从/HEAD/LIST
开始的子树中的所有段落元素。
XPath 数据类型和运算符
XPath 表达式产生一组节点、一个字符串、一个布尔值(真/假值)或一个数字。表 4-2 列出了可以在 Xpath 表达式中使用的运算符:
表 4-2 XPath 运算符
运算符 | 含义 |
| |
替代。例如,PARA|LIST 选择所有PARA 和LIST 元素。 |
or , and |
返回两个布尔值的或/与。 |
= , != |
等于或不等于,适用于布尔值、字符串和数字。 |
< , > , <= , >= |
小于、大于、小于或等于、大于或等于,适用于数字。 |
+ , - , * , div , mod |
加、减、乘、浮点除法和模运算(例如,6 mod 4 = 2)。 |
表达式可以用括号分组,因此您不必担心运算符优先级。
注意 - 运算符优先级是一个术语,用来回答这个问题,“如果你指定 a + b * c,这是意味着 (a+b) * c 还是 a + (b*c)?”(运算符优先级与表中显示的大致相同)。
元素的字符串值
元素的字符串值是所有后代文本节点的连接,无论有多深。考虑这个混合内容的 XML 数据:
<PARA>This paragraph contains a <b>bold</b> word</PARA>
元素的字符串值为这个段落包含一个粗体词。特别要注意的是,是
的子元素,而文本
bold
是的子元素。
关键是节点的所有子节点中的所有文本都连接在一起以形成字符串值。
此外,值得理解的是,XPath 定义的抽象数据模型中的文本是完全规范化的。因此,无论 XML 结构中是否包含实体引用<
或<
在CDATA
部分中,元素的字符串值将包含<
字符。因此,在使用 XSLT 样式表生成 HTML 或 XML 时,必须将<
的出现转换为<
或将其置于CDATA
部分中。类似地,&
的出现必须转换为&
。
XPath 函数
这一部分以 XPath 函数的概述结束。您可以使用 XPath 函数来选择一组节点,就像您使用元素规范一样。其他函数返回一个字符串、一个数字或一个布尔值。例如,表达式/PROJECT/text()
获取PROJECT
节点的字符串值。
许多函数依赖于当前上下文。在前面的示例中,每次调用text()
函数的上下文是当前选择的PROJECT
节点。
有许多 XPath 函数-太多了,无法在此详细描述。本节提供了一个简要列表,显示了可用的 XPath 函数以及它们的功能摘要。有关函数的更多信息,请参阅XPath 规范的第 4 节。
节点集函数
许多 XPath 表达式选择一组节点。实质上,它们返回一个节点集。一个函数也是如此。id(...)
函数返回具有指定 ID 的节点。(元素仅在文档具有指定哪个属性具有 ID 类型的 DTD 时才具有 ID)。
位置函数
这些函数返回基于位置的数值。
last()
: 返回最后一个元素的索引。例如,/HEAD[last()]
选择最后一个HEAD
元素。position()
: 返回索引位置。例如,/HEAD[position() <= 5]
选择前五个HEAD
元素。count(...)
: 返回元素的计数。例如,/HEAD[count(HEAD)=0]
选择所有没有子标题的HEAD
元素。
字符串函数
这些函数操作或返回字符串。
concat(string, string, ...)
: 连接字符串值。starts-with(string1, string2)
: 如果string1
以string2
开头,则返回 true。contains(string1, string2)
: 如果string1
包含string2
,则返回 true。substring-before(string1, string2)
: 返回string1
中string2
出现之前的部分。substring-after(string1, string2)
: 返回string1
中string2
之后的剩余部分。substring(string, idx)
: 返回从索引位置到末尾的子字符串,其中第一个char
的索引 = 1。substring(string, idx, len)
: 返回从索引位置开始的指定长度的子字符串。string-length()
: 返回上下文节点的字符串值的大小;上下文节点是当前选择的节点-通过应用诸如string-length()
的函数选择的 XPath 表达式中的节点。string-length(string)
: 返回指定字符串的大小。normalize-space()
: 返回当前节点的规范化字符串值(没有前导或尾随空格,并且空格字符序列转换为单个空格)。normalize-space(string)
: 返回指定字符串的规范化字符串值。translate(string1, string2, string3)
: 将string1
转换,用string2
中的字符替换为string3
中对应的字符。
注意 - XPath 定义了三种获取元素文本的方式:text()
、string(object)
,以及在表达式中隐含的元素名称所暗示的字符串值,例如:/PROJECT[PERSON="Fred"]
。
布尔函数
这些函数操作或返回布尔值。
not(...)
: 反转指定的布尔值。true()
: 返回 true。false()
: 返回 false。lang(string)
: 如果上下文节点的语言(由xml:Lang
属性指定)与指定的语言相同(或是指定语言的子语言),则返回 true;例如,对于...
,Lang("en")
为 true。
数值函数
这些函数操作或返回数值。
sum(...)
: 返回指定节点集中每个节点的数值之和。floor(N)
: 返回不大于N的最大整数。ceiling(N)
: 返回不小于N的最小整数。round(N)
: 返回最接近N的整数。
转换函数
这些函数将一个数据类型转换为另一个数据类型。
string(...)
: 返回数字、布尔值或节点集的字符串值。boolean(...)
: 为数字、字符串或节点集返回一个布尔值(非零数字、非空节点集和非空字符串均为 true)。number(...)
: 返回布尔值、字符串或节点集的数值(true 为 1,false 为 0,包含数字的字符串变为该数字,节点集的字符串值转换为数字)。
命名空间函数
这些函数让你确定节点的命名空间特征。
local-name()
: 返回当前节点的名称,不包括命名空间前缀。local-name(...)
: 返回指定节点集中第一个节点的名称,不包括命名空间前缀。namespace-uri()
: 返回当前节点的命名空间 URI。namespace-uri(...)
: 返回指定节点集中第一个节点的命名空间 URI。name()
: 返回当前节点的扩展名称(URI 加上本地名称)。name(...)
: 返回指定节点集中第一个节点的扩展名称(URI 加上本地名称)。
总结
XPath 运算符、函数、通配符和节点寻址机制可以以各种方式组合。到目前为止,你所学到的内容应该让你能够很好地开始指定你需要的模式。
将 DOM 写出为 XML 文件
原文:
docs.oracle.com/javase/tutorial/jaxp/xslt/writingDom.html
在构建了一个 DOM(通过解析 XML 文件或以编程方式构建)之后,您经常希望将其保存为 XML。本节将向您展示如何使用 Xalan 转换包来实现这一点。
使用该包,您将创建一个转换器对象,将DOMSource
连接到StreamResult
。然后,您将调用转换器的transform()
方法将 DOM 写出为 XML 数据。
读取 XML
第一步是通过解析 XML 文件在内存中创建一个 DOM。到目前为止,您应该已经对这个过程感到熟悉了。
注意:
本节讨论的代码位于文件TransformationApp01.java
中。下载 XSLT 示例并将其解压缩到install-dir/jaxp-1_4_2-
release-date/samples
目录中。
以下代码提供了一个基本的模板供参考。基本上,这与文档对象模型课程开头使用的代码相同。
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.FactoryConfigurationError; import javax.xml.parsers.ParserConfigurationException; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.w3c.dom.Document; import org.w3c.dom.DOMException; import java.io.*; public class TransformationApp01 { static Document document; public static void main(String argv[]) { if (argv.length != 1) { System.err.println("Usage: java TransformationApp01 filename"); System.exit (1); } DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); try { File f = new File(argv[0]); DocumentBuilder builder = factory.newDocumentBuilder(); document = builder.parse(f); } catch (SAXParseException spe) { // Error generated by the parser System.out.println("\n** Parsing error" + ", line " + spe.getLineNumber() + ", uri " + spe.getSystemId()); System.out.println(" " + spe.getMessage() ); // Use the contained exception, if any Exception x = spe; if (spe.getException() != null) x = spe.getException(); x.printStackTrace(); } catch (SAXException sxe) { // Error generated by this application // (or a parser-initialization error) Exception x = sxe; if (sxe.getException() != null) x = sxe.getException(); x.printStackTrace(); } catch (ParserConfigurationException pce) { // Parser with specified options // cannot be built pce.printStackTrace(); } catch (IOException ioe) { // I/O error ioe.printStackTrace(); } } }
创建一个转换器
下一步是创建一个转换器,您可以使用它将 XML 传输到System.out
。首先,需要以下导入语句。
import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import java.io.*;
在这里,您添加了一系列类,这些类现在应该形成一个标准模式:一个实体(Transformer
)、用于创建它的工厂(TransformerFactory
)以及每个类可能生成的异常。因为转换始终有一个源和一个结果,所以您导入了使用 DOM 作为源(DOMSource
)和用于结果的输出流(StreamResult
)所需的类。
接下来,添加执行转换的代码:
try { File f = new File(argv[0]); DocumentBuilder builder = factory.newDocumentBuilder(); document = builder.parse(f); // Use a Transformer for output TransformerFactory tFactory = TransformerFactory.newInstance(); Transformer transformer = tFactory.newTransformer(); DOMSource source = new DOMSource(document); StreamResult result = new StreamResult(System.out); transformer.transform(source, result); }
在这里,您创建一个转换器对象,使用 DOM 构造一个源对象,并使用System.out
构造一个结果对象。然后告诉转换器操作源对象并输出到结果对象。
在这种情况下,“转换器”实际上并没有改变任何内容。在 XSLT 术语中,您使用的是身份转换,这意味着“转换”生成源的副本,未更改。
注意:
您可以为转换器对象指定各种输出属性,如 W3C 规范中定义的www.w3.org/TR/xslt#output
。例如,要获得缩进输出,可以调用以下方法:
% transformer.setOutputProperty(OutputKeys.INDENT, "yes");
最后,以下突出显示的代码捕获可能生成的新错误:
} catch (TransformerConfigurationException tce) { System.out.println("* Transformer Factory error"); System.out.println(" " + tce.getMessage()); Throwable x = tce; if (tce.getException() != null) x = tce.getException(); x.printStackTrace(); } catch (TransformerException te) { System.out.println("* Transformation error"); System.out.println(" " + te.getMessage()); Throwable x = te; if (te.getException() != null) x = te.getException(); x.printStackTrace(); } catch (SAXParseException spe) { // ... }
注意
- 转换器对象会抛出
TransformerExceptions
。 - 工厂会抛出
TransformerConfigurationExceptions
。 - 要保留 XML 文档的
DOCTYPE
设置,还需要添加以下代码:
import javax.xml.transform.OutputKeys; ... if (document.getDoctype() != null) { String systemValue = (new File (document.getDoctype().getSystemId())).getName(); transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, systemValue); }
要了解更多关于配置工厂和处理验证错误的信息,请参阅将 XML 数据读入 DOM。
运行TransformationApp01
示例
- 导航到
samples
目录。
% cd *install-dir*/jaxp-1_4_2-*release-date*/samples.
- 点击此链接下载 XSLT 示例并将其解压缩到install-dir
/jaxp-1_4_2-
release-date/samples
目录中。 - 导航到
xslt
目录。
% cd xslt
- 编译
TransformationApp01
示例。
输入以下命令:
% javac TransformationApp01.java
- 在 XML 文件上运行
TransformationApp01
示例。
在下面的情况下,在解压示例包后,TransformationApp01
在xslt/data
目录中的foo.xml
文件上运行。
% java TransformationApp01 data/foo.xml
- 你将看到以下输出:
<?xml version="1.0" encoding="UTF-8" standalone="no"?><doc> <name first="David" last="Marston"/> <name first="David" last="Bertoni"/> <name first="Donald" last="Leslie"/> <name first="Emily" last="Farmer"/> <name first="Joseph" last="Kesselman"/> <name first="Myriam" last="Midy"/> <name first="Paul" last="Dick"/> <name first="Stephen" last="Auriemma"/> <name first="Scott" last="Boag"/> <name first="Shane" last="Curcuru"/>
- 如创建转换器中所述,这个转换器实际上并没有改变任何内容,而只是执行了恒等变换,生成了源的副本。真正的转换将在从任意数据结构生成 XML 中执行。
写出 DOM 的子树
也可以操作 DOM 的子树。在本节中,你将尝试这个选项。
注意:
本节讨论的代码在 TranformationApp02.java 中。如果你还没有这样做,下载 XSLT 示例并将其解压缩到install-dir/jaxp-1_4_2-
release-date/samples
目录中。
过程中唯一的区别是现在将使用 DOM 中的一个节点创建DOMSource
,而不是整个 DOM。第一步是导入你需要获取的节点的类,如下面突出显示的代码所示:
import org.w3c.dom.Document; import org.w3c.dom.DOMException; import org.w3c.dom.Node; import org.w3c.dom.NodeList;
下一步是找到一个好的节点进行实验。以下突出显示的代码选择第一个元素。
try { File f = new File(argv[0]); DocumentBuilder builder = factory.newDocumentBuilder(); document = builder.parse(f); NodeList list = document.getElementsByTagName("name"); Node node = list.item(0); }
在创建转换器中,源对象是通过以下代码行从整个文档构造的
DOMSource source = new DOMSource(document);
然而,下面突出显示的代码行构造了一个由特定节点为根的子树组成的源对象。
DOMSource source = new DOMSource(node); StreamResult result = new StreamResult(System.out); transformer.transform(source, result);
运行TranformationApp02
示例
- 导航到
samples
目录。
% cd *install-dir*/jaxp-1_4_2-*release-date*/samples.
- 点击此链接下载 XSLT 示例并将其解压缩到install-dir
/jaxp-1_4_2-
release-date/samples
目录中。 - 导航到
xslt
目录。
cd xslt
- 编译
TranformationApp02
示例。
输入以下命令:
% javac xslt/TranformationApp02.java
- 在 XML 文件上运行
TranformationApp02
示例。
在下面的情况下,在解压示例包后,TranformationApp02
在xslt/data
目录中的foo.xml
文件上运行。
% java TranformationApp02 data/foo.xml
- 你将看到以下输出:
<?xml version="1.0" encoding="UTF-8" standalone="no"?><doc><name first="David" last="Marston"/>
- 这次,只打印出了第一个
元素。
到目前为止,你已经看到如何使用转换器写出 DOM,以及如何使用 DOM 的子树作为转换中的源对象。在下一节中,你将看到如何使用转换器从你能够解析的任何数据结构创建 XML。
Java 中文官方教程 2022 版(三十九)(2)https://developer.aliyun.com/article/1488157