技巧: 设置 SAX 解析器

在 Java 中使用 XML 是一个内容相当丰富的主题;可以使用多个 API,而且许多 API 使得使用 XML 简单得如同从文本文档读取行。基于树的 API(如 DOM)展现了一个内存中的 XML 结构,该结构对于 GUI 和编辑器来说是最理想的,基于流的 API(如 SAX)对于只需要获取文档数据的高性能应用程序来说很重要。在本技巧文章系列中,我将从基础知识开始一步步地教您如何在 Java 中使用 XML。同时,您将学习许多甚至连众多专业人士都不知道的诀窍,所以即使您已经具有一些 XML 经验,也应该仔细阅读本教程。

我从 SAX(Simple API for XML)开始。虽然该 API 或许是 Java 和 XML API 中最难以掌握的,但它可能也是功能最强的 API。另外,大多数其它 API 实现(象 DOM 解析器、JDOM 和 dom4j 等)都部分地基于 SAX 解析器。对于用 XML 和 Java 语言所做的每一件事情,理解 SAX 会给予您一个良好的开端。特别在本篇技巧文章中,我将讨论如何获取 SAX 解析器实例以及如何对该解析器设置一些基本功能和属性。

:我假设您已经下载了一个符合 SAX 的解析器(如 Apache Xerces-J)(请参阅 参考资料以获取链接)。Apache 站点有大量关于如何进行设置的信息,但是,基本上您只需要将已下载的 JAR 文件放入 CLASSPATH 中。这些示例假设您的解析器可用。

获取解析器

使用 SAX 的第一步实际上是获取解析器实例。在 SAX 中,由 org.xml.sax.XMLReader 类的实例表示解析器。我在上一篇技巧文章(“Achieving vendor independence with SAX”— 请参阅 参考资料)中对它进行了详细讨论,所以我将不会在这里花大量的时间在上面。清单 1 显示了在无需编写与供应商相关的代码的情况下获取新 SAX 解析器实例的正确方法。


清单 1. 获取 SAX 解析器实例

// Obtain an instance of an XMLReader implementation from a system property
XMLReader parser = org.xml.sax.helpers.XMLReaderFactory.createXMLReader(); 

 

通过使用这个方法,您需要将系统属性 org.xml.sax.driver 设置成想要装入的解析器的类名。这是特定于供应商的类;对于 Xerces,它应该是 org.apache.xerces.parsers.SAXParser 。用 -D 开关将这个参数指定给 Java 编译器:

java -Dorg.xml.sax.driver=org.apache.xerces.parsers.SAXParser some.sample.Class

 

当然,您要确保指定的类存在并在类路径上。


功能

一旦有了解析器实例,就需要配置它。请注意,这与设置解析器来处理 XML 中的错误、内容或结构不同;相反,配置是实际告诉解析器如何操作的过程。您可以打开验证、关闭名称空间检查及扩展实体。这些行为完全独立于特定的 XML 文档,因此涉及与新解析器实例的交互。

注:对于那些过于急燥的人来说(我知道您不是这样的),我当然会处理内容、错误处理及类似的东西。然而,这些主题将在未来的技巧文章中讨论,所以您还得复查。眼下,我们只关注配置、功能和属性。

可以用两种方法配置解析器:功能和属性。 功能包括打开或关闭特定功能,比如验证。 属性包括设置解析器所使用的特定项的值,如用来验证所有文档的模式位置。我将先讨论功能,然后在下一节研究属性。

功能是通过解析器上名为 setFeature() 的方法设置的,这一点并不奇怪。语法类似于清单 2 所示。


清单 2. 设置 SAX 解析器的功能

// Obtain an instance of an XMLReader implementation from a system property
XMLReader parser = org.xml.sax.helpers.XMLReaderFactory.createXMLReader(); 
String featureName = "some feature URI";
boolean featureOn = true;
try {
  parser.setFeature(featureName, featureOn);
} catch (SAXNotRecognizedException e) {
  System.err.println("Unknown feature specified: " + e.getMessage());
} catch (SAXNotSupportedException e) {
  System.err.println("Unsupported feature specified: " + e.getMessage());
} catch (SAXException e) {
  System.err.println("Error in setting feature: " + e.getMessage());
}

 

这相当清楚,不需要说明;关键是知道可用于 SAX 解析器的常见功能。每个功能均由一个特定的 URI 标识。可以从 SAX 网站在线获得这些 URI 的完整列表(请参阅 参考资料)。一些最常见的功能是验证和名称空间处理。清单 3 显示了设置这两种属性的示例。


清单 3. 一些常见功能

// Obtain an instance of an XMLReader implementation from a system property
XMLReader parser = org.xml.sax.helpers.XMLReaderFactory.createXMLReader(); 
try {
  // Turn on validation
  parser.setFeature("http://xml.org/sax/features/validation", true);
  // Ensure namespace processing is on (the default)
  parser.setFeature("http://xml.org/sax/features/namespaces", true);
} catch (SAXNotRecognizedException e) {
  System.err.println("Unknown feature specified: " + e.getMessage());
} catch (SAXNotSupportedException e) {
  System.err.println("Unsupported feature specified: " + e.getMessage());
} catch (SAXException e) {
  System.err.println("Error in setting feature: " + e.getMessage());
}

 

请注意,虽然解析器有几个标准 SAX 功能,但这些解析器可以自由地添加自己的特定于供应商的功能。例如,Apache Xerces-J 添加了一些考虑动态验证以及遇到致命错误之后继续处理的功能。请参考解析器供应商的文档,以获取相关的功能 URI。


属性

一旦理解了功能,就很容易理解属性。除了属性将对象作为参数而功能获取布尔值外,它们以完全相同的方式操作。我们使用 setProperty() 方法来设置属性,如清单 4 所示。


清单 4. 设置 SAX 解析器的属性

// Obtain an instance of an XMLReader implementation from a system property
XMLReader parser = org.xml.sax.helpers.XMLReaderFactory.createXMLReader(); 
String propertyName = "some property URI";
try {
  parser.setProperty(propertyName, obj-arg);
} catch (SAXNotRecognizedException e) {
  System.err.println("Unknown property specified: " + e.getMessage());
} catch (SAXNotSupportedException e) {
  System.err.println("Unsupported property specified: " + e.getMessage());
} catch (SAXException e) {
  System.err.println("Error in setting property: " + e.getMessage());
}

 

这里使用相同的错误处理框架,所以您可以容易地在两种类型的配置选项之间复制代码。和功能一样,SAX 提供了一组标准属性,供应商可以添加他们自己的扩展。常见的 SAX 标准的属性考虑到了设置词法处理程序(Lexical Handler)和声明处理程序(Declaration Handler)(我将在以后的技巧文章中讨论这两个处理程序)。像 Apache Xerces 之类的解析器对它们进行了扩展,例如,使它们能够设置输入缓冲区大小以及要在验证中使用的外部模式的位置。清单 5 显示了几个实际使用中的属性。


清单 5. 一些常见属性

// Obtain an instance of an XMLReader implementation from a system property
XMLReader parser = org.xml.sax.helpers.XMLReaderFactory.createXMLReader(); 
try {
  // Set the chunk to read in by SAX
  parser.setProperty("http://apache.org/xml/properties/input-buffer-size", 
      new Integer(2048));
  // Set a LexicalHandler
  parser.setProperty("http://xml.org/sax/properties/lexical-handler", 
      new MyLexicalHandler());
} catch (SAXNotRecognizedException e) {
  System.err.println("Unknown feature specified: " + e.getMessage());
} catch (SAXNotSupportedException e) {
  System.err.println("Unsupported feature specified: " + e.getMessage());
} catch (SAXException e) {
  System.err.println("Error in setting feature: " + e.getMessage());
}

 

通过理解功能和属性,您几乎可以让解析器做任何事情。一旦掌握了用这种方式来设置解析器,您就可以准备阅读我的下一篇技巧文章,该文章将讨论有关构建基本内容处理程序方面的知识。到那时,我们将在 XML and Java technology 论坛上再见。

 

在我的 上一个技巧 中, 您学习了如何设置 SAX 分析器的某些基本分析特性和属性(以 XMLReader 类实例的形式)。那些特性和属性全都跟分析器与之交互的所有 XML 文档的基本处理相关,并且包括诸如验证、名字空间处理和实体扩充等内容。虽然这些当然是分析的重要方面,但是它们不是为特定文档格式(比如处理在线商店订单的 XML 或者表示机械工厂库存的 XML)定制的。当谈到编写与分析过程本身交互的逻辑时,您需要编写一个 SAX ContentHandler

ContentHandler 是一个特殊的 SAX 接口,位于 org.xml.sax.ContentHandler 。这个接口定义了如清单 1 所示的方法;您应该熟悉这些方法,因为它们是所有 SAX 处理的基础。


清单 1. The org.xml.sax.ContentHandler 接口

package org.xml.sax;
public interface ContentHandler
{
    public void setDocumentLocator (Locator locator);
    public void startDocument ()
	throws SAXException;
    public void endDocument()
	throws SAXException;
    public void startPrefixMapping (String prefix, String uri)
	throws SAXException;
    public void endPrefixMapping (String prefix)
	throws SAXException;
    public void startElement (String uri, String localName,
			      String qName, Attributes atts)
	throws SAXException;
    public void endElement (String uri, String localName,
			    String qName)
	throws SAXException;
    public void characters (char ch[], int start, int length)
	throws SAXException;
    public void ignorableWhitespace (char ch[], int start, int length)
	throws SAXException;
    public void processingInstruction (String target, String data)
	throws SAXException;
    public void skippedEntity (String name)
	throws SAXException;
}

这个接口中的每个方法都提供了一个将自定义代码插入 XML 分析过程的钩子(hook)。然而在深入到每个方法的细节之前,我将展示一个虚构的实现,并展示如何将该实现注册到分析器中。清单 2 是一个简单的处理器 DummyHandler ,它为所有必需的 ContentHandler 方法提供空的方法体。


清单 2. 实现 ContentHandler 接口(非常简单)

import org.xml.sax.*;
public class DummyHandler implements ContentHandler
{
    public void setDocumentLocator (Locator locator) { }
    public void startDocument ()
	throws SAXException  { }
    public void endDocument()
	throws SAXException  { }
    public void startPrefixMapping (String prefix, String uri)
	throws SAXException  { }
    public void endPrefixMapping (String prefix)
	throws SAXException  { }
    public void startElement (String uri, String localName,
			      String qName, Attributes atts)
	throws SAXException  { }
    public void endElement (String uri, String localName,
			    String qName)
	throws SAXException { }
    public void characters (char ch[], int start, int length)
	throws SAXException { }
    public void ignorableWhitespace (char ch[], int start, int length)
	throws SAXException { }
    public void processingInstruction (String target, String data)
	throws SAXException { }
    public void skippedEntity (String name)
	throws SAXException { }
}

现在,您可以创建一个 DummyHandler 实例并将它附加到分析器,如清单 3 所示。


清单 3. 注册 ContentHandler

// Obtain an instance of an XMLReader implementation 
// from a system property
XMLReader 
    parser = org.xml.sax.helpers.XMLReaderFactory.createXMLReader();
// Create a new instance and register it with the parser
ContentHandler contentHandler = new DummyHandler();
parser.setContentHandler(contentHandler);
// Don't worry about this for now -- we'll get to it later
parser.parse(myXMLURI);

要确切理解分析器和 ContentHandler 之间的交互是如何进行的,您需要首先理解什么是 回调方法。当 XML 分析器开始分析 XML 输入文档时,它会遇到某些特殊的事件,比如文档的开头、元素中的字符数据以及元素的结尾。这其中的每个事件都与 ContentHandler 接口中的某个特定方法具有某种关联;就这里的例子而言,相关的方法分别是 startDocument()characters()endElement() 。对于每个事件,分析器都要 回调到内容处理器,临时地把控制权连同关于该事件的某些信息(比如元素的名称或者正在处理的字符)转交给内容处理器。正是在这一点,在回调方法中,您的编程逻辑参与进来了。当回调方法结束时,程序流将返回到分析器,分析过程将再次重复。

这听起来有点不好理解,因此我将提供一个例子。清单 4 中的例子类名为 HelloHandler ,它具有非常简单的输出语句,这些语句嵌入到 ContentHandler 接口的每个方法中。您可以使用它来进行一些简单的测试。


清单 4. HelloHandler

import org.xml.sax.*;
public class HelloHandler implements ContentHandler
{
    public void setDocumentLocator (Locator locator) { 
	  System.out.println("Hello from setDocumentLocator()!"); 
	}
    public void startDocument ()
	throws SAXException  { 
	  System.out.println("Hello from startDocument()!"); 
	}
    public void endDocument()
	throws SAXException {  
	  System.out.println("Hello from endDocument()!"); 
	}
    public void startPrefixMapping (String prefix, String uri)
	throws SAXException {  
	  System.out.println("Hello from startPrefixMapping()!"); 
	}
    public void endPrefixMapping (String prefix)
	throws SAXException  {  
	  System.out.println("Hello from endPrefixMapping()!"); 
	}
    public void startElement (String uri, String localName,
			      String qName, Attributes atts)
	throws SAXException  {  
	  System.out.println("Hello from startElement()!"); 
	}
    public void endElement (String uri, String localName,
			    String qName)
	throws SAXException {  
	  System.out.println("Hello from endElement()!"); 
	}
    public void characters (char ch[], int start, int length)
	throws SAXException {  
	  System.out.println("Hello from characters()!"); 
	}
    public void ignorableWhitespace (char ch[], int start, int length)
	throws SAXException {  
	  System.out.println("Hello from ignorableWhitespace()!"); 
	}
    public void processingInstruction (String target, String data)
	throws SAXException {  
	  System.out.println("Hello from processingInstruction()!"); 
	}
    public void skippedEntity (String name)
	throws SAXException {  
	  System.out.println("Hello from skippedEntity()!"); 
	}
}

这足够简单了,对吧?它展示了每个方法是在何时被调用的。惟一还没有做的事情就是定义一个 XML 文档,然后让一切运行起来。清单 5 显示了一个非常简单 XML 文档。


清单 5. foo.xml 文档

<?xml version="1.0"?>
<root>
  <some-element>Some content in the element</some-element>
  <some-other-element>
    <child>
      More content
    </child>
  </some-other-element>
</root>

现在可以运行这个测试分析程序了——清单 6 中包括了一个完整的工作版本以供参考,您应该会得到类似清单 7 所示的输出。


清单 6. TestParse 示例类

import org.xml.sax.*;
public class TestParse {
  public static void main(String[] args) {
    try {
      XMLReader parser = 
          org.xml.sax.helpers.XMLReaderFactory.createXMLReader();
      // Create a new instance and register it with the parser
      ContentHandler contentHandler = new HelloHandler();
      parser.setContentHandler(contentHandler);
      // Don't worry about this for now -- we'll get to it later
      parser.parse("foo.xml");
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}


清单 7. TestParse 类的输出
[aragorn:~/dev] bmclaugh% 
    java -Dorg.xml.sax.driver=org.apache.xerces.parsers.SAXParser 
    TestParse
Hello from setDocumentLocator()!
Hello from startDocument()!
Hello from startElement()!
Hello from characters()!
Hello from startElement()!
Hello from characters()!
Hello from endElement()!
Hello from characters()!
Hello from startElement()!
Hello from characters()!
Hello from startElement()!
Hello from characters()!
Hello from characters()!
Hello from endElement()!
Hello from characters()!
Hello from endElement()!
Hello from characters()!
Hello from endElement()!
Hello from endDocument()!

仔细观察一下这个输出,您会对回调方法是如何使用的有个初步的认识:当分析开始时,文档定位器得到设置(我将在以后的一个技巧中详细讨论这点),文档的开头得到处理,然后处理流程继续往下进行。首先是遇到一个元素的开头( some-element ),然后是一些内容,之后是该元素的结尾,等等。当然,这个处理器并不是很有用,因为它通常很难确切告诉您当前正在处理哪个元素,尤其是在有嵌套元素的情况下。

但是由于时间和空间所限,我将把改进这个处理器的任务留给读者作为练习。请试着编写这样一个 HelloHandler 版本(把它命名为 InfoHandler ),它会输出正在被调用的方法以及提供给该方法的参数。这样将帮助 您更清楚地看到每次回调中所发生的事情。在下一个技巧中,我将展示我自己为该处理器编写的代码,并将开始更深入地钻研回调,看看每次回调究竟做了些什么事情。届时,希望您有兴趣,我将在新闻组中和您在线相见。

 

参考资料

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值