通過正則表達式實現簡單xml文件解析


這是我通過正則表達式實現的xml文件解析工具,有些XHTML文件中包含特殊符號,暫時還無法正常使用。

設計思路:常見的xml文件都是單根樹結構,工具的目的是通過遞歸的方式將整個文檔樹裝載進一個Node對象。xml文檔樹上的每一個節點都能看做一個Node對象,它擁有title、attribute和text三個自身變量以及一個childrenNode集合用來存放子節點,使用正則表達式完整裝載。

一、編寫Node類

Node對象是文檔解析的基礎,最終可以通過對象的不同屬性實現對文檔信息的訪問。

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();
        }
    }
}
Node.java

二、創建接口

把文檔的讀取和分析抽象成接口方便今后替換實現。

過濾器:讀取文檔的字符流並刪除注釋的部分。這些信息通常是提供給人閱讀的,程序分析直接忽略。

/*
 * 過濾器的作用是刪除xml文件中不重要的部分。
 * 通常都是一些注釋性文字,不需要被機器解析。
 */
public interface XmlFilter {
    String filter();

    // 提供自定義正則表達式,識別符合過濾條件的字符串
    String filter(String[] regex);
}
XmlFilter.java

解析器:將一個父節點解析成多條子節點的字符串。如果返回值為null,代表當前節點下不存在可以繼續解析的對象。

import java.util.List;
/*
 * 解析器可以對一段完整的父節點字符串提供解析服務。
 * 將一條父節點的字符串解析成為多條子節點字符串
 */
public interface XmlParser {
    // 解析一段父節點,返回子節點字符串
    List<String> parser(String str);
}
XmlParser.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;
    }

}
SimpleXmlFilter.java

主要是通過正則表達式區分一個節點內部的子節點,考慮到節點的類型我將它們分為自閉合與非自閉合兩種類型。<title attributes .../>這樣的節點屬於自閉合類型,它們不包含子節點和text屬性,它們屬於文檔樹的葉子節點。<title attributes ...>text ...</title>這樣的節點屬於非自閉合類型,它們屬於文檔樹的分支節點。

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;
    }

}
SimpleXmlParser.java

四、編寫NodeBuilder類

根據過濾器和解析器獲取Node節點各屬性的值。

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);
            }
        }
    }
}
NodeBuilder.java

五、測試

編寫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>
測試文件

編寫測試類

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);
    }
}
Demo.java

輸出

<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>
System.out.println...

六、后記

這個工具是我春節期間在旅游的途中無聊的時候寫的,如果以后有用可能還會回來修改。暫時先寫到這里吧...


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM