本文实例讲述了Java基于正则表达式实现xml文件的解析功能。分享给大家供大家参考,具体如下:
这是我通过正则表达式实现的xml文件解析工具,有些XHTML文件中包含特殊符号,暂时还无法正常使用。
设计思路:常见的xml文件都是单根树结构,工具的目的是通过递归的方式将整个文档树装载进一个Node对象。xml文档树上的每一个节点都能看做一个Node对象,它拥有title、attribute和text三个自身变量以及一个childrenNode集合用来存放子节点,使用正则表达式完整装载。
一、编写Node类
Node对象是文档解析的基础,最终可以通过对象的不同属性实现对文档信息的访问。
Node.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
import java.io.Serializable; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; public class Node implements Serializable { // 可以对Node对象持久化保存 private static final long serialVersionUID = 1L; private int id; // 节点类型 private String title; // 节点内容 private String text; // 节点属性集合 private Map<String, String> attributes = new HashMap<String, String>(); // 子节点集合 private List<Node> childNodes = new LinkedList<Node>(); public int getId() { return id; } public void setId( int id) { this .id = id; } public String getTitle() { return title; } public void setTitle(String title) { this .title = title; } public Map<String, String> getAttribute() { return attributes; } public void setAttribute(Map<String, String> attribute) { this .attributes = attribute; } public String getText() { return text; } public void setText(String text) { this .text = text; } public List<Node> getChildNode() { return childNodes; } public void setChildNode(List<Node> childNode) { this .childNodes = childNode; } // 将属性集合转换成一条完整的字符串 private String attrToString() { if (attributes.isEmpty()) { return "" ; } Iterator<Entry<String, String>> its = attributes.entrySet().iterator(); StringBuffer buff = new StringBuffer(); while (its.hasNext()) { Entry<String, String> entry = its.next(); buff.append(entry.getKey() + "=\"" + entry.getValue() + "\" " ); } return " " + buff.toString().trim(); } // 输出完整的节点字符串也用到了递归 @Override public String toString() { String attr = attrToString(); if (childNodes.isEmpty() && text == null ) { return "<" + title + attr + "/>\n" ; } else if (childNodes.isEmpty() && text != null ) { return "<" + title + attr + ">\n" + text + "\n" + "</" + title + ">\n" ; } else { StringBuffer buff = new StringBuffer(); buff.append( "<" + title + attr + ">\n" ); if (!text.isEmpty()) { buff.append(text + "\n" ); } for (Node n : childNodes) { buff.append(n.toString()); } buff.append( "</" + title + ">\n" ); return buff.toString(); } } } |
二、创建接口
把文档的读取和分析抽象成接口方便今后替换实现。
过滤器:读取文档的字符流并删除注释的部分。这些信息通常是提供给人阅读的,程序分析直接忽略。
XmlFilter.java:
1
2
3
4
5
6
7
8
9
|
/* * 过滤器的作用是删除xml文件中不重要的部分。 * 通常都是一些注释性文字,不需要被机器解析。 */ public interface XmlFilter { String filter(); // 提供自定义正则表达式,识别符合过滤条件的字符串 String filter(String[] regex); } |
解析器:将一个父节点解析成多条子节点的字符串。如果返回值为null,代表当前节点下不存在可以继续解析的对象。
XmlParser.java:
1
2
3
4
5
6
7
8
9
|
import java.util.List; /* * 解析器可以对一段完整的父节点字符串提供解析服务。 * 将一条父节点的字符串解析成为多条子节点字符串 */ public interface XmlParser { // 解析一段父节点,返回子节点字符串 List<String> parser(String str); } |
三、根据接口编写实现类
回车、换行、制表符以及各种注释部分的内容都被删除,简化字符输出。
SimpleXmlFilter.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; public class SimpleXmlFilter implements XmlFilter { private String text; // 常用的过滤正则表达式 public final static String[] REG = { "\t" , "<\\?.*?\\?>" , "<!.*?>" , "<%.*?%>" , "\\s{2,}" }; // 读取xml文档返回字符串 public SimpleXmlFilter(File file) throws IOException { BufferedReader in = new BufferedReader( new FileReader(file)); StringBuffer buff = new StringBuffer(); String temp = null ; while ((temp = in.readLine()) != null ) { buff.append(temp); } in.close(); text = buff.toString().trim(); } @Override public String filter() { return filter(REG); } @Override public String filter(String[] regex) { String result = text; for (String reg : regex) { result = result.replaceAll(reg, "" ); } return result; } } |
主要是通过正则表达式区分一个节点内部的子节点,考虑到节点的类型我将它们分为自闭合与非自闭合两种类型。<title attributes .../>这样的节点属于自闭合类型,它们不包含子节点和text属性,它们属于文档树的叶子节点。<title attributes ...>text ...</title>这样的节点属于非自闭合类型,它们属于文档树的分支节点。
SimpleXmlParser.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; public class SimpleXmlParser implements XmlParser { @Override public List<String> parser(String text) { List<String> childrenDocs = new ArrayList<String>(); // 捕获根节点中间的文本 Pattern p = Pattern.compile( "<.*?>(.*)</.*?>" ); Matcher m = p.matcher(text); if (m.matches()) { String inner = m.group( 1 ); // 匹配节点字符串 p = Pattern.compile( "<(.*?)>" ); m = p.matcher(inner); while (m.find()) { String s1 = m.group( 1 ); // 如果节点以/结尾,代表此节点不包含子节点 if (s1.endsWith( "/" )) { childrenDocs.add(m.group()); // 如果节点既不以/开头,也不以/结尾则表示需要查找对应的闭合节点 } else if (!s1.startsWith( "/" ) && !s1.endsWith( "/" )) { // 计算起始字符数 int start = m.end() - m.group().length(); // 如果捕获到未闭合节点则index++,如果捕获到闭合节点则index-- int index = 1 ; while (m.find()) { String s2 = m.group( 1 ); if (!s2.startsWith( "/" ) && !s2.endsWith( "/" )) { index++; } else if (s2.startsWith( "/" )) { index--; } // 找到符合条件的闭合节点则循环终止 if (index == 0 ) { break ; } } // 计算结束字符数 int end = m.end(); // 截取对应字符串 childrenDocs.add(inner.substring(start, end)); } } } return childrenDocs; } } |
四、编写NodeBuilder类
根据过滤器和解析器获取Node节点各属性的值。
NodeBuilder.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
|
import java.io.File; import java.io.IOException; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; // 生成Node public class NodeBuilder { private Node root = new Node(); private XmlParser parser; private XmlFilter filter; // 提供合适的过滤器和解析器 public NodeBuilder(XmlParser parser, XmlFilter filter) { this .parser = parser; this .filter = filter; } public Node getRoot(String... regex) { String str = null ; if (regex.length == 0 ) { str = filter.filter(); } else { str = filter.filter(regex); } buildNodeTree(str, root); return root; } // 设置节点类型 private void buildNodeTitle(String str, Node n) { Pattern p = Pattern.compile( "<.*?>" ); Matcher m = p.matcher(str); if (m.find()) { String temp = m.group(); String s = temp.substring( 1 , temp.length() - 1 ).split( " " )[ 0 ]; if (s.endsWith( "/" )) { n.setTitle(s.substring( 0 , s.length() - 1 )); } else { n.setTitle(s.split( " " )[ 0 ]); } } } // 设置节点属性集合 private void buildNodeAttribute(String str, Node n) { Pattern p = Pattern.compile( "<.*?>" ); Matcher m = p.matcher(str); if (m.find()) { String temp = m.group(); String s = temp.substring( 1 , temp.length() - 1 ); // 匹配字符串 p = Pattern.compile( "(\\S*)=\"(.*?)\"" ); m = p.matcher(s); while (m.find()) { String key = m.group( 1 ).trim(); String value = m.group( 2 ).trim(); n.getAttribute().put(key, value); } // 匹配数字 p = Pattern.compile( "(\\S*)=(-?\\d+(\\.\\d+)?)" ); m = p.matcher(s); while (m.find()) { String key = m.group( 1 ).trim(); String value = m.group( 2 ).trim(); n.getAttribute().put(key, value); } } } // 设置节点内容,节点的内容是删除了所有子节点字符串以后剩下的部分 private void buildNodeText(String str, Node n) { Pattern p = Pattern.compile( "<.*?>(.*)</.*?>" ); Matcher m = p.matcher(str); List<String> childrenDocs = parser.parser(str); if (m.find()) { String temp = m.group( 1 ); for (String s : childrenDocs) { temp = temp.replaceAll(s, "" ); } n.setText(temp.trim()); } } // 通过递归生成完整节点树 private void buildNodeTree(String str, Node n) { buildNodeTitle(str, n); buildNodeAttribute(str, n); buildNodeText(str, n); // 如果存在子节点则继续下面的操作 if (!parser.parser(str).isEmpty()) { // 对每一个子节点都应该继续调用直到递归结束 for (String temp : parser.parser(str)) { Node child = new Node(); buildNodeTitle(temp, child); buildNodeAttribute(temp, child); buildNodeText(temp, child); n.getChildNode().add(child); buildNodeTree(temp, child); } } } } |
五、测试
编写xml测试文件
测试文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
< package > <!-- 这里是注释1 --> package message before! < class id = "exp1" path = "www.sina.com" /> < class id = "exp2" > < class id = "inner" > class message inner. </ class > </ class > package message middle! <!-- 这里是注释2 --> < class id = "exp3" > < method id = "md" name = "setter" order = 1 > <!-- 这里是注释3 --> <!-- 这里是注释4 --> < para ref = "String" /> < para ref = "exp1" > method message inner! </ para > </ method > </ class > package message after! </ package > |
编写测试类
Demo.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
import java.io.File; import java.io.IOException; public class Demo { public static void main(String[] args) { File f = new File( "xxx" ); XmlFilter filter = null ; try { filter = new SimpleXmlFilter(f); } catch (IOException e) { e.printStackTrace(); } XmlParser parser = new SimpleXmlParser(); NodeBuilder builder = new NodeBuilder(parser, filter); Node node = builder.getRoot(); System.out.println(node); } } |
输出:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
< package > package message before!package message middle!package message after! < class path = "www.sina.com" id = "exp1" /> < class id = "exp2" > < class id = "inner" > class message inner. </ class > </ class > < class id = "exp3" > < method name = "setter" id = "md" order = "1" > < para ref = "String" /> < para ref = "exp1" > method message inner! </ para > </ method > </ class > </ package > |
希望本文所述对大家java程序设计有所帮助。
原文链接:http://www.cnblogs.com/learnhow/p/5217450.html