拿起笔来做刀枪 · 之一 再造一个dom4j
2014-07-28 14:48
302 查看
我们的目标并不是做一个大而全的xml解析器,而是,希望能针对性的对特定的(比如spring-style)xml格式进行解析。
所以,根据我们的“从需求出发”原则,我们先看看目标:
解析:
并构造一个节点树结构,供BeanFactory使用。
根据这个xml格式,我们可以看到:
一个节点有:
名称 如 bean 一个
属性 key : value 名值对 多个
子节点 多个
因此,我们定义如下的数据结构:
接着,我们继续写解析器,由于我们附加了很多让问题简单化的约束条件,所以解析并不复杂。
由于此文基于”尽量少依赖“的论述方式,所以我既不会使用正则,也不会使用antlr,
而是简单的读取一个个字符进行实时处理,采取”从左往右读取,从右往左匹配,前溯一个字符“的方式进行解析。
所以,从观察得到:
节点的开始是: < + char
结束是: /> 或者 </
由于存在嵌套的可能,因此,我们要使用一个 stack堆栈, 对节点进行 入栈 和 出栈 处理。否则,按照上述开始/结束的判断逻辑,就会将嵌套的内容提前结束,如:
实现代码如下:
在上述代码中,出现了一个新类 AttrParser,这个类的功能是用于解析 节点的 属性 即一系列的名值对。
新建一个类的目的,是为了将问题简化成两个部分:
识别节点和节点的嵌套关系——xmlparser
解析并获得单个节点的名值对内容——attrparser
代码如下:
注意,因为我们采取的是顺序读取,因此我们这里使用了一个状态机,用以记录和切换解析的步骤状态。
最后,我们将解析出来的节点树命名为 DomTree 并提供根据ID查询节点内容的方法:
代码至此完毕,让我们测试一下:
输出:
结果符合预期。
所以,根据我们的“从需求出发”原则,我们先看看目标:
解析:
<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}
结果符合预期。
相关文章推荐
- 拿起笔来做刀枪 · 之六 再造一个hibernate
- 拿起笔来做刀枪 · 之五 再造一个lucene
- 拿起笔来做刀枪 · 之四 再造一个struts
- 拿起笔来做刀枪 · 之三 再造一个jsp(java sign pages)
- 拿起笔来做刀枪 · 之二 再造一个spring
- 拿起笔来做刀枪 · 序言
- 拿起笔来做刀枪 · 之七 最终幻想 Final Fantasy
- 一个博士的悲情经历~失败的经验最美···小木虫上的系列精华帖(科研的、被科研的,共勉。转载,个人整理)
- 写一个sql中视图和存储过程的简单创建和调用方法!!!自我备忘用····
- 怎么样从一个疯狂下载者成为一个学习者!!!值得反省下的问题·~~
- 苹果史蒂夫·乔布斯辞世 一个传奇时代的终结
- 刷 百度排名,百度(google)搜索提示下拉关联词的一个简易思路··
- ONE · 一个简单更新
- 【数说·大数据圈】机器学习在生物大数据应用的一个例子 文/飞扬
- 切·格瓦拉 ———— 一个英雄的一生!
- [每天一个知识点]7-冯·诺依曼体系结构
- 「ONE · 一个 」优雅PC客户端
- 汉斯·乌尔里希·鲁德尔-唯一一个钻石金双剑金橡叶骑士勋章获得者
- 微软二十五年(比尔·盖茨) --- 我们都在同一个游泳池里游泳
- javascript入门·脚本执行的时间的四种类型(赠送一个转换的小例题)