自制XML解析器源码分析

首先,我们确定一下需求:

1)我们希望它能把XML字符串解析成JSON对象。

2)至少能兼容FireFoxIE

3)这个工具类最好是单例的。

代码:

/**

 * XML解析成JSON对象

 * 既可以直接解析字符串拼接成的XML

 * 也可以解析通过Ajax请求获得的XML文件对象

 * 主要兼容:IEFireFoxOpera,其他浏览器中不能运行

 * @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("无法读取响应数据,responsenull或没有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;

        }

    }

})();

源码解析:

约定一些解析规则:

1XML的标签名变成对象的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江湖》

图书详细信息: