SAX,功能强大的 API

sax:用于XML和HTML的sax-js sax样式解析器的维护分支 svg / sax 用于XML和HTML的 sax样式解析器的维护分支。 设计时考虑了,但在浏览器或其他CommonJS实现中应该可以正常工作。 这是什么 一个非常简单的工具来解析XML字符串。 流HTML解析器的垫脚石。 一种方便的方式来处理RSS和其他几乎可以但有点破损的XML文档。 这是什么(可能)不是 HTML解析器-这是一个很好的目标,但事实并非如此。 这只是XML。 DOM构建器-您可以使用它来构建XML之外的对象模型,但是并不能做到这一点。 XSLT-没有DOM =没有查询。 100%兼容(某些其他SAX实施)-大多数SAX实施均使用Java进行,其功能远不止于此。 XML验证程序-在严格模式下,它会进行少量验证,但不会进行太多验证。 模式意识XSD事物-模式是恋物癖受虐狂中的一项练习。 认识DTD的事物-提取DTD是一项更大的工作。 关于<!DOCTYP 立即下载

本文由即将出版的 XML by Example第二版中的一章改编,介绍了 SAX,它是用于处理 XML 的基于事件的 API,SAX 是对“文档对象模型”或者 DOM 的补充,DOM 是用于由 W3C 发布的 XML 语法分析器的基于对象的 API。

您将了解到,SAX:

  • 是基于事件的 API。
  • 在一个比 DOM 低的级别上操作。
  • 为您提供比 DOM 更多的控制。
  • 几乎总是比 DOM 更有效率。
  • 但不幸的是,需要比 DOM 更多的工作。

为什么出现另一个 API?

不要被名称欺骗。SAX 可能是 Simple API for XML,但它需要比 DOM 更多的工作。其回报 - 更紧凑的代码 - 是值得努力的。

图 1 显示了典型 XML 程序的两个组件:

  • 语法分析器,代表应用程序解码 XML 文件的软件组件。语法分析器有效地使开发者避开复杂的 XML 语法。
  • 应用程序,它使用文件内容。

图 1. XML 程序的体系结构
XML 应用程序体系结构的流程图

显然,应用程序可以很简单(例如,在欧元和美元之间转换价格的应用程序)也可以非常复杂,例如,通过因特网订购货物的分布式电子贸易应用程序。

本章集中讨论图 1 中的虚线 - 语法分析器和应用程序之间的接口或 API(应用程序编程接口)。


基于对象和基于事件的接口

您可能已经知道语法分析器有两类接口 - 基于对象的和基于事件的接口。

在拙作的另一章中详细讨论了由 W3C 开发并发布的 DOM,它是基于对象的语法分析器的标准 API。这个关于 DOM 的简要概述只为您提供背景知识,以便您更好地全面理解 SAX。

作为基于对象的接口,DOM 通过在内存中显示地构建对象树来与应用程序通信。对象树是 XML 文件中元素树的精确映射。

DOM 易于学习和使用,因为它与基本 XML 文档紧密匹配。它对于我称为以 XML 为中心的应用程序(例如,浏览器和编辑器)也是很理想的。以 XML 为中心的应用程序为了操纵 XML 文档而操纵 XML 文档。

然而,对于大多数应用程序,处理 XML 文档只是其众多任务中的一种。例如,记帐软件包可能导入 XML 发票,但这不是其主要活动。计算帐户余额、跟踪支出以及使付款与发票匹配才是主要活动。记帐软件包可能已经具有一个数据结构(最有可能是数据库)。DOM 模型不太适合记帐应用程序,因为在那种情况下,应用程序必须在内存中维护数据的两份副本(一个是 DOM 树,另一个是应用程序自己的结构)。至少,在内存维护两次数据会使效率下降。对于桌面应用程序来说,这可能不是主要问题,但是它可能导致服务器瘫痪。

对于不以 XML 为中心的应用程序,SAX 是明智的选择。实际上,SAX 并不在内存中显式地构建文档树。它使应用程序能用最有效率的方法存储数据。

图 2 说明了应用程序如何在 XML 树及其自身数据结构之间进行映射。


图 2. 将 XML 结构映射成应用程序结构
图说明 XML 结构如何映射成应用程序结构

基于事件的接口

正如其名称所暗示的,基于事件的语法分析器将事件发送给应用程序。这些事件类似于用户界面事件,例如,浏览器中的 ONCLICK 事件或者 Java 中的 AWT/Swing 事件。

事件通知应用程序发生了某件事并需要应用程序作出反应。在浏览器中,通常为响应用户操作而生成事件:当用户单击按钮时,按钮产生一个 ONCLICK 事件。

在 XML 语法分析器中,事件与用户操作无关,而与正在读取的 XML 文档中的元素有关。有对于以下方面的事件:

  • 元素开始和结束标记
  • 元素内容
  • 实体
  • 语法分析错误

图 3 显示语法分析器在读取文档时如何生成事件。


图 3. 语法分析器生成事件
图显示语法分析器如何生成事件

清单 1 显示了 XML 格式的清单。它详细列出了不同公司对 XML 培训的收费。图 4 显示了价目表文档的结构。


清单 1. pricelist.xml
<?xml version="1.0"?>
<xbe:price-list xmlns:xbe="http://www.psol.com/xbe2/listing8.1">
   <xbe:product>XML Training</xbe:product>
   <xbe:price-quote price="999.00"  vendor="Playfield Training"/>
   <xbe:price-quote price="699.00"  vendor="XMLi"/>
   <xbe:price-quote price="799.00"  vendor="WriteIT"/>
   <xbe:price-quote price="1999.00" vendor="Emailaholic"/>
</xbe:price-list>


图 4. 价目表的结构
价目表的结构图

XML 语法分析器读取并解释该文档。每当它识别出文档中的某些内容,就会生成一个事件。

读取 清单 1 时,语法分析器首先读取 XML 声明并生成文档开始事件。当它遇到第一个开始标记 <xbe:price-list> 时,语法分析器生成它的第二个事件来通知应用程序已经遇到了 price-list 元素。

接下来,语法分析器看到 product 元素的开始标记(为简单起见,在本文其余部分,我将忽略名称空格和缩进空格)并生成它的第三个事件。

在开始标记后,语法分析器看到 product 元素的内容: XML Training ,它产生另一个事件。

下一个事件指出 product 元素的结束标记。语法分析器已经完成了对 product 元素的语法分析。到目前为止,它已经激发了 5 个事件: product 元素的 3 个事件,一个文档开始事件和一个 price-list 开始标记事件。

语法分析器现在移动到第一个 price-quote 元素。它为每个 price-quote 元素生成两个事件:一个开始标记事件和一个结束标记事件。

是的,即使将结束标记简化为开始标记中的 / 字符,语法分析器仍然生成一个结束事件。

有 4 个 price-quote 元素,所以语法分析器在分析它们时生成 8 个事件。最后,语法分析器遇到 price-list 的结束标记并生成它的最后两个事件:结束 price-list 和文档结束。

如图 5 所示,这些事件共同向应用程序描述了文档树。开始标记事件意味着“转到树的下一层”,而结束标记元素意味着“转到树的上一层”。


图 5. 语法分析器如何隐含地构建树
图说明 SAX 语法分析器如何隐含地构建 DOM 树

请注意,语法分析器传递了足够信息以构建 XML 文档的文档树,但是与 DOM 语法分析器不同,它并不显式地构建该树。

为什么使用基于事件的接口?

现在,我敢肯定你已经糊涂了。应该使用哪一种类型的 API,应该何时使用它 - SAX 还是 DOM?不幸的是,这个问题没有明确的答案。这两种 API 中没有一种在本质上更好;他们适用于不同的需求。

经验法则是在需要更多控制时使用 SAX;要增加方便性时,则使用 DOM。例如,DOM 在脚本语言中很流行。

注:自然的接口

对于语法分析器来说,基于事件的接口是最理想的选择:它只需报告它看见了什么。

采用 SAX 的主要原因是效率。SAX 比 DOM 做的事要少,但提供了对语法分析器的更多控制。当然,如果语法分析器的工作减少,则意味着您(开发者)有更多的工作要做。

而且,正如我们已讨论的,SAX 比 DOM 消耗的资源要少,这只是因为它不需要构建文档树。

在 XML 早期,DOM 得益于 W3C 批准的官方 API 这一身份。逐渐地,开发者选择了功能性而放弃了方便性,并转向了 SAX。

SAX 的主要限制是它无法向后浏览文档。实际上,激发一个事件后,语法分析器就将其忘记。如您将看到的,应用程序必须显式地缓冲其感兴趣的事件。

注:SAX 构建的树

如果需要,应用程序可以用它从语法分析器接收的事件构建 DOM 树。事实上,几个 DOM 语法分析器是在 SAX 语法分析器的基础上构建的。


SAX,功能强大的 API

当然,无论它实现 SAX 还是 DOM API,语法分析器都做许多工作:它读取文档,强制实施 XML 语法并解析实体 - 先只列举这几个。验证语法分析器还强制实施文档模式。

使用语法分析器有很多原因,并且您应该掌握 API、SAX 和 DOM。它使您能灵活地根据手上的任务来选择最好的 API。幸好,现代语法分析器同时支持两种 API。

SAX 是由 XML-DEV 邮件列表的成员开发的一种用于基于事件的语法分析器的标准和简单的 API。SAX 是“Simple API for XML”的缩写。

SAX 最初是为 Java 而定义,但是它也可以用于 Python、Perl、C++ 和 COM(Windows 对象)。以后一定还有更多的语言绑定。而且,通过 COM,SAX 语法分析器还可以用于所有 Windows 编程语言,包括 Visual Basic 和 Delphi。

与 DOM 不同,SAX 没有经过官方标准机构的认可,但是它被广泛使用并被视为事实上的标准。(现在,SAX 由 David Megginson 编辑,但是他已经宣布将要退休。)

如您所见,在浏览器中,DOM 是首选的 API。因此,本章中的示例是用 Java 编写的。(如果您觉得需要一个 Java 速成课程,请转至拙作的附录 A 或者 developerWorks Java 区的“教学”部分。)

一些支持 SAX 的语法分析器包括 Xerces,Apache parser(以前的 IBM 语法分析器)、MSXML(Microsoft 语法分析器)和 XDK(Oracle 语法分析器)。这些语法分析器是最灵活的,因为它们还支持 DOM。

有几个语法分析器仅提供 SAX,例如 James Clark 的 XP和 Vivid Creations 的 ActiveSAX(请参阅 参考资料)。


SAX 入门

清单 2是查找清单 1 中最便宜价格的 Java 应用程序。该应用程序打印出最优的价格和供应商名称。

编译示例

要编这个应用程序,需要适用于您平台的“Java 开发工具箱(JDK)”(请参阅 参考资料)。对于该示例,Java Runtime(Java 运行时环境)是不够的。

注意

Java 难以处理包含空格的路径。如果“最便宜”的公司抱怨它无法找到文件,请检查目录中的错误空格。

从 作者网站的 XBE2 页面下载本摘录的清单。下载内容包括 Xerces。如果清单有问题,请访问作者网站以获取更新。

在名为 Cheapest.java 的文件中保存 清单 2 。转至 DOS 提示符,更改到保存 Cheapest.java 的目录,然后在 DOS 提示符处发出下列命令来编译:

mkdir classes
set classpath=classes;lib\xerces.jar  
javac -d classes src\Cheapest.java

编译将在 classes 目录中安装 Java 程序。这些命令假设您已经在 lib 目录中安装了 Xerces,并且在 src 目录中安装了清单 2。如果在另一个目录下安装语法分析器,则可能必须修改 classpath (第二条命令)。

要对价目表运行应用程序,请发出下面的命令:

java com.psol.xbe2.Cheapest data\pricelist.xml

结果应该是:

The cheapest offer is from XMLi ($699.00)

这条命令假设 清单 1在一个名为 data\pricelist.xml 的文件中。同样,您可能需要修改系统路径。

技巧:关于事件处理器

事件处理器不调用语法分析器。实际上正好相反:语法分析器调用事件处理器。困惑了?想想 AWT 事件。连接到按钮的事件处理器不调用按钮。它等待按钮被单击。

事件处理器的逐步讨论

将 SAX 中的事件定义为连接到特定 Java 接口的方法。本节将逐步复查清单 2。下面一节为您提供关于主要 SAX 接口的更多信息。

声明事件处理器的最简单方案是继承 SAX 提供的 DefaultHandler :

public class Cheapest
   extends DefaultHandler

该应用程序仅实现一个事件处理器 startElement() ,语法分析器在遇到开始标记时调用它。语法分析器将对文档 <xbe:price-list> 、 <xbe:product> 和 <xbe:price-quote> 中的每个开始标记调用 startElement() 。

在清单 2 中,事件处理器仅对 price-quote 感兴趣,所以仅对它测试。该处理器对其它元素的事件不作任何处理。

if(uri.equals(NAMESPACE_URI) && name.equals("price-quote"))
{
   // ...
}

当事件处理器发现 price-quote 元素时,它从属性列表中抽取供应商名称和价格。有了这些信息,查找最便宜的产品就是一个简单的比较处理了。

String attribute =
   attributes.getValue("","price");
if(null != attribute)
{
   double price = toDouble(attribute);
   if(min > price)
   {
      min = price;
      vendor = attributes.getValue("","vendor");
   }
}

请注意,事件处理器接收元素名称、名称空间和属性列表作为来自语法分析器的参数。

现在,让我们将注意力转向 main() 方法。它创建一个事件处理器对象和一个语法分析器对象:

Cheapest cheapest = new Cheapest();
XMLReader parser =
   XMLReaderFactory.createXMLReader(PARSER_NAME);

XMLReader 和 XMLReaderFactory 由 SAX 定义。 XMLReader 是一种 SAX 语法分析器。factory 是用于创建 XMLReaders 的帮助器类。

main() 设置一个语法分析器功能以请求名称空间处理,并且使用语法分析器注册事件处理器。最后,main() 使用至 XML 文件的 URI 调用 parse() 方法:

parser.setFeature("http://xml.org/sax/features/namespaces",true);
parser.setContentHandler(cheapest);
parser.parse(args[0]);

技巧:名称空间

缺省情况下,将 http://xml.org/sax/features/namespaces 设置为真,但是显式地将它设置为真将使代码更具可读性。

看似无关的 parse() 方法触发对 XML 文档的语法分析,这导致了调用事件处理器。我们的 startElement() 方法正是在执行这个方法期间被调用的。在调用 parse() 背后发生了很多事情。

最后但很重要的一点, main() 打印出结果:

Object[] objects = new Object[]
{
   cheapest.vendor,
   new Double(cheapest.min)
};
System.out.println(MessageFormat.format(MESSAGE,objects));

等一下! Cheapest.vendor 和 Cheapest.min 何时获取它们的值?我们不在 main() 中显式地设置它们!确实如此;这是事件处理器的工作。最后由 parse() 调用事件处理器。这就是事件处理的美妙之处。

注意
请记住,除非已经安装了“Java 开发工具箱”,否则不能编译这些示例。最后,可能有一个错误类似于:
 
src\Cheapest.java:7: Package org.xml.sax 
  not found in import.
import org.xml.sax.*;

或
 
Can't find class com/psol/xbe2/Cheapest 
   or something it requires

这极有可能出自以下原因:
  • 类路径(第二个命令, classes;lib\xerces.jar) )不正确。
  • 在最后一个命令 (com.psol.xbe2.Cheapest) 中输入了不正确的类名称。


常用的 SAX 接口和类

到目前为止,我们仅讨论了一个事件( startElement() )。在继续之前,让我们研究一下 SAX 定义的主接口。

注:SAX 版本

到目前为止,有两个 SAX 版本:SAX1 和 SAX2。本章仅介绍 SAX2 API。SAX1 与 SAX2 很相似,但是它缺少名称空间处理。

SAX 将其事件分为几个接口:

  • ContentHandler 定义与文档本身关联的事件(例如,开始和结束标记)。大多数应用程序都注册这些事件。
  • DTDHandler 定义与 DTD 关联的事件。然而,它不定义足够的事件来完整地报告 DTD。如果需要对 DTD 进行语法分析,请使用可选的 DeclHandler。DeclHandler 是 SAX 的扩展,并且不是所有的语法分析器都支持它。
  • EntityResolver 定义与装入实体关联的事件。只有少数几个应用程序注册这些事件。
  • ErrorHandler 定义错误事件。许多应用程序注册这些事件以便用它们自己的方式报错。

注:SAX 的成功之处

本节不是 SAX 的全面参考。相反,它集中讨论最常用的类。

为简化工作,SAX 在 DefaultHandler 类中提供了这些接口的缺省实现。在大多数情况下,为应用程序扩展 DefaultHandler 并覆盖相关的方法要比直接实现一个接口更容易。

XMLReader

为注册事件处理器并启动语法分析器,应用程序使用 XMLReader 接口。如我们所见, parse() ,这种 XMLReader 方法,启动语法分析:

parser.parse(args[0]);

XMLReader 的主要方法是:

  • parse() 对 XML 文档进行语法分析。 parse() 有两个版本;一个接受文件名或 URL,另一个接受 InputSource 对象(请参阅“InputSource”一节)。
  • setContentHandler() 、 setDTDHandler() 、 setEntityResolver() 和 setErrorHandler() 让应用程序注册事件处理器。
  • setFeature() 和 setProperty() 控制语法分析器如何工作。它们采用一个特性或功能标识(一个类似于名称空间的 URI 和值)。功能采用 Boolean 值,而特性采用“对象”。

最常用的 XMLReaderFactory 功能是:

  • http:// xml.org/sax/features/namespaces ,所有 SAX 语法分析器都能识别它。如果将它设置为 true(缺省值),则在调用 ContentHandler 的方法时,语法分析器将识别出名称空间并解析前缀。
  • http://xml.org/sax/features/validation ,它是可选的。如果将它设置为 true,则验证语法分析器将验证该文档。非验证语法分析器忽略该功能。

XMLReaderFactory

XMLReaderFactory 创建语法分析器对象。它定义 createXMLReader() 的两个版本:一个采用语法分析器的类名作为参数,另一个从 org.xml.sax.driver 系统特性中获得类名称。

对于 Xerces,类是 org.apache.xerces.parsers.SAXParser 。应该使用 XMLReaderFactory ,因为它易于切换至另一种 SAX 语法分析器。实际上,只需要更改一行然后重新编译。

XMLReader parser = XMLReaderFactory.createXMLReader(
                       "org.apache.xerces.parsers.SAXParser");

为获得更大的灵活性,应用程序可以从命令行读取类名或使用不带参数的 createXMLReader() 。因此,甚至可以不重新编译就更改语法分析器。

InputSource

InputSource 控制语法分析器如何读取文件,包括 XML 文档和实体。

在大多数情况下,文档是从 URL 装入的。但是,有特殊需求的应用程序可以覆盖 InputSource 。例如,这可以用来从数据库中装入文档。

ContentHandler

ContentHandler 是最常用的 SAX 接口,因为它定义 XML 文档的事件。

如您所见, 清单 2 实现在 ContentHandler 中定义的事件 startElement() 。它用语法分析器注册 ContentHandler :

Cheapest cheapest = new Cheapest();
// ...
parser.setContentHandler(cheapest);

ContentHandler 声明下列事件:

  • startDocument() / endDocument() 通知应用程序文档的开始或结束。
  • startElement() / endElement() 通知应用程序标记的开始或结束。属性作为 Attributes 参数传递(请参阅下面一节“属性”)。即使只有一个标记,“空”元素(例如, <img href="logo.gif"/> )也生成 startElement() 和 endElement() 。
  • startPrefixMapping() / endPrefixMapping() 通知应用程序名称空间作用域。您几乎不需要该信息,因为当 http://xml.org/sax/features/namespaces 为 true 时,语法分析器已经解析了名称空间。
  • 当语法分析器在元素中发现文本(已经过语法分析的字符数据)时, characters() / ignorableWhitespace() 会通知应用程序。要知道,语法分析器负责将文本分配到几个事件(更好地管理其缓冲区)。 ignorableWhitespace 事件用于由 XML 标准定义的可忽略空格。
  • processingInstruction() 将处理指令通知应用程序。
  • skippedEntity() 通知应用程序已经跳过了一个实体(即,当语法分析器未在 DTD/schema 中发现实体声明时)。
  • setDocumentLocator() 将 Locator 对象传递到应用程序;请参阅后面的 Locator 一节。请注意,不需要 SAX 语法分析器提供 Locator ,但是如果它提供了,则必须在任何其它事件之前激活该事件。

属性
在 startElement() 事件中,应用程序在 Attributes 参数中接收属性列表。

String attribute = attributes.getValue("","price");

Attributes 定义下列方法:

  • getValue(i) / getValue(qName) /getValue(uri,localName) 返回第 i 个属性值或给定名称的属性值。
  • getLength() 返回属性数目。
  • getQName(i) / getLocalName(i) /getURI(i) 返回限定名(带前缀)、本地名(不带前缀)和第 i 个属性的名称空间 URI。
  • getType(i)/getType(qName)/getType(uri,localName) 返回第 i 个属性的类型或者给定名称的属性类型。类型为字符串,即在 DTD 所使用的: “CDATA” 、 “ID” 、 “IDREF” 、 “IDREFS” 、 “NMTOKEN” 、 “NMTOKENS” 、 “ENTITY” 、 “ENTITIES” 或 “NOTATION”

注意

Attributes 参数仅在 startElement() 事件期间可用。如果在事件之间需要它,则用 AttributesImpl 复制一个。

定位器

Locator 为应用程序提供行和列的位置。不需要语法分析器来提供 Locator 对象。

Locator 定义下列方法:

  • getColumnNumber() 返回当前事件结束时所在的那一列。在 endElement() 事件中,它将返回结束标记所在的最后一列。
  • getLineNumber() 返回当前事件结束时所在的行。在 endElement() 事件中,它将返回结束标记所在的行。
  • getPublicId() 返回当前文档事件的公共标识。
  • getSystemId() 返回当前文档事件的系统标识。

DTDHandler

DTDHandler 声明两个与 DTD 语法分析器相关的事件。

  • notationDecl() 通知应用程序已经声明了一个标记。
  • nparsedEntityDecl() 通知应用程序已经发现了一个未经过语法分析的实体声明。

EntityResolver

EntityResolver 接口仅定义一个事件 resolveEntity() ,它返回 InputSource (在另一章讨论)。

因为 SAX 语法分析器已经可以解析大多数 URL,所以很少应用程序实现 EntityResolver 。例外情况是目录文件(在另一章中讨论),它将公共标识解析成系统标识。如果在应用程序中需要目录文件,请下载 Norman Walsh 的目录软件包(请参阅 参考资料)。

ErrorHandler

ErrorHandler 接口定义错误事件。处理这些事件的应用程序可以提供定制错误处理。

安装了定制错误处理器后,语法分析器不再抛出异常。抛出异常是事件处理器的责任。

接口定义了与错误的三个级别或严重性对应的三个方法:

  • warning() 警示那些不是由 XML 规范定义的错误。例如,当没有 XML 声明时,某些语法分析器发出警告。它不是错误(因为声明是可选的),但是它可能值得注意。
  • error() 警示那些由 XML 规范定义的错误。
  • fatalError() 警示那些由 XML 规范定义的致命错误。

SAXException

SAX 定义的大多数方法都可以抛出 SAXException 。当对 XML 文档进行语法分析时, SAXException 通知一个错误。

错误可以是语法分析错误也可以是事件处理器中的错误。要报告来自事件处理器的其它异常,可以将异常封装在 SAXException 中。

示例: 假设在处理 startElement 事件时,事件处理器捕获了一个 IndexOutOfBoundsException 。事件处理器可以将 IndexOutOfBoundsException 封装在 SAXException 中:

public void startElement(String uri,
                         String name,
                         String qualifiedName,
                         Attributes attributes)
{
   try
   {
      // the code may throw an IndexOutOfBoundsException
   }
   catch(IndexOutOfBounds e)
   {
      throw new SAXException(e);
   }
}

SAXException 一直向上传递到 parse() 方法,它在那里被捕获并进行解释。

try
{
   parser.parse(uri);
}
catch(SAXException e)
{
   Exception x = e.getException();
   if(null != x)
      if(x instanceof IndexOutOfBoundsException)
         // process the IndexOutOfBoundsException
}


维护状态

清单 1 对于 SAX 语法分析器是很方便的,因为它将信息存储为价格元素的属性。应用程序只需要注册 startElement() 。

示例清单 3 更复杂,因为信息分散到了几个元素中。特别是,根据不同的交付延迟,供应商有不同的价格。如果用户愿意等待,他(或她)可能得到更好的价格。图 6 演示了文档结构。


清单 3. xtpricelist.xml
<?xml version="1.0"?>
<xbe:price-list xmlns:xbe="http://www.psol.com/xbe2/listing8.3">
   <xbe:name>XML Training</xbe:name>
   <xbe:vendor>
      <xbe:name>Playfield Training</xbe:name>
      <xbe:price-quote delivery="5">999.00</xbe:price-quote>
      <xbe:price-quote delivery="15">899.00</xbe:price-quote>
   </xbe:vendor>
   <xbe:vendor>
      <xbe:name>XMLi</xbe:name>
      <xbe:price-quote delivery="3">2999.00</xbe:price-quote>
      <xbe:price-quote delivery="30">1499.00</xbe:price-quote>
      <xbe:price-quote delivery="45">699.00</xbe:price-quote>
   </xbe:vendor>
   <xbe:vendor>
      <xbe:name>WriteIT</xbe:name>
      <xbe:price-quote delivery="5">799.00</xbe:price-quote>
      <xbe:price-quote delivery="15">899.00</xbe:price-quote>
   </xbe:vendor>
   <xbe:vendor>
      <xbe:name>Emailaholic</xbe:name>
      <xbe:price-quote delivery="1">1999.00</xbe:price-quote>
   </xbe:vendor>
</xbe:price-list>


图 6. 价目表结构
价目表结构图

要找到最好的生意,应用程序必须从几个元素搜集信息。但是,语法分析器可以最多为每个元素生成三个事件 - startElement() 、 characters() 和 endElement() 。应用程序必须以某种方法将事件和元素相关联。

清单 4是一个新建的 Java 应用程序,它查找价目表中的最优价格。当查找最优价格时,它考虑到了客户对交付日期的需求。实际上,清单 3 中最便宜的供应商(XMLi)也是最慢的。另一方面,Emailaholic 很贵,但是它可以在两天内交付。

您可以如前面介绍的 Cheapest 应用程序那样编译并运行该应用程序。结果取决于对交付日期的需求。您将注意到这个程序采用两个参数:文件名和客户愿意等待的最长延迟。

java com.psol.xbe2.BestDeal data/xtpricelist.xml 60

返回:

The best deal is proposed by XMLi. A(n) XML Training delivered[ccc]
in 45 days for $699.00

而:

java com.psol.xbe2.BestDeal data/xtpricelist.xml 3

返回:

The best deal is proposed by Emailaholic. A(n) XML Training[ccc]
delivered in 1 days for $1,999.00


分层体系结构

清单 4 是目前为止您所见到的最复杂的应用程序。这没什么不寻常的:SAX 语法分析器的级别很低,所以应用程序必须接管本来由 DOM 才能完成的大量工作。

应用程序是围绕两个类组织的: SAX2BestDeal 和 BestDeal 。 SAX2BestDeal 管理 SAX 语法分析器之间的接口。它用一致的方法来管理状态并将事件分组。

BestDeal 具有执行价格比较的逻辑。它还以结构形式保持为应用程序而不是为 XML 优化的信息。图 7 演示了该应用程序的体系结构。图 8 显示了 UML 类图。


图 7. 应用程序的体系结构
Best Deal 应用程序体系结构图

图 8. 应用程序的类图
Best Deal 应用程序的类图

SAX2BestDeal 处理几个事件: startElement() 、 endElement() 和 characters() 。 SAX2BestDeal 一直跟踪其在文档树中的位置。

例如,在 characters() 事件中, SAX2BestDeal 需要知道文本是名称、价格还是可以忽略的空格。而且,有两个 name 元素: price-list 的 name 和 vendor 的 name 。

状态

与 DOM 语法分析器不同,SAX 语法分析器不提供状态信息。应用程序负责跟踪它自己的状态。这有几个可选实体。清单 4 标识有意义的状态以及它们之间的转换。从图 6 中的文档结构中获得该信息并不困难。

很明显,应用程序将首先遇到 price-list 标记。因此,第一个状态应该是 位于 price-list 内。 从那里开始,应用程序到达一个 name 。因此,第二个状态是 位于 price-list 的 name 内。

下一个元素必须是 vendor ,因此第三个状态是 位于 price-list 的 vendor 内。 第四个状态是 位于 price-list 的 vendor 的 name 内, 因为 name 跟在 vendor 后。

name 后面是一个 price-quote 元素,相应的状态是 位于 price-list 的 vendor 的 price 内。 随后,语法分析器遇到已经有状态存在的 price-quote 或 vendor 。

在带有状态和转换的图上(例如图 9 所示)会更容易使这个概念可视化。请注意根据您在处理 price-list/name 还是 price-list/vendor/name ,有两个不同的状态与两个不同的名称元素相关联。


图 9. 状态转换图
状态转换图

在清单 4 中状态变量存储当前状态:

final protected int START = 0,
                    PRICE_LIST = 1,
                    PRICE_LIST_NAME = 2,
                    VENDOR = 3,
                    VENDOR_NAME = 4,
                    VENDOR_PRICE_QUOTE = 5;
protected int state = START;

转换

转换 1状态变量的值根据事件而相应更改。在本示例中,elementStart() 更新状态:

