首页 > 编程 > Java > 正文

Java基于正则表达式实现xml文件的解析功能详解

2019-11-26 11:34:50
字体:
来源:转载
供稿:网友

本文实例讲述了Java基于正则表达式实现xml文件的解析功能。分享给大家供大家参考,具体如下:

这是我通过正则表达式实现的xml文件解析工具,有些XHTML文件中包含特殊符号,暂时还无法正常使用。

设计思路:常见的xml文件都是单根树结构,工具的目的是通过递归的方式将整个文档树装载进一个Node对象。xml文档树上的每一个节点都能看做一个Node对象,它拥有title、attribute和text三个自身变量以及一个childrenNode集合用来存放子节点,使用正则表达式完整装载。

一、编写Node类

Node对象是文档解析的基础,最终可以通过对象的不同属性实现对文档信息的访问。

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;  // 节点类型  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:

/* * 过滤器的作用是删除xml文件中不重要的部分。 * 通常都是一些注释性文字,不需要被机器解析。 */public interface XmlFilter {  String filter();  // 提供自定义正则表达式,识别符合过滤条件的字符串  String filter(String[] regex);}

解析器:将一个父节点解析成多条子节点的字符串。如果返回值为null,代表当前节点下不存在可以继续解析的对象。

XmlParser.java:

import java.util.List;/* * 解析器可以对一段完整的父节点字符串提供解析服务。 * 将一条父节点的字符串解析成为多条子节点字符串 */public interface XmlParser {  // 解析一段父节点,返回子节点字符串  List<String> parser(String str);}

三、根据接口编写实现类

回车、换行、制表符以及各种注释部分的内容都被删除,简化字符输出。

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

主要是通过正则表达式区分一个节点内部的子节点,考虑到节点的类型我将它们分为自闭合与非自闭合两种类型。<title attributes .../>这样的节点属于自闭合类型,它们不包含子节点和text属性,它们属于文档树的叶子节点。<title attributes ...>text ...</title>这样的节点属于非自闭合类型,它们属于文档树的分支节点。

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>();    // 捕获根节点中间的文本    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:

import java.io.File;import java.io.IOException;import java.util.List;import java.util.regex.Matcher;import java.util.regex.Pattern;// 生成Nodepublic 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测试文件

测试文件:

<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.VeVB.COm/regex/javascript

正则表达式在线生成工具:
http://tools.VeVB.COm/regex/create_reg

更多关于java算法相关内容感兴趣的读者可查看本站专题:《Java正则表达式技巧大全》、《Java数据结构与算法教程》、《Java操作DOM节点技巧总结》、《Java文件与目录操作技巧汇总》和《Java缓存操作技巧汇总

希望本文所述对大家java程序设计有所帮助。

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表