自制XML解析器源码分析
首先,我们确定一下需求:
(1)我们希望它能把XML字符串解析成JSON对象。
(2)至少能兼容FireFox和IE。
(3)这个工具类最好是单例的。
代码:
/**
* 把XML解析成JSON对象
* 既可以直接解析字符串拼接成的XML
* 也可以解析通过Ajax请求获得的XML文件对象
* 主要兼容:IE、FireFox、Opera,其他浏览器中不能运行
* @author 大漠穷秋
* @since 2010-12-24
* @ver 1.0
*/
XmlParser=(function(){
function createXmlDocument(){
var doc=null;
if(Ext.isIE){
var docIDs = [
"Msxml2.DOMDocument.6.0",
"Msxml2.DOMDocument.5.0",
"Msxml2.DOMDocument.4.0",
"Msxml2.DOMDocument.3.0",
"MSXML2.DOMDocument",
"MSXML.DOMDocument"
];
for(var i=0;i<docIDs.length;i++){
//尝试为IE创建XMLDocument对象
try{
doc=new ActiveXObject(docIDs[i]);
return doc
}catch(e){}
}
throw new Error("无法为你的浏览器创建XMLDocument对象。");
}else{//FireFox,Opera,Safari,Chrome
doc = document.implementation.createDocument ("","",null);
}
return doc;
};
this.stack=[];
function _preParse(xmlNode){
if(xmlNode.childNodes){
for(var i=0;i<xmlNode.childNodes.length;i++){
var node=xmlNode.childNodes[i];
var nodeType=node.nodeType;
if(nodeType==1){ //复合节点
var obj={};
obj.name=node.tagName;
obj.children=[];
stack.push(obj);
_preParse(node);
if(nodeType==1){
var top=stack.pop();
if(stack.length==0){
return top;
}
var top2=stack[stack.length-1];
top2.children.push(top);
}
}else if(nodeType==3){ //文本节点
//过滤掉空行、回车、制表符
var text=null;
if(Ext.isIE){
text=node.text;
}else{
text=node.textContent;
}
var result=text.replace(/(\r|\n|\t|\s)/g, '');
if(result){
var top=stack[stack.length-1];
delete top.children;
top.value=result;
}
}
}
}
}
this.xmlDoc=createXmlDocument();
function xmlToJSON(xmlNode){
stack=[];
var result=_preParse(xmlNode);
return result;
};
return {
/**
* 解析xmlObj并返回JSON对象
*/
parse:function(xmlObj){
if(!xmlObj){
return null;
}
/**
* 直接解析字符串拼接成的XML
* 尚未实现
*/
if(Ext.type(xmlObj)=='string'){
throw new Error("直接解析字符串的功能尚未实现。");
return null;
}
return xmlToJSON(xmlObj);
},
/**
* 解析Ajax加载的XML文件
*/
parseResponse:function(response){
if(!response||!response.responseXML){
throw new Error("无法读取响应数据,response为null或没有xml 数据。");
return null;
}
return this.parse(response.responseXML);
},
/**
* 直接加载远程XML文件
* 貌似有缓存问题
*/
loadXml:function(filePath,fn){
if(Ext.isIE){
xmlDoc.onreadystatechange=function(){
if(xmlDoc.readyState==4){//XML文档已经加载完毕
fn(xmlDoc);
}
}
}else{
xmlDoc.οnlοad=fn.createCallback(xmlDoc);
}
xmlDoc.load(filePath);
},
/**
* 获取当前正在解析的XMLDocument对象
*/
getDocObj:function(){
return xmlDoc;
}
}
})();
源码解析:
约定一些解析规则:
(1)XML的标签名变成对象的name属性。
比如:
<name>大漠穷秋1</name>
会被解析成:
{name:'name',value:'大漠穷秋1'}
(2)如果标签存在子节点,将自动创建一个children属性,用来存储子对象。
比如:
<skill>
<name>Java</name>
<year>4</year>
</skill>
会被解析成:
{name:'skill',children:[
{name:'name',value:'Java'},
{name:'year',value:'4'}
]}
在XmlParser中,核心工具函数是createXmlDocument()、_preParse()这两个。
createXmlDocument看起来比较烦琐,其实没有技术含量,主要完成根据不同浏览器创建出XmlDocument对象的任务。与Ajax中的XmlHttpRequest对象类似,XmlDocument这个对象依赖于具体浏览器的实现。在不同的浏览器中,创建XmlDocument的方式不同,并且最终暴露出来的属性和方法都有很大的差别。
作为Ext的超级粉丝,我们见识了大量的JS技巧和框架设计思想,想象一下,你也是Ext框架开发团队中的一员,你会如何去设计这个解析工具?秉承Ext一贯的思路和手法,必须先把浏览器的差异屏蔽掉,然后对上层调用代码暴露一组通用的编程接口,这就是createXmlDocument这个函数存在的理由。
XML是一种“树形”的结构,它拥有唯一的根节点,然后标签可以层层嵌套。只要注意标签的配对、关闭及不能出现特殊的符号等,其他并没有特别的限制。那么在解析过程中必然会涉及树节点的遍历问题,这里使用的解析方法是:前序遍历、栈存储节点、递归3个手段结合。
在这个过程中,最核心的一个手法是使用一个数组做栈,在递归的过程用来存储访问过的节点。当发现某个节点还有子节点的时候,就把这个节点做成这样一个对象入栈:{name:'节点标签名',value:'',children[]},然后继续访问它的第一个孩子节点。当发现某个节点已经是简单节点(没有子节点),则把它“做成”JSON对象,然后弹出栈顶元素,把简单节点对应的JSON对象插入弹出元素的children数组中去。
核心的思路如上所述,如图7-28所示,但是纯文字描述必然比较抽象,请读者自行使用Firebug跟踪以上过程。
——本段文字节选自《EXT江湖》
图书详细信息: