SAX 解析和设计模式的运用

我们在使用 SAX 对 XML 文件做解析时,往往会被大量的 if 或者 switch 语句所困绕。如果使用适当的设计模式,结合适当的算法,则可以避免在解析程序中到处散布着大量的判断语句。


关于SAX

了解XMLParser的朋友都应该知道,XML有2种基本的解析方式:DOM(DocumentObject Model)、SAX(Simple Api ofXml)。其中,SAX对XML文档的解析不会象DOM那样需要形成树,从而减少了内存的占用,而且SAX是事件触发,用户只需要实现自己关心的接口即可,简化了编程量。

在实际的应用中,如果只是对XML的内容扫描一次,不需要对节点重复操作,则SAX解析方式应该是用户的首选。但是,它也有自己的弱点:

SAX模式是事件触发,用户实现相应的接口方法,对不同节点的判断,主要是根据节点名称。按照常规的处理方式,为了判断当前处理的节点名称,需要使用大量的判断语句。这样,大量的判断语句就散布到解析程序中去了。对于节点的变更,需要修改解析程序中所有接口方法的判断语句,使得整个程序框架难以维护。

组合的设计模式(Composite)

关于设计模式

谈到程序设计,就不能不考虑设计模式。设计模式可以成为我们做设计时相互交流的语言,就象UML是我们建模时统一使用的语言一样。

每一个模式描述了一个在我们做设计时不断重复遇到的问题,以及该问题的解决方案的核心。这样,我们就能一次又一次地使用该方案而不必做重复劳动。其核心在于提供了相关问题的解决方案。

设计模式不能够随意使用。通常我们通过引入额外的间接层次获得灵活性和可变性的同时,也使设计变得更复杂并牺牲了一定的性能。一个设计模式只有当它提供的灵活性是真正需要的时候,才有必要使用。

结构图:

意图:

将对象组合成树形结构以表示"部分-整体"的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。

适用性:

在下列情况下可以考虑使用"组合"模式:

  • 你想表示对象的部分-整体层次结构。
  • 你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。

我们的使用背景

我们小组计划使用XML作为配置文件,来描述页面的显示,从而能够自动生成相应的JSP代码。下面,我会说明我们的设计思想及其变迁过程,与大家共勉。

在我们的系统里,我们把页面简化后,将页面元素限制在下面若干种。最初,我们的DTD定义如下:

<?xml version="1.0" encoding="gb2312-1"?>
<!-- edited with XML Spy v3.0 NT (http://www.xmlspy.com) by liwei (lulusoft) -->
<!ELEMENT UserInterface (option+, datashow, control)>
<!ELEMENT datashow (maintable?, detailtable, maintable?)>
<!--maintable定义主表(表单)的内容-->
<!ELEMENT maintable (lable, input+)+>
<!--detailtable定义细表(列表)的表头、实际内容;header在生成JSP样式时使用,column在JSP动态生成HTML页面时使用。-->
<!ELEMENT detailtable (header, column)+>
<!--control包括所有的按钮-->
<!ELEMENT control (button+, pagebutton?)>
<!--lable的内容就是它的value-->
<!ELEMENT lable (#PCDATA)>
<!ELEMENT input EMPTY>
<!ATTLIST input
    hidden (true | false) "false"
    onkeypress CDATA #IMPLIED>
<!--checkbox没有属性、没有值-->
<!ELEMENT checkbox EMPTY>
<!--button的内容就是它的显示值,action属性指定该按钮的连接,onclick属性指定onclick事件的响应-->
<!ATTLIST button
    action CDATA #IMPLIED
    onclick CDATA #IMPLIED
>
<!--每个header代表列表表头的一列,只有复选框才允许表头为button(全选/全不选)-->
<!ELEMENT header (lable | button)>
<!--column定义JSP动态生成HTML页面时该列的内容,每个column可能会包含若干个隐含域,只有第1列可能出现checkbox,并且,checkbox不能有隐含域-->
<!ELEMENT button (#PCDATA)>
<!ELEMENT column (input+ | checkbox)>
<!--pagebutton只是一个标签,定义了一组翻页按钮,包括:上一页、下一页、第几页-->
<!ELEMENT pagebutton EMPTY>
<!--option定义在当前情况(type)下,每个按钮是否需要输出;每种情况都需要定义一套option-->
<!ELEMENT option (type, (enable | disable)+)>
<!ELEMENT type (#PCDATA)>
<!ELEMENT enable EMPTY>
<!ELEMENT disable EMPTY>

在最初的XML解析器中,没有规划对象接口,每个节点的操作针对其特点,各不相同,属性、子元素的存取也没有统一。而且,某些节点没有设计对应的JAVA类,某些JAVA类对应了若干个节点。所以,解析程序中充斥着大量的判断语句。

后来,我们总结了系统需求,发现:有几个基本节点:lable、input、botton,其它的是容器节点,可以包含若干种基本节点。如果想去掉程序中所有的判断语句,就需要对任意节点的操作完全一致。这样,就需要修改我们的设计:

  1. 扩充DTD,对每个节点增加了classname属性,使得每个节点对象的创建完全一致,通过classname来动态创建;新的DTD见 下载
  2. 扩充IxmlElement接口,而且,每个节点都有对应的JAVA类,同时,采用了组合的设计模式(Composite),使得对叶子节点(基本节点)和非叶子节点(容器节点)的处理能够统一。

新的类系图如下:

其次,需要采用新的算法。

  1. 考虑到SAX是事件触发,我们使用了出入堆栈的方式,基本描述如下:
  2. 在每个节点元素的开始(startElement接口方法),创建节点对象,并且,保存所有的属性,然后入栈;
  3. 在每个节点元素的结束(endElement接口方法),从堆栈中弹出2个对象,第1个对象增加到第2个对象中,再把第2个对象入栈;

解析程序的详细代码如下:

package lulusoft.jspbuilderv10.parser;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import java.io.*;
import org.apache.xerces.parsers.SAXParser;
import java.util.Stack;
import lulusoft.jspbuilderv10.common.*;
public class CXmlParser extends org.xml.sax.helpers.DefaultHandler{
    /**私有属性*/
    CXmlElement root = null;
    private CharArrayWriter contents  =  new CharArrayWriter();
    private Stack stack = null;
   /**
    * 功能描述:构造函数
    */
    public CXmlParser(){
        super();
        stack = new Stack();
    }
   /**
    * 功能描述:标签起始
    */
    public void startElement(     String namespaceURI,
                      String localName,
                  String qName,
                  Attributes attr ) throws SAXException {
        contents.reset();
        String cn = attr.getValue("classname");
        if(cn==null || cn.trim().equals("")){
            System.out.println("标签未有对应的类!请检查!");
        }
        CXmlElement element = CXmlElement.createElement(cn);
        int size = attr.getLength() ;
        for(int i=0;i<size;i++){
            String name = attr.getQName(i);
            String value = attr.getValue(i);
            if(name.equals("classname")){
                continue;
            }
            element.addAttribute(name,value);
        }
        stack.push(element);
    }
   /**
    * 功能描述:标签结束
    */
    public void endElement(String namespaceURI, String localName,
                  String qName ) throws SAXException {
        if(stack.size() > 1){
            CXmlElement element1 = (CXmlElement)stack.pop();
            CXmlElement element2 = (CXmlElement)stack.pop();
            if(contents.toString().trim()!= null){
                element1.setElementValue(contents.toString().trim());
            }
            element2.addContainElement(element1);
            stack.push(element2);
        }
       else{
           root = (CXmlElement)stack.peek();
       }
    }
    /**
     * 功能描述:获得root数据包
     */
     public CXmlElement getRoot(){
         return root;
     }
    public void characters( char[] ch, int start, int length )
            throws SAXException {
        contents.write(ch,start,length);
    }
    /**
     * 功能描述:解析xml并返回数据
     * @param fileName String  xml文件名称
     * @return CXmlElement
     */
    public static CXmlElement parseDocument(String fileName){
        CXmlParser m_oXmlParser = new CXmlParser();
        try {
            XMLReader xr =  new SAXParser();
            xr.setContentHandler(m_oXmlParser);
            xr.parse( new InputSource(new FileReader( fileName )) );
        }
        catch ( SAXParseException saxParseException )  {
            System.out.println("Parsing Error:");
            System.out.println("行:"+saxParseException.getLineNumber());
            System.out.println("列:"+saxParseException.getColumnNumber());
            System.out.println("  "+saxParseException.getMessage());
        }
        catch(SAXException saxException){
            saxException.printStackTrace();
        }
        catch(Exception e){
            e.printStackTrace();
        }
        return m_oXmlParser.getRoot();
    }  
}

下载

描述名字大小
实例源代码code.zip5.8K

参考资料

学习

获得产品和技术

  • IBM 试用版软件:使用 IBM 试用版软件构建您的下一个开发项目,这些试用版软件可直接从 developerWorks 下载获得。

讨论


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值