您的位置:首页 > 其它

拿起笔来做刀枪 · 之一 再造一个dom4j

2014-07-28 14:48 302 查看
我们的目标并不是做一个大而全的xml解析器,而是,希望能针对性的对特定的(比如spring-style)xml格式进行解析。

所以,根据我们的“从需求出发”原则,我们先看看目标:

解析:

<beans >

<bean id="searchService" class="cn.com.sitefromscrath.service.SearchServiceInRealBiz">
<constructor-arg index="1" ref="luceneDAO" />
<constructor-arg index="2" ref="mysqlDAO" />
</bean>

<bean id="luceneDAO" class="cn.com.sitefromscrath.dao.LuceneDAOMock" />

<bean id="mysqlDAO" class="cn.com.sitefromscrath.dao.MysqlDAOMock" />

<bean id="testlevel_one" class="cn.com.sitefromscrath.none1" >
<bean id="testlevel_two" class="cn.com.sitefromscrath.none2" />
</bean>

</beans>


并构造一个节点树结构,供BeanFactory使用。

根据这个xml格式,我们可以看到:

一个节点有:

名称 如 bean 一个

属性 key : value 名值对 多个

子节点 多个

因此,我们定义如下的数据结构:

package net.csdn.blog.deltatang.dom4me;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Node {

public String name = null;

protected Map<String, String> attributes = new HashMap<String, String>();

protected List<Node> children = new ArrayList<Node>();

public String toString(){
return "{attr : " + this.attributes.toString() + ", children : " + children.size() + "}";
}

public String getName() {
return name;
}

public Map<String, String> getAttributes() {
return attributes;
}

public List<Node> getChildren() {
return children;
}

}


接着,我们继续写解析器,由于我们附加了很多让问题简单化的约束条件,所以解析并不复杂。

由于此文基于”尽量少依赖“的论述方式,所以我既不会使用正则,也不会使用antlr,

而是简单的读取一个个字符进行实时处理,采取”从左往右读取,从右往左匹配,前溯一个字符“的方式进行解析。

所以,从观察得到:

节点的开始是: < + char

结束是: /> 或者 </

由于存在嵌套的可能,因此,我们要使用一个 stack堆栈, 对节点进行 入栈 和 出栈 处理。否则,按照上述开始/结束的判断逻辑,就会将嵌套的内容提前结束,如:

<bean id="searchService" class="cn.com.sitefromscrath.service.SearchServiceInRealBiz">
<constructor-arg index="1" ref="luceneDAO" />


实现代码如下:

package net.csdn.blog.deltatang.dom4me;

import java.io.InputStream;
import java.util.Stack;

public class XmlParser {

DomTree build(InputStream is) {
DomTree dt = new DomTree();
Node root = null;

int b = -1;
try {

Stack<Node> ns = new Stack<Node>();
AttrParser ap = null;

Node n = null;
char prevc = 0;

boolean isblank = false;

while((b = is.read()) >= 0) {
char c = (char) b;

isblank = (c == ' ' || c == '\t' || c == '\n');

//node start
if(prevc == '<' && !isblank && c != '/') {

n = new Node();
if(!ns.isEmpty()) {
ns.peek().children.add(n);
}
ns.push(n);

ap = new AttrParser(n);
}

//node end   /> or </
if(
(prevc == '/' && c == '>')
||
(prevc == '<' && c == '/')
) {

n = ns.pop();
if(ns.isEmpty()) {
root = n;
break;
}

ap = null;
}

if(ap != null) {
ap.read(c);
}

prevc = isblank ? prevc : c;

}

} catch (Exception e) {
e.printStackTrace();
}

dt.root = root;
return dt;
}

}


在上述代码中,出现了一个新类 AttrParser,这个类的功能是用于解析 节点的 属性 即一系列的名值对。

新建一个类的目的,是为了将问题简化成两个部分:

识别节点和节点的嵌套关系——xmlparser

解析并获得单个节点的名值对内容——attrparser

代码如下:

package net.csdn.blog.deltatang.dom4me;

public class AttrParser {

private Node node;

private StringBuffer name;
private StringBuffer key;
private StringBuffer val;

// 0 start 1 name 2 key 3 val start 4 val end & new key-val
int stat = 0;

public AttrParser(Node node) {
super();
this.node = node;
}

public void read(char c) {
boolean isblank = (c == ' ' || c == '\t' || c == '\n');
switch(stat) {
case 0 : {
if(!isblank) {
name = new StringBuffer();
name.append(c);
stat = 1;
}
break;
}
case 1 : {
if(!isblank) name.append(c);
else {
//name end
String n = this.name.toString();
node.name = n;
name = null;

key = new StringBuffer();
stat = 2;
}
break;
}
case 2 : {
if(c != '=') {
if(!isblank) key.append(c);
}else {
val = new StringBuffer();
stat = 3;
}
break;
}
case 3 : {
if(c == '"') {
stat = 4;
}
break;
}
case 4 : {
if(c == '"') {
String k = this.key.toString();
String v = this.val.toString();

node.attributes.put(k, v);
key = null;
val = null;

key = new StringBuffer();
stat = 2;

} else {
val.append(c);
}
break;
}
}
}

}


注意,因为我们采取的是顺序读取,因此我们这里使用了一个状态机,用以记录和切换解析的步骤状态。

最后,我们将解析出来的节点树命名为 DomTree 并提供根据ID查询节点内容的方法:

package net.csdn.blog.deltatang.dom4me;

public class DomTree {

protected Node root = null;

public Node getById(String id) {
return _getById(id, root);
}

private Node _getById(String id, Node node) {
if(node == null) {
return null;
}
if(id.equals(node.attributes.get("id"))) {
return node;
}
if(node.children.isEmpty()) {
return null;
}
Node r = null;
for(int i = 0; i < node.children.size(); i++) {
Node c = (Node)node.children.get(i);
r = _getById(id, c);
if(r != null) return r;
}
return null;
}

public String toString(){
if(root == null) {
return "EMTPY TREE!";
}

StringBuilder sb = new StringBuilder();
getContent(root, 0, sb);
return sb.toString();
}

private void getContent(Node n, int idx, StringBuilder sb) {

for (int i = 0; i < idx; i++) {
sb.append('\t');
}
sb.append(n).append("\n");

for (Node nc : n.children) {
getContent(nc, idx + 1, sb);
}
}

}


代码至此完毕,让我们测试一下:

public static void main(String[] args) throws Exception {

XmlParser xmlParser = new XmlParser();
InputStream is = xmlParser.getClass().getResourceAsStream("./test.xml");
DomTree dt = xmlParser.build(is);
is.close();

System.out.println(dt);

System.out.println("search : " + dt.getById("searchService"));
System.out.println("search : " + dt.getById("mysqlDAO"));
System.out.println("search : " + dt.getById("testlevel_one"));
System.out.println("search : " + dt.getById("testlevel_two"));
}


输出:

{attr : {}, children : 4}
{attr : {id=searchService, class=cn.com.sitefromscrath.service.SearchServiceInRealBiz}, children : 2}
{attr : {ref=luceneDAO, index=1}, children : 0}
{attr : {ref=mysqlDAO, index=2}, children : 0}
{attr : {id=luceneDAO, class=cn.com.sitefromscrath.dao.LuceneDAOMock}, children : 0}
{attr : {id=mysqlDAO, class=cn.com.sitefromscrath.dao.MysqlDAOMock}, children : 0}
{attr : {id=testlevel_one, class=cn.com.sitefromscrath.none1}, children : 1}
{attr : {id=testlevel_two, class=cn.com.sitefromscrath.none2}, children : 0}

search : {attr : {id=searchService, class=cn.com.sitefromscrath.service.SearchServiceInRealBiz}, children : 2}
search : {attr : {id=mysqlDAO, class=cn.com.sitefromscrath.dao.MysqlDAOMock}, children : 0}
search : {attr : {id=testlevel_one, class=cn.com.sitefromscrath.none1}, children : 1}
search : {attr : {id=testlevel_two, class=cn.com.sitefromscrath.none2}, children : 0}


结果符合预期。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: