欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Digester 解析XML

程序员文章站 2022-04-24 15:44:07
...

Digester 解析 XML 成 java 对象

 

惯例,提供参考连接, 高大全:http://www.massapi.com/class/di/Digester.html

                                          api:http://commons.apache.org/proper/commons-digester/commons-digester-3.0/apidocs/

 

1. 其实现思路是基于XML元素节点读取事件驱动的,依赖SAX。使用W3C 的XPATH来监听xml元素节点的读取。

 

2. 简单例子

   现有xml, test-members.xml:

<members>
	<member name="Oham" species="human">
		<skill>create</skill>
		<equipment id="11" version="v1.0">Saurcer ship</equipment>
		<equipment id="12" version="v1.2-beta">Predator mask</equipment>
		<level>6</level>
	</member>
	
	<member name="Oham" species="dog">
		<skill>know the truth</skill>
		<skill>free the soul</skill>
		<level>12</level>
	</member>
	
</members>

  

  构造java bean,以映射xml,这里构造两个bean,Members.java对应元素<members>;Member.java对应<member>。

 

Members.java:

package org.oham.xml;

import java.util.ArrayList;
import java.util.List;

public class Members {
	private List<Member> members = new ArrayList<Member>();

	public List<Member> getMembers() {
		return members;
	}

	public void addMember(Member member) {
		members.add(member);
	}
}

 

Member.java:

package org.oham.xml;

import java.util.HashSet;
import java.util.Set;

public class Member {

	private String name;
	private String species;
	
	private Set<String> skills = new HashSet<String>();
	private Set<String> equipments = new HashSet<String>();
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getSpecies() {
		return species;
	}
	public void setSpecies(String species) {
		this.species = species;
	}
	public Set<String> getSkills() {
		return skills;
	}
	public Set<String> getEquipments() {
		return equipments;
	}
	public void addSkill(String skill) {
		this.skills.add(skill);
	}
	public void addEquipment(String equipment, int id, String version) {
		System.out.println("id: " + id + ", version: " + version + " stand by.");
		this.equipments.add(equipment);
	}
}

  

 

使用Digester读取并解析xml,有几种种方式,介绍两种:

//使用方式一,调用Digester中的方法解析XPATH,
	public Members parse(File xmlFile) throws IOException, 
												SAXException {
		System.out.println("parse XPATH with API method");
		
		Digester digester = new Digester();
		
		// 遇到members元素节点开始时,构造Members类的对象
		digester.addObjectCreate("members", Members.class);
		
		// 遇到members元素的子元素member开始时,构造Member类的对象
		digester.addObjectCreate("members/member", Member.class);
		
		// set up members元素的子元素member的属性值, 前提是xml中的属性名必须与java bean中的一致
		// 并且java bean 要有对应的setter方法
		digester.addSetProperties("members/member");
		
		// 将当前members元素的子元素member所对应的bean 通过调用其parent members所对应的Members实例
		// 中的方法 addMember,并以其所对应的bean作为参数传入,这样就可以在Members中初始化member的实例了
		digester.addSetNext("members/member", "addMember");
		
		// 遇到members元素的子元素member中的skill节点时调用其直接parent member实例中的addSkill方法
		// 第三个参数为xml的参数索引,这里 0 表示去取skill元素body内的值,并且取出的只能是String类型(假如是数字,而addSkill中的参数为int类型,这样会抛No such accessible method exception,
		// 就是说默认只认识String类型的参数,改成String类型参数就能取到,看下面的level就知道)
		digester.addCallMethod("members/member/skill", "addSkill", 0);
		
		// 当需要参入不同类型的多个参数时,这样用
		digester.addCallMethod("members/member/equipment", "addEquipment", 3, 
				new String[]{"java.lang.String", "java.lang.Integer", "java.lang.String"});
		// 标记equipment 元素 body中的值为参数一
		digester.addCallParam("members/member/equipment", 0);
		// 标记equipment 元素 属性id的值为参数二
		digester.addCallParam("members/member/equipment", 1, "id");
		// 标记equipment 元素 属性version的值为参数三
		digester.addCallParam("members/member/equipment", 2, "version");
		
		// 抛No such accessible method: setLevel() on object: org.oham.xml.Member,setLevel中传入的是int类型参数,它不认
		//digester.addCallMethod("members/member/level", "setLevel", 0);
		
		//解决1:调用CallParam标记参数
		digester.addCallMethod("members/member/level", "setLevel", 1, 
											new String[]{"java.lang.Integer"});
		digester.addCallParam("members/member/level", 0);
		
		//解决2:调用addBeanPropertySetter,去call bean中相应的serter方法
		//digester.addBeanPropertySetter("members/member/level","level");
		
		return digester.parse(xmlFile);
	}

 

测试块代码:

public static void main(String[] args) {
		try {
			// 读入xml文件
			String fPath = MembersParser.class.getClass().getResource("/org/oham/xml/test-members.xml").getPath();
			File xmlFile = new File(fPath); 
                         
                        //方式一
		        Members members = new MembersParser().parse(xmlFile);
                        //方式二
			//Members members = new MembersParser().parseInXMLRule(xmlFile);
			
			List<Member> mList = members.getMembers();
			assert mList.size() == 2 : mList.size();
			
			assert mList.get(0).getSkills().size() == 1 : mList.get(0).getSkills().size();
			
			assert mList.get(0).getEquipments().size() == 2 : mList.get(0).getEquipments().size();
			
			assert mList.get(0).getLevel() == 6 : mList.get(0).getLevel();
			
			assert mList.get(1).getSkills().size() == 2 : mList.get(1).getSkills().size();
			
			assert mList.get(1).getEquipments().size() == 0 : mList.get(1).getEquipments().size();
			
			assert mList.get(1).getLevel() == 12 : mList.get(1).getLevel();
			
			assert "Lulu".equals(mList.get(1).getName()) : mList.get(1).getName();
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		}
	}

 

 

 

	// 使用方式二,将XPATH写到另一个xml封装起来,使用FromXmlRulesModule这个类读入xml并用其生成digester实例
	
	// 使用一个内部类读入并解析rules xml 
	private class RulesModule extends FromXmlRulesModule{

		@Override
		protected void loadRules() {
			loadXMLRules(MembersParser.class.getClass().getResource("/org/oham/xml/test-members-rules.xml"));
		}
	}
	
	
	public Members parseInXMLRule(File xmlFile) throws IOException, SAXException {
		System.out.println("parse XPATH with XML rules");
		
		// 使用RulesModule生成digester实例
		Digester digester = DigesterLoader.newLoader(new RulesModule()).newDigester();
		return digester.parse(xmlFile);
	}

 

封装XPATH规则的xml文件, test-members-rules.xml:

<?xml version="1.0"?>
<!DOCTYPE digester-rules PUBLIC
  "-//Apache Commons //DTD digester-rules XML V1.0//EN"
  "http://commons.apache.org/digester/dtds/digester-rules-3.0.dtd">
  
<digester-rules>

	<pattern value="members">
		<!-- 对应digester.addObjectCreate -->
		<object-create-rule classname="org.oham.xml.Members" />
		
		<pattern value="member">
			<object-create-rule classname="org.oham.xml.Member" />
			
			<!-- 对应digester.addSetProperties -->
			<set-properties-rule />
			
			<!-- 对应digester.addSetNext -->
			<set-next-rule methodname="addMember" paramtype="org.oham.xml.Member"/>
			
			<!-- 对应digester.addCallMethod -->
			<call-method-rule pattern="skill" methodname="addSkill" paramcount="0" />
			
			<pattern value="equipment">
				<call-method-rule methodname="addEquipment" paramcount="3" paramtypes="java.lang.String,java.lang.Integer,java.lang.String" />
				
				<!-- 对应digester.addCallParam -->
				<call-param-rule paramnumber="0" />
				<call-param-rule paramnumber="1" attrname="id" />
				<call-param-rule paramnumber="2" attrname="version" />
				
			</pattern>
			
			<pattern value="level">
				<call-method-rule methodname="setLevel" paramcount="1" paramtypes="java.lang.Integer" />
				<call-param-rule paramnumber="0" />
				
				<!-- 对应digester.addBeanPropertySetter  -->
				<!-- <bean-property-setter-rule propertyname="level" /> -->
			</pattern>
			
		</pattern>
	</pattern>
	
</digester-rules>

 

 

使用schema验证

现在有以下规则用于test-members.xml,不符合规则者不得被解析,

1)members为根元素

2)member元素必须指定属性:name和species, member元素可以为空,多个

3)member元素中skill至少有一,equipment 可为空, level又且只有一,值的类型为正整数,最小为1,最大为99

4)equipment元素的id属性值唯一,注意在members元素范围内

 

据此定义schema如下test-members.xsd,关于schema,可参考w3c shcool 的教程

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
		targetNamespace="http://www.example.org/test-members" 
		xmlns="http://www.example.org/test-members" 
		xmlns:tns="http://www.example.org/test-members" 
		elementFormDefault="qualified" >
		
		
	<xs:complexType name="eqipment-type" >
		<xs:simpleContent>
			<xs:extension base="xs:string">
				<xs:attribute name="id" use="required" type="xs:positiveInteger" />
				<xs:attribute name="version"  use="required" type="xs:string"/>
			</xs:extension>
		</xs:simpleContent>
	</xs:complexType>
	
	<xs:complexType name="member-type">
		<xs:sequence>
			<xs:element name="skill" type="xs:string" minOccurs="1" maxOccurs="unbounded" />
			
			<xs:element name="equipment" type="eqipment-type" minOccurs="0" maxOccurs="unbounded" />
				
			<xs:element name="level">
				<xs:simpleType>
					<xs:restriction base="xs:positiveInteger">
						<xs:minInclusive value="1"/>
						<xs:maxInclusive value="99"/>
					</xs:restriction>
				</xs:simpleType>
			</xs:element>
		</xs:sequence>
		
		<xs:attribute name="name" use="required" type="xs:string" />
		<xs:attribute name="species"  use="required" type="xs:string" />
	</xs:complexType>
	
	<xs:element name="members">
		<xs:complexType>
			<xs:sequence>
				<xs:element name="member" type="member-type" minOccurs="0"  maxOccurs="unbounded">
				</xs:element>
			</xs:sequence>
		</xs:complexType>
		
		<xs:unique name="idUnique">
			<!-- 注意此处不加命名空间是无效的,这告了我N久 -->
			<xs:selector xpath="tns:member/tns:equipment"/>
			<xs:field xpath="@id" />
		</xs:unique>
		
	</xs:element>
</xs:schema>

 

修改test-members.xml如下:

<?xml version="1.0" encoding="UTF-8"?>
<members xmlns="http://www.example.org/test-members"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.example.org/test-members test-members.xsd">
	
	
	<member name="Oham" species="human">
		<skill>create</skill>
		<equipment id="11" version="v1.0">Saurcer ship</equipment>
		<equipment id="12" version="v1.2-beta">Predator mask</equipment>
		<level>6</level>
	</member>
	
	<member name="Lulu" species="dog">
		<skill>know the truth</skill>
		<skill>free the soul</skill>
		<!-- 按照规则,此处非法,因为前面已有id为12 的equipment -->
		<equipment id="12" version="v1.2-beta">Predator mask</equipment>
		<level>12</level>
	</member>
	
</members>

 

修改MemberParser.java, 加入设置schema验证代码:

private void setSchemaValidate(Digester digester, File xmlFile) throws SAXException, IOException {
		SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
		Schema schema = factory.newSchema(MembersParser.class.getClass().getResource("/org/oham/xml/test-members.xsd"));
		Validator validator = schema.newValidator();
		validator.validate(new StreamSource(xmlFile));
	}

 调用parseInXMLRule测试,在其加入验证方法setSchemaValidate:

 

public Members parseInXMLRule(File xmlFile) throws IOException, SAXException {
		System.out.println("parse XPATH with XML rules");
		
		// 使用RulesModule生成digester实例
		Digester digester = DigesterLoader.newLoader(new RulesModule()).newDigester();
		
		// 加入schema验证
		setSchemaValidate(digester, xmlFile);
		
		return digester.parse(xmlFile);
	}
	

 运行,结果抛了org.xml.sax.SAXParseException: cvc-identity-constraint.4.1:为元素“members”的标识约束“idUnique”声明了重复的唯一值 [12]。

注意一点,对于test-members.xml:

<?xml version="1.0" encoding="UTF-8"?>
<members xmlns="http://www.example.org/test-members"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.example.org/test-members test-members.xsd">

...

 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 和 xsi:schemaLocation="http://www.example.org/test-members test-members.xsd" 只是在编辑xml的时候告知schema的位置,方便使用快捷方式编写,但跟程序运行时做schema验证无关,把这两句去掉,照样没问题,但必须指明命名空间,这里指明默认命名空间:xmlns="http://www.example.org/test-members"