ifswitch(state)
{
   case START:
      if(name.equals("price-list"))
         state = PRICE_LIST;
      break;
   case PRICE_LIST:
      if(name.equals("name"))
         state = PRICE_LIST_NAME;
         // ...
      if(name.equals("vendor"))
         state = VENDOR;
         break;
   case VENDOR:
      if(name.equals("name"))
         state = VENDOR_NAME;
         // ...
      if(name.equals("price-quote"))
         state = VENDOR_PRICE_QUOTE;
         // ...
      break;
}

SAX2BestDeal 有几个实例变量来存储当前 name 和 price-quote 的内容。实际上,它维护树的一个小子集。请注意,与 DOM 不同,它从不拥有整个树,因为当应用程序使用过 name 和 price-quote 之后,它会废弃它们。

这是很有效的内存策略。事实上,您可以处理几十亿字节的文件,因为在任何时候,内存中只有一个小子集。

转换 2 语法分析器对文档中的每个字符(包括缩进)调用 characters() 。只有记入 name 和 price-quote 中的文本才有意义,因此事件处理器使用状态。

switch(state)
{
   case PRICE_LIST_NAME:
   case VENDOR_NAME:
   case VENDOR_PRICE_QUOTE:
      buffer.append(chars,start,length);
      break;
}

转换 3endElement() 的事件处理器更新状态,并调用 BestDeal 来处理当前元素:switch(state)

{
   case PRICE_LIST_NAME:
      if(name.equals("name"))
      {
         state = PRICE_LIST;
         setProductName(buffer.toString());
         // ...
      }
      break;
   case VENDOR_NAME:
      if(name.equals("name"))
         state = VENDOR;
         // ...
      break;
   case VENDOR_PRICE_QUOTE:
      if(name.equals("price-quote"))
      {
         state = VENDOR;
         // ...
         compare(vendorName,price,delivery);
         // ...
      }
      break;
   case VENDOR:
      if(name.equals("vendor"))
         state = PRICE_LIST;
         // ...
      break;
   case PRICE_LIST:
      if(name.equals("price-list"))
         state = START;
      break;
}


学过的课程

清单 4 是典型的 SAX 应用程序。有一个 SAX 事件处理器( SAX2BestDeal ),它用最适合应用程序的格式将事件打包。

注:状态还是堆栈?

使用状态变量的替换方法是使用“ 堆栈 ”。将元素名(或另一个标识)推入 startElement() ;然后在 endElement() 中弹出它。

应用程序逻辑(在 BestDeal 中)与事件处理器保持分离。事实上,在很多情况下,都独立于 XML 来编写应用程序逻辑。

分层方法在应用程序逻辑和语法分析之间建立一个明显的分界。

示例也清晰地说明了 SAX 比 DOM 更高效,但是它需要程序员完成更多工作。特别是,程序员必须显式地管理状态和状态之间的转换。(在 DOM 中,状态在树的递归遍历过程中是隐含的。)


灵活性

XML 是非常灵活的标准。但实际上,XML 应用程序的灵活性取决于您,程序员,如何创建它们。本节提供一些技巧,以确保您的应用程序利用 XML 的灵活性。

为灵活性而构建

BestDeal 应用程序对 XML 文档结构的约束很少。如果在 XML 文档中添加元素,它们就会被忽略。例如,BestDeal 将接受下列 vendor 元素:

<xbe:vendor>
   <xbe:name>Playfield Training</xbe:name>
   <xbe:contact>John Doe</xbe:contact>
   <xbe:price-quote delivery="5">999.00</xbe:price-quote>
   <xbe:price-quote delivery="15">899.00</xbe:price-quote>
</xbe:vendor>

但是将忽略联系信息。通常,简单地忽略未知元素是个好主意 - HTML 浏览器就总这样做。

强制实施结构

但是,从事件处理器验证它们的结构并不困难。下列代码片断(摘自 startElement() )检查结构,并且如果 vendor 元素包含除名称或价格以外的任何元素,则抛出 SAXException 。

case VENDOR:
   if(name.equals("name"))
   {
      state = VENDOR_NAME;
      buffer = new StringBuffer();
   }
   else if(name.equals("price-quote"))
   {
      state = VENDOR_PRICE_QUOTE;
      String st = attributes.getValue("","delivery");
      delivery = Integer.parseInt(st);
      buffer = new StringBuffer();
   }
   else
      throw new SAXException("Expecting <xbe:name> or <xbe:price-quote>");
   break;

如果清单带有 contact 元素,它将报告:

org.xml.sax.SAXException: Expecting <xbe:name> or <xbe:price-quote>

但是,如果实际上应用程序真正依赖于文档的结构,那么最好编写一个模式并使用验证语法分析器。


下一步做什么?

在这个 XML by Example第二版的摘录中,您学习了如何读取 XML 文档。该书的其余部分将指导您学习如何编写文档的整个过程,并就此结束本课程。或者,您可以参考在线教程和文章,以完成学习。(当然,我希望您选择我的书。)


参考资料

【微信读书】数据内容接口逆向解析实战:从请求捕获到文本提取 本文详细解析了微信读书网页版数据内容接口的逆向工程实战过程。通过浏览器开发者工具捕获网络请求,定位关键API接口,并深入JavaScript代码分析数据解析逻辑,最终实现从请求模拟到文本提取的完整流程。文章旨在技术学习,探讨Web应用数据交互原理,帮助开发者理解现代前端数据流与编码机制。 阅读详情

相关推荐

C++和蓝图1:Unreal Engine 中 C++ 与蓝图的桥梁

摘要: 本文介绍了Unreal Engine(UE)中C++与蓝图可视化脚本的结合机制,重点解析了宏系统(UCLASS、UPROPERTY、UFUNCTION)如何实现代码与蓝图的交互。相比Unity的纯C#脚本模式,UE的双轨制开发允许程序员用C++处理核心逻辑,同时通过蓝图让非程序员快速迭代。文章对比了两大引擎的编程范式差异,指出UE的反射系统和宏标记提供了更精细的控制,并给出了从Unity迁移到UE的具体思路。核心在于理解UE的混合编程优势:C++保证性能,蓝图提升开发效率,二者通过宏系统无缝协作。

qq_46348216的博客 1407

EasyExcel

Apache POI是Apache软件基金会的开源函式库,提供跨平台的Java API实现Microsoft Office格式档案读写。对POI有过深入了解的应该知道POI有SAX模式(Dom解析模式)。但SAX模式相对比较复杂,excel有03(xls文件)和07(xlsx文件)两种版本,两个版本数据存储方式截然不同,sax解析方式也各不一样。

... 970

FPS游戏自动枪械识别+压枪(以PUBG为例)

使用Python3.9作为开发环境,实现一个通用的FPS游戏自动检测枪械配件+压枪宏的软件开发思路

weixin_37827742的博客 3万+

【亲测免费】 sax-js: 快速、轻量级的XML解析库

是一个用于处理XML数据的JavaScript库。它是一个基于事件的简单API,能够快速解析大型XML文档,而不会消耗大量的内存。 ## 什么是sax-js? `sax-js` 是一个简单的XML解析器,支持同步和异步操作。通过将XML解析成一系列的事件,如开始元素、结束元素、字符数据等,你可以轻松地处理XML文档中的信息。 ## sax-js可以用来做什么? `sax-js` 可以用来处...

gitblog_00001的博客 899

RT-Thread NANO V4.1.1 在Cortex-M0上移植踩坑记:解决内存分配宏导致的线程创建失败

本文详细记录了RT-Thread NANO V4.1.1在Cortex-M0平台移植过程中遇到的内存分配宏配置问题,导致线程创建失败的解决方案。通过分析内存管理算法宏定义,将RT_USING_SMALL_MEM替换为RT_USING_SMALL_MEM_AS_HEAP,成功解决了线程创建问题,为开发者提供了实用的移植经验。

weixin_29189373的博客 306

YOLOv8改进-bifpn

跨尺度连接通过添加一个跳跃连接和双向路径来实现,自此实现了加权融合和双向跨尺度连接。该结构是加权且双向连接的,即自顶向下和自底向上结构,通过构造双向通道实现跨尺度连接,将特征提取网络中的特征直接与自下而上路径中的相对大小特征融合,保留了更浅的语义信息,而不会丢失太多的深层语义信息。传统的特征融合时将尺度不同的特征图以相同权重进行加权,但是当输入的特征图分辨率不同时,以相同的权重进行加权对输出的特征图不平等。所以BiFPN根据不同输入特征的重要性设置不同的权重,同时反复采用这种结构来加强特征融合。

weixin_45303602的博客 8139

SAX 解析和设计模式的运用

我们在使用 SAX 对 XML 文件做解析时,往往会被大量的 if 或者 switch 语句所困绕。如果使用适当的设计模式,结合适当的算法,则可以避免在解析程序中到处散布着大量的判断语句。 关于SAX 了解XMLParser的朋友都应该知道,XML有2种基本的解析方式:DOM(DocumentObject Model)、SAX(Simple Api ofXml)。其中,SAX对

kongji103的博客 1606

从入门到精通|Yalmip+Cplex在电力系统中的应用(Matlab代码实现)

​ 火电成本包括运行成本、碳捕集成本,其中火电运行成本由运行维护成 本和发电煤耗成本构成,发电煤耗与其出力关系: ,F 为机组煤耗量(kg/h),P 为机组出力/MW;运行维护成本按照 0.5 倍煤耗成本考虑,碳捕集成本取决于碳排放量及碳捕集单价,火电机组相关参数如附表 1 所示,电煤价格为 700 元/t ​

weixin_46039719的博客 1552

SAX,功能强大的 API(转)

本文由即将出版的 XML by Example第二版中的一章改编,介绍了 SAX,它是用于处理 XML 的基于事件的 API,SAX 是对“文档对象模型”或者 DOM 的补充,DOM 是用于由 W3C 发布的 XML 语法分析器的基于对象的 API。 您将了解到,SAX: 是基于事件的 API。 在一个比 D...

a551546150的博客 173

海康VisionMaster与西门子PLC1200进行S7通信

通信是连通算法平台和外部设备的重要渠道,在海康VisionMaster算法平台中既支持外部数据的读入也支持数据的写出,当通信构建起来以后既可以把软件处理结果发送给外界,又可以通过外界发送字符来触发相机拍照或者软件运行。 本文以西门子PLC1200作为外部设备为例,讲解海康VisionMaster与西门子PLC1200进行S7通信。......

haier123888的博客 1万+

AI产品经理面试宝典第9天:关于AI产品经理的挑战与转型怎么回答

AI技术重构产品逻辑,AI产品经理需直面三大核心命题:用户定位差异、技术融合边界、商业价值转化。本文从千道真题中提炼出面试高频考点,剖析AI产品经理如何突破传统思维定式,在B端需求与C端体验间构建价值桥梁,用数据驱动产品创新,破解技术落地的隐性壁垒。掌握这三重跃迁法则,助你在AI浪潮中脱颖而出。

打造全国最全的AI Agent开发知识领域的博客 175

SAX,功能强大的API

--在摘自XML by Example的本篇预览中比较了DOM和SAX,然后开始使用SAX Benoit Marchal(bmarchal@pineapplesoft.com) 顾问,Pineapplesoft 2001 年 8 月 这篇对 Benoit Marchal 所著的 XML by Example 第二版的预览给出了对 SAX 的翔实介绍,SAX 是用于处理 XML

jixianzilang的专栏 870

基于STC8G1K08A单片机的PCA捕获外部信号(100~1000HZ)程序实现

基于STC8G1K08A单片机的PCA捕获外部信号(100~1000HZ)程序实现

weixin_74013772的博客 255

Java解析xml——DOM、SAX、JDOM和DOM4J四种方式的汇总与比较

一、四种方式的对比 【DOM】 DOM解析是将xml整个读入内存中,并在内存中以DOM树的形式存储。它是与平台无关的官方解析方式,所以不止在Java平台可以用DOM解析,在其他的平台也可以使用。 -优点: 1、树形结构,直观,易于理解,读取方便,代码易编写。 2、树形结构保存在内存中,容易修改。 -缺点 1、因为DOM是将xml文件整个读入内存中,所以对内存消耗大,在xml文件较大时

记录只为更好地前行 1691

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

JAVA SE 读写XML(DOM、SAX、JDOM、DOM4J区别)

简介 相关文章——读写xml系列方式: DOM:文章地址 SAX:文章地址 JDOM:文章地址 DOM4J:文章地址 目的(获取): 节点名 节点值 属性名 属性值 四种方式对比 DOM 优点: 形成了树结构,直观好理解,代码更易编写 解析过程中树结构保留在内存中,方便修改 缺点: 当xml文件较大时,对内存耗费比较大,容易影响解析性能并造成内存溢出 SAX 优点...

遇见1995 326

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

Java中解析XML文件

在Java中解析XML文件共有四种方式A、DOM方式解析XML数据树结构,有助于更好地理解、掌握,代码易于编写,在解析过程中树结构是保存在内存中,方便修改B、SAX方式解析采用事件驱动模式,对内存消耗比较小,适用于仅处理xml中的数据时使用C、JDOM方式解析大量采用了 Collections 类D、DOM4J方式解析JDOM的一种智能分支,合并了许多超出基本XML文档表示的功能

QQ156881887的专栏 5402

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

XML和JSON解析

XML 理解XML: 可扩展标记语言(eXtensible Markup Language)。 特性: xml具有平台无关性, 是一门独立的标记语言. xml具有自我描述性 关于其他的XML这里不做过多了解,咋们这章主要来看看如何使用XML解析的 XML解析的方式 首先XML的解析方式大体分为4种,不过现在的一些企业项目一般使用较少。。。 那么看看这4种方式: SAX解析 DOM解析 JDOM解析 DOM4J解析 SAX解析方式: 解析方式是事件驱动机制 ! SAX解析器, 逐行读取XML文件

weixin_44230419的博客 285

Java中几种解析XML的方式,以及优缺点

Java,XML,Dom4j

weixin_48891324的博客 1368

从AI单点调用到生产级工作流:钩子与管道工程化实践

在AI技术应用中,工作流自动化是连接模型能力与生产部署的关键桥梁。其核心原理在于通过任务编排和状态管理,将离散的AI调用转化为稳定、可复用的流程。从技术价值看,这不仅提升了处理效率,更通过错误处理、重试机制和可观测性设计,确保了复杂任务执行的可靠性。在应用场景上,无论是批量图片处理、多步骤内容生成,还是数据预处理与模型推理串联,自动化工作流都能显著降低运维复杂度。本文聚焦的‘钩子’与‘管道’模式,正是实现这一目标的有效方法论——钩子在任务生命周期关键节点嵌入管理逻辑,管道则将复杂流程分解为可组合的步骤,共同

weixin_34319111的博客 410

JAVA解析和创建XML文件的四种方法(三)

四种方法的比较 一、DOM 优点: 1、形成了树结构,有助于更好的理解、掌握,且代码容易编写。 2、解析过程中,树结构保存在内存中,方便修改。 缺点: 1、由于文件是一次性读取,所以对内存的耗费比较大。 2、如果XML文件比较大,容易影响解析性能且可能会造成内存溢出。 二、SAX 优点: 1、采用事件驱动模式,对内存耗费比较小。 2、适用于只处理XML文件中的数据时。 缺点: 1、编码比较麻烦。 ...

weixin_44462294的博客 352

计划用此账号整理分享一下Java可能遇见的面试题

Java中有几种XML解析方式 ? 分别是什么 ? 有什么样的优缺点 ? 可以是四种,但本质上是两种,后面两种是基于前面两种做的提升。 1.SAX解析 解析方式是事件驱动机制 ! SAX解析器,逐行读取XML文件解析 , 每当解析到一个标签的开始/结束/内容/属性时,触发事件. 我们可以编写程序在这些事件发生时, 进行相应的处理。 优点:分析能够立即开始,而不是等待所有的数据被处理逐行加载,节省内存。有助于解析大于系统内存的文档 ,有时不必解析整个文档,它可以在某个条件得到满足时停止解析。 缺..

Fly_666888的博客 127

java 解析中文xml_java如何解析XML

DOM方式解析XMLDom解析是将xml文件全部载入到内存,组装成一颗dom树,然后通过节点以及节点之间的关系来解析xml文件,与平台无关,java提供的一种基础的解析XML文件的API,理解较简单,但是由于整个文档都需要载入内存,不适用于文档较大时。 (推荐学习:java课程)SAX方式解析XML基于事件驱动,逐条解析,适用于只处理xml数据,不易编码,而且很难同时访问同一个文档中的...

weixin_42358594的博客 522

java解析xml文件的开源工具 — dom4j

dom4j是一个Java的XML API,类似于jdom,用来读写XML文件的。dom4j是一个非常非常优秀的JavaXML API,具有性能优异、功能强大和极端易用使用的特点,同时它也是一个开放源代码的软件,可以在SourceForge上找到它。在IBM developerWorks上面可以找到一篇文章,对主流的Java XML API进行的性能、功能和易用性的评测,dom4j无论在哪个方面

IT小虫 1520

四种xml 解析的特点

DOM 解析: 形成了树结构,有助于更好的理解、掌握,且代码容易编写。 解析过程中,树结构保存在内存中,方便修改。 2)SAX 解析: 采用事件驱动模式,对内存耗费比较小。 适用于只处理 XML 文件中的数据时 3)JDOM 解析: 仅使用具体类,而不使用接口。 API 大量使用了 Collections 类。4)DOM4J 解析: JDOM 的一种智能分支,它合并了许多超出基本 XML 文档表 示的功能。 它使用接口和抽象基本类方法。 具有性能优异、灵活性好

猿小白IT 308

XML文件解析

1、dom4j Dom4j是一个易用的、开源的库,用于XML、XPath和XSLT。 它应用于Java平台,采用了Java集合框架并完全支持DOM、SAX和JAXP。 在Java开发社区里,DOM4J的使用最为广泛。 JDOM的操作非常简单,但是在处理大XML文件时性能表现不佳,解析10M文档时就会内存溢出。 而DOM4J是一个非常优秀的Java XML API,具有性能优异、功能强大和易于使用的...

Li_Nn_偏头痛 192
上一篇: android log
下一篇: xStream完美转换XML、JSON
lb8607
博客等级 码龄18年 17粉丝 18原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值