当前位置: 首页 > 编程日记 > 正文

使用 SAX 解析器简化文档处理程序的编写

http://www-900.ibm.com/developerWorks/cn/xml/x-dochan.shtml

有时候 SAX 文档处理程序的代码可能变得非常麻烦、结构性差而且难以维护,尤其是对于那些包含多种不同元素的复杂 XML 结构。本文介绍了应对这种问题的设计策略,从而有助于改进代码的质量和可维护性。

要将 SAX 接口用于 XML 文件,需要编写一个文档处理程序类,在处理程序类的方法中为不同的 XML 标签指定处理方式:

  • startElement
  • endElement
  • characters

这意味着每当 XML 文件中增加了新的元素类型,都必须修改这些方法增加新元素的处理方式。如果原有的元素需要某种修改或者调整,也必须修改这些方法。文档处理程序类的大小和复杂性随着不同 XML 标签数量的增加而增加,直到某个时候可读性和维护性都变得非常差。

本文说明如何通过把关于每种 XML 标签的所有代码隔离在单独的类中来改进设计结构。通过使用 Java 反射机制来泛化文档处理程序类,并使用一个抽象类来实现元素类。

注意: 本篇技巧使用的是 Xerces-Java 2,但这些概念适用于任何 SAX 兼容的解析器。

设计策略
设计策略的第一步是编写一个通用的文档处理程序。只要在解析的 XML 文档中遇到一个标签,该处理程序就为相应的 XML 元素调用特定的类。按照这种策略,该元素类是一个外部文件,实现了和所处理元素有关的方法(比如,startElementendElementcharacters)。为了实现 SAX 解析器对外部类的动态调用,可以使用 Java 反射机制,即 Class.forName 方法,如清单 1 中所示。

清单 1. SAX XML 通用文档处理程序

import org.xml.sax.*;
import org.xml.sax.helpers.*;public class SaxParseSample extends DefaultHandler {String lastName;static XMLReader parser;  public static void main (String args[]) throws Exception{/*** Create a parser.*/try {   parser = XMLReaderFactory.createXMLReader();}catch (Exception e) {   parser = null;   System.err.println("error: Unable to instantiate parser("+parser+")");}SaxParseSample handler = new SaxParseSample();parser.setContentHandler(handler);parser.setErrorHandler(handler);parser.parse(new InputSource(args[0]));}/*** Handle the start of an element.*/public void startElement (String uri, String name,String qName, Attributes atts){lastName = new String(name); try {Class classToRun = Class.forName(name, true, ClassLoader.getSystemClassLoader()); XmlElementsInterface etlElement =  (XmlElementsInterface) classToRun.newInstance(); etlElement.startXmlElement(uri, name, qName, atts);}catch (Exception e) {  System.out.println( e );};}/*** Handle the end of an element.*/public void endElement (String uri, String name, String qName){try {Class classToRun = Class.forName(name, true, ClassLoader.getSystemClassLoader());XmlElementsInterface etlElement =  (XmlElementsInterface) classToRun.newInstance();etlElement.endXmlElement(uri, name, qName);}catch (Exception e) {System.out.println( e );};}/*** Handle character data.*/public void characters (char ch[], int start, int length){try {Class classToRun = Class.forName(lastName, true, ClassLoader.getSystemClassLoader());XmlElementsInterface etlElement =  (XmlElementsInterface) classToRun.newInstance();etlElement.XmlCharacters(lastName, ch , start, length);}catch (Exception e) {System.out.println( e );}}
}

从清单 1 中可以看出,这里没有明确引用任何 XML 标签。而是通过加载和运行一个类来实现元素的处理,这个类的名称包含在从 SAX 解析器接收到的 name 参数中。程序员负责保证这个类的存在,以免出现 ClassLoadingException 异常。

这样就可以实现要加载的类名的动态解释。但是获得这种灵活性也在时间上付出了小小的代价(参见本文后面关于性能的讨论)。

此外,本例中使用元素的本地名标识类名。这意味着如果使用多个存在冲突元素的名称空间可能无法正常工作。这种情况下必须使用某种扩展命名机制。比如,类名可以定义成 namespace_localname 或者等价的表示。

如果分析运行期间执行的动作序列——比方说一个 XML 元素的开始——就需要:

  1. 解析器读入元素开始的 XML 标签并调用文档处理程序的 startElement 方法。
  2. startElement 方法加载并运行本地名对应所找到的标签的类。
  3. 这个外部类负责每当新的 XML 元素开始时所要执行的动作。

同样的逻辑也适用于 XML 元素的结束和元素内容的处理。

为了保证这种机制能够工作,外部类必须实现 XmlElementsInterface 接口,该接口也用于强制转换所加载的类。清单 2 说明了 XmlElementsInterface 类:

清单 2. XmlElementsInterface 接口

public interface XmlElementsInterface {public void startXmlElement (String uri, String name, String qName, org.xml.sax.Attributes atts) ;public void endXmlElement (String uri, String name, String qName) ; public void XmlCharacters (String lastName , char ch[], int start, int length);  }

在清单 2 中只包含了 startXmlElementendXmlElementXMLCharacters 方法,不过也可以增加其他的方法。

有了清单 2 所示的接口类,现在就可以对所有的 XML 元素实现外部类。比如,对于清单 3 中的 XML 片段,需要创建三个外部类,Commands.classComment.classSyscommand.class,以便对应其中的三个 XML 元素:

清单 3. XML 片段

<Commands>
<Comment>The dir command displays a list of the files in a directory</Comment>
<Syscommand>DIR >> c:\\directory.log</Syscommand>
<Comment>The write command is used to edit the content of a file</Comment>
<Syscommand>write.exe c:\\directory.log</Syscommand>
</Commands>

每个类的最小实现可以是:

清单 4. 外部 XML 元素类的最小实现

public class Syscommand implements XmlElementsInterface {public void startXmlElement (String uri, String name, String qName,org.xml.sax.Attributes atts) {System.out.println(  "Start element " + qName); 		};public void endXmlElement (String uri, String name, String qName)  {System.out.println(  "End element " + qName);};public void XmlCharacters (String lastName , char ch[], int start, int length)  {System.out.println(  "Content of element " + lastName);};}

其他元素类 Commands.classComment.class 的定义与此类似。

按照这些定义,XML 片段解析在控制台上的输出如下:

清单 5. 默认方法创建的控制台日志

Start element Commands
Content of element CommandsStart element Comment
Content of element Comment
End element CommentStart element Syscommand
Content of element Syscommand
End element SyscommandStart element Comment
Content of element Comment
End element CommentStart element Syscommand
Content of element Syscommand
End element SyscommandEnd element Commands

现在可以改变一个元素的行为,而不必修改文档处理程序代码或者其他元素的代码。所有的修改都局限在单个类中,与其他类互不影响。比如,您可以修改 Syscommand 让它执行所传递的命令。这样,Syscommand 类就应该变成:

清单 6. 增强 Syscommand.java

public class Syscommand implements XmlElementsInterface {public void XmlCharacters (String lastName , char ch[], int start, int length) 	{String COMMAND   = new String(ch,start,length);System.out.println(  "Executing command: " + COMMAND);try{  Runtime rt = Runtime.getRuntime();String[] callAndArgs = { "cmd.exe" ,"/C", COMMAND };try {Process child = rt.exec(callAndArgs);int rc = child.waitFor();System.out.println("Process exit code is: " + rc);}catch(Exception e) { System.err.println("Exception " + e ); }}catch(Exception e){ System.err.println("Exception " + e );}} 		
// . . .
// The other methods remain unchanged
// . . . 
}

Syscommand 类中的代码更新时,这个系统的行为就发生了变化。SAX 解析器、文档处理程序以及用于其他元素的外部类都不需要作任何修改。

这种方法可以极大提高代码的质量,特别是如果 XML 文件很复杂,或者要处理许多其他的 XML 元素。

改进这种机制
为了充分利用上述基本方法,需要解决两方面的局限性。首先,如果增加了新的 XML 元素或者拼写 XML 标签时出现错误怎么办?在这种基本方法中,将引发一个 ClassNotFound 异常。为了避免这种情况,或者至少提供意义明确的调试信息,与 XML Schema 检查(或者 DTD 检查)结合使用是一种不错的方法。使用的元素必须首先在模式中定义,解析器检查该模式就可以发现与解析文件不一致的地方。模式检查的语法可能随着不同的 SAX 解析器略有不同。比如,在 Xerces-Java 2 实现中,就需要在 SaxParseSample 类(如清单 1 所示)的 main 方法中加上下面这一行:

清单 7. 在 main 方法中增加 XML Schema 检查
...parser.setFeature("http://xml.org/sax/features/validation",true);parser.setFeature("http://apache.org/xml/features/validation/schema",true);...

第二方面的局限是只有在不需要将数据从一个元素传递到另一个元素时才有效,因为每次解析一个元素时,都会实例化类的一个新副本,不会保留在内存中。该例中的 XML 片段包含一系列要执行的命令,每一个命令都独立于前一个。但是,如果命令的执行以前一个命令的成功退出为条件时该怎么办呢?按照上述的基本方法,这是不可能的,因为当一个元素被处理时它完全不知道上一个元素。

因此,对于更复杂的情况必须修改上面的模型,以便把参数从一个元素传递到另一个元素。

程序之间传递信息有多种方法。比如,可以在程序中增加对 Java Naming and Directory Interface(命名和目录接口,JNDI)目录服务的应用。

使用 JNDI 目录服务
目录服务提供了在分布式环境中存储和检索信息的一种方法,比如序列化对象。JNDI 是多种目录服务实现的标准接口,定义在 javax.naming.directory 包中,要使用 JNDI 必须将该包导入程序。

清单 8 引用了 Lightweight Directory Access Protocol(轻型目录访问协议,LDAP)实现。并假设该实现所需要的环境信息已经保存在 env 散列表中。

按照上述假设,您必须在本文所定义的元素类中增加以下代码,以便在它们之间互相交换 commandResult 字符串:

清单 8. 共享对象所需的修改

try { 	    DirContext dirCtx = new InitialDirContext(env);String commandResult = new String("Successful");dirCtx.bind("cn=result" , commandResult);} catch (NamingException e) {System.out.println("Operation failed: " + e);
}

下面的代码可以从另一个元素类中读取共享的对象:

清单 9. 读取共享对象所需要的修改

try { 	    DirContext dirCtx = new InitialDirContext(env);String previousResult = (String) dirCtx.get("cn=result" , commandResult);} catch (NamingException e) {System.out.println("Operation failed: " + e);
} 	    

性能问题
有时候设计策略中所用的 Java 反射机制可能对应用程序的性能产生负面影响,因此一定要考虑到这样是否会降低性能。对于简单的应用程序,上述机制没有很大的影响。比如根据测试,在 600 MHz 的处理器上,包含 10,000 个元素的 XML 文件只多花了不到半秒钟的时间。事实上,多花的执行时间是和 XML 文件中的元素数成比例的。因此,只有在非常复杂的大量使用循环和递归调用的应用程序中,才需要考虑这种潜在的影响。

结束语
本文展示了在需要处理已解析 XML 文件中不同元素的文档处理程序代码中,如何通过为每种元素创建单独的类来简化编码。通过这种方式可以创建更小的类和方法,使代码更容易测试、调试和修改,从而改进整体质量和生产率。

可以使用 Java 反射方法 Class.forNameclassToRun.newInstance 创建通用的解析代码,作为调用所定义元素类的接口。XML 文件中定义的所有元素的类都必须创建,并且实现接口 XmlElementsInterface

此外,在这个例子中,我还说明了如何在通用解析代码 SaxParseSample 中增加 XML Schema 检查,以捕获新的或者错误的 XML 元素,避免产生 ClassNotFound 异常。另外还介绍了如何使用 JNDI 目录服务在不同的代码片中共享变量或对象。

转载于:https://www.cnblogs.com/soldier/archive/2004/09/06/40145.html

相关文章:

软件体系结构风格之C/S,三层C/S,与BS

C/S的物理结构&#xff0c;其发展历程为(1)->(3)->(2)&#xff0c;本文接下来要介绍的C/S为(3),即胖客户端瘦服务器,服务器只管数据库&#xff0c;接下来要介绍的三层C/S为(2)&#xff0c;即客户端不胖不瘦。 C/S软件体系结构 背景&#xff1a;基于资源不对等&#xff0…

HDU-2020

绝对值排序 Problem Description输入n(n<100)个整数&#xff0c;按照绝对值从大到小排序后输出。题目保证对于每一个测试实例&#xff0c;所有的数的绝对值都不相等。 Input输入数据有多组&#xff0c;每组占一行&#xff0c;每行的第一个数字为n,接着是n个整数&#xff0c;…

Java归去来第2集:利用Eclipse创建Maven Web项目

一、前言 如果还不了解剧情&#xff0c;请返回第一集的剧情 Java归去来第1集&#xff1a;手动给Eclipse配置Maven环境 二、利用Eclipse创建Maven Web项目 选择File-New-Project 然后选择Maven-Maven Project 选择项目所在的工作空间目录&#xff0c;然后下一步 选择模…

vs2005什么时候能出正式版

2005试用版本出了很久了&#xff0c;不知道什么时候能出正式版&#xff0c;不会真得要推迟到2005年吧。企盼中…… 转载于:https://www.cnblogs.com/playboy2005/archive/2004/09/22/45563.html

人工神经网络是如何实现存算一体的

摘要 本文内容如题。 下图是一个神经元模型 可以将其抽象为一个数学函数yf(w1x1w2x2…wn*xnb)。也就是这个模型同时涵盖输入输出和进行计算。

编码能力的提升?_20131228

我在编程过程中发现&#xff0c;编码也是一很重要的能力&#xff0c;故花一点时间对其进行思考。 现将思考的成果与各位同行分享&#xff1b;我希望大家在看到以后&#xff0c;能提供宝贵的意见&#xff0c;先谢之&#xff01; 转载于:https://www.cnblogs.com/uestc999/p/3495…

Microsoft PowerToys for Windows XP

它们又卷土重来了&#xff01;PowerToys是由开发人员在软件产品正式发布并投放生产后相继编制的附加程序。它们的出现不仅使Windows体验更加兴味盎然&#xff0c;而且&#xff0c;还为Windows系统平添了许多功能特性。 说明&#xff1a;我们已为确保PowerToys的正常运行而付出了…

mysql主从配置读写分离笔记

第二次回头看了&#xff0c;第一次学完后感觉都会了&#xff0c;回头再看才发现什么都想不起来了。还得查资料再学习&#xff0c;虽然很简单。还是做个笔记吧&#xff01;笔记有点糙 就是自己看的因为主要是测试主从和读写分离 所以直接 yum install -y mariadb mariadb-server…

通用软件测试的6个角度

之后的课程都会围绕此展开 以QQ邮箱为例 外观界面测试 页面外观背景颜色、字体、字体格式、页面图案、动画、窗体布局功能测试 输入正确的用户名和密码可以登录&#xff0c;其他都不可性能测试 输入完邮箱的用户名和密码并单击登录按钮后&#xff0c;用户等待多长时间可以登录…

easyui源码翻译1.32--Messager(消息窗口)

前言 使用$.messager.defaults重写默认值对象。下载该插件翻译源码 消息窗口提供了不同的消息框风格&#xff0c;包含alert(警告框), confirm(确认框), prompt(提示框), progress(进度框)等。所有的消息框都是异步的。用户可以在交互消息之后使用回调函数去处理结果或做一些自己…

新闻网站个人中心(查询用户的关注信息)流程分析

1.获取当前用户登录状态 2.获取参数 用户的id user_id 3.获取新闻作者的id 4.如果新闻作者id和用户id 5.返回内容给前端转载于:https://www.cnblogs.com/zxt-cn/p/9158860.html

物联网技术与应用(第1-2课时)(cont.)

物联网的定义&#xff1a; 通过射频识别&#xff08;RFID&#xff09;、红外感应器、全球定位系统、激光扫描器等信息传感设备&#xff0c;按约定的协议&#xff0c;将任何物品通过有线与无线方式与互联网连接&#xff0c;进行通信和信息交换&#xff0c;以实现智能化识别、定位…

db2 基础语法

一、db2 基础 基本语法 注释&#xff1a;“--”&#xff08;两个减号&#xff09; 字符串连接&#xff1a;“||” 如set msg’aaaa’||’bbbb’&#xff0c;则msg为’aaaabbbb’ 字符串的引用&#xff1a;‘’&#xff08;一定用单引号&#xff09;&#xff0c;如果需要输入单引…

Repeater分页代码

//ASP.NET中的DataList和Repeater提供了简单快速的方法來显示,其间&#xff0c;我们可以使用<ItemTemplate>更是使我们能随心所欲的控制数据的排放样式&#xff01; //.可惜的是它们不像Datagrid那样&#xff0c;有内置的分页功能。 // //如何解决呢&#xff1f; // //其…

消失,只为一个人存在……

好像很久很久没有24小时挂在网上了…… 每天收到的短消息来源越来越单一…… 在朋友中消失&#xff0c;所有的来电和消息&#xff0c;都不愿意理会…… 朋友虽然很重要&#xff0c;可是现在只想为一个人存在。 请各位好朋友原谅&#xff0c; 我就是那种重色轻友的人嘛…… 转载…

统一建模语言——UML(第9-20课时)(cont.)

文章目录UML概述UML是什么UML能做什么为什么要学UMLUML构造需求模型用例建模技术绘制用例图识别执行者识别用例检查形式关联关系编写用例文档用例文档的组成部分详细说明检查用例模型状态图定义组成元素复杂一点活动图定义作用组成元素绘制技巧例顺序图定义作用组成元素UML构造…

xunsearch 迅搜初探

2014年1月2日 19:34:12 1 [rootlocalhost bin]# ./php /usr/local/lamp/xunsearch/sdk/php/util/Quest.php demo "俗话说 OR pid:1"2 在 3 条数据中&#xff0c;大约有 2 条包含 俗话说 OR pid:1 &#xff0c;第 1-2 条&#xff0c;用时&#xff1a;0.0036 秒。3 4 1…

Aria2打造属于自己的下载神器

请关注微信公众号( ?sharingplus) 我使用Aria2差不多已经2年了。在这段时间使用很多下载工具&#xff0c;最开始使用的是迅雷极速版 、后面各种原因不好使了。Free Download Manager、uTorrent、qBittorrent、BitComet、IDM等等全部折腾了一遍。各有千秋&#xff0c;在上面中使…

GARFIELD@12-10-2004

无所不用其极 转载于:https://www.cnblogs.com/rexhost/archive/2004/12/10/75223.html

机器学习中的三对性能度量参数

文章目录1 分类结果混淆矩阵2 错误率和精度3 查准率P和查全率R4 真正例率TPR和假正例率FPR1 分类结果混淆矩阵 2 错误率和精度 错误率&#xff1a;分类错误的样本数占样本总数的比例 精度&#xff1a;分类正确的样本数占样本总数的比例 关系&#xff1a;两者之和为1 3 查准率…

管理输入光标的 WIN32 API 函数

下面的 API 函数是用来管理输入光标的 API 函数&#xff0c;下面是在使用C#时声明的这些函数&#xff1a; /**//// <summary> /// 参数说明&#xff1a; /// hwnd : 要创建光标的控件的句柄 /// hbm : 一个位图的句柄&#xff0c;如果指定则使用指定…

linux 防火墙 -netfilter

2019独角兽企业重金招聘Python工程师标准>>> 关于iptables 什么是iptables? 常见于linx系统下的应用层防火墙工具 firewalld 和netfilter Linux 防火墙-netfilter selinux 临时关闭 setenforce 0selinux 永久关闭 vi /etc/selinux/configcentos7 之前使用 netfilte…

信号完整性 带宽的确定

对于只包含很少电容的电路&#xff0c;可认为是梯形边沿&#xff0c;最大带宽可以采用 f1/tr.其中tr为上升时间。 对于有容性负载的电路&#xff0c;可以认为是指数边沿&#xff0c;最大带宽可以采用f4/tr.其中tr为上升时间。 参考书籍《数字信号完整性&#xff1a;互连、封装的…

验证(verification)和确认(validation)

验证&#xff1a;看软件产品是否符合需求文档 确认&#xff1a;看软件产品是否满足用户需求 整个软件测试做的事是验证

dva + antd + mockjs 实现用户管理

1.安装dva-cli npm install dva-cli -g 2.创建应用 dva new dvadashboard [dvadashboard为项目名] 3.安装mockjs npm install mockjs --save4.配置mockjs 打开.roadhogrc.mock.js 设置如下const fsrequire(fs); const pathrequire(path); const mockPathpath.join(__dirname/…

用S60操作系统SDK开发NOKIA手机应用程序(4)- 界面层框架及一些特性

Uikon和Avkon Series 60 将一个用户界面层(Avkon)添加在Symbian OS v7.0s 底层的Uikon 之上。Uikon是Symbian 核心用户界面&#xff0c;Avkon是S60平台的用户界面。Avkon 提供了一 套UI 组件和一个专为Series 60 设备设计的软件框架。UIKON 是所有Symbian OS设备都支持的一种用…

VS2010 发布web项目 问题

载&#xff1a;http://www.cnblogs.com/shaocm/archive/2012/08/10/2632116.html 转载于:https://www.cnblogs.com/zcttxs/p/3507007.html

软件生命周期中出现的文档名称(cont.)

需求相关&#xff1a;需求规格说明书 测试相关&#xff1a;测试计划书&#xff0c;测试报告

转:45 Useful JavaScript Tips, Tricks and Best Practices

原文来自于&#xff1a;http://flippinawesome.org/2013/12/23/45-useful-javascript-tips-tricks-and-best-practices/ 1 – Don’t forget var keyword when assigning a variable’s value for the first time. Assignment to an undeclared variable automatically results…

聊聊spring cloud gateway的PreserveHostHeaderGatewayFilter

序 本文主要研究下spring cloud gateway的PreserveHostHeaderGatewayFilter GatewayAutoConfiguration spring-cloud-gateway-core-2.0.0.RC2-sources.jar!/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java Configuration ConditionalOnProperty(name…