ibm的x-usax教程 SAX教程

 

来源:ibm教程下载

SAX 是什么?

 

用于读取和操作 XML 文件的标准是文档对象模型(Document Object ModelDOM)。 遗憾的是,DOM 方法涉及读取整个文件并将该文件存储在一个树结构中,而这样可能是低效的、缓慢的,并且很消耗资源。

一种替代技术就是 Simple API for XML,或称为 SAXSAX 允许您在读取文档时处理它,从而不必等待整个文档被存储之后才采取操作。

SAX 是由 XML-DEV 邮件列表的成员开发的,对应的 Java 版本现在是一个 SourceForge 项目(请参阅参考资料)。该项目的目的是为 XML 的使用提供一种更自然的手段 —— 换句话说,也就是不涉及 DOM 所必需的开销和概念跳跃。

项目的成果是一个基于事件 API。解析器向一个事件处理程序发送事件,比如元素开始和元素结束,而事件处理器则处理该信息。然后应用程序本身就能够处理该数据。原始的文档仍然保留完好无损,但是 SAX 提供了操作数据的手段,而后数据可以引入另一个进程或文档。

SAX 没有官方的标准机构;它不由万维网联盟(Wide Web ConsortiumW3C)或其他任何官方机构维护,但它是 XML 社区事实上的标准。

工具

    

本教程中的例子(您应该试验一下它们)需要安装以下工具,并确保它们能正确地工作。运行这些例子并不是理解本教程所必需的。

SAX 处理是如何工作的

SAX 在读取 XML 流的同时处理它们,这很像以前的自动收报机纸带(ticker tape)。请考虑下面的 XML 代码片断:


   
   
   
   

   
   
    
    
   
   
   
   
   
    
    UNIX
   
   
   
   
   
   
   
    
    color
   
   
   
   

  
  

分析这个代码片断的 SAX 处理器一般情况下将产生以下事件:

Start document
   
   
Start element (samples)
   
   
Characters (white space)
   
   
Start element (server)
   
   
Characters (UNIX)
   
   
End element (server)
   
   
Characters (white space)
   
   
Start element (monitor)
   
   
Characters (color)
   
   
End element (monitor)
   
   
Characters (white space)
   
   
End element (samples)
  
  

SAX API 允许开发人员捕捉这些事件并对它们作出反应。

SAX 处理涉及以下步骤:

  1. 创建一个事件处理程序。
  2. 创建 SAX 解析器。
  3. 向解析器分配事件处理程序。
  4. 解析文档,同时向事件处理程序发送每个事件。

基于事件的处理的优点和缺点

 

这种处理的优点非常类似于流媒体的优点。分析能够立即开始,而不是等待所有的数据被处理。而且,由于应用程序只是在读取数据时检查数据,因此不需要将数据存储在内存中。这对于大型文档来说是个巨大的优点。事实上,应用程序甚至不必解析整个文档;它可以在某个条件得到满足时停止解析。一般来说,SAX 还比它的替代者 DOM 快许多。

另一方面,由于应用程序没有以任何方式存储数据,使用 SAX 来更改数据或在数据流中往后移是不可能的。

示例文件

 

本教程将展示如何构造这样一个应用程序,它使用 SAX 来记录一组用户的答案,这些用户被要求就他们与外星人的遭遇接受调查。

下面是调查表:

答案将存储在一个 XML 文件中:


   
   
   
   

   
   
    
    
   
   

   
   
    
    
   
   
   
   
   
    
    A
   
   
   
   
   
   
   
    
    B
   
   
   
   
   
   
   
    
    A
   
   
   
   
   
   
   
    
    D
   
   
   
   
   
   
   
    
    B
   
   
   
   

   
   

   
   
    
    
   
   
   
   
   
    
    C
   
   
   
   
   
   
   
    
    A
   
   
   
   
   
   
   
    
    A
   
   
   
   
   
   
   
    
    D
   
   
   
   
   
   
   
    
    A
   
   
   
   

   
   

   
   
    
    
   
   
   
   
   
    
    A
   
   
   
   
   
   
   
    
    C
   
   
   
   
   
   
   
    
    A
   
   
   
   
   
   
   
    
    D
   
   
   
   
   
   
   
    
    C
   
   
   
   

   
   

  
  

创建事件处理程序

 

在应用程序能够使用 SAX 来处理 XML 文档之前,它必须首先创建一个事件处理程序。SAX 提供了一个类DefaultHandler,应用程序可以扩展该类。

要使用 SAX 来进行正确的解析,需要对处理程序的方法进行特殊调用,而这些调用 不能是静态的。这意味着您需要特别地实例化处理程序对象,因此下面将提供简单的概述,以防您还不习惯使用对象。

当新的对象被创建时,它会寻找要执行的任何类构造函数。例如:

