欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2024-03-31 11:08:46
本文实例讲述了java基于正则表达式实现xml文件的解析功能。分享给大家供大家参考,具体如下: 这是我通过正则表达式实现的xml文件解析工具,有些xhtml文件中包含特殊...

本文实例讲述了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;
// 生成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);
      }
    }
  }
}

五、测试

编写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正则表达式在线测试工具:

正则表达式在线生成工具:

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

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