15.1 浏览器中的XML DOM支持
仅有两个浏览器IE和Mozilla可以支持客户端的XML处理。
微软在JavaScript中引入了用于创建ActiveX对象的ActiveXObject类。ActiveXObject的构造函数只有一个参数——要进行实例化的ActiveX对象的字符串代号。例如,XML DOM对象的第一个版本称为Microsoft.XmlDom。所以,要创建这个对象的实例,使用以下代码:
执行这行代码后, oXmlDom 对象就同其他 DOM Document 的对象行为完全一样了
开发人员首次使用这个XML处理方法时,经常会出现问题,因为用户常常未安装MSXML。绝大多数情况,开发人员必须直接从微软下载这个库。不过,IE 5.0修复了这个问题,它直接搭载了MSXML,这就确保了用IE 5.0或更高版本的用户一定可以使用这个功能。
1.DOM创建
对每个新版本的MSXML,都会创建出不同的XML DOM对象,而它们各自的名称也不相同。MSXML最新的版本是5.0,也就是说,存在以下XML DOM实现:
q Microsoft.XmlDom(最原始的);
q MSXML2.DOMDocument;
q MSXML2.DOMDocument.3.0;
q MSXML2.DOMDocument.4.0;
q MSXML2.DOMDocument.5.0。
为确保使用了正确的XML DOM版本,也为避免任何其他错误,我们可以创建一个函数来测试每个XML DOM字符串,出现错误即捕获:
2.载入XML
现在已有个可用的XML DOM对象,就可以载入一些XML了。微软的XML DOM有两种载入XML的方法:loadXML()和load()。
loadXML()方法可直接向XML DOM输入XML字符串:
load()方法用于从服务器上载入XML文件。不过,load()方法只可以载入与包含JavaScript的页面存储于同一服务器上的文件,也就是说,不可以通过其他人的服务器载入XML文件。
还有两种载入文件的模式:同步和异步。以同步模式载入文件时,JavaScript代码会等待文件完全载入后才继续执行代码;而以异步模式载入时,不会等待,可以使用事件处理函数来判断文件是否完全载入了。
默认情况下,文件按照异步模式载入。要进行同步载入,只需设置async特性为false:
然后使用load()方法,并给出要载入的文件名:
执行这一行代码后,oXmlDom会包含能表示XML文件结构的一个DOM文档,这样就可以使用DOM所有的特性和方法了:
异步载入文件时,要使用readyState特性和onreadystatechange事件处理函数:
readyState特性有五种可能的值:
q 0——DOM尚未初始化任何信息;
q 1——DOM正在载入数据;
q 2——DOM完成了数据载入;
q 3——DOM已经可用,不过某些部分可能还不能用;
q 4——DOM已经完全被载入,可以使用了。
一旦readyState特性的值发生变化,就会触发readystatechange事件。如果使用onreadystatechange事件处理函数,就可以在DOM完全载入时,发出通知:
必须在调用load()方法前分配好onreadystatechange事件处理函数,就像下面代码中那样:
无论同步或者异步地载入文件,load()方法都可以接受部分的、相对的或者完整的XML文件路径,如下:
3.获取XML
把XML载入到DOM中后,肯定还需将XML取出来。微软为每个节点(包括文档节点)都添加了一个xml特性,使得这个操作十分方便,它会将XML表现形式作为字符串返回。所以要获取载入后的XML十分简单:
也可以仅获取某个特定节点的XML:
4.解释错误
在尝试将XML载入到XML DOM对象中时,无论使用loadXML()方法还是load()方法,都有可能出现XML格式不正确的情况。为解决这个问题,微软的XML DOM的parseError的特性包含了关于解析XML代码时所遇到的问题的所有信息。
parseError特性实际上是包含以下特性的对象:
q errorCode——表示所发生的错误类型的数字代号(当没有错误时为0);
q filePos——错误发生在文件中的位置;
q line——遇到错误的行号;
q linepos——在遇到错误的那一行上的字符的位置;
q reason——对错误的一个解释;
q srcText——造成错误的代码;
q url——造成错误的文件的URL(如果可用)。
当直接对parseError自身取值,它会返回errorCode的值,也就是说可以这样进行检查:
检查时应该检查错误代码是否不等于0,因为错误代码可能为正也可能为负。
可以使用parseError对象来创建自己的错误对话框:
另一个选项是抛出自己的错误:
不管最终如何显示错误,最好在XML DOM载入完毕后就立即检查错误。
与Mozilla其他方面一样,它提供的XML DOM版本要比IE的更加标准。以下既是在Mozilla如何使用XML
1.创建DOM
DOM标准指出,document.implementation对象有个可用的createDocument()方法。Mozilla严格遵循了这个标准,可以这样创建XML DOM:
createDocument()的三个参数分别是文档的命名空间URL,文档元素的标签名以及一个文档类型对象(总是为null,因为在Mozilla中还没有对文档类型对象的支持)。前面这行代码创建一个空的XML DOM。要创建包含一个文档元素的XML DOM,只需将标签名作为第二个参数:
这行代码创建了代表XML代码
2.载入XML
Mozilla只支持一个载入数据的方法:load()。Mozilla中的load()方法和IE中的load()工作方式一样。只要指定要载入的XML文件,以及同步还是异步(默认)载入。
如果同步载入XML文件,代码基本上IE差不多:
进行异步载入,情况就有些不同了。
Mozilla的XML DOM并不支持微软的readyState特性(实际上,readyState并非Mozilla的实现所遵循的DOM Level 3载入和保存规范的一部份)。相反,Mozilla的XML DOM 会在文件完全载入后触发load事件,也就是说必须使用onload事件处理函数来判断DOM何时可用:
Mozilla的XML DOM不支持loadXML()方法。要将XML字符串解析为DOM,必须使用DOMParser对象:
这段代码创建了代表
因为XML DOM只是Mozilla的JavaScript实现的一部分,所以它可自己添加loadXML()方法。XML DOM实际的类称为Document,所以添加新方法同使用prototype对象一样容易:
然后,用DOMParser创建新的XML DOM:
下面,原来的文档必须清空其内容。可用while循环来删除所有的文档的子节点:
记住,因为这个函数是一个方法,所以this关键词指向XML DOM对象。在删除所有的子节点后,所有的oXmlDom的子节点必须导入到文档中(使用importNode()方法)并作为子节点添加(使用appendChild()):
只需包含这段代码,在Mozilla中就可以与在IE中一样地使用loadXML()方法了。
3.获取XML
Mozilla提供了XMLSerializer对象:
这段简单的代码段用XMLSerializer唯一的方法——serializeTostring()为oXmlDom创建了XML代码。serializeTostring()接受要进行序列化的节点和内容类型作为参数。这次,内容类型可以是"text/xml"或者"application/xml"。使用这个对象,就可以自己为Mozilla合成xml特性,只需使用defineGetter()方法。
defineGetter()方法只存在于Mozilla中,用于为某个特性定义获取函数,也就是说,读取特性时,就会调用这个函数并返回它的结果。例如:
第一行代码读取nodeValue特性,即解释器读取特性的值。如果定义了获取函数,就会运行这个函数并将函数值作为特性的值返回。第二行代码对nodeValue特性进行设置,即为其赋值。如果定义设置函数(与获取函数相对),则调用这个函数,并以New value作为参数值。当然,还有个defineSetter()方法,但在这里用不到。
方法defineGetter()是按照JavaScript对于私有特性和方法的标准隐藏的——在名字前后加上两个下划线:
可以看见,defineGetter()需要两个参数:特性的名称和要调用的函数。指定的特性名不能再用作一般的特性名。例如,不能这样:
一般获取函数和设置函数是成对定义的,但是也可以只分配一个获取函数来创建只读特性。这样就可创建xml特性了。
因为文档中每种类型的节点都有xml特性。所以最好将其添加到Node类中(其他的节点类型都是继承Node的):
分配给xml特性的函数十分简单,对前面的例子的唯一改变是this是serializeToString()方法的第一个参数(在此上下文中,this指向节点自身)。如果将这段代码包含在页面中,就可以与使用微软的xml特性方式一样地使用自定义的xml特性:
4.解析错误
在XML文件的解析过程中发生错误时,XML DOM会创建文档来解释这个错误。假设运行以下代码:
尽管未抛出任何错误,但是oXmlDom会显示出错误。在此例子中,它会呈现出这段代码:
所以要判断是否在XML代码的解析过程中有错误,必须测试文档元素的标签名:
可惜,唯一能获取确切的错误信息的方法是,解析错误信息文本。最简单的方法是使用正则表达式:
这段正则表达式抓取了所有XML代码中潜在的信息。第一个捕获性分组获取错误信息,第二个获取文件名,第三个获取行号,第四个获取列号,第五个获取造成错误的源代码(不包含最后的横线和脱字符号)。只需使用test()方法就可创建错误信息了:
通用接口
有了跨浏览器的解决方案时,使用XML DOM进行开发才真正有用。IE和Mozilla的实现之间有着十分明显的区别,开发时会造成很严重的问题。所以必须找出一套能在两种浏览器中都可使用XML DOM的通用办法。
1.修改DOM创建
第一步是创建IE和Mozilla通用的创建XML DOM对象的方法。最简单的方法是创建伪类,使之可以这样创建XML DOM:
当然,在XmlDom构造函数中必须进行浏览器检测:
这段代码使用了对象/特性检测法来判断要走哪条路。因为Windows上的IE是唯一支持ActiveXObject类的浏览器,所以这样测试IE是可以的。第二种测试比较通用,用于判断浏览器是否支持DOM标准的createDocument()方法。虽然目前只有Mozilla浏览器支持这个方法,但可以预见将来会有其他的浏览器采纳这个功能,所以这样测试确保了代码是经得起未来考验的。
2.IE分支
对于构造函数的IE部分,只需把本书前面的createXMLDOM()函数中的代码插进来即可:
这就是你要对IE所做的。对Mozilla则要做较复杂的事情。
3.Mozilla分支
Mozilla分支的第一步是,用createDocument()方法创建XML DOM对象。
下面的任务是让Mozilla支持readyState特性以及onreadystatechange事件处理函数。这要求必须对Document类再进行一些修改。
首先,添加readyState特性,并初始化为0。
下面,创建onreadystatechange特性,并初始化为null:
一旦readyState特性发生变化,必须调用onreadystatechange函数。为达到这个目的,最好创建一个方法:
这个方法将新状态作为参数并将其分配给readyState特性。注意调用前要先检测onreadystatechange确实是个函数(否则会出现错误)。因为这个方法不能在Document对象外调用,所以使用了JavaScript对于私有方法的标记(前后加上两个下划线)。
IE的XML DOM支持有五种状态,但不可能每种都能在Mozilla中进行模拟。其实,readyState唯一重要的值是4,它表示XML DOM已完全载入并可使用了。这个值可通过onload事件处理函数来模拟。模拟readyState 1也很容易,它表示XML DOM即将开始载入。其他状态不是特别重要,且模拟起来比较困难。
影响到readyState特性的两个方法是,loadXML()和load()。loadXML()方法比较容易更新,因为这是自己创建的方法。只需添加两行代码:
更新后的loadXML()方法最初将readyState设置为1,结束时将其设置为4。
要更新load()方法,首先要创建指向原来方法的引用:
下面,定义新的load()方法,它先将readyState特性设置为1,然后调用原来的load()方法:
要使用onload事件处理函数适时地将readyState设置为4。因为只能在XML DOM对象被实例化之后才能分配事件处理函数,所以必须放在XmlDom构造函数中:
现在,Mozilla的XML DOM就能较好地支持readyState特性(仅对值0、1和4),onreadystatechange事件处理函数,loadXML()方法以及xml特性了。下面的例子可以在IE和Mozilla中使用:
其实,两种XML DOM处理错误的方式才是主要的区别。不过这个也可以处理好。
4.错误处理
最后一步是为Mozilla创建parseError对象。不过这次在Mozilla解析器中依然不能模拟所有的IE特性,但是却有足够的信息来解析XML以模拟大部分的IE特性。
首先,必须在XmlDom构造函数中创建这个对象并初始化所有的值:
这段代码使用对象字面量记号来创建parseError对象,节省了空间。并定义valueOf()方法返回errorCode特性,这与IE的实现一样;toString()方法也返回errorCode特性,不过是以字符串形式。initError()方法初始化所有parseError对象的特性。代码如下:
下一步是检测解析错误。必须在load()和loadXML()方法中完成,因为解析错误可能发生在其中任何一个中。因为在两处重复相同的代码是种不好的编码实践,最好创建函数来处理解析错误:
注意,不管发生什么错误,errorCode都设为-999999。映射微软所有的错误代码是一项繁琐的任务,且没有什么必要。大部分情况下,只需要检查parseError是否为0,而不是某个特定的值。
下面,load()和loadXML()方法必须进行更新以使用initError()(进行解析前清除所有的错误值)和checkForErrors()(解析完毕时检测有没有解析错误):
注意,initError()必须在readyState设为1之前调用。类似的,checkForErrors()也必须在readyState设为4(这就是必须在onload事件处理函数中调用的原因)之前调用。这些方法必须按顺序调用,因为每次readyState更改时都要调用onreadystatechange。如果在parseError对象中存在旧的数据,必须在调用onreadystatechange前重置,否则可能会造成混淆。在同一行上,parseError对象必须在readyState变成4后包含正确的数据,因为那时所有的处理都应该已完成了。
加入这段代码后,就可以写一段完整的可以同时在IE和Mozilla中处理解析错误的代码了:
这个例子载入了有错误的XML文件。readyState特性设成4时(文件载入并进行了解析)。检验parseError的值是否等于0 (代表是否有错误)。如果发生了错误,出现一个警告框,显示错误代码、行号、行位置(列号)以及错误原因。
5.完整的代码
在这一章中,开发代码时,我跳过了许多内容。下面看一下完整的代码(注意它使用了本书前面创建的浏览器检测代码):
15.2浏览器中对xpath的支持
因为XML用于处理多种数据,所以必须有一种可以在XML代码中定位数据的方式。这个问题的答案就是XPath,它是专门用于定位匹配模式的一个或多个节点的小语言。(不再细述)
15.3浏览器中对XSLT的支持
XSLT(可扩展样式表语言转换)可以对XML进行操作,将其转换成任何基于文本的形式。目前,很多开发人员都用XSLT将XML转换成HTML,当然,这只是其中一种用途(见图15-1)。
XSLT文件称为样式表,由一些模板组成。模板属于XML的某个特性的一部分(使用XPath指定),它可以决定为这一部分输出什么文本。通过为不同的元素和条件定义模板,XSLT样式表变成了一种XML 解析器。例如,考虑前面用过的XML:
现在假设你想将这个雇员列表按照以下的HTML格式显示:
其实,就是从内容中拉出
可以看到,XSLT其实就是另一个基于XML的语言。文档元素是
下面一行包含
下面讨论模板。第一个模板匹配了文档元素,由match= "/".;制定;/ XPath表达式总是指向文档元素。在模板中还有HTML代码,直到
在第二个模板中,注意元素。后面紧跟着
XSLT样式表应用于XML文件时,就可以出现前面的HTML结果。尽管例子很简单,不过还是显示了XSLT的一些独特能力。
如果想学习XSLT,请参阅XSLT 2.0: Programmer’s Reference, 3rd Edition(Wiley 出版社ISBN 0-7645-6909-0)。
15.3.1 IE中的XSLT支持
从MSXML 3.0开始,IE就完全支持XSLT 1.0了。如果你还在使用IE 5.0或者5.5,必须手工安装新版的MSXML;如果在使用IE 6.0,那么就表示你至少已经有了MSXML 3.0。
最简单的进行XSLT转换的方法是,分别将XML源代码和XSLT文件载入各自的DOM,并使用特有的transformNode()方法:
这个例子中的一个DOM载入了XML,而另一个DOM载入了XSLT样式表(注意XSLT也可以载入XML DOM因为它也是一种XML格式)。然后,第三行调用文档的transformNode()方法,并将包含XSLT代码的DOM作为唯一的参数传给它。然后在变量sResult中放入转换后的文本结果。
你无需从文档层次开始转换;每一个节点都有个transformNode()方法。下面的均有效:
如果在非文档元素中调用transformNode(),则从这个点开始转换,但是XSLT样式表能访问包含这个节点的整个XML文档。
在IE中使用XSLT的另外一个比较复杂的方法是,使用XSL模板和处理器。这种方法必须使用MSXML的其他几个ActiveX控件。首先,XSLT文件必须载入到一个自由线程DOM文档中(行为和普通DOM一样,但是线程是安全的)。
自由线程DOM文档建立并加载好后,必须将其分配到XSL模板中,这是另一个ActiveX对象:
然后,可以用XSL模板来创建一个XSL处理器(当然也是另一个ActiveX对象):
创建处理器后,将input特性设置为要进行转换的XML DOM节点,然后调用transform()方法:
然后从output特性中访问结果字符串:
这些代码模仿了transformNode()的功能。你肯定在想,如果两种方法都一样,为什么会有人使用XSL模板/处理器的方法。答案是处理器允许你更多地控制XSLT。
例如,XSLT样式表可以接受传入的参数,并将其用作局部变量。考虑下面的样式表:
这个样式表添加了两行代码。第一行是
要设置message的值,可在调用transform()前使用addParameter()方法。AddParameter ()方法有两个参数,要设置的参数的名称(与
为参数设置一个值后,输出就变成这样了:
可以看到,通过JavaScript传入的值正确地输出了HTML结果。这样,就可以通过加入基于参数的不同行为使样式表更加有弹性。
另一个XSL处理器的高级特性是,设置操作的模式的能力。在XSLT中,可以定义模板的模式。定义mode后,除非
这个样式表定义了mode特性为"position-first"的模板(注意可以任意地命名;无任何预定义的模式)。在这个模板中,首先输出雇员的位置,然后输出雇员的名称。为使用这个模板,
setStartMode()方法只接受一个参数,要设置的模式。与addParameter()一样,必须在transform()之前调用.
如果要使用同一个样式表进行多次转换,则可在每次转换后重置处理器。调用reset()方法,输入和输出特性则会被清除,处理器又可以使用了。
因为处理器已经编译过XSLT样式表,这要比重复调用transformNode()快得多。
MSXML只支持XSLT 1.0。MSXML的开发自从转到.NET Framework后就停止了。可能,在未来的某天,JavaScript会允许访问XML和XSL的.NET对象。
15.3.2 Mozilla中XSLT支持
从Mozilla 1.2开始,JavaScript开发人员就可用XSLTProcessor对象来进行客户端的XSLT转换了。这个对象使用Mozilla内置的XSLT处理器——Transformiix。
转换过程的第一步是将XML和XSLT载入DOM:
然后,创建XSLTProcessor并使用importStylesheet()方法来分配XSLT DOM:
最后一步是调用transformToDocument()或者transformToFragment(),并以XML DOM为参数,最后返回结果。transformToDocument()返回的是新的DOM文档,transformTo Fragement()返回新的文档碎片,一般来说,应该使用transformToDocument(),除非你想直接将结果添加到某个已经存在的文档中,后一种情况才使用transformToFragement()。
使用transformToFragment()时,仅传入XML DOM并将结果作为另一个完整不同的DOM。
使用transformToFragment()时,传入XML DOM和要添加到结果的文档。这确保了新的文档碎片在目标文档中是有效的。
在前面的例子中,处理器创建了由document对象所有的碎片。这使得碎片添加到了页面中的元素中。
在XSLT的输出方法是HTML或者XML时,这些当然很有意义,但如果输出的是文本方式呢?要解决这个问题,Mozilla创建了单个元素的XML文档
记住这一点,就可为Mozilla创建transformNode()方法了:
这个方法是用给定的XML DOM创建结果文档。然后使用xml特性(本章前面定义的)将结果XML代码存储到sResult中。然后检查代码是否包含
也可以为Mozilla中的XSLTProcessor设置XSLT参数。setParameter()方法接受三个参数:命名空间URI,参数本地名称及要设置的值。一般来说,命名空间URI为null,本地名称就是参数的名称,这个方法必须在transformToDocument()或者transformToFragment()方法之前调用。
还有另外两个方法与参数有关,getParameter()和removeParameter(),分别用于获取某个参数当前的值和删除参数值。它们都以一个命名空间URI(也可以为null)和参数的本地名称为参数:
这些方法不常用,主要是为了方便才提供的。