Java基于正則表達式實現(xiàn)xml文件的解析功能詳解
本文實例講述了Java基于正則表達式實現(xiàn)xml文件的解析功能。分享給大家供大家參考,具體如下:
這是我通過正則表達式實現(xiàn)的xml文件解析工具,有些XHTML文件中包含特殊符號,暫時還無法正常使用。
設計思路:常見的xml文件都是單根樹結(jié)構(gòu),工具的目的是通過遞歸的方式將整個文檔樹裝載進一個Node對象。xml文檔樹上的每一個節(jié)點都能看做一個Node對象,它擁有title、attribute和text三個自身變量以及一個childrenNode集合用來存放子節(jié)點,使用正則表達式完整裝載。
一、編寫Node類
Node對象是文檔解析的基礎,最終可以通過對象的不同屬性實現(xiàn)對文檔信息的訪問。
Node.java:
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;
// 節(jié)點類型
private String title;
// 節(jié)點內(nèi)容
private String text;
// 節(jié)點屬性集合
private Map<String, String> attributes = new HashMap<String, String>();
// 子節(jié)點集合
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;
}
// 將屬性集合轉(zhuǎn)換成一條完整的字符串
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();
}
// 輸出完整的節(jié)點字符串也用到了遞歸
@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();
}
}
}
二、創(chuàng)建接口
把文檔的讀取和分析抽象成接口方便今后替換實現(xiàn)。
過濾器:讀取文檔的字符流并刪除注釋的部分。這些信息通常是提供給人閱讀的,程序分析直接忽略。
XmlFilter.java:
/*
* 過濾器的作用是刪除xml文件中不重要的部分。
* 通常都是一些注釋性文字,不需要被機器解析。
*/
public interface XmlFilter {
String filter();
// 提供自定義正則表達式,識別符合過濾條件的字符串
String filter(String[] regex);
}
解析器:將一個父節(jié)點解析成多條子節(jié)點的字符串。如果返回值為null,代表當前節(jié)點下不存在可以繼續(xù)解析的對象。
XmlParser.java:
import java.util.List;
/*
* 解析器可以對一段完整的父節(jié)點字符串提供解析服務。
* 將一條父節(jié)點的字符串解析成為多條子節(jié)點字符串
*/
public interface XmlParser {
// 解析一段父節(jié)點,返回子節(jié)點字符串
List<String> parser(String str);
}
三、根據(jù)接口編寫實現(xiàn)類
回車、換行、制表符以及各種注釋部分的內(nèi)容都被刪除,簡化字符輸出。
SimpleXmlFilter.java:
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;
}
}
主要是通過正則表達式區(qū)分一個節(jié)點內(nèi)部的子節(jié)點,考慮到節(jié)點的類型我將它們分為自閉合與非自閉合兩種類型。<title attributes .../>這樣的節(jié)點屬于自閉合類型,它們不包含子節(jié)點和text屬性,它們屬于文檔樹的葉子節(jié)點。<title attributes ...>text ...</title>這樣的節(jié)點屬于非自閉合類型,它們屬于文檔樹的分支節(jié)點。
SimpleXmlParser.java:
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>();
// 捕獲根節(jié)點中間的文本
Pattern p = Pattern.compile("<.*?>(.*)</.*?>");
Matcher m = p.matcher(text);
if (m.matches()) {
String inner = m.group(1);
// 匹配節(jié)點字符串
p = Pattern.compile("<(.*?)>");
m = p.matcher(inner);
while (m.find()) {
String s1 = m.group(1);
// 如果節(jié)點以/結(jié)尾,代表此節(jié)點不包含子節(jié)點
if (s1.endsWith("/")) {
childrenDocs.add(m.group());
// 如果節(jié)點既不以/開頭,也不以/結(jié)尾則表示需要查找對應的閉合節(jié)點
} else if (!s1.startsWith("/") && !s1.endsWith("/")) {
// 計算起始字符數(shù)
int start = m.end() - m.group().length();
// 如果捕獲到未閉合節(jié)點則index++,如果捕獲到閉合節(jié)點則index--
int index = 1;
while (m.find()) {
String s2 = m.group(1);
if (!s2.startsWith("/") && !s2.endsWith("/")) {
index++;
} else if (s2.startsWith("/")) {
index--;
}
// 找到符合條件的閉合節(jié)點則循環(huán)終止
if (index == 0) {
break;
}
}
// 計算結(jié)束字符數(shù)
int end = m.end();
// 截取對應字符串
childrenDocs.add(inner.substring(start, end));
}
}
}
return childrenDocs;
}
}
四、編寫NodeBuilder類
根據(jù)過濾器和解析器獲取Node節(jié)點各屬性的值。
NodeBuilder.java:
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;
}
// 設置節(jié)點類型
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]);
}
}
}
// 設置節(jié)點屬性集合
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);
}
// 匹配數(shù)字
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);
}
}
}
// 設置節(jié)點內(nèi)容,節(jié)點的內(nèi)容是刪除了所有子節(jié)點字符串以后剩下的部分
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());
}
}
// 通過遞歸生成完整節(jié)點樹
private void buildNodeTree(String str, Node n) {
buildNodeTitle(str, n);
buildNodeAttribute(str, n);
buildNodeText(str, n);
// 如果存在子節(jié)點則繼續(xù)下面的操作
if (!parser.parser(str).isEmpty()) {
// 對每一個子節(jié)點都應該繼續(xù)調(diào)用直到遞歸結(jié)束
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測試文件
測試文件:
<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:
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);
}
}
輸出:
<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>
PS:這里再為大家提供2款非常方便的正則表達式工具供大家參考使用:
JavaScript正則表達式在線測試工具:
http://tools.jb51.net/regex/javascript
正則表達式在線生成工具:
http://tools.jb51.net/regex/create_reg
更多關于java算法相關內(nèi)容感興趣的讀者可查看本站專題:《Java正則表達式技巧大全》、《Java數(shù)據(jù)結(jié)構(gòu)與算法教程》、《Java操作DOM節(jié)點技巧總結(jié)》、《Java文件與目錄操作技巧匯總》和《Java緩存操作技巧匯總》
希望本文所述對大家java程序設計有所幫助。
相關文章
Java?IO及BufferedReader.readline()出現(xiàn)的Bug
這篇文章主要介紹了Java?IO及BufferedReader.readline()出現(xiàn)的Bug,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12
Springboot實現(xiàn)Java郵件任務過程解析
這篇文章主要介紹了Springboot實現(xiàn)Java郵件任務過程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-09-09
Java使用Statement接口執(zhí)行SQL語句操作實例分析
這篇文章主要介紹了Java使用Statement接口執(zhí)行SQL語句操作,結(jié)合實例形式詳細分析了Java使用Statement接口針對mysql數(shù)據(jù)庫進行連接與執(zhí)行SQL語句增刪改查等相關操作技巧與注意事項,需要的朋友可以參考下2018-07-07
JVM入門之類加載與字節(jié)碼技術(類加載與類的加載器)
Java字節(jié)碼增強指的是在Java字節(jié)碼生成之后,對其進行修改,增強其功能,這種方式相當于對應用程序的二進制文件進行修改。Java字節(jié)碼增強主要是為了減少冗余代碼,提高性能等2021-06-06
使用Idea maven創(chuàng)建Spring項目過程圖解
這篇文章主要介紹了使用Idea maven創(chuàng)建Spring項目過程圖解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-02-02

