好久都没写博客了,原因是上个月电脑坏了,最近才拿回来,前两天有在忙乎着关于解析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