NuttyCoder's profile初级程序员BlogLists Tools Help

Blog


    7/26/2006

    ADO记录集向XML文档的转换(已批注)

    (NC:文章写于2003年秋天...XML刚刚开始嚣张的年代,看看,我在2004年秋天开始写Asp,2005年才知道XML这东西,现在居然也要这里那里的用它,这世界变化多快.)

    面对漫天飞舞的XML曾经迷惑,不就是一种带格式的文本文件吗,为什么会惹得众多软件巨头竞相追捧,以致成为网络时代的通用表达语言?仔细研究下来,XML的以下特点使之成为互联网上的新宠:

    1.  通用性:自从其诞生的那天起,便立志成为电子商务时代的世界语,所以它采用了一种结构化的ASCII文本文件,任何系统都可以方便的与之沟通。

    2.  开放:XML为W3C所制定的标准语法,并已获得软件工业的认可。

    3.  可扩展:无固定不变的标记,可根据需求创建新标记

    4.  自我描述能力:DTD将XML的每个部分做了声明与精确的格式定义。而SCHEMA是XML文档元素的规则组合,它指定文本中所允许的元素,及其可能的组合。

    正因为此,通过ADO取得的数据记录,保存为XML文档,有着广泛的应用价值,下面详细讨论之。

    目标

    现有一个ACCESS数据库PeiXun.mdb,一个kecheng数据表

    我们将把其内容保存为如下格式的XML文档:

    -<xml>

    -<id>3</id>

      <classid>1 </classid>

      <date>2003年5月24日</date>

    <curriculum_name>MBA考前辅导第一期基础班</curriculum_name>

     </row>

     </xml>

    正如我们所熟悉的用ASP操作数据库模式,先创建一个conn..asp文件,用于打开数据库,在后面的代码中都会用到:

    <%

        on error resume next

           dim conn,connstr

           dim dbpath

           connstr="DBQ="+server.mappath("DataBase/peixun.mdb")+";DefaultDir=;DRIVER={Microsoft Access Driver (*.mdb)};"

         set conn=server.createobject("adodb.connection")

           conn.Open  connstr

    %>


    方案一

    基本上,维护XML文档的技术有三种,第一种是使用DOM(Document Object Modal)接口,第二种是通过SAX(Simple API for XML),第三种是采用传统的字符串维护(NC:一直用第三种方式)。

    所谓DOM就是把整个XML文档加载到一个树状结构的节点中,然后再通过维护树状结构的方式来改变XML文档,使用DOM时,整个XML文档必须先加载到内存中。

    实际上,很多家公司都实现了DOM,包括IBM,Apache,Microsoft等,而Microsoft公司是把DOM的实现放在MSXML SDK内,而MS IE 也使用DOM 。下面我们就使用MSXML对象实现从ADO RecordSet 到XML文档的转化。

    1.  取得ADO数据记录集:

    <!--#include file ="conn.asp"-->

    <%

        Dim strsql

        Dim objRecordset

       Dim fileObj

        strsql=" select  id,class_id,date, curriculum_name  from kecheng "                 

        set objRecordset=conn.execute (strsql)                 

     %>

    2.  创建XMLDOM对象

    <%

    Set objXMLDOM = Server.CreateObject("MSXML2.DOMDocument.3.0")

        ''''''''创建根节点<xml>

        Set objRootNode = objXMLDOM.createElement("xml")

        objXMLDOM.documentElement = objRootNode

        ‘用createProcessingInstruction方法处理指令

    objXMLDOM.appendChild(objXML.createProcessingInstruction("xml","version=""1.0"""))

    %>

      

     

    3. 循环把数据集中的每条记录写到DOM的节点中去:

    <%

    Do While NOT objRecordset.EOF

        ''''''''创建子元素 row

        Set objRowNode = objXMLDOM.createElement("row")

    <--start banner ad--><--ba--> <Script language="JavaScript1.1" src="http://ad.cn.doubleclick.net/adj/messagingplus.zdnet.com.cn/developer/code;sz=1x1;ord=972790391?" type="text/javascript"> </Script> <--end banner ad-->

        ''''''''创建子元素 id

        Set objNode = objXMLDOM.createElement("id")

        objNode.text = _

             objRecordset.Fields.Item("id").Value

        ''''''''id 作为row 的子节点添加到row下

        objRowNode.appendChild(objNode)

        ''''''''创建 classid节点,并使之为row的子节点

    Set objNode = objXMLDOM.createElement("classid")

        objNode.text = _

             objRecordset.Fields.Item("classid").Value

        objRowNode.appendChild(objNode)

        ''''''''创建 date节点,并使之为row的子节点

    Set objNode = objXMLDOM.createElement("date")

        objNode.text = _

             objRecordset.Fields.Item("date").Value

        objRowNode.appendChild(objNode)

        ''''''''创建curriculum_name节点,并使之为row的子节点

    Set objNode = objXMLDOM.createElement("curriculum_name")

        objNode.text = _

             objRecordset.Fields.Item("curriculum_name").Value

        objRowNode.appendChild(objNode)

        ''''''''row 作为xml的子节点添加

    objRootNode.appendChild(objRowNode)

        ''''''''移到下一条记录

    objRecordset.MoveNext

    Loop

    %>

    4.  调用save方法保存XML文件

    <%

    objXMLDOM.save(server.MapPath("kecheng.xml"))

    (NC:这种写入是否需要IIS中的写权限?如果需要,是否影响安全性?)

    %>

    5.  释放资源

    <%

    Set objNode = Nothing

    Set objRowNode = Nothing

    Set objRootNode = Nothing

    Set objRecordset = Nothing

    %>

    您肯定发现了,第3步写得好罗嗦啊,完全可以用RecordSet得Fields属性,循环取出所有的字段,如下所示:

    <%

    Do While NOT objRecordset.EOF

            ''''''''创建子元素 row

            Set objRowNode = objXMLDOM.createElement("row")

            For Each varItem In objRecordset.Fields

               Set objNode = objXMLDOM.createElement(varItem.name)

               objNode.text =varItem.value

               objRowNode.appendChild(objNode)

            Next

       ''''''''    row 作为xml的子节点添加

            objRootNode.appendChild(objRowNode)

           

            objRecordset.MoveNext

    Loop

    %>

    这样写即简洁又通用,实际上写程序的过程就是不断精益求精的过程啊(NC:赞,单纯的程序员应该是一个有洁癖的完美主义者)

    方案二(NC:也是我以前用的方法)

    <--start banner ad--><--ba--> <Script language="JavaScript1.1" src="http://ad.cn.doubleclick.net/adj/messagingplus.zdnet.com.cn/developer/code;sz=1x1;ord=1648567173?" type="text/javascript"> </Script> <--end banner ad-->

    我们把记录集和XML格式串放到字符串中,生成目标中所示的格式,然后把字符串写到一个文本文件中。

    1.  同方案一中第一步取得ADO数据记录集。

    2.  定义XML格式初始化字符串:

    <%

    Dim strXML

    strXML = "<?XML version="&""""&"1.0"&""""&" encoding="&""""&"gb2312"&""""&"?>"

    strXML = strXML & "<xml>"

    %>

    注意我们在这里把XML文档的encoding属性定义为gb23312,以防止中文字符在XML解析器中(如IE)解析时出现错误。

    3.  循环把数据集中的每条记录附加到所定义的字符串中:

    <%

    objRecordset.MoveFirst

        Do While NOT objRecordset.EOF

            For Each varItem In objRecordset.Fields

               strXML = strXML _

                                &"<"_

                                &varItem.name_

                             &">"_

                                &varItem.value_

                                &"</"_

    &varItem.name_

    &">"

            Next

       Loop

         strXML = strXML & "</xml>"

    Set objRecordset = Nothing

    %>

     

     

    4. 把字符串保存到文件中去:

       <%

     Dim FileObject,OutFile

    ‘调用系统文件对象

    SET FileObject=Server.CreateObject("Scripting.FileSystemObject")

       ‘创建文本文件

    Set OutFile=FileObject.CreateTextFile(server.MapPath("kc1.xml"),TRUE,FALSE)         

        ‘把字符串写到文件中去。

        OutFile.WriteLine(strXML)

        OutFile.Close

        Set OutFile=Nothing

    Set FileObject=Nothing

    %>

    方案三

    实际上ADO的RecordSet对象中有一个Save方法,可以直接把数据保存为XML格式。下面我们看看是如何使用的:

    1.  同方案一中第一步取得ADO数据记录集。

    2.  创建DOMDocument对象,并调用RecordSet的Save方法,把数据保存到DOMDocument中去。

    Save方法在ADODB 的RecordSet 的对象中是这样声明的:

    Sub Save([FileName As String], _         [PersistFormat As PersistFormatEnum = adPersistADTG])

    Save 方法的两个参数都是可选的,但若是第一次调用,必须指定文件名Filename。

    Save 不关闭 Recordset 或 FileName,从而可以继续使用 Recordset 并保存最新的更改。在 Recordset 关闭之前 FileName将保持打开,在这段时间其它应用程序可以读取但不能写入 FileName。

    PersistFormatEnum 值,指定保存 Recordset 所使用的格式。可以是如下的某个常量

    AdPersistADTG (默认)使用专用的“Advanced Data Tablegram”格式保存。
    AdPersistXML 使用XML 格式保存。

    以上是MSDN中对该方法的描述,据此,可以利用Save把RecordSet保存为XML文件:

    objRecordset.save server.MapPath("ceshi.xml"), adPersistXML

    需要注意的是,Filename并不一定是磁盘文件,也可以是内存流对象,比如DOMDocument。

    下面把第二步中的代码列于此:

    <%

    Const adPersistXML = 1

    Dim objXMLDOM

    Set objXMLDOM = Server.CreateObject("MSXML2.DOMDocument.3.0")

    objRecordset.save objXMLDOM, adPersistXML

    %>

    这样,数据记录就保存到了objXMLDOM对象中了,为什么不直接保存成XML文件呢?这是因为通过该方法保存成的XML文件附加了一些我们目标中不需要的信息:


    图片一

    这些信息是XML文档的架构(Schema)信息。它包含了RecordSet中记录的描述,其中<s:Schema> 定义了各字段的名称,类型及其它一些属性; <rs:data> 是RecordSet中的所有数据记录<z:row>则具体到每一条记录的信息。我们的任务下一步就是利用XSLT把该文档转化为目标中XML文档的格式。

    3.  利用XSLT对XML文档进行转换。(NC:3、4步是以前没有想到也不会的,不过有必要比较一下与方案二的效率差异。之所以强调效率,因为这种利用xml作为中间数据层的方法一旦在站点内广为使用,就会代替原数据库成为耗用资源的主力)

    那么什么是XSLT呢?它是Extensible Stylesheet Language Transformations的缩写,是一种专为XML设计的样式表语言,负责将XML源代码转化为另一种格式。XSLT转换必须由特定的软件来担任,这样的软件称为XSL处理器。XSL处理器在工作之前,得先借助XML解析器替它把XSLT样式表和待转换的XML文档中的节点和属性分离出来,分离的结果我们程之为源树,指的是转换前的XML结构。然后,XSL处理器根据XSL中的命令对XML文档的源树进行操作,得到转换的成品,我们称之为结果树。下图就是利用XSLT对XML进行转换的原理图。


    图片二

    下面我们就编写XSLT文件 RecordsetCleaner.xsl

    <%

    <?xml version="1.0"?>

    <!-- RecordsetCleaner.xsl -->

    <xsl:stylesheet version="1.0"

        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"

        xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882"

        xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"

        xmlns:rs="urn:schemas-microsoft-com:rowset"

        xmlns:z="#RowsetSchema">

    <xsl:output omit-xml-declaration="yes"/>

    <xsl:template match="/">

        <xsl:element name="xml">

            <xsl:for-each select="/xml/rs:data/z:row">

                <xsl:element name="row">

                    <xsl:for-each select="@*">

                        <xsl:element name="{name()}">

                            <xsl:value-of select="."/>

                        </xsl:element>

                    </xsl:for-each>

                </xsl:element>

            </xsl:for-each>

        </xsl:element>

    </xsl:template>

    </xsl:stylesheet>

    %>

    4.  调用XSLT转换。
    现在XSLT文件已经准备好了,开始让它与XML文档发生作用吧。记住,我们在第二步的时候已经把RecordSet保存在了objXMLDOM中。

    <%

    Dim strCleanXML, objXMLDOM_XSLT

    Set objXMLDOM_XSLT = CreateObject("MSXML2.DOMDocument.3.0")

    objXMLDOM_XSLT.load(Server.MapPath("RecordsetCleaner.xsl"))

    strCleanXML = objXMLDOM.transformNode(objXMLDOM_XSLT)

    %>

    现在,strCleanXML字符串中就保存着我们目标中所要求的XML文档。下一步就是要把它保存在磁盘文件中去。

    5.保存为文件

    <%

    objXMLDOM.loadXML(strCleanXML)

    objXMLDOM.save(server.MapPath("test1.xml"))

    Set objXMLDOM = Nothing

    Set objXMLDOM_XSLT = Nothing

    Set objRecordset = Nothing

    %>

    在这里,我们复用了objXMLDOM对象,通过调用loadXML方法,其内容已经变为已经减肥过的XML文档内容了。

    遗留问题

    当数据集中的数据包含“,’,<,,>等XML保留字符时,转换将会失败,我们在上面的讨论中并没有注意这个问题。在实际开发中,要首先对这些字符进行过滤,然后才能进行转换。

    使用ado2.1访问XML文件(已批注)

    MDAC 2.1有了许多新的特性,比如数据链结,三维数据分形和重定形数据(Data shaping and reshaping,实在不知道该怎么翻译),离线保持记录集,自动同步客户端游标,建立新记录集(自己加入数据,不从查询中得到),DDL和JRO等,其中的离线保持记录集就是采用XML来实现的。
    这样,我们就可以象使用Recordset一样使用XML了,而且,也可以把Recordset保存为XML了,请看这一段Example Code:
    Dim objRS
    Const adCmdFile = 256
    Const adPersistXML = 1
    Set objRS = Server.CreateObject("ADODB.Recordset")
    ''''''''''''''''这里我新建一个记录集,当然从数据库中查询得到的也完全可以
    objRS.Fields.Append "ID", adInteger
    objRS.Fields.Append "Content", adVarChar, 20
    objRS.Open
    objRS.AddNew Array("ID", "Content"), Array(1, "test")
    ''''''''''''''''很简单,一个Save Method就可以转化为XML了
    objRS.Save Server.MapPath("test.xml"), adPersistXML
    objRS.Close
    ''''''''''''''''要打开XML也非常的容易
    objRS.Open Server.MapPath("test.xml"), , , , adCmdFile
    不过呢,Recordset保存的XML比我们平常写的XML要复杂得多,因为它要记录字段的详细信息,让我们来看一下就知道了。
    这是Recordset保存的:
    <xml xmlns:s=''''''''''''''''uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882''''''''''''''''
    xmlns:dt=''''''''''''''''uuid:C2F41010-65B3-11d1-A29F-00AA00C14882''''''''''''''''
    xmlns:rs=''''''''''''''''urn:schemas-microsoft-com:rowset''''''''''''''''
    xmlns:z=''''''''''''''''#RowsetSchema''''''''''''''''>
    <s:Schema id=''''''''''''''''RowsetSchema''''''''''''''''>
    <s:ElementType name=''''''''''''''''row'''''''''''''''' content=''''''''''''''''eltOnly'''''''''''''''' rs:updatable=''''''''''''''''true''''''''''''''''>
      <s:attribute type=''''''''''''''''ID''''''''''''''''/>
      <s:attribute type=''''''''''''''''Content''''''''''''''''/>
      <s:extends type=''''''''''''''''rs:rowbase''''''''''''''''/>
    </s:ElementType>
    <s:AttributeType name=''''''''''''''''ID'''''''''''''''' rs:number=''''''''''''''''1'''''''''''''''' rs:write=''''''''''''''''true''''''''''''''''>
      <s:datatype dt:type=''''''''''''''''int'''''''''''''''' dt:maxLength=''''''''''''''''4'''''''''''''''' rs:precision=''''''''''''''''0'''''''''''''''' rs:fixedlength=''''''''''''''''true'''''''''''''''' rs:maybenull=''''''''''''''''false''''''''''''''''/>
    </s:AttributeType>
    <s:AttributeType name=''''''''''''''''Content'''''''''''''''' rs:number=''''''''''''''''2'''''''''''''''' rs:write=''''''''''''''''true''''''''''''''''>
      <s:datatype dt:type=''''''''''''''''string'''''''''''''''' dt:maxLength=''''''''''''''''20'''''''''''''''' rs:precision=''''''''''''''''0'''''''''''''''' rs:maybenull=''''''''''''''''false''''''''''''''''/>
    </s:AttributeType>
    </s:Schema>
    <rs:data>
    <rs:insert>
      <z:row ID=''''''''''''''''1'''''''''''''''' Content=''''''''''''''''test''''''''''''''''/>
    </rs:insert>
    </rs:data>
    </xml>  (NC:汗)
    我自己写的:
    <?xml version="1.0" encoding="gb2312" ?>
    <root>
        <z:row ID=''''''''''''''''1'''''''''''''''' Content=''''''''''''''''test''''''''''''''''/>
    </root>
    用ADO访问XML并没有给我们带来什么方便,用XMLDOM也是比较简单的,但是它给我们提供了一种新的编程方式:用XML实现三层结构。
    第一,如果数据库服务器上的一些数据将在一段时间内保持不变,比如一些对照表,而数据库服务器又不和Web Server在一起,那么就可以用XML在中间层放置这些数据,比用文本文件还是要来得方便和快捷的。 (NC:所见略同,解决目前NP的问题,终于为自己的想法找到依据了)
    第二,比如我不想用数据库,又想放一些数据,比如我刚做的电子贺卡(实际上只是想用点新技术而已),那么也可以用XML来存放数据。
    第三,比如某些数据不经常变动,比如一天变动一次,那么我不希望每次查询都去访问数据库,而是在有数据变动的时候保存到一个XML里面,而让客户端代码去读XML。 (按固定的时间刷新会影响用户体验,其实合理设置刷更新点是件很容易的且可行的事情)

    (NC:如果自己写一个将RecordSet转换成XML的东东,算法多加锤炼,相比效率怎样呢?)

    以上内容部分来自PDC99资料DATA01:ADO体系结构和性能调整、WIN13:XML-实现3层Web应用程序。
    由于本人水平有限,文中有谬误和遗漏之处,还望大家多指教。