-- 作者:yuji1998
-- 发布时间:5/31/2004 12:23:00 AM
-- SAX教程系列四(事件处理程序和SAX事件)----来源:IBM developerWorks
欢迎您通过电子邮件nicholas@nicholaschase.com直接与作者 Nicholas Chase 联系。 注:本教程仅供本站成员的学习和研究使用,严禁用于商业目的! 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,就象下面这样: <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() 第 8 页(共10 页) 当然,一旦完成文档的解析,您将需要打印出最终记录,如下所示。 这也是组合处理期间可能出现的任何零碎信息的很好机会。 ... 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() 第 9 页(共10 页) 所有这一切都令人满意,但有时您可能希望直接包括处理数据的应用程序的信息。一个很好的例子就是您希望使用样式表来处理文档,比如: <?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() 事件获取,然后将它划分为 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 作为错误处理程序以及内容处理程序,您需要改写那些方法的默认实现。 你需要关心三个事件:warning、error 和 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); }
|