好久都没写博客了,原因是上个月电脑坏了,最近才拿回来,前两天有在忙乎着关于解析XML文件的项目。呵呵,在这里跟大家分享一下。
xml的解析无非就是对文件的分解,首先将每一个节点的标签读取出来,然后再读节点中是否包含有参数,如果存在参数的话则遍历节点中的参数,也就是分解"="两边的字符串,获取左边的作为参数名,而右边的则为参数对应的值;再往下就判断该节点他是否包含有"innerText",当然这里有要求,若当前节点以"/>"结尾,同时却又包含"innerText",这种情况将视为语法错误;最后就是将节点下面的innerText解析成节点,如果存在节点的话。基本上XML解析就这点了,下面贴出部分我做的解析代码,并作些解释。
private synchronized Node parser(Node baseNode, String document) {
// 该段内容为空,不做任何解析
if (document == null) {
return null;
}
// 该方法为对子节点进行处理
if (document.indexOf('<') == -1 && document.indexOf('>') == -1) {
// 该段内容为innerText不做任何解析
return null;
}
if (document.indexOf('<') == -1 && document.indexOf('>') != -1) {
// 该段内容有误,抛出异常
throw new XMLContentException();
}
if (document.indexOf('<') != -1 && document.indexOf('>') == -1) {
// 该段内容有误,抛出异常
throw new XMLContentException();
}
if(document.indexOf( "<!--") != -1 && document.indexOf( "-->") == -1){
//该段内容有误,抛出异常
throw new XMLContentException();
}
if(document.indexOf( "<!--") == -1 && document.indexOf( "-->") != -1){
//该段内容有误,抛出异常
throw new XMLContentException();
}
// 用于匹配"<"后面出现的字符串是否为开始部分(第一个字母为英文字母),节点名称长度不得小于1个字符
String regExTagStart = " *[A-Za-z][\\w.\\-:]+[\\da-zA-Z]+ *";
Pattern regexTagStart = Pattern.compile(regExTagStart);
document = document.substring(document.indexOf('<') + 1).trim();
// 如果当前节点不存在空格(比如<root>),则说明当前节点为"纯节点节点"
// 用于截取节点名称的索引
int endIndex = -1;
// 指示当前节点是否包含有参数
boolean hasParams = true;
if (document.substring(0, document.indexOf('>')).indexOf(' ') == -1) {
endIndex = document.indexOf('>');
hasParams = false;
} else {
endIndex = document.indexOf(' ');
}
// 获取当前节点名称
String tag = document.substring(0, endIndex);
// 此处加一些验证
if (!regexTagStart.matcher(tag).matches()) {
// 如果验证失败,抛出异常
throw new XMLContentException();
}
// 创建用于存储当前节点的节点对象
Node node = new Node(tag);
node.addLisener(nodeHandler);
// 如果验证通过,并且当前节点中包含参数,则取出其节点中的参数及参数值
if (hasParams) {
document = document.substring(document.indexOf(' ')).trim();
// 获取当前标签行
String tagInline = document.substring(0, document.indexOf('>') + 1).trim();
if (tagInline.indexOf( "/>") != -1
&& document.indexOf('>') == document.indexOf( "/>") + 1) {
document = document.substring(document.indexOf( "/>"));
} else if (tagInline.indexOf( "/>") == -1) {
document = document.substring(document.indexOf('>'));
}
// 用于匹配标签行
Pattern regExInline = Pattern
.compile( "(\\w+ *= *\"[^\\n\\f\\r\"]*\"[\\n\\r\\t ]*)*/?>$");
if (!regExInline.matcher(tagInline).matches()) {
// 抛出异常
throw new XMLContentException();
}
// 遍历节点所有属性,并将其添加至节点集合中
while ( true) {
// 如果当前节点中不存在参数,跳出循环
if (tagInline.indexOf('=') == -1) {
break;
}
String paramName = new String();
String paramValue = new String();
boolean paramIsKeyword = false;
paramName = tagInline.substring(0, tagInline.indexOf('='))
.trim();
tagInline = tagInline.substring(tagInline.indexOf('=') + 1);
paramValue = tagInline.substring(tagInline.indexOf('"') + 1);
paramValue = paramValue.substring(0, paramValue.indexOf('"'));
tagInline = tagInline.substring(tagInline.indexOf('"')
+ paramValue.length() + 2);
// 如果节点参数名称为关键名字如"name"和"value",则将其添加至特定的属性当中
if (paramName.equalsIgnoreCase( "name")) {
paramIsKeyword = true;
node.setName(paramValue);
}
if (paramName.equalsIgnoreCase( "value")) {
paramIsKeyword = true;
node.setValue(paramValue);
}
// 当节点参数名称不为关键名字则将其添加至参数集中
if (!paramIsKeyword) {
// 将参数添加至节点列表
node.addParam(paramName, paramValue);
}
}
}
// 如果当前节点以"/>"结尾,则忽略node对象的innerText值
if (document.indexOf( "/>") != -1
&& document.indexOf('>') == document.indexOf( "/>") + 1) {
// 当前节点已完毕,如果document中还存有文本,则继续查找下一个节点
document = document.substring(document.indexOf('>') + 1);
if (document.length() > 0) {
baseNode.addNode(node);
node = parser(baseNode, document);
}
return node;
}
//获取结束标签,去掉标签空格
document = document.replaceFirst( "</[ ]*" + tag + "[ ]*>", "</" + tag + ">");
// 获取当前节点的innerText值:此处有些许问题,假如</[tag]>中间包含有空格,则当前节点无法结束,导致语法错误
String innerText = document.substring(document.indexOf('>') + 1,
document.indexOf( "</" + tag + ">"));
node.setInnerText(innerText);
// 当前节点已完毕,如果document中还存有文本,则继续查找下一个节点:此处同上
document = document.substring(
document.indexOf( "</" + tag + ">") + tag.length() + 3).trim();
if (document.length() > 0) {
baseNode.addNode(node);
node = parser(baseNode, document);
}
return node;
}
代码中基本上都是采用indexOf和substring来截取获得标签及其参数,有些难看,不过我目前正准备将他改写成以正则来解析,好了闲话不说多了,现在来分析他吧。
前面部分主要对xml文件(与其说xml文件,还不如说是参数baseNode的父节点的innerText)进行分析,如果xml文件出现语法错误,那么便立即停止对他的解析,并抛出异常;中间有这么一句:node.addLisener(nodeHandler);此句为向当前创建的节点对象添加一个监听器,这里很关键,在实现该解析器之前就了解到很多的xml解析都是一次从文件中加载所有文档到内存中,然后再交给解析方法来遍历文档中所有的节点,并创建对应的对象,该例的特别之处就在于:他并不是读取完xml文档到内存中后遍历所有的节点,而只是获取其根节点,若有需要(即当用户调用他的子节点的时候,因为首先需要获得根节点下的节点集合,所以就可以在此处设置一个时间源,用来触发解析当前节点的innerText),再去为他做解析,这样若读取了xml文档并没有完全用到其中的节点时,为系统提高了效率,而又因为这样所以解析方法只需要负责当前父节点下innerText的解析,因此对系统的实现有很大的帮助,也便于理解程序。下面还有一段代码也非常关键,做完后足足为此调试了一下午。举个例子,若当前父节点下面存在多个并列子节点,而方法只能返回一个节点对象,若遍历所有节点必然覆盖先前的节点,后来就想个办法在方法里面再加了一个参数,即baseNode,他是调用该解析方法是传入的父节点对象,如果存在多个节点对象,那么就将其添加至baseNode中,因为类对象是存在于堆中,因此对象不会被覆盖,只会在原来的对象上做修改。
下面是有关当节点被调用的时候触发时间处理
@Override
public
void innerTextUsing(Node node, String innerText) {
// TODO Auto-generated method stub
Node n = parser(node, innerText);
if (n ==
null)
return;
node.addNode(n);
}
此处代码很简单,事件触发的时候事件源会传入触发事件的节点对象及他的innerText值,然后直接交给解析方法处理就可以了。
结语:整个解析过程很简单,文档对象创建之后会自动调用一个预处理方法,该方法主要对xml文件头进行处理及去掉文档中的注释,然后再执行解析方法,解析方法第一次工作只解析xml文档根节点,然后将剩余的内容存至根节点的innerText,当调用节点的获取节点方法时便触发一个事件,然后事件里面就将节点对应的innerText进行解析,如此循环。解析中不免出现各种意外,还望各位大虾修正。
转载于:https://blog.51cto.com/xiaodpro/381758