解析xml格式文件(不用第三方库函数)

题目:

将下面xml格式文件,按树状结构进行输出,格式如书本目录,条件是不能用第三方库,比如什么dom,sax,pull解析方式,一句话,把里面的节点自己慢点抠出来吧。
(纯属自娱自乐,有问题的地方或者有更好的想法,欢迎交流,谢谢,qq:413686520)

<people  >
    <person id="001">  
        <name>XY1</name>  
        <age>22</age>  
    </person>

    <name key1="fsefsef" key2="gdrhr" />
    <person id="002">  
        <name>XY2</name>
        <age>22</age>
        <grade>
            <math>98</math>
            <english>100</english>
            <music>28</music>
        </grade>
    </person>
    <wenzhang key1="fsefsef" key2="gdrhr" />
    <life key1="fsefsef" key2="gdrhr" />

</people>

对于这个题目,最开始我对出题人的要求搞错了,以为只要节点名字,节点深度,最后输出成目录结构就行。所以,我就悲剧了,我最开始是用in.read()一个一个读到内存中,按字符来解析的,可以想象一下最后是多么艰难的写出来的,花了4个小时,可能时间更长,早上没有吃饭,写完的时候,已经饿的肚子疼了。一个字符一个字符的解析,竟然让我搞出来了。最开始的想法决定了算法最终的难度。下面给出这两天重新写过后的结果:

图片.png

其中,我们拿grade节点说明一下,它的节点深度为3,它有孩子节点mat,h,english,music,其他的节点可能还存在key:value键值对等等(直接看图吧)。

思路:

首先,一次读取一行,并且从读取的内容中找到第一个右括号‘>’,这样我们最后需要处理的内容都会变为如下几种形式:

(1).***<******>
(2).***<******/>
(3).***</******>

注意:先规定下本文节点的格式
例如


图片.png

对于上面的三种状态,对于‘<’左边的只可能是节点的text文本值,然后对于“<>”,里面只可能是节点名字,键值对,节点结束标志。

首先构建我们的节点,如下所示:

package com.lifestudy.stdy.lifestudy.readxml;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by lj on 2017/6/16.
 */

public class LjXMLNode {
    int id;//唯一标识
    String name;//节点的名字
    List<Integer> children = new ArrayList();//当前节点的孩子节点
    int parent = -1;//默认父节点,用id表示
    Map<String,String> mapKeyValue = new HashMap();//当前节点的key:value
    String text="";//当前节点的文本值
    int level;//当前节点的深度
}

然后,开始开始遍历我们的xml文件:

public void readXMLFile(InputStream inputStream){
        try {
            InputStreamReader inputStreamReader = new InputStreamReader(putStream);
            BufferedReader in = new BufferedReader(inputStreamReader);
            System.out.println("开始输入:");
            StringBuffer sb = new StringBuffer();
            String s;
            String rest = "";
            while( ( s = in.readLine() ) != null ){
                s = s.trim();
                s = rest + s;

                int start = 0;
                for( int i = 0; i < s.length(); i++ ){
                    if( s.charAt(i) == '>'){
                        process(s.substring(start,i+1));
                        start = i + 1;
                    }
                }
                if( start >= s.length() ){//說明處理到最後一個字符了
                    //do nothing
                }else{
                    rest = s.substring(start,s.length());
                }
            }
//            System.out.println( sb.toString() );
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

然后,在process()方法中处理最开始的三种情况:
说明:对于'<'左边的字符都是节点的text值,对于‘<’右边的第一个单词肯定是节点的name,当我们读取完name时,我们创建一个LjXMLNode节点node,赋值node.name,最后,入栈的并将其父节点node.parent指向栈顶元素,同时让栈顶元素的孩子节点加1。然后是读取节点的key:value(这个可以根据是否有等号来判断),并且更新node的值。最后当前节点只可能以右括号'>'或者'/>'两种形式结尾,当我们读取到“/”时,将当前节点出栈(同时,如果想对节点做操作,也是在出栈时,因为出栈的节点元素是完整的,它肯定是遍历完成了,本文是将出栈的元素保存到一个map中了,其中key是id,value是LjXMLNode)。

/**
     * 處理
     * 主要是對stack的操作
     */
    private void process(String s){

        String nodeName = "";
        String text = "";

        for( int i = 0; i < s.length();i++ ){
            if( s.charAt(i) == '<'){

                text = s.substring(0,i);
                if( mStack.size() > 0){
                    LjXMLNode topNode = mStack.peek();
                    topNode.text = text;
                }

                if( s.charAt(i+1) != '/'){

                    nodeName = findNodeName(s.substring(i+1));
                    //push
                    LjXMLNode node = new LjXMLNode();
                    node.id = ID_0++;
                    node.name = nodeName;
                    if( mStack.size() > 0){
                        LjXMLNode parentNode = mStack.peek();
                        node.parent = parentNode.id;
                        parentNode.children.add(node.id);
                    }
                    mStack.push(node);

                    for( int j = i+1; j < s.length();j++ ){
                        if( s.charAt(j) == '='){//key:value
                            String key = findFirstLeftWord(s,j);
                            String value = findFirstRightWord(s,j);
                            if( key != ""){
                                LjXMLNode curNode = mStack.peek();
                                curNode.mapKeyValue.put(key,value);
                            }
                        }
                    }

                    for( int j = i+2; j < s.length();j++ ){
                        if( s.charAt(j) == '/'){
                            LjXMLNode shuchuNode =  mStack.pop();
                            shuchuNode.level = mStack.size() + 1;
                            mFinalNodes.put(shuchuNode.id,shuchuNode);
                            System.out.println("shuchuNode:" + "id:"+shuchuNode.id + " name:"+shuchuNode.name
                                    + " key_count:"+shuchuNode.mapKeyValue.size()
                                    + " text:" + shuchuNode.text
                                    +"  parent:"+ shuchuNode.parent);
                        }
                    }

                }else {
                    //pop
                    LjXMLNode shuchuNode =  mStack.pop();
                    shuchuNode.level = mStack.size() + 1;
                    mFinalNodes.put(shuchuNode.id,shuchuNode);
                    System.out.println("shuchuNode:" + "id:"+shuchuNode.id + " name:"+shuchuNode.name
                            + " key_count:"+shuchuNode.mapKeyValue.size()
                            + " text:" + shuchuNode.text
                            +"  parent:"+ shuchuNode.parent);
                }
                break;
            }
        }
    }

最后是对节点的遍历,因为处理后的节点,最后会存储为成森林的树状结构,如下所示:

图片.png

所以,这块采用树的深度优先遍历算法。思想很简单,构建一个栈结构,首先将根节点入栈,也就是图中的A节点,然后开始遍历,先将根节点A出栈,同时访问根节点A,并且将其孩子节点入栈(注意此时的入栈顺序是从最右边的孩子开始往左遍历,也就是说越在左边的孩子应该越早访问到)。后面仿照根节点A出栈的同时访问,并且将其孩子节点入栈,代码如下所示:

public void depthLjFirst(){
        Stack<LjXMLNode> depthStack = new Stack();
        LjXMLNode head = mFinalNodes.get(0);
        depthStack.push(head);
        while ( depthStack.size()> 0 ){
            LjXMLNode cur = depthStack.pop();
            visit(cur);
            for( int i = cur.children.size(); i > 0;i-- ){
                int childId = cur.children.get(i-1);
                depthStack.push( mFinalNodes.get(childId) );
            }
        }
    }

最后完整的代码如下所示:

package com.lifestudy.stdy.lifestudy.readxml;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

/**
 * Created by lj on 2017/6/16.
 */

public class LjXMLParser {
    private Stack<LjXMLNode> mStack = new Stack();
    Map<Integer,LjXMLNode> mFinalNodes = new HashMap();
    private int ID_0 = 0;
    public void readXMLFile(InputStream in1){
        try {
            InputStreamReader inputStreamReader = new InputStreamReader(in1);
            BufferedReader in = new BufferedReader(inputStreamReader);
            System.out.println("开始输入:");
            StringBuffer sb = new StringBuffer();
            String s;
            String rest = "";
            while( ( s = in.readLine() ) != null ){
                s = s.trim();
                s = rest + s;

                int start = 0;
                for( int i = 0; i < s.length(); i++ ){
                    if( s.charAt(i) == '>'){
                        process(s.substring(start,i+1));
                        start = i + 1;
                    }
                }
                if( start >= s.length() ){//說明處理到最後一個字符了
                    //do nothing
                }else{
                    rest = s.substring(start,s.length());
                }
            }
//            System.out.println( sb.toString() );
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 處理
     * 主要是對stack的操作
     */
    private void process(String s){

        String nodeName = "";
        String text = "";

        for( int i = 0; i < s.length();i++ ){
            if( s.charAt(i) == '<'){

                text = s.substring(0,i);
                if( mStack.size() > 0){
                    LjXMLNode topNode = mStack.peek();
                    topNode.text = text;
                }

                if( s.charAt(i+1) != '/'){

                    nodeName = findNodeName(s.substring(i+1));
                    //push
                    LjXMLNode node = new LjXMLNode();
                    node.id = ID_0++;
                    node.name = nodeName;
                    if( mStack.size() > 0){
                        LjXMLNode parentNode = mStack.peek();
                        node.parent = parentNode.id;
                        parentNode.children.add(node.id);
                    }
                    mStack.push(node);

                    for( int j = i+1; j < s.length();j++ ){
                        if( s.charAt(j) == '='){//key:value
                            String key = findFirstLeftWord(s,j);
                            String value = findFirstRightWord(s,j);
                            if( key != ""){
                                LjXMLNode curNode = mStack.peek();
                                curNode.mapKeyValue.put(key,value);
                            }
                        }
                    }

                    for( int j = i+2; j < s.length();j++ ){
                        if( s.charAt(j) == '/'){
                            LjXMLNode shuchuNode =  mStack.pop();
                            shuchuNode.level = mStack.size() + 1;
                            mFinalNodes.put(shuchuNode.id,shuchuNode);
                            System.out.println("shuchuNode:" + "id:"+shuchuNode.id + " name:"+shuchuNode.name
                                    + " key_count:"+shuchuNode.mapKeyValue.size()
                                    + " text:" + shuchuNode.text
                                    +"  parent:"+ shuchuNode.parent);
                        }
                    }

                }else {
                    //pop
                    LjXMLNode shuchuNode =  mStack.pop();
                    shuchuNode.level = mStack.size() + 1;
                    mFinalNodes.put(shuchuNode.id,shuchuNode);
                    System.out.println("shuchuNode:" + "id:"+shuchuNode.id + " name:"+shuchuNode.name
                            + " key_count:"+shuchuNode.mapKeyValue.size()
                            + " text:" + shuchuNode.text
                            +"  parent:"+ shuchuNode.parent);
                }
                break;
            }
        }
    }

    /**
     * 找到右邊的第一個單詞,只能以空格或者是右括號“>”結尾
     */
    private String findNodeName(String s){
        int start,end;
        end = -1;
        start = -1;
        for( int i = 0; i < s.length(); i++){
            if( s.charAt(i) != ' '){ //找到左边的第一个字母
                start = i;
//                System.out.println("nodeName_start:" + start);
                break;
            }
        }
        if( start < s.length() && start >= 0){
            for( int i = start + 1; i < s.length(); i++ ){
                if( s.charAt(i) == ' ' || s.charAt(i) == '>'){
                    end = i;
                    break;
                }
            }
        }

        if( start <= end && start != -1){
            return s.substring(start,end);
        }
        return "";
    }

    /**
     * 找到下标为loc左边第一个单词
     */
    private String findFirstLeftWord(String s,int loc){
//        System.out.println("开始解析:" + s + "###loc:" + loc);
        String key= "";
        int start,end;
        end = -1;
        start = -1;
        for( int i = loc-1; i > 0; i--){
            if( s.charAt(i) != ' '){ //找到左边的第一个字母
                end = i;
//                System.out.println("end:" + end);
                break;
            }
        }
//        System.out.println("end解析完成");
        if( end < loc && end > 0){
            for( int i = end; i > 0; i--){
                if( s.charAt(i) == ' '){ //找到左边的第一个空格
                    start = i+1;
//                    System.out.println("start:" + start);
                    break;
                }
            }
        }
        if( start <= end && start != -1){
            return s.substring(start,end+1);
        }
        return "";
    }

    /**
     * 找到loc右边第一个双引号包括的单词
     */
    private String findFirstRightWord(String s,int loc){
//        System.out.println("s:" + s);
        String value= "";
        int start,end;
        end = -1;
        start = -1;
        for( int i = loc + 1; i < s.length(); i++){
            if( s.charAt(i) == '"'){ //找到右边的第一个双引号
                start = i;
//                System.out.println("start:" + start);
                break;
            }
        }
        if( start > loc && start < s.length()){
            for( int i = start+1; i < s.length(); i++){
                if( s.charAt(i) == '"'){ //找到右边的第一个双引号
                    end = i+1;
//                    System.out.println("end:" + end);
                    break;
                }
            }
        }
        if( start <= end && start != -1){
            return s.substring(start,end);
        }
        return "";
    }

    public void depthLjFirst(){
        Stack<LjXMLNode> depthStack = new Stack();
        LjXMLNode head = mFinalNodes.get(0);
        depthStack.push(head);
        while ( depthStack.size()> 0 ){
            LjXMLNode cur = depthStack.pop();
            visit(cur);
            for( int i = cur.children.size(); i > 0;i-- ){
                int childId = cur.children.get(i-1);
                depthStack.push( mFinalNodes.get(childId) );
            }
        }
    }

    private void visit(LjXMLNode node){
        StringBuffer sb = new StringBuffer();
        for( int i = 0; i < node.level; i++){
            sb.append("  ");//三个空格
        }
        sb.append("" + node.level);
        sb.append("  "+ node.name);
        Set<Map.Entry<String, String>> set2=node.mapKeyValue.entrySet();
        for (Iterator<Map.Entry<String, String>> iterator = set2.iterator(); iterator.hasNext();) {
            Map.Entry<String, String> entry = (Map.Entry<String, String>) iterator.next();
            String key=entry.getKey();
            String valueString=entry.getValue();
            sb.append("  "+ key + "=" + valueString );
        }
        // TODO: 2017/6/16 此处用“!=”来判断text是否为空会出错 (why?)
        if( node.text != null&& !node.text.equals("")  ){
            sb.append("  text:" + node.text);
        }

        System.out.println( sb.toString());
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容