import org.xml.sax.helpers.DefaultHandler;
   
   
public class SurveyReader extends DefaultHandler
   
   
{
   
   
   public SurveyReader() {
    
    
      System.out.println("Object Created.");
    
    
   }
   
   
   public void showEvent(String name) {
   
   
      System.out.println("Hello, "+name+"!");
   
   
   }
   
   
   public static void main (String args[]) { 
   
   
      SurveyReader reader = new SurveyReader();
   
   
      reader.showEvent("Nick");
   
   
   }
   
   
}
  
  

main 方法执行时,它会创建 SurveyReader 类的一个新实例。这样会导致构造函数执行,从而输出 Object Created(以及下面的问候语)。然后您可以使用该对象来执行 showEvent() 方法。

接指定 SAX 驱动程序

 

在准备好事件处理程序之后,下一步就是使用 SAX 驱动程序来创建解析器,或 XMLReader。您可以使用三种方式中的任一种来创建解析器:

  • 直接调用驱动程序
  • 允许在运行时指定驱动程序
  • 将驱动程序作为 createXMLReader() 的参数来传递

如果知道 SAX 驱动程序的类名称,您可以直接调用它。例如,如果该类是(虚构的)com.nc.xml.SAXDriver,您可以使用如下代码:

try {
   
   
   XMLReader xmlReader = new com.nc.xml.SAXDriver();
   
   
} catch (Exception e) {
   
   
   System.out.println("Can't create the parser: " + e.getMessage());
   
   
}
  
  

来直接创建 XMLReader

您还可以使用系统属性来使得应用程序更灵活。例如,您可以在运行应用程序时,从命令行将类名称作为 org.xml.sax.driver 属性的值来指定。

java -Dorg.xml.sax.driver=com.nc.xml.SAXDriver SurveyReader
  
  

(注意,-D 后面 应该有空格。)

这使得该信息对 XMLReaderFactory 可用,因此您可以使用这样的代码:

try {
   
   
   XMLReader xmlReader = XMLReaderFactory.createXMLReader();
   
   
} catch (Exception e) {
   
   
   System.out.println("Can't create the parser: " + e.getMessage());
   
   
}
  
  

如果您知道驱动程序名称,还可以将它作为 createXMLReader() 的参数来直接传递。

创建解析器

 

这个例子使用了一对类 SAXParserFactory SAXParser 来创建解析器,因此您不必知道驱动程序本身的名称。

首先声明 XMLReaderxmlReader,然后使用 SAXParserFactory 来创建一个 SAXParser XMLReader 是由 SAXParser 提供的。

import org.xml.sax.helpers.DefaultHandler;
   
   
import javax.xml.parsers.SAXParser;
    
    
import javax.xml.parsers.SAXParserFactory;
    
    
import org.xml.sax.XMLReader;
   
   
public class SurveyReader extends DefaultHandler
   
   
{
   
   
   public SurveyReader() {
   
   
   }
   
   
   public static void main (String args[]) {  
   
   
   XMLReader xmlReader = null;
    
    
      try {
    
    
         SAXParserFactory spfactory = SAXParserFactory.newInstance();
    
    
         SAXParser saxParser = spfactory.newSAXParser();
    
    
         xmlReader = saxParser.getXMLReader();
    
    
      } catch (Exception e) {
    
    
            System.err.println(e);
    
    
            System.exit(1);
    
    
      }
   
   
   }
   
   

验证与非验证解析器

要使用 XML 文档做任何事情,你都必须读取其中的信息。做这个工作的应用程序称为解析器

解析器的两种类型分别是:非验证 验证

