通过正则表达式实现简单xml文件解析

原创 2016-11-11 17:09:09 704
摘要:这是我通过正则表达式实现的xml文件解析工具,有些XHTML文件中包含特殊符号,暂时还无法正常使用。设计思路:常见的xml文件都是单根树结构,工具的目的是通过递归的方式将整个文档树装载进一个Node对象。xml文档树上的每一个节点都能看做一个Node对象,它拥有title、attribute和text三个自身变量以及一个childrenNode集合用来存放子节点,使用正则表达式完整装载。一、编写N

这是我通过正则表达式实现的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...


发布手记

热门词条