zoukankan      html  css  js  c++  java
  • 通过正则表达式实现简单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 + "/>
    ";
            } else if (childNodes.isEmpty() && text != null) {
                return "<" + title + attr + ">
    " + text + "
    " + "</" + title + ">
    ";
            } else {
                StringBuffer buff = new StringBuffer();
                buff.append("<" + title + attr + ">
    ");
                if (!text.isEmpty()) {
                    buff.append(text + "
    ");
                }
                for (Node n : childNodes) {
                    buff.append(n.toString());
                }
                buff.append("</" + title + ">
    ");
                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 = { "	", "<\?.*?\?>", "<!.*?>", "<%.*?%>", "\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...

    六、后记

    这个工具是我春节期间在旅游的途中无聊的时候写的,如果以后有用可能还会回来修改。暂时先写到这里吧...

  • 相关阅读:
    进程同步和死锁;事务、悲观锁、乐观锁、表锁、行锁
    快速排序
    oracle索引分类
    数据结构
    MySQL视图
    sql优化的方法
    MySQL索引
    转:关于ROWNUM的使用
    转载:mybatis踩坑之——foreach循环嵌套if判断
    Spring 注意事项
  • 原文地址:https://www.cnblogs.com/learnhow/p/5217450.html
Copyright © 2011-2022 走看看