使用XWAF框架(5)——XML解析器:CXDP
XWAF推出了自己的组合式XML文档解析器,英文名叫:“CXDP”,是“Combined XML Document Parser”的缩写。核心代码属XWAF原创,注释、日志和帮助文档采用全中文描述,特别适合于中文背景的初级程序员学习和使用。
CXDP解析器融合了DOM解析和DSE解析两种XML解析技术。
DOM解析就是基于文档对象树的解析技术,编码简单,功能强大,且能对XML原文进行“增、删、改、查”操作,是行业内最早推出的XML解析方案,应用广泛,但是对用户计算机的性能和内存要求较高,解析速度稍慢。
DSE(Document Scanning Events)解析是基于文档扫描事件机制的解析方案。就是以XML原文的节点和元素为最小解析单元,“一边扫描文档内容,一边抛出解析数据”。每次抛出解析数据,就引发一次事件,解析器就会执行与此事件关联的方法。程序员可以在该事件方法中编写捕获数据的代码,为自己所用。DSE解析技术的最大特点,就是“边扫描、边解析、边抛出”,解析器不保存解析的历史数据,交与开发者自己去处理。因此,该方案对计算机的硬件配置和内存要求不高,且解析速度快,特别适合于解析XML大数据文档。与行业内的SAX解析思想一致。
为了简化编程,CXDP解析器提供了一个名叫XmlManager的管理器类。该类包含创建、解析和保存XML文档的静态方法。方法要求使用者指定XML文件的路径或者符合XML语法规范的字符串。如果希望解析器采用DSE解析模式,则需要用户额外提供一个实现了XmlUserParser<T> 泛型抽象类的实例参数。该实例包括捕获解析数据的各种事件方法,以及返回解析数据的对象类型。若采用DOM解析模式,则不需要此参数。也就是说,开发者调用XmlManager管理器的解析方法时,管理器会自动选择DOM模式或DSE模式来完成解析工作。唯一的差别是提供的参数是否包含XmlUserParser<T>实例。这就简化了应用程序的编码。
XWAF框架包含CXDP解析器的所有类。如果不想使用XWAF框架,也可以下载或使用独立的压缩文件“com.xwaf.cxdp.jar”来获取CXDP解析器。
5.1 基于DOM的XML解析
DOM解析XML文档的基本步骤,分为以下四步:
1)加载XML文档,获取XmlDocument对象。参考代码如下:
String fileName = "D:\\myXmlFile.xml";
XmlDocument xd = XmlManager.loadXmlFile(fileName);
2)提取根元素节点对象(XmlElement)。参考代码如下:
XmlElement xeRoot = xd.getDocumentElement();
3)遍历根元素的子节点或后代节点,提取想要的数据。
参考代码如下:
XmlNodeList xbl = xeRoot.getChildNodes();
for(INode n : xbl.getNodeList()){
if("userName".equals(n.getNodeName())){
...... // 根据用户的XML结构提取数据!
}
}
4)销毁XmlDocument对象,释放宝贵的内存资源。
也就是调用文档对象的方法:xd.destroy();
说明:最核心的解析代码,必须由知道被解析XML文本的结构和各个节点含义的应用程序设计人员,根据元素节点的名称,属性节点的设置,以及文本节点与元素节点的父子关系,分别提取相应的属性值,或是文本子节点的内容,来完成XML数据的解析和逻辑组装。用户也可以使用获得的XML数据创建自己的实体对象,然后在应用程序中引用或共享。
程序员可以根据需求,找到特定的元素节点,并将创建的新元素或文本节点添加到所找到节点的子节点集合中;或者将找到的节点修改或删除后,重新保存到文件,从而实现对原XML文件的增、删、改、查操作。
CXDP解析器实现了DOM3规范的绝大多数接口、类和方法,常用的接口、类和方法如下:
INode接口:是DOM的顶级接口。在DOM对象树中,所有的XML数据项都被看做是节点(Node),包括属性和文本。所以,INode也是最重要的接口,它定义了XML操作的绝大多数方法,如:
getNodeName()方法:获取节点的限定名(包含名称空间前缀)。
getLocalName()方法:获取当前节点的本地名称。
getPrefix()方法:获取节点所属名称空间的前缀。
getNextSibling()方法:获取下一个兄弟节点。
getPreviousSibling()方法:获取上一个兄弟节点。
getParentNode()方法:获取当前节点的父节点。
getFirstChild()方法:获取第一个子节点。
getLastChild()方法:获取最后一个子节点。
getChildNodes()方法:获取当前节点的全部子节点集合。
getNodeType()方法:获取当前节点的类型常量。
getNodeValue()方法:获取节点值。
getTextContent()方法:返回节点及其子孙节点的文本内容。如果文本子孙节点中包含实体引用,将被替换为实体值。
appendChild(INode)方法:将指定的新节点添加到当前节点的子节点集合中;
insertAfter(INode newChild, INode refChild)方法:在指定的子节点之后插入一个新节点。
insertBefore(INode newChild, INode refChild)方法:在指定的子节点前插入一个新节点。
removeChild(INode)方法:移除指定的子节点。
replaceChild(INode newChild, INode oldChild)方法:将当前节点中指定的子节点替换成指定的新节点。
cloneNode(boolean deep)方法:克隆(复制)当前节点对象的副本。
INode接口还是文档接口(IDocument)和元素接口(IElement)的父接口。
XmlNode类:是DOM的顶级类,它实现了INode接口的全部方法。是文档类(XmlDocument)和元素类(XmlElement)的父类。
XmlDocument类:XML文档节点类,是XmlNode的子类,继承了XmlNode的全部protected属性和方法。另外增加了如下方法:
adoptNode(INode srcNode) :从另一个文档向本文档过继一个节点,并返回过继后的节点。
createElement(String qualifiedName):创建指定限定名(可以包含前缀、冒号和本地名)的元素节点。
createTextNode(String data) :创建带有指定字符串数据的文本节点。
load(String xmlFileName) :将指定的文件名加载到当前文档对象。该方法有四个重载,最重要的重载方法是load(String xmlFileName, XmlUserParser<?> xup, boolean createDOM)方法。该重载方法除了要求指定文件名以外,还要求提供一个实现了泛型抽象类(XmlUserParser<?>)的实例,以及是否创建DOM对象树的参数。如果泛型抽象类参数为null,则第三个参数会自动为true,即自动创建DOM对象树。
loadXML(String srcXml, String baseURI) :从指定的XML文档片段构建文档对象。第一个参数为文档片段,第二个参数是基础DOM资源标识符(可以为null)。
saveFile(String xmlFileName, boolean formatXml) :以指定的文件名和格式处理模式将XML文档内容保存到磁盘。第二个参数表示是否对文档内容进行缩进格式化。
toXmlString(boolean formatXml) :将当前XML文档对象的节点树,根据参数formatXml的值输出为紧凑格式或排版格式的字符串。如果参数formatXml为true,将对XML内容以元素为单元进行换行和缩进等排版处理。
XmlElement类:XML元素节点类,是XmlNode的子类,继承了XmlNode的全部protected属性和方法。另外增加了如下方法:
createAttribute(String name, String val) :创建带有指定名称和值的属性节点,并返回创建的 XmlAttribute对象。
getAttribute(String name):获取指定名称的属性值。
getAttributeNode(String name)方法:获取指定名称的属性节点对象(XmlAttribute)。
getAttributes():获取元素的属性集合。
getElementById(String eId):从文档中查找并返回ID类型的属性值匹配参数值(eId)的第一个元素节点。
getElementsByTagName(String tagName):获取与指定元素标签名称相匹配的元素节点及其子元素节点的列表。
getTagName():获取当前元素节点的标签名。
removeAttribute(String name):删除指定名称的属性节点。
setAttribute(String name, String value):添加或修改当前元素中,属性名称为 name 的属性值。说明:如不存在同名属性,就添加一个新属性。
hasAttribute(String name):判断是否存在与指定名称相匹配的属性节点。
5.2 基于DSE的XML解析
采用了XML文档扫描事件机制,要求程序员提供实现了XmlUserParser<T>泛型抽象类的实例参数。XmlUserParser<T> 是一个泛型抽象类,它定义了解析器在扫描XML文档的过程中,与扫描事件对应的抽象方法。包括:
1)start_Element():该方法在解析器遇到一个元素的开始节点时被执行,包括5个参数,分别是:父元素的限定名称、元素的本地名称、名称空间的前缀、名称空间标识符、包含的属性集合。
2)end_Element():该方法在解析器遇到一个元素的结束节点时被执行,包括2个参数,分别是:元素的本地名称、名称空间的前缀。
3)Text_Parsed():该方法在解析器解析完一个文本节点后被执行,包括2个参数,分别是:父元素的限定名称、文本内容。
4)EntityReference_Parsed():该方法在解析器解析到一个实体引用节点时被执行,包括3个参数,分别是:父元素的限定名称、引用实体名、引用实体值(字符串)。
5)Comment_Parsed():该方法在解析器解析到一个注释节点时被执行,包括2个参数,分别是:父元素的限定名称、注释内容。
6)ProcessingInstruction_Parsed():该方法在解析器解析到一个XML处理指令节点时被执行,包括2个参数,分别是:目标处理器名称、处理指令的内容。
7)getParsedData():该方法返回用户解析得到的数据对象,其真实类型是程序员事先指定的泛型类。
程序员可以在自己的实现子类中,选择与要解析的XML文件内容相关的方法并编码实现它,其它的抽象方法可以采用无代码实现!
注意:编写解析代码时要使用方法中的参数来获取解析器传递出来的数据,并根据XML数据的结构来还原数据逻辑。
方法“getParsedData()”是为方便用户提取解析数据而设计的,程序员应该设计合适的类来存储解析得到的数据,并作为该泛型类的类型参数,也是本方法的返回类型,在实现代码中返回该类型的对象。
基于DSE的解析特点就是:一边读取XML文档,一边解析XML数据结构,一边抛出各种扫描事件,并通过响应事件的方法参数输出XML数据。所以有人将这种方式称为基于扫描事件的XML解析。该模式的解析步骤分为两步:
1)创建继承XmlUserParser<T>的子类,并实现其中的抽象方法;
2)调用XmlManager的静态方法parseXml()或parseXmlFile(),并将文档内容或文件名,以及XmlUserParser<T>子类对象传递给静态方法。
5.3 XmlManager管理类
该类提供了操作XML文档最基本的静态方法,目的是简化应用程序的编码。
1)loadXml(String, String)方法:加载参数1指定的XML文本,使用并遵循DOM规范,返回一个XmlDocument对象;
2)loadXmlFile(String)方法:加载指定的XML文件,使用和遵循DOM规范,并返回一个XmlDocument文档对象;
3)parseXml(String,XmlUserParser<?>,String)方法:快速解析参数1指定的XML文本,但不实现DOM接口,也不返回XmlDocument对象,而是在解析过程中,通过各种解析事件以参数将XML节点和属性数据传递给实现了xmlUserParser<T>的子类实例,该子类在方法中处理解析得到的XML数据。
4)parseXmlFile(String,XmlUserParser<?>)方法:与parseXml()类似,但它快速解析一个XML文件。
5)createXmlDocument()方法:创建一个新的XML文档并保存到文件。
6)saveXmlToFile(String xmlFileName, String xmlContent)方法:保存XML文本到指定的文件。
说明:parseXml()和parseXmlFile()方法不创建节点树,但要求提供一个实现了XmlUserParser<T>抽象类的子类实例。用户在该实例类中要编码实现全部的抽象方法(响应各种解析事件),正是在重写这些抽象方法的代码中,完成了提取和使用XML文档数据的工作。因此,程序员会觉得编码工作比较困难,而且很难同时访问同一个文档中的不同数据。
5.4 小结
基于DOM的XML解析,核心思想是把XML文档内容转化为一个节点对象树,可以对树节点进行遍历、添加、修改或删除;当然,还可以创建XML文档。功能十分强大,且编程简单。因此,常用于XML文档需要频繁改变的服务中。但缺点也非常明显,因为解析器在对XML节点树进行遍历、添加、修改或删除之前,必须先读入整个XML文档,并在内存中创建DOM节点对象树。因此,要消耗大量的内存资源。若文档比较小,还不会造成大的问题。但若XML文档比较大,则对内存的需求将成倍增长,这会从解析速度上明显的体现出来,甚至会因内存被消耗殆尽而死机。
基于DSE的解析,采用了扫描XML文档并抛出解析数据的事件机制,即:一边读入XML内容,一边扫描节点、一边通过节点事件抛出解析数据。解析器并不保存每个XML节点对象,所以对内存的消耗与XML文档的大小无关,特别适合于XML大文档的快速解析。当然,这种“路过式”解析并处理数据的策略,不能实现对原文档内容的修改和删除,而且先后抛出的数据之间的逻辑,需要程序员在拦截扫描事件的方法中,自己编写代码进行暂时保存、关联和最终处理。编程难度稍大。好在XWAF提供了XmlUserParser<T>泛型抽象类,提供了应用解析的基本框架和公共处理代码,程序员只需要创建继承子类,并真正实现满足自己功能需求的部分抽象方法,就大功告成。另外,程序员只需要关注、截留和处理自己需要的节点数据,无关的数据可以不管并随即抛弃。即节省了内存资源,有加快了解析速度。对于只读XML解析,DSE是最好的方案!
CXDP集成了DOM和DSE两种解析方案,提供的XmlManager管理器进一步简化了编码,是程序员解析XML文档最好的帮手和选择!