我們的目標並不是做一個大而全的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}
結果符合預期。