pull sax_了解SAX

pull sax

教程介绍

我应该学习本教程吗?

本教程检查XML 2.0.x版或SAX 2.0.x版的简单API的使用。 它面向的是了解XML并希望学习这种轻量级的,基于事件的API以用于XML数据的开发人员。 假定您熟悉XML文档的概念,例如格式正确和标记性质。 (如果需要,您可以通过XML 入门教程来获得XML本身的基础知识。)在本教程中,您将学习如何使用SAX来检索,操纵和输出XML数据。

先决条件:SAX支持多种编程语言,例如Java,Perl,C ++和Python。 本教程在演示中使用Java语言,但是所有语言的概念基本相似,您无需实际操作示例就可以全面了解SAX。

什么是SAX?

读取和处理XML文件的标准方法是文档对象模型(DOM)。 不幸的是,这种方法涉及读取整个文件并将其存储在树形结构中,效率低下,速度慢,并且对资源造成压力。

一种替代方法是用于XML或SAX的简单API。 SAX允许您在读取文档时对其进行处理,从而避免了采取所有措施之前等待所有文档被存储的情况。

SAX由XML-DEV邮件列表的成员开发,而Java版本现在是SourceForge项目(请参阅参考资料 )。 该项目的目的是提供一种使用XML的更自然的方法-换句话说,该方法不涉及DOM所需的开销和概念上的飞跃。

结果是基于事件的API。 解析器将事件(例如元素的开始或结束)发送到事件处理程序,该处理程序处理信息。 然后,应用程序本身可以处理数据。 原始文档保持不变,但是SAX提供了操作数据的方法,然后可以将其定向到另一个过程或文档。

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

工具类

如果您决定尝试使用本教程中的示例,则需要安装以下工具并使其正常工作。 运行示例不是理解的必要条件。

DOM,SAX,以及何时合适

SAX处理如何工作

SAX会像过去的自动收录机磁带一样分析XML流。 考虑以下XML代码段:

<?xml version="1.0"?>
<samples>
   <server>UNIX</server>
   <monitor>color</monitor>
</samples>

通常,分析此代码片段的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对其进行更改或在数据流中向后移动。

DOM和基于树的处理

DOM是处理XML数据的传统方式。 使用DOM,数据以树状结构加载到内存中。

例如,在SAX处理工作原理中用作示例的同一文档将表示为节点,如下所示:

矩形框代表元素节点,椭圆形代表文本节点。

DOM使用父子关系。 例如,在这种情况下, samples是具有五个子元素的根元素:三个文本节点(空白),以及两个元素节点servermonitor

要意识到的一件事是server节点和monitor节点实际上具有null 。 相反,它们将文本节点( UNIXcolor )作为子节点。

基于树的处理的优缺点

DOM以及通过扩展的基于树的处理具有多个优点。 首先,由于树在内存中是持久的,因此可以对其进行修改,以便应用程序可以更改数据和结构。 与一键处理SAX相比,它还可以随时在树上上下移动。 DOM也可以更简单地使用。

另一方面,在内存中构建这些树涉及很多开销。 大文件完全超过系统容量的情况并不罕见。 此外,创建DOM树可能是一个非常缓慢的过程。

如何在SAX和DOM之间选择

您选择DOM还是SAX将取决于几个因素:

  • 应用程序的目的:如果您将不得不对数据进行更改并将其输出为XML,那么在大多数情况下,DOM是可行的方法。 并不是说您不能使用SAX进行更改,而是该过程要复杂得多,因为您必须更改数据的副本而不是数据本身。
  • 数据量:对于大文件,SAX是更好的选择。
  • 数据的使用方式:如果实际上仅使用少量数据,则最好使用SAX将其提取到应用程序中。 另一方面,如果您知道需要参考已处理的大量信息,则SAX可能不是正确的选择。
  • 对速度的需求: SAX实现通常比DOM实现快。

重要的是要记住,SAX和DOM不能互斥。 您可以使用DOM创建SAX事件流,也可以使用SAX创建DOM树。 实际上,大多数用于创建DOM树的解析器实际上都是使用SAX来完成的!

创建SAX解析器

样本文件

本教程演示了使用SAX汇总一组要求对他们的外星人绑架经历进行调查的用户的响应的应用程序的构造。

这是调查表:

响应存储在XML文件中:

<?xml version="1.0"?>
<surveys>
<response username="bob">
   <question subject="appearance">A</question>
   <question subject="communication">B</question>
   <question subject="ship">A</question>
   <question subject="inside">D</question>
   <question subject="implant">B</question>
</response>
<response username="sue">
   <question subject="appearance">C</question>
   <question subject="communication">A</question>
   <question subject="ship">A</question>
   <question subject="inside">D</question>
   <question subject="implant">A</question>
</response>
<response username="carol">
   <question subject="appearance">A</question>
   <question subject="communication">C</question>
   <question subject="ship">A</question>
   <question subject="inside">D</question>
   <question subject="implant">C</question>
</response>
</surveys>

创建一个事件处理程序

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

使用SAX进行正确的解析需要对处理程序的方法进行特定的调用,并且这些调用不能是static 。 这意味着您需要专门实例化处理程序对象,因此在不习惯使用对象的情况下,我将对其进行快速概述。

创建新对象时,它将查找要执行的所有类构造函数。 例如:

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()的参数传递。

创建解析器

本示例使用一对类SAXParserFactorySAXParser来创建解析器,因此您不必知道驱动程序本身的名称。

首先声明XMLReaderxmlReader ,然后使用SAXParserFactory创建一个SAXParser 。 正是SAXParser为您提供了XMLReader

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文档执行任何操作,您必须阅读其中的信息。 执行此操作的应用程序称为解析器

两种解析器是: 非验证和验证 。

  • 如果文件格式正确,则将使用非验证解析器 。 它获取每个信息单元,并将其添加到文档中-或在使用SAX应用程序的情况下,将处理事件-不考虑实际结构或内容。
  • 另一方面, 验证解析器根据定义的语法检查XML文档的内容和结构。 有时,这种语法采用文档类型定义(DTD)的形式,但是如今,它更有可能在XML Schema文档中定义。 无论哪种情况,解析器都会检查文档以确保每个元素和属性均已定义并且包含正确的内容类型。 例如,您可以指定每个order必须具有status 。 如果您尝试创建一个没有它的代码,则验证解析器将发出问题信号。

经验证解析器验证的文档称为有效文档。

设置验证选项

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

...

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 。 为简单SurveyReaderSurveyReader既是主应用程序又是内容处理程序,因此,请创建它的新实例,并使用XMLReadersetContentHandler()方法将其设置为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方法的实现。

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

...

xmlReader.setContentHandler(new SurveyReader());

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

但是,如果您运行该应用程序,则不会发生任何事情,因为每个事件的默认实现都不会执行任何操作。 在下一节中,我将介绍添加新的实现以处理解析期间发生的事件。

事件处理程序和SAX事件

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/ ://www.nicholaschase.com/surveys/revised/相对于简单revised:
  • 本地名称。 这是元素的实际名称,例如question 。 如果文档未提供名称空间信息,则解析器可能无法确定qName哪一部分是localName
  • 任何属性。 元素的属性实际上是作为对象的集合传递的,如下一面板所示。

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

...


 import org.xml.sax.Attributes; 

public class SurveyReader extends DefaultHandler
{
...
   public void startDocument() throws SAXException {
      System.out.println("Tallying survey results...");   
   }
   
 public void startElement(String namespaceURI, String localName, 
             String qName, Attributes atts) throws SAXException {
   
       System.out.print("Start element: ");
       System.out.println(qName);
          
   }
	   
   public static void main (String args[]) {
...

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()检索实际数据。 请看一下此方法的签名:

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

请注意,此方法中的任何地方都没有任何信息,这些字符属于什么元素。 如果您需要此信息,则必须将其存储。 本示例添加变量以存储当前元素和问题信息。 (它还会删除显示的许多无关的信息。)

请注意此处的两个重要事项:

  • 范围: characters()事件不仅仅包含一个字符串。 它还包括开始和长度信息。 实际上, ch字符数组包含整个文档。 应用程序不得尝试读取事件提供给characters()事件的范围之外的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 ,如下所示:

<code-snippet xml:space="preserve">
   <line> public void endElement(</line>
                <line> String namespaceURI,</line>
                <line> String localName,</line>
                <line> String qName)</line>
                    <line> throws SAXException</line>
</code-snippet>

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()

所有这些都很好,但是有时您可能希望直接包含处理数据的应用程序的信息。 一个很好的例子是,当您想使用样式表处理文档时,如下所示:

<?xml version="1.0" encoding="UTF-8"?>

 <?xml-stylesheet href="survey.xsl" version="1.0" type="text/xsl" ?> 

<surveys>
...

其他应用程序也可以类似的方式获取信息。 例如,一项调查的统计样本肯定不足以引起重视。 您可以为您的应用程序添加一条处理指令,该指令指定一个将响应乘以的因子:

<?xml version="1.0" encoding="UTF-8"?>

 <?SurveyReader factor="2" ?> 

<surveys>
...

这将由processingInstruction()事件processingInstruction() ,该事件将其分为targetdata

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

ErrorHandler事件

正如ContentHandler具有用于处理内容的预定义事件一样, ErrorHandler也具有用于处理错误的预定义事件。 因为您将SurveyReader指定为错误处理程序以及内容处理程序,所以您需要覆盖这些方法的默认实现。

您需要关注三个事件: warningerrorfatalError

...


 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和命名空间

命名空间

SAX 2.0版中添加的主要增强功能之一是对名称空间的支持,这使开发人员可以使用来自不同来源或具有不同目的的信息而不会发生冲突。 这通常发生在生产环境中,在该环境中,SAX流中的数据可能来自许多不同的来源。

命名空间是概念性区域,其中所有名称都必须是唯一的。

例如,我以前在办公室里工作,我的名字与客户相同。 如果我在办公室并且接待员宣布“尼克,接电话1号”,那么每个人都知道她的意思是我,因为我在“办公室”名称空间中。 同样,如果她宣布“ Nick在第1行”,则每个人都知道它是客户端,因为呼叫者不在办公室名称空间之内。

另一方面,如果我不在办公室,而她也宣布相同的消息,则可能会出现混乱,因为存在两种可能性。

当XML数据来自不同的来源时,例如在本教程后面将详细介绍的样本文件中的修订后的调查信息进行组合时,也会出现相同的问题。

创建名称空间

因为名称空间的标识符必须是唯一的,所以使用统一资源标识符(URI)来指定它们。 例如,将使用xmlns属性指定样本数据的默认名称空间:

<?xml version="1.0"?>
<surveys  xmlns="http://www.nicholaschase.com/surveys/" >
<response username="bob">
   <question subject="appearance">A</question>
...

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

重要的是要注意,默认名称空间与根本没有名称空间之间存在巨大的区别。 在这种情况下,没有名称空间前缀的元素将位于默认名称空间中。 以前,当不存在默认名称空间时,这些元素就不在名称空间中。 当处理属性上的命名空间时,这种区别变得很重要。

您还可以创建辅助名称空间,并向其添加元素或属性。

指定名称空间

也可以为数据指定其他名称空间。 例如,可以通过创建revised名称空间来添加第二组数据(例如,催眠后),而不会干扰原始响应。

通常(但不一定)在文档的根元素上创建名称空间以及别名。 此别名用作元素和属性的前缀(必要时,当使用多个名称空间时),以指定正确的名称空间。 考虑以下代码:

<?xml version="1.0"?>
<surveys xmlns="http://www.nicholaschase.com/surveys/"
         xmlns:revised="http://www.nicholaschase.com/surveys/revised/">
<response username="bob">
   <question subject="appearance">A</question>
   <question subject="communication">B</question>
   <question subject="ship">A</question>
   <question subject="inside">D</question>
   <question subject="implant">B</question>

   <revised:question subject="appearance">D</revised:question>
   <revised:question subject="communication">A</revised:question>
   <revised:question subject="ship">A</revised:question>
   <revised:question subject="inside">D</revised:question>
   <revised:question subject="implant">A</revised:question>
   
</response>
<response username="sue">
...

名称空间和别名revised已用于创建其他question元素。

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

检查名称空间

SAX 2.0版增加了识别不同名称空间的功能,如startElement()中简要提到的那样。

您可以通过多种方式使用这些新功能,但首先要确保结果中仅包含原始答案。 否则,鲍勃的答案将计算两次。

因为除非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 ,如下所示:

<?xml version="1.0"?>
<surveys xmlns="http://www.nicholaschase.com/surveys/"
         xmlns:revised="http://www.nicholaschase.com/surveys/revised/">
<response username="bob">
   <question subject="appearance">A</question>
   <question subject="communication">B</question>
   <question subject="ship">A</question>
   <question subject="inside">D</question>
   <question subject="implant">B</question>
   <revised:question subject="appearance"
revised:subject="looks" >D</revised:question>
   <revised:question subject="communication">A</revised:question>
   <revised:question subject="ship">A</revised:question>
   <revised:question subject="inside">D</revised:question>
   <revised:question subject="implant">A</revised:question>
</response>
<response username="sue">
...

关于属性的一件事有些奇怪,那就是它们从不在默认名称空间中。 即使已声明默认的名称空间,不带前缀的属性也被视为根本没有名称空间。 这意味着response位于http://www.nicholaschase.com/surveys/名称空间中,但其属性username不在。 这只是XML Recommendation本身定义的一个怪异之处。

Attributes列表具有允许您确定属性的名称空间的方法。 这些方法getURI()getQName的使用与元素本身的qnamelocalName非常相似。

命名空间奇数

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进程,一个转换,或者(当然)定向到一个文件。 在本节中,我将向您展示其中一些选项。

将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可接受值为xmltexthtml并设置其OutputStream 。 该流实际上可以是任何流类型的对象,例如文件(如此处)或System.out

然后,您可以将Serializer设置为解析器的内容处理程序,这样,当解析器解析文件时, Serializer接收事件。

XML过滤器

由于SAX涉及在经过时分析数据(而不是存储数据),因此您可能认为在分析数据之前无法更改数据。

这是XMLFilter解决的问题。 尽管它们不是SAX 2.0版的新功能,但是聪明的程序员实际上在1.0版中使用了它们,他们意识到可以将SAX流“链接”在一起,并在到达最终目的地之前对其进行有效的操作。

基本上,它是这样的:

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

创建一个过滤器

现在,您要创建一个过滤器,该过滤器允许您丢弃用户的原始答案,而使用修改后的答案。 为此,您需要擦除原始SurveyReader ,然后在修订后的答案上更改名称空间,以便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还可以用于使用XSLT快速轻松地转换数据。 转换本身不在本教程的讨论范围之内,但是在此快速看一下如何应用它:

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 。 但是,最终目的地是串行器。

SAX摘要

摘要

与文档对象模型(DOM)相比,SAX是一种更快,更轻便的读取和操作XML数据的方式。 SAX是基于事件的处理器,它使您可以处理原始文档中显示的元素,属性和其他数据。 由于这种体系结构,SAX是一个只读系统,但这并不能阻止您使用数据。 本教程还探讨了使用SAX确定名称空间以及使用XSL输出和转换数据的方法。 最后,您研究了过滤器,该过滤器允许您链接操作。


翻译自: https://www.ibm.com/developerworks/xml/tutorials/x-usax/x-usax.html

pull sax

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值