Apache Common Digester是一个很优秀的XML解析工具,居于SAX技术,下面是我一个简单实现(
点击下载源代码):
1、类图:
2、规则定义:
2-1、MyRule抽象类:
package mydigest;
import org.xml.sax.Attributes;
/** *//**
* 规则抽象类
* @author kinkding
*/
public abstract class MyRule {
protected MyDigester dig = null;
protected String namespaceURI = null;
public void setDigester(MyDigester dig) {
this.dig = dig;
}
public void setNamespaceURI(String namespaceURI) {
this.namespaceURI = namespaceURI;
}
public String getNamespaceURI() {
return this.namespaceURI;
}
/** *//** 规则使用完成 */
public void finish() throws Exception {
}
/** *//** 开始处理 */
public void begin(String namespace, String name, Attributes attributes) throws Exception {
}
/** *//** 处理中 */
public void body(String namespace, String name, String text) throws Exception {
}
/** *//** 处理结束 */
public void end(String namespace, String name) throws Exception {
}
}
2-2、创建对象规则:
package mydigest;
import org.xml.sax.Attributes;
/** *//** 创建对象 */
public class MyObjCreateRule extends MyRule {
protected String className = null;
public MyObjCreateRule(Class<?> clazz) {
this.className = clazz.getName();
}
public void begin(String namespace, String name, Attributes attributes) throws Exception {
String realClassName = className;
if (name != null) {
// 形如:person="com.info.Person"
String value = attributes.getValue(name);
if (value != null) {
realClassName = value;
}
}
// 创建实例并放置到堆栈中
Class<?> clazz = dig.getClassLoader().loadClass(realClassName);
Object instance = clazz.newInstance();
dig.push(instance);
}
public void end(String namespace, String name) throws Exception {
// 从堆栈中弹出对象
dig.pop();
}
}
2-3、设置属性规则:
package mydigest;
import java.util.HashMap;
import org.apache.commons.beanutils.BeanUtils;
import org.xml.sax.Attributes;
public class MySetPropRule extends MyRule {
public void begin(String namespace, String name, Attributes attributes) throws Exception {
HashMap<String, String> values = new HashMap<String, String>();
// 获取属性及值
for (int i = 0; i < attributes.getLength(); i++) {
String temp = attributes.getLocalName(i);
if ("".equals(temp)) {
temp = attributes.getQName(i);
}
String value = attributes.getValue(i);
if (temp != null) {
values.put(temp, value);
}
}
Object top = dig.peek();
BeanUtils.populate(top, values);
}
}
2-4、MySetNextRule:
package mydigest;
import org.apache.commons.beanutils.MethodUtils;
public class MySetNextRule extends MyRule {
protected String methodName = null;
public MySetNextRule(String methodName) {
this.methodName = methodName;
}
public void end(String namespace, String name) throws Exception {
Object child = dig.peek(0);
Object parent = dig.peek(1);
Class<?> paramTypes[] = new Class<?>[1];
paramTypes[0] = child.getClass();
MethodUtils.invokeMethod(parent, methodName, new Object[] { child }, paramTypes);
}
}
2-5、方法调用规则:
package mydigest;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.MethodUtils;
import org.xml.sax.Attributes;
public class MyCallMethodRule extends MyRule {
protected int targetOffset = 0;
protected String methodName = null;
protected int paramCount = 0;
protected Class<?> paramTypes[] = null;
protected String bodyText = null;
public MyCallMethodRule(int targetOffset, String methodName, int paramCount) {
this.targetOffset = targetOffset;
this.methodName = methodName;
this.paramCount = paramCount;
// 参数类型初始化
if (paramCount == 0) {
this.paramTypes = new Class[] { String.class };
} else {
this.paramTypes = new Class[paramCount];
for (int i = 0; i < this.paramTypes.length; i++) {
this.paramTypes[i] = String.class;
}
}
}
public void begin(String namespace, String name, Attributes attributes) throws Exception {
if (paramCount > 0) {
Object parameters[] = new Object[paramCount];
for (int i = 0; i < parameters.length; i++) {
parameters[i] = null;
}
// 参数入栈
dig.pushParams(parameters);
}
}
public void body(String namespace, String name, String text) throws Exception {
// 只针对0个参数的情况提取元素中间值
if (paramCount == 0) {
this.bodyText = text.trim();
}
}
public void end(String namespace, String name) throws Exception {
Object parameters[] = null;
if (paramCount > 0) {
parameters = (Object[]) dig.popParams();
if (paramCount == 1 && parameters[0] == null) {
return;
}
} else if (paramTypes != null && paramTypes.length != 0) {
if (bodyText == null) {
return;
}
parameters = new Object[1];
parameters[0] = bodyText;
if (paramTypes.length == 0) {
paramTypes = new Class[1];
paramTypes[0] = String.class;
}
}
Object paramValues[] = new Object[paramTypes.length];
for (int i = 0; i < paramTypes.length; i++) {
// 确定是否需要转换
if (parameters[i] == null
|| (parameters[i] instanceof String && !String.class.isAssignableFrom(paramTypes[i]))) {
paramValues[i] = ConvertUtils.convert((String) parameters[i], paramTypes[i]);
} else {
paramValues[i] = parameters[i];
}
}
Object target;
if (targetOffset >= 0) {
target = dig.peek(targetOffset);
} else {
target = dig.peek(dig.getCount() + targetOffset);
}
if (target == null) {
StringBuffer sb = new StringBuffer();
sb.append("[CallMethodRule]{");
sb.append(dig.match);
sb.append("} Call target is null (");
sb.append("targetOffset=");
sb.append(targetOffset);
sb.append(",stackdepth=");
sb.append(dig.getCount());
sb.append(")");
throw new org.xml.sax.SAXException(sb.toString());
}
MethodUtils.invokeMethod(target, methodName, paramValues, paramTypes);
}
}
2-6、参数调用规则:
package mydigest;
import java.util.Stack;
import org.xml.sax.Attributes;
public class MyCallParamRule extends MyRule {
protected String attributeName = null;
protected int paramIndex = 0;
protected Stack<String> bodyTextStack;
public MyCallParamRule(int paramIndex, String attributeName) {
this.paramIndex = paramIndex;
this.attributeName = attributeName;
}
public void begin(String namespace, String name, Attributes attributes) throws Exception {
Object param = null;
if (attributeName != null) {
param = attributes.getValue(attributeName);
}
if (param != null) {
// 给参数赋值
Object parameters[] = (Object[]) dig.peekParams();
parameters[paramIndex] = param;
}
}
public void body(String namespace, String name, String text) throws Exception {
if (attributeName == null) {
if (bodyTextStack == null) {
bodyTextStack = new Stack<String>();
}
bodyTextStack.push(text.trim());
}
}
public void end(String namespace, String name) throws Exception {
if (bodyTextStack != null && !bodyTextStack.empty()) {
Object parameters[] = (Object[]) dig.peekParams();
parameters[paramIndex] = bodyTextStack.pop();
}
}
}
2-7、嵌套属性设置:
package mydigest;
import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.xml.sax.Attributes;
public class MySetNestedPropRule extends MyRule {
public void begin(String namespace, String name, Attributes attributes) throws Exception {
AnyChildRule anyChildRule = new AnyChildRule();
anyChildRule.setDigester(dig);
AnyChildRules newRules = new AnyChildRules(anyChildRule);
newRules.init(dig.getMatch() + "/", dig.getRules());
dig.setRules(newRules);
}
private class AnyChildRules implements MyRules {
private MyRules decoratedRules = null;
private String matchPrefix = null;
private ArrayList<MyRule> rules = new ArrayList<MyRule>(1);
private AnyChildRule rule;
public AnyChildRules(AnyChildRule rule) {
this.rule = rule;
rules.add(rule);
}
public void add(String pattern, MyRule rule) {
}
public List<MyRule> match(String namespaceURI, String pattern) {
List<MyRule> match = decoratedRules.match(namespaceURI, pattern);
if ((pattern.startsWith(matchPrefix)) && (pattern.indexOf('/', matchPrefix.length()) == -1)) {
if ((match == null || match.size() == 0)) {
return rules;
} else {
LinkedList<MyRule> newMatch = new LinkedList<MyRule>(match);
newMatch.addLast(rule);
return newMatch;
}
} else {
return match;
}
}
public List<MyRule> rules() {
return decoratedRules.rules();
}
public void setDigester(MyDigester dig) {
}
public void init(String prefix, MyRules rules) {
matchPrefix = prefix;
decoratedRules = rules;
}
public MyRules getOldRules() {
return decoratedRules;
}
}
private class AnyChildRule extends MyRule {
private String currChildElementName = null;
public void begin(String namespace, String name, Attributes attributes) throws Exception {
currChildElementName = name;
}
public void body(String namespace, String name, String text) throws Exception {
String propName = currChildElementName;
Object top = dig.peek();
text = text.trim();
// 验证是否存在相应属性
PropertyDescriptor desc = PropertyUtils.getPropertyDescriptor(top, propName);
if (desc == null) {
throw new NoSuchMethodException("Bean has no property named " + propName);
}
try {
BeanUtils.setProperty(top, propName, text);
} catch (NullPointerException e) {
throw e;
}
}
public void end(String namespace, String name) throws Exception {
currChildElementName = null;
}
}
}
3、MyRules及其实现类:
3-1、接口:
package mydigest;
import java.util.List;
public interface MyRules {
public void setDigester(MyDigester dig);
public void add(String pattern, MyRule rule);
public List<MyRule> rules();
public List<MyRule> match(String namespaceURI, String pattern);
}
3-2、实现类:
package mydigest;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class MyRulesBase implements MyRules {
protected MyDigester digester = null;
// 缓存:同一个pattern会对应多个规则
protected HashMap<String, List<MyRule>> cache = new HashMap<String, List<MyRule>>();
protected String namespaceURI = null;
// 所有规则列表
protected ArrayList<MyRule> rules = new ArrayList<MyRule>();
public void setDigester(MyDigester dig) {
this.digester = dig;
}
public void add(String pattern, MyRule rule) {
int patternLength = pattern.length();
// 去掉最后一个斜线
if (patternLength > 1 && pattern.endsWith("/")) {
pattern = pattern.substring(0, patternLength - 1);
}
List<MyRule> list = cache.get(pattern);
if (list == null) {
list = new ArrayList<MyRule>();
cache.put(pattern, list);
}
list.add(rule);
rules.add(rule);
if (this.digester != null) {
rule.setDigester(this.digester);
}
if (this.namespaceURI != null) {
rule.setNamespaceURI(this.namespaceURI);
}
}
public List<MyRule> rules() {
return this.rules;
}
public List<MyRule> match(String namespaceURI, String pattern) {
List<MyRule> rulesList = lookup(namespaceURI, pattern);
if ((rulesList == null) || (rulesList.size() < 1)) {
String longKey = "";
for (String key : cache.keySet()) {
if (key.startsWith("*/")) {
// pattern -> key:
// (1):foo -> */foo
// (2):foo/bar->*/bar
if (pattern.equals(key.substring(2)) || pattern.endsWith(key.substring(1))) {
// 查找最匹配的
if (key.length() > longKey.length()) {
rulesList = lookup(namespaceURI, key);
longKey = key;
}
}
}
}
}
if (rulesList == null) {
rulesList = new ArrayList<MyRule>();
}
return (rulesList);
}
/** *//** 根据patter查找匹配的规则列表 */
protected List<MyRule> lookup(String namespaceURI, String pattern) {
List<MyRule> list = this.cache.get(pattern);
if (list == null) {
return (null);
}
if ((namespaceURI == null) || (namespaceURI.length() == 0)) {
return (list);
}
ArrayList<MyRule> results = new ArrayList<MyRule>();
for (MyRule item : list) {
if ((namespaceURI.equals(item.getNamespaceURI())) || (item.getNamespaceURI() == null)) {
results.add(item);
}
}
return (results);
}
}
4、Digester类:
package mydigest;
import java.io.File;
import java.io.FileInputStream;
import java.util.List;
import java.util.Stack;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
public class MyDigester extends DefaultHandler {
// 堆栈
protected Stack<Object> stack = new Stack<Object>();
// 根对象
protected Object root = null;
//
protected MyRules rules = null;
protected XMLReader reader = null;
protected SAXParser parser = null;
protected Stack<StringBuffer> bodyTexts = new Stack<StringBuffer>();
protected StringBuffer bodyText = new StringBuffer();
protected String match = "";
protected Stack<List<MyRule>> matches = new Stack<List<MyRule>>();
protected ClassLoader classLoader = null;
protected Stack<Object> params = new Stack<Object>();
public void push(Object object) {
if (stack.size() == 0) {
root = object;
}
stack.push(object);
}
public void addRule(String pattern, MyRule rule) {
rule.setDigester(this);
getRules().add(pattern, rule);
}
public MyRules getRules() {
if (this.rules == null) {
this.rules = new MyRulesBase();
this.rules.setDigester(this);
}
return this.rules;
}
public void addObjectCreate(String pattern, Class<?> clazz) {
addRule(pattern, new MyObjCreateRule(clazz));
}
public void addSetProperties(String pattern) {
addRule(pattern, new MySetPropRule());
}
public void addSetNext(String pattern, String methodName) {
addRule(pattern, new MySetNextRule(methodName));
}
public void addCallMethod(String pattern, String methodName, int paramCount) {
addRule(pattern, new MyCallMethodRule(0, methodName, paramCount));
}
public void addCallParam(String pattern, int paramIndex, String attributeName) {
addRule(pattern, new MyCallParamRule(paramIndex, attributeName));
}
public void addCallParam(String pattern, int paramIndex) {
addRule(pattern, new MyCallParamRule(paramIndex, null));
}
public void addSetNestedProperties(String pattern) {
addRule(pattern, new MySetNestedPropRule());
}
public Object parse(File file) throws Exception {
InputSource input = new InputSource(new FileInputStream(file));
input.setSystemId(file.toURI().toURL().toString());
getXMLReader().parse(input);
return root;
}
public XMLReader getXMLReader() throws Exception {
if (reader == null) {
reader = getParser().getXMLReader();
}
reader.setDTDHandler(this);
reader.setContentHandler(this);
reader.setEntityResolver(this);
reader.setErrorHandler(this);
return reader;
}
public SAXParser getParser() throws Exception {
if (parser != null) {
return (parser);
}
parser = SAXParserFactory.newInstance().newSAXParser();
return parser;
}
public void startDocument() throws SAXException {
}
public void endDocument() throws SAXException {
for (MyRule r : this.getRules().rules()) {
try {
r.finish();
} catch (Exception e) {
e.printStackTrace();
}
}
stack.clear();
}
public void startElement(String namespaceURI, String localName, String fullName, Attributes attributes)
throws SAXException {
bodyTexts.push(bodyText);
bodyText = new StringBuffer();
String name = localName;
if ((name == null) || (name.length() < 1)) {
name = fullName;
}
StringBuffer sb = new StringBuffer(match);
if (match.length() > 0) {
sb.append('/');
}
sb.append(name);
// 得到新的匹配字符
match = sb.toString();
List<MyRule> rules = getRules().match(namespaceURI, match);
matches.push(rules);
if ((rules != null) && (rules.size() > 0)) {
for (int i = 0; i < rules.size(); i++) {
try {
MyRule rule = rules.get(i);
rule.begin(namespaceURI, name, attributes);
} catch (Exception e) {
e.printStackTrace();
throw new SAXException(e);
}
}
}
}
public void characters(char buffer[], int start, int length) throws SAXException {
bodyText.append(buffer, start, length);
}
public void endElement(String namespaceURI, String localName, String fullName) throws SAXException {
String name = localName;
if ((name == null) || (name.length() < 1)) {
name = fullName;
}
List<MyRule> rules = matches.pop();
if ((rules != null) && (rules.size() > 0)) {
String bodyText = this.bodyText.toString();
for (int i = 0; i < rules.size(); i++) {
try {
MyRule rule = rules.get(i);
rule.body(namespaceURI, name, bodyText);
} catch (Exception e) {
e.printStackTrace();
throw new SAXException(e);
}
}
}
// bodyText 退栈
bodyText = bodyTexts.pop();
if (rules != null) {
// 顺序是从后到前
for(int i=rules.size()-1;i>=0;i--){
try {
MyRule rule = rules.get(i);
rule.end(namespaceURI, name);
} catch (Exception e) {
e.printStackTrace();
throw new SAXException(e);
}
}
}
// 将匹配字符向前回缩一个单元
int slash = match.lastIndexOf('/');
if (slash >= 0) {
match = match.substring(0, slash);
} else {
match = "";
}
}
public ClassLoader getClassLoader() {
if (this.classLoader != null) {
return (this.classLoader);
}
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if (classLoader != null) {
return (classLoader);
}
return (this.getClass().getClassLoader());
}
public Object peek() {
// 取得最后入栈的对象
return this.stack.peek();
}
// 0:最后一个入栈的对象
public Object peek(int n) {
int index = (stack.size() - 1) - n;
if (index < 0) {
return (null);
}
// 第一个入栈的index为0,之后递增
return (stack.get(index));
}
public Object pop() {
return this.stack.pop();
}
public int getCount() {
return (stack.size());
}
public void pushParams(Object object) {
this.params.push(object);
}
public Object popParams() {
return this.params.pop();
}
public Object peekParams() {
return this.params.peek();
}
public String getMatch() {
return match;
}
public void setRules(MyRules rules) {
this.rules = rules;
this.rules.setDigester(this);
}
}
5、测试:
5-1、业务类:
public class AddressBook {
LinkedList people = new LinkedList();
public void addPerson(Person p) {
people.addLast(p);
}
public void print() {
System.out.println("Address book has " + people.size() + " entries");
for(Iterator i = people.iterator(); i.hasNext(); ) {
Person p = (Person) i.next();
p.print();
}
}
}
public class Person {
private int id;
private String category;
private String name;
private HashMap emails = new HashMap();
private List addresses = new ArrayList();
/**
* A unique id for this person. Note that the Digester automatically
* converts the id to an integer.
*/
public void setId(int id) {
this.id = id;
}
public void setCategory(String category) {
this.category = category;
}
public void setName(String name) {
this.name = name;
}
/** we assume only one email of each type */
public void addEmail(String type, String address) {
emails.put(type, address);
}
public void addAddress( Address addr ) {
addresses.add( addr );
}
public void print() {
System.out.println("Person #" + id);
System.out.println(" category=" + category);
System.out.println(" name=" + name);
for(Iterator i = emails.keySet().iterator(); i.hasNext(); ) {
String type = (String) i.next();
String address = (String) emails.get(type);
System.out.println(" email (type " + type + ") : " + address);
}
for(Iterator i = addresses.iterator(); i.hasNext(); ) {
Address addr = (Address) i.next();
addr.print(System.out, 2);
}
}
}
public class Address {
private String type;
private String street;
private String city;
private String state;
private String zip;
private String country;
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(" address (type " + type + ")\n");
sb.append(" " + street + "\n");
sb.append(" " + city + " " + state + " " + zip + "\n");
sb.append(" " + country + "\n");
return sb.toString();
}
public void print(java.io.PrintStream out, int indentAmount) {
StringBuffer indentStr = new StringBuffer(indentAmount);
for (; indentAmount > 0; --indentAmount) {
indentStr.append(' ');
}
out.print(indentStr);
out.print("address type: ");
out.println(type);
out.print(indentStr);
out.println(" " + street);
out.print(indentStr);
out.println(" " + city + " " + state + " " + zip);
out.print(indentStr);
out.println(" " + country);
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getZip() {
return zip;
}
public void setZip(String zip) {
this.zip = zip;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
5-2、XML文件:
<?xml version='1.0'?>
<address-book>
<person id="1" category="acquaintance">
<name>Gonzo</name>
<email type="business">gonzo@muppets.com</email>
<address>
<type>home</type>
<street>123 Maine Ave.</street>
<city>Las Vegas</city>
<state>NV</state>
<zip>01234</zip>
<country>USA</country>
</address>
<address>
<type>business</type>
<street>234 Maple Dr.</street>
<city>Los Angeles</city>
<state>CA</state>
<zip>98765</zip>
<country>USA</country>
</address>
</person>
<person id="2" category="rolemodel">
<name>Kermit</name>
<email type="business">kermit@muppets.com</email>
<email type="home">kermie@acme.com</email>
<address>
<type>business</type>
<street>987 Brown Rd</street>
<city>Las Cruces</city>
<state>NM</state>
<zip>75321</zip>
<country>USA</country>
</address>
</person>
</address-book>
5-3、测试类:
public class MyDigestTest {
public static void main(String[] args) {
String file = "example.xml";
MyDigester dig = new MyDigester();
AddressBook book = new AddressBook();
dig.push(book);
// 添加生成规则(有先后顺序):
// 创建对象
dig.addObjectCreate("address-book/person", Person.class);
// 设置属性
dig.addSetProperties("address-book/person");
dig.addSetNext("address-book/person", "addPerson");
// 一个参数的情况
dig.addCallMethod("address-book/person/name", "setName", 0);
// 两个参数的情况
dig.addCallMethod("address-book/person/email", "addEmail", 2);
dig.addCallParam("address-book/person/email", 0, "type");
dig.addCallParam("address-book/person/email", 1);
dig.addObjectCreate("address-book/person/address", Address.class);
dig.addSetNext("address-book/person/address", "addAddress");
dig.addSetNestedProperties("address-book/person/address");
java.io.File srcfile = new java.io.File(file);
try {
dig.parse(srcfile);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
book.print();
}
}
5-4、输出结果:
Address book has 2 entries
Person #1
category=acquaintance
name=Gonzo
email (type business) : gonzo@muppets.com
address type: home
123 Maine Ave.
Las Vegas NV 01234
USA
address type: business
234 Maple Dr.
Los Angeles CA 98765
USA
Person #2
category=rolemodel
name=Kermit
email (type business) : kermit@muppets.com
email (type home) : kermie@acme.com
address type: business
987 Brown Rd
Las Cruces NM 75321
USA