  • 非验证解析器是适用于格式良好(well-formed)文档的解析器。 它读取每个信息单元,并将其添加到文档 —— 或者在 SAX 应用程序的情况下处理事件,而不管实际的结构和内容如何。
  • 另一方面,验证解析器根据已定义的语法检查 XML 文档的内容和结构。 有时,这个语法是文档类型定义(Document Type DefinitionDTD)的形式,但是近来它更可能在 XML Schema 文档中定义。在任一种情况下,解析器都会检查文档,以确保每个元素和属性都已定义,并且包含正确类型的内容。例如,您可以指定每个 order(订单) 都有一个status(状态) 如果在没有语法定义的情况下尝试创建文档,验证解析器将会提示错误。

已经由验证解析器检验过的文档被认为是有效的文档。

设置验证选项

 

对于本教程,您将不会验证调查结果,因此可以通过设置 validating 属性,关闭 SAXParserFactory 创建的任何解析器的验证特性:

...
   
   
public static void main (String args[]) {
   
   
   XMLReader xmlReader = null;
   
   
   try {
   
   
      SAXParserFactory spfactory = 
   
   
           SAXParserFactory.newInstance();
    
    
      spfactory.setValidating(false);
   
   
      SAXParser saxParser = 
   
   
                 spfactory.newSAXParser();
   
   
      xmlReader = saxParser.getXMLReader();
   
   
   } catch (Exception e) {
   
   
         System.err.println(e);
   
   
         System.exit(1);
   
   
   }
   
   
}
   
   
...

设置内容处理程序

 

解析器必须将它的事件发送到一个 ContentHandler。为简单起见,SurveyReader 将同时用作主应用程序和内容处理程序,因此创建它的一个新实例,并使用 XMLReader setContentHandler() 方法来将它设置为 ContentHandler

...
   
   
    xmlReader = saxParser.getXMLReader();
    
    
    xmlReader.setContentHandler(new SurveyReader());
   
   
 } catch (Exception e) {
   
   
...
  
  

当然,这是内容处理程序的唯一选项。当本教程在后面考察序列化 SAX 时,您还会看到其他选项。

解析 InputSource

要实际解析一个文件(或解析其他任何内容!)您需要一个 InputSource。这个 SAX 类包装了你要处理的任何数据,因此您不必(太)关心它来自何处。

现在您就准备好实际解析文件了。parse() 方法接受该文件,将它包装到 InputSource 中,然后处理它,同时将每个事件发送给 ContentHander

...
   
   
import org.xml.sax.InputSource;
   
   
...
   
   
    xmlReader = saxParser.getXMLReader();
   
   
    xmlReader.setContentHandler(new SurveyReader()); 
   
   
    InputSource source = new InputSource("surveys.xml");
    
    
    xmlReader.parse(source);
   
   
 } catch (Exception e) {
   
   
...
  
  

您可以编译和运行这个程序,但此时不应该会发生任何事情,因为应用程序还没有定义任何事件。

设置 ErrorHandler

此时什么事情也不 应该 会发生,当然,您尝试要处理的数据总是可能存在问题。在这样的情形下,有一个处理错误以及处理内容的处理程序是有所帮助的。

您可以像创建内容处理程序一样创建一个错误处理程序。通常,您会把这个处理程序创建为 ErrorHandler 的一个单独的实例,但是为简化这里的例子,我们将把错误处理直接包括在 SurveyResults 中。这种双重用途是可以实现的,因为该类扩展了 DefaultHandler,因而同时包括了 ContentHandler 方法和 ErrorHandler 方法的实现。

设置新的 ErrorHandler 就像设置 ContentHandler 一样:

...
   
   
xmlReader.setContentHandler(new SurveyReader());
    
    
xmlReader.setErrorHandler(new SurveyReader());
   
   
InputSource source = new InputSource("surveys.xml");
   
   
...
  
  

同样,如果此时运行应用程序,仍然应该什么事情也不会发生,因为每个事件的默认实现不做任何事情。在下一节中,我们将考察添加新的实现来处理解析期间发生的事件。

startDocument()

现在已经设置好了要解析的文档,下面可以开始使用在处理程序接收到适当的事件时实际做某件事情的反法,替换作为 DefaultHandler 的组成部分的默认实现。

首先使用 startDocument() 事件来开始文档,但是不做任何事情。这个事件像其他 SAX 事件一样,会抛出一个 SAXException

...
   
   
import org.xml.sax.SAXException;
   
   
public class SurveyReader extends DefaultHandler
   
   
{
   
   
   public SurveyReader() {
   
   
   }
   
   
   public void startDocument() throws SAXException {
    
    
      System.out.println("Tallying survey results...");   
    
    
   }
   
   
   public static void main (String args[]) {
   
   
      XMLReader xmlReader = null;
   
   
...
  
  

startElement()

 

现在开始查看实际的数据。对于每个元素,这个例子都回显传递给 startElement() 事件的名称(参见下面的“元素清单”屏幕快照)。

解析器实际上传递每个元素的多个信息:

  • 限定名称,或 qName 这实际上是名称空间信息(如果有的话)和元素的实际名称的组合。如果有的话,qName 还包括冒号(: —— 例如, revised:response
  • 名称空间 URI正如在名称空间中所讨论的,实际的名称空间是某种形式的 URI,而不是被添加到元素或属性名称的别名。例如,http://www.nicholaschase.com/surveys/revised/ 而不是简单的 revised:
  • 本地名称。这是元素的实际名称,比如 question。如果文档没有提供名称空间信息,解析器也许不能确定 qName 的哪个部分是 localName
  • 任何属性。元素的属性实际上是作为对象的集合来传递的,正如从下一小节中可以看到的那样。

首先列出每个元素的名称:

...
   
   
public void startElement(String namespaceURI, String localName, 
   
   
                         String qName, Attributes atts) throws SAXException {
   
   
    System.out.print("Start element: ");
   
   
    System.out.println(qName);
   
   
    for (int att = 0; att < atts.getLength(); att++) {
    
    
       String attName = atts.getQName(att);
    
    
       System.out.println("   " + attName + ": " + atts.getValue(attName));   
    
    
   } 
   
   
}
   
   
...   
  
  

startElement():检索属性

startElement() 事件还提供对元素属性的访问。这些属性在一个 Attributes 数据结构中传入。

您可以基于它在数组中的位置或基于属性的名称检索一个属性值。

...
   
   
public void startElement(String namespaceURI, String localName, 
   
   
                         String qName, Attributes atts) throws SAXException {
   
   
    System.out.print("Start element: ");
   
   
    System.out.println(qName);
   
   
    for (int att = 0; att < atts.getLength(); att++) {
    
    
       String attName = atts.getQName(att);
    
    
       System.out.println("   " + attName + ": " + atts.getValue(attName));   
    
    
   } 
   
   
}
   
   
...   
  
  

 

endElement()

 

您会找到很多关注元素结尾的很好理由。例如,它可能是处理元素内容的一个信号。这里您将使用它来在某种程度上完美地打印文档,同时缩进每一层元素。基本的思路是在新元素开始时增加缩进的值,在元素结束时减小缩进的值:

...
   
   
int indent = 0;
   
   
public void startDocument() throws SAXException {
   
   
   System.out.println("Tallying survey results...");   
   
   
   indent = -4;
   
   
}
    
    
public void printIndent(int indentSize) {
    
    
   for (int s = 0; s < indentSize; s++) {
    
    
      System.out.print(" ");
    
    
   }
    
    
} 
   
   
public void startElement(String namespaceURI, String localName, 
   
   
             String qName, Attributes atts) throws SAXException {
   
   
   indent = indent + 4;
    
    
   printIndent(indent);
   
   
   System.out.print("Start element: ");
   
   
   System.out.println(qName);
   
   
   for (int att = 0; att < atts.getLength(); att++) {
   
   
      printIndent(indent + 4);
   
   
      String attName = atts.getLocalName(att);
   
   
      System.out.println("   " + attName + ": " + atts.getValue(attName));   
   
   
   }
   
   
}
   
   
public void endElement(String namespaceURI, String localName, String qName)
    
    
   throws SAXException {
    
    
           
    
    
   printIndent(indent);
    
    
   System.out.println("End Element: "+localName);
    
    
   indent = indent - 4;   
    
    
}
   
   
...
  
  

characters()

现在您已经获得了元素,下面可以继续使用 characters() 来检索实际的数据。这里花一会儿功夫查看一下这个方法的签名:

public void characters(char[] ch, 
   
   
                       int start, 
   
   
                       int length) 
   
   
                       throws SAXException
  
  

注意在这个方法中,没有任何地方有任何信息表明这些字符属于哪个元素。如果您需要这个信息,就必须存储它。本例添加几个变量来存储当前元素和问题信息。(它还删除了所显示的大量无关信息。)

这里请注意两个重要的事项:

  • 范围: characters() 事件不仅包括不仅一个字符串。它还包括起始和长度信息。实际上,ch 字符数组包括整个文档。应用程序一定不能尝试读取馈送给 characters() 事件的范围之外的字符。
  • 频率:SAX 规范没有要求处理器以任何特定方式返回字符,因此在多个部分中返回单个文本块是可能的。在假设您有某个元素的所有内容之前,始终要确保endElement()事件已经发生。而且,处理器可能使用ignorableWhitespace()来返回某个元素内的空白。对验证解析器而言就总是这种情况。
...
   
   
public void printIndent(int indentSize) {
   
   
   for (int s = 0; s < indentSize; s++) {
   
   
      System.out.print(" ");
   
   
   }
   
   
}
    
    
String thisQuestion = "";
    
    
String thisElement = "";
   
   
public void startElement(String namespaceURI, String localName, 
   
   
                String qName, Attributes atts) throws SAXException {
    
    
   if (qName == "response") {
    
    
      System.out.println("User: " + atts.getValue("username"));
    
    
   } else if (qName == "question") {
    
    
      thisQuestion = atts.getValue("subject");
    
    
   } 
    
    
   thisElement = qName;
   
   
}
   
   
public void endElement(String namespaceURI, String localName, String qName)
   
   
                                                        throws SAXException {
    
    
   thisQuestion = "";
    
    
   thisElement = "";
   
   
}
    
    
public void characters(char[] ch, int start, int length)
    
    
                                             throws SAXException  {
    
    
   if (thisElement == "question") {
    
    
      printIndent(4);
    
    
      System.out.print(thisQuestion + ": ");
    
    
      System.out.println(new String(ch, start, length));
    
    
   }
    
    
}
   
   
...
  
  

记录答案

现在您已经获得了数据,下面可以继续添加实际的记录。

这就像在完成调查时构造一个字符串来进行分析一样简单。

...
   
   
String appearance = null;
    
    
String communication = null;
    
    
String ship = null;
    
    
String inside = null;
    
    
String implant = null;
   
   
public void characters(char[] ch,
   
   
                       int start,
   
   
                       int length)
   
   
                       throws SAXException  {
   
   
   if (thisElement == "question") {
    
    
      if (thisQuestion.equals("appearance")) {
    
    
         appearance = appearance + new String(ch, start, length);
    
    
      }
    
    
      if (thisQuestion.equals("communication")) {
    
    
         communication = communication + new String(ch, start, length);
    
    
      }
    
    
      if (thisQuestion.equals("ship")) {
    
    
         ship = ship + new String(ch, start, length);
    
    
      }
    
    
      if (thisQuestion.equals("inside")) {
    
    
         inside = inside + new String(ch, start, length);
    
    
      }
    
    
      if (thisQuestion.equals("implant")) {
    
    
         implant = implant  + new String(ch, start, length);
    
    
      }
   
   
   }
   
   
}
   
   
...
  
  

务必记住,您能够在 characters() 方法中执行这个任务的唯一原因,是因为您要寻找的回答仅有一个字符长度。 如果您要寻在的内容具有任意长度,就必须把从每次调用中获得的数据收集起来,然后在元素的末尾用 endElement() 方法来分析它。

ignorableWhitespace()

 

人工编制(而不是程序产生)的 XML 文档通常包括附加的空白,以使文档更易于阅读。空白包括换行、制表符和空格。 在大多数情况下,这种空白是无关的,应该在处理数据时忽略。

所有验证解析器以及某些非验证解析器不是在 characters() 中将这些空白字符传递给内容处理程序,而是在 ignorableWhitespace() 事件中传递的。这样很方便,因为这样您就可以仅集中在实际的数据上。

但是如果您确实 需要 空白,那该怎么办呢?在这样的场景中,您可以设置元素上的一个属性,以指示处理器不要忽略空白字符。这个属性就是 xml:space,它通常被假定为 default。(这意味着除非处理器的默认行为是保留空白,否则空白将被忽略。)

为了告诉处理器 不要 忽略空白,可见这个值设置为 preserve,就象下面这样:


   
   
    
    
   
   
   
   
   
    
    public void endElement(
   
   
   
   
                
   
   
    
    String namespaceURI,
   
   
   
   
                
   
   
    
    String localName,
   
   
   
   
                
   
   
    
    String qName)
   
   
   
   
                    
   
   
    
    throws SAXException
   
   
   
   

  
  

endDocument()

 

当然,一旦完成文档的解析,您将需要打印出最终记录,如下所示。

这也是组合处理期间可能出现的任何零碎信息的很好机会。

...
   
   
      if (thisQuestion.equals("implant")) {
   
   
         implant = implant + new String(ch, start, length);
   
   
      }
   
   
   }
   
   
}
    
    
public int getInstances (String all, 
    
    
                            String choice) {
    
    
   ...
    
    
   return total;
    
    
} 
    
    
public void endDocument() {
    
    
   System.out.println("Appearance of the aliens:");
    
    
   System.out.println("A: " + getInstances(appearance, "A"));
    
    
   System.out.println("B: " + getInstances(appearance, "B"));
    
    
   ...
    
    
}
   
   
                                                 
   
   
   public static void main (String args[]) {
   
   
...
  
  

processingInstruction()

所有这一切都令人满意,但有时您可能希望直接包括处理数据的应用程序的信息。一个很好的例子就是您希望使用样式表来处理文档,比如


   
   
   
   

    
    
   
   

   
   
    
    
   
   
...
   
   

其他应用程序能够以类似的方法获取信息。例如,调查的统计采样无疑没有足够大到适合严肃考虑的地步。您可以仅为应用程序添加处理指令,从而指定一个因子来放大被调查者的反应:


   
   
   
   

    
    
   
   

   
   
    
    
   
   
...
  
  

这可以由 processingInstruction() 事件获取,然后将它划分为 target data

...
   
   
public void processingInstruction(String target, String data)
   
   
      throws SAXException {
   
   
   System.out.println("Target = ("+target+")");
   
   
   System.out.println("Data = ("+data+")");
   
   
}
   
   
...
   
   

ErrorHandler 事件

 

就像 ContentHandler 具有预定义用于处理内容的事件一样,ErrorHandler 也有预定义用于处理错误的事件。由于已指定了 SurveyReader 作为错误处理程序以及内容处理程序,您需要改写那些方法的默认实现。

你需要关心三个事件:warningerror fatalError

...
   
   
import org.xml.sax.SAXParseException;
   
   
public class SurveyReader 
   
   
                        extends DefaultHandler
   
   
{
   
   
   public SurveyReader() {
   
   
   }
    
    
   public void error (SAXParseException e) {
    
    
      System.out.println("Error parsing the file: "+e.getMessage());
    
    
   }
    
    
   public void warning (SAXParseException e) {
    
    
      System.out.println("Problem parsing the file: "+e.getMessage());
    
    
   }
    
    
   public void fatalError (SAXParseException e) {
    
    
      System.out.println("Error parsing the file: "+e.getMessage());
    
    
      System.out.println("Cannot continue.");
    
    
      System.exit(1);
    
    
   }
   
   
...
   
   

名称空间

 

SAX 20 版的一个主要增强是添加了对名称空间的支持,名称空间允许开发人员无冲突地使用不同来源或具有不同用途的信息。这通常出现在生产环境中,生产环境中的 SAX 数据流中的数据可能来自许多不同的来源。

名称空间是一个概念范围,其中的所有名称都需要是唯一的。

例如,我过去常在这样一间办公室工作,我的名字和那里的一位客户相同。如果我在办公室,而接待员宣布“Nick,请接 1 号电话,”那么每个人都知道她指的是我,因为我在“办公室”名称空间中。类似地,如果她宣布“Nick 在一号线上,”每个人都知道她指的是那位客户,因为呼叫者在办公室名称空间之外。

另一方面,如果我出了办公室,而她做出相同的宣布,混淆就可能产生,因为两种可能性都存在。

XML 数据库由多种来源组合而成时,同样的问题也会产生,比如本教程稍后将会详细描述的示例文件中修订过的调查信息。

创建名称空间

2 页(共6 页)

由于名称空间的标识符必须是唯一的,因而使用统一资源标识符(或 URI)来指定它们。例如,本教程的示例数据的默认名称空间将使用 xmlns 属性来指定:


   
   
   
   

   
   
    
    xmlns="http://www.nicholaschase.com/surveys/">
    
    
   
   

   
   
    
    
   
   
   
   
   
    
    A
   
   
   
   
...
   
   

没有指定名称空间的任何节点都在默认名称空间 http://www.nicholaschase.com/surveys/中。实际的 URI 本身并没有表示任何意义。信息可能在也可能没有在该地址,重要的是它必须是唯一的。

注意到默认名称空间和根本没有名称空间之间的巨大区别是很重要的。在本例中,没有名称空间前缀的元素都在默认名称空间中。以前,当不存在默认名称空间时,那些元素就不在任何名称空间中。当您处理关于属性的名称空间时,这个区别就变得重要了。

您还可以创建次级名称空间,并向它们添加元素或属性。

指定名称空间

3 页(共6 页)

也可以为数据指定其他名称空间。例如,通过创建一个 revised 名称空间,您可以添加第二组数据,比如后期催眠,而不致干扰原先的数据。

名称空间连同别名通常在在文档的根元素上(但不一定是这样)创建。当正在使用多个名称空间时,这个别名根据需要用作元素或属性的前缀,以指定正确的名称空间。请考虑下面的 XML 代码片断:


   
   
   
   

   
   
    
    
   
   
         xmlns:revised="http://www.nicholaschase.com/surveys/revised/">
   
   

   
   
    
    
   
   
   
   
   
    
    A
   
   
   
   
   
   
   
    
    B
   
   
   
   
   
   
   
    
    A
   
   
   
   
   
   
   
    
    D
   
   
   
   
   
   
   
    
    B
   
   
    
    
   
    
    
    
    
     
     D
    
    
    
    
   
    
    
     
     A
    
    
    
    
   
    
    
     
     A
    
    
    
    
   
    
    
     
     D
    
    
    
    
   
    
    
     
     A
    
    
    
    
   
   
   

   
   

   
   
    
    
   
   
...
   
   
 
    
    

它使用了名称空间和别名 revised 来创建 question 元素。

记住 revised: 不是名称空间,而是别名!实际的名称空间是 http://www.nicholaschase.com/surveys/revised/

检查名称空间

 

正如startElement()中简要地提及到的,SAX 2.0 版添加了用于识别不同名称空间的功能。

您可以通过多种方式使用这三种新能力,但是首先要确保结果中仅出现原先的答案。否则,Bob 的答案就会被重复计数。

由于只有当 thisElement "question" 时才会记录答案,您应该在设置该变量之前执行检查。

...
   
   
public void startElement(
   
   
             String namespaceURI, 
   
   
             String localName, 
   
   
             String qName, 
   
   
             Attributes atts) 
   
   
                throws SAXException {
    
    
   if (namespaceURI == 
    
    
     "http://www.nicholaschase.com/surveys/") { 
   
   
      if (localName == "question") {
   
   
         thisQuestion = atts.getValue("subject");
   
   
      }
    
    
   } 
   
   
   thisElement = localName;
   
   
}
   
   
...
  
  

注意应用程序实际上是在检查名称空间 URI(或像本例中一样是 URL),而不是检查别名。

关于属性的名称空间

 

属性也可能属于某个特定的名称空间。例如,如果问题的名称在第二回合的调查中发生了变化,您可以添加第二个相关的属性 revised:subject,比如:


   
   
    
    
   
   
         xmlns:revised="http://www.nicholaschase.com/surveys/revised/">
   
   

   
   
    
    
   
   
   
   
   
    
    A
   
   
   
   
   
   
   
    
    B
   
   
   
   
   
   
   
    
    A
   
   
   
   
   
   
   
    
    D
   
   
   
   
   
   
   
    
    B
   
   
   
   
   
   
   
    
    
     
     
   
   
revised:subject="looks">D
   
   
   
   
   
    
    A
   
   
   
   
   
   
   
    
    A
   
   
   
   
   
   
   
    
    D
   
   
   
   
   
   
   
    
    A
   
   
   
   

   
   

   
   
    
    
   
   
...
  
  

关于属性的稍微有点奇怪的地方在于,它们 永远不会 在默认名称空间中。即使已经声明了一个默认名称空间,没有前缀的属性也被认为是根本就没有名称空间。这意味着 response http://www.nicholaschase.com/surveys/ 名称空间中,但是其属性 username 却不在该名称空间中。 这只是 XML 推荐标准的定义本身存在的一个奇怪之处。

Attributes 列表具有允许您确定属性的名称空间的方法。这些方法,即 getURI() getQName,使用起来非常类似元素本身的 qname localName

名称空间的奇怪之处

 

SAX 解析器处理本地名称和 QName 的方式可能有点奇怪。例如,除非专门打开名称空间处理,否则默认的 Java 解析器就不会报告本地名称的值:

...
   
   
   try {
   
   
      SAXParserFactory spfactory = SAXParserFactory.newInstance();
   
   
      spfactory.setValidating(false); 
   
   
      spfactory.setFeature("http://xml.org/sax/features/namespace-prefixes", 
    
    
              true);
    
    
      spfactory.setFeature("http://xml.org/sax/features/namespaces", 
    
    
              true);
   
   
      SAXParser saxParser = spfactory.newSAXParser();
   
   
...
   
   
如果获取信息有困难,可尝试设置解析器的这些特性。

序列化 SAX

 

到目前为止的例子已考察了使用数据的基础,但这只是简单地考查 SAX 所能做的事情。可以将数据指引到另一个 SAX 进程、转换,当然还可以指引到某个文件。 本节将展示这其中的一些选项。

SAX 事件流输出到文件的过程称为序列化。您可以自己编写文件,但是使用 Serializer 对象要简单得多:

import org.apache.xalan.serialize.Serializer;
    
    
import org.apache.xalan.serialize.SerializerFactory;
    
    
import org.apache.xalan.templates.OutputProperties;
    
    
import java.io.FileOutputStream;
   
   
...
   
   
public static void main (String args[]) {
   
   
 
   
   
   XMLReader xmlReader = null;      
   
   
   try {
   
   
      SAXParserFactory spfactory = SAXParserFactory.newInstance();
   
   
      spfactory.setValidating(false);
   
   
      SAXParser saxParser = spfactory.newSAXParser();
   
   
      xmlReader = saxParser.getXMLReader();
    
    
      Serializer serializer = SerializerFactory.getSerializer(
    
    
               OutputProperties.getDefaultMethodProperties("xml"));      
    
    
      serializer.setOutputStream(new FileOutputStream("output.xml"));
   
   
      xmlReader.setContentHandler(
    
    
      serializer.asContentHandler()
    
    
);                
   
   
      InputSource source = new InputSource("surveys.xml");
   
   
      xmlReader.parse(source);
   
   
   } catch (Exception e) {
   
   
         System.err.println(e);
   
   
         System.exit(1);
   
   
   }
   
   
}
   
   
...
  
  

创建 Serializer 对象(OutputProperties 可接受的值为 xmltext html),并设置它的 OutputStream。这个流几乎可以是任何流类型的对象,比如文件(本例中就是这样)或者 System.out

您可以将 Serializer 设置为解析器的内容处理程序,以便在解析器解析文件时,它就是接收事件的 Serializer

XMLFilters

2 页(共5 页)

由于 SAX 涉及在读取数据时分析数据(而不是在存储之后再分析),您可能认为没有办法在分析数据之前更改数据。

这就是 XMLFilter 要解决的问题。尽管它们是 SAX 2.0 版中新引入的,但实际上聪明的程序员在 1.0 版中就已经在使用它了,他们认识到能够将 SAX 流“链接”在一起,从而能够在数据到达最终目的地之前有效地操作它们。

基本上,它像下面这样工作:

  1. 创建 XMLFilter。这通常是一个简单的类。
  2. 创建 XMLFilter 的一个实例,并将它的父亲设置为通常负责解析文件的 XMLReader
  3. 将过滤器的内容处理程序设置为通常的内容处理程序。
  4. 解析文件。 过滤器介于 XMLReader 和内容处理程序之间。

创建过滤器

 

 

现在您需要创建一个过滤器,以允许您丢弃用户的原先答案,而相反地使用修订过的答案。为此,您需要删除原先的答案,然后更改修订答案的名称空间,以便 SurveyReader 能够选取它们。

这是通过创建一个扩展 XMLFilterImpl 的新类来实现的。

考察一下这里发生了什么事情。当 startElement() 事件被激发时,它将检查原先的名称空间 URI。如果这是 revised 元素,名称空间将被改为默认名称空间。如果不是这个元素,则元素名称实际上将被更改,以便原先的记录例程(在 SurveyReader 中)不会将它识别为一个问题,从而不会对答案计数。

修改过的数据将被传递给父亲(原先的 XMLReader)的 startElement(),以便内容处理器将负责处理它。

import org.xml.sax.helpers.XMLFilterImpl;

import org.xml.sax.XMLReader;
   
   
import org.xml.sax.SAXException;
   
   
import org.xml.sax.Attributes;
   
   
 
   
   
public class SurveyFilter extends XMLFilterImpl
   
   
{
   
   
  public SurveyFilter ()
   
   
  {
   
   
  }
   
   
  public SurveyFilter (XMLReader parent)
   
   
  {
   
   
    super(parent);
   
   
  }
   
   
  public void startElement (String uri, 
   
   
                            String localName,
   
   
                            String qName, 
   
   
                            Attributes atts)
   
   
                                 throws SAXException
   
   
  {
   
   
      if (uri == "http://www.nicholaschase.com/surveys/revised/") {
   
   
          uri = "http://www.nicholaschase.com/surveys/";
   
   
          qName = "question";
   
   
      } else {
   
   
          localName = "REJECT";
   
   
      }
   
   
      super.startElement(uri, localName, qName, atts);
   
   
  }
   
   
 
   
   
}
  
  

调用过滤器

现在是该使用过滤器的时候了。要做的第一件事情是创建一个新实例,然后将原先的 XMLReader 指定为父亲。

接下来设置过滤器的内容和错误处理程序,而不是设置读取器。最后使用过滤器而不是 XMLReader 来解析文件。

由于 XMLReader 被指定为过滤器的父亲,它仍然要处理信息。

...
   
   
public static void main (String args[]) {
   
   
   XMLReader xmlReader = null;      
   
   
   try {
   
   
      SAXParserFactory spfactory = 
   
   
             SAXParserFactory.newInstance();
   
   
      spfactory.setValidating(false);
   
   
      SAXParser saxParser = 
   
   
                    spfactory.newSAXParser();
   
   
      xmlReader = saxParser.getXMLReader();
    
    
      SurveyFilter xmlFilter = new SurveyFilter();
    
    
      xmlFilter.setParent(xmlReader);
   
   
                                                    
    
    
      xmlFilter.setContentHandler(new SurveyReader());
   
   
      xmlFilter.setErrorHandler(new SurveyReader());           
   
   
      InputSource source = new InputSource("surveys.xml");
   
   
      xmlFilter.parse(source);               
   
   
      } catch (Exception e) {
   
   
            System.err.println(e);
   
   
            System.exit(1);
   
   
      }
   
   
   }
   
   
...
  
  

这些特性能够嵌套的深度是无限的。 从理论上讲,您可以创建过滤器的一个很长的链,每个过滤器都调用下一个过滤器。

使用 XMLFilter 来转换数据

 

XMLFilters 还可用来方便快捷地使用 XLST 转换数据。转换本身超出了本教程的范围,不过下面将简要考察一下如何应用它:

import javax.xml.transform.stream.StreamSource;
    
    
import javax.xml.transform.Transformer;
    
    
import javax.xml.transform.TransformerFactory;
    
    
import javax.xml.transform.sax.SAXTransformerFactory;
    
    
import org.xml.sax.XMLFilter;
   
   
...
   
   
public static void main (String args[]) {
   
   
   XMLReader xmlReader = null;     
   
   
   try {
   
   
      SAXParserFactory spfactory = SAXParserFactory.newInstance();
   
   
      spfactory.setValidating(false);
   
   
      SAXParser saxParser = spfactory.newSAXParser();
   
   
      xmlReader = saxParser.getXMLReader();
    
    
      TransformerFactory tFactory = TransformerFactory.newInstance();
    
    
      SAXTransformerFactory 
    
    
                  saxTFactory = ((SAXTransformerFactory) tFactory);
    
    
      XMLFilter xmlFilter = 
    
    
                  saxTFactory.newXMLFilter(new StreamSource("surveys.xsl")); 
   
   
      xmlFilter.setParent(xmlReader);
   
   
        
    
    
      Serializer serializer = 
    
    
          SerializerFactory.getSerializer(
    
    
                  OutputProperties.getDefaultMethodProperties("xml"));        
    
    
      serializer.setOutputStream(System.out);
   
   
      xmlFilter.setContentHandler(
    
    
      serializer.asContentHandler());
   
   
      InputSource source = new InputSource("surveys.xml");
   
   
      xmlFilter.parse(source);
   
   
   } catch (Exception e) {
   
   
         System.err.println(e);
   
   
         System.exit(1);
   
   
   }
   
   
}
   
   
...
   
   
首先,您需要创建一个过滤器 —— 但是不是从头创建,而是创建一个专门设计用于根据样式表执行转换的过滤器。

然后,就像您在直接输出文件时所做的那样,创建一个 Serializer 来输出转换结果。

基本上,过滤器执行转换,然后将事件移交给 XMLReader。然而最终的目的地是序列化器。

总结

相比于文档对象模型(Document Object Model,DOM),SAX 是读取和操作 XML 数据的更快速、更轻量的方法。SAX 是一个基于事件的处理器,允许您处理元素、属性以及出现在原先文档中的其他数据。由于具有这样一种体系结构,SAX 是一个只读的系统,但是那并不会阻止您使用数据。本教程还考察了使用 SAX 来确定名称空间,以及使用 XSL 来输出和转换数据的方法。本教程最后考察了过滤器,过滤器允许您将操作链接起来。

1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 、5资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值