第21章 XML
自从DOM出现后,所有浏览器都内置了对XML的原生支持(XML DOM),同时也提供了一系列相关的技术支持。DOM 2是第一个提到动态创建XML DOM概念的规范;DOM 3进一步增强了XML DOM,新增了解析和序列化等特性。IE 9+、Firefox、Opera、Chrome和Safari等现代主流浏览器都支持XML DOM标准规范。本章将介绍JavaScript处理XML数据的基本方法。
【学习重点】
▲ 了解XML语法
▲ 正确读取XML
▲ 在主流浏览器中操作XML数据:使用XPath
21.1 XML概述
1998年W3C推出XML标准(eXtensible Markup Language,表示可扩展的标记语言)。 XML具有可扩展性,根据XML标准,用户可以自定义标记,以此来表示各种形式的数据。
为了更好地理解XML文档,先来看一个简单的实例,通过该实例可以了解XML文档的创建以及结构。
【示例】在本示例中创建一个简单的XML文档,模拟留言栏结构,包括编号、标题、时间和内容等。代码如下:
在上面的代码中,第1行表示XML声明,从第2行开始表示文档中各个元素。与HTML一样,XML也是一个基于文本的标记语言,用“<”和“>”标记(一对尖括号)来表示数据。不同的是,XML标记说明了数据的含义,而不是如何显示它。在浏览器中预览,则显示效果如图21-1所示。
图21-1 XML文件显示效果
XML文档的内容都是由一个根元素构成(如blog),它由开始标记<blog>和结束标记</blog>组成。开始标记与结束标记之间就是这个元素的内容。由于各个元素的内容被各自的元素标记所包含,在XML中各种数据的分类查找和处理变得非常容易。
提示:XML文档一般包含3个部分:XML声明、处理指令和XML元素,其中处理指令是可选部分。
☑ 每个XML文档都必须有声明。声明包含XML版本,以及所使用的字符集等信息。这些声明信息是XML处理程序正确解析XML数据的基础。在XML文档前面不允许有其他任何字符,甚至是空格。XML声明必须是XML文档中的第一行内容。
☑ XML声明以分隔符“<?”开始,“?>”结束。“<?”标记后面的XML关键字表示该文件是一个XML类型的文件。
☑ version="1.0"表示该文档遵循的是XML l.0标准。在XML声明中要求必须指定version的属性值,指明文档所采用的XML版本号,且它必须在属性列表中排在第一位。
encoding="utf-8"表示该文档使用的是GB2312字符集。在XML规范中,包括很多编码类型,常见的编码如下。
☑ 简体中文编码:GB2312。
☑ 繁体中文编码:BIG5。
☑ 压缩的Unicode编码:UTF-8。
☑ 压缩的UCS编码:UTF-16。采用哪一种编码取决于文档中用到的字符集。在上面的示例中标记都是用中文来写的,内容也含有中文,因此需要在声明中加上encoding="utf-8"属性。
处理指令是用来给处理XML文档的应用程序提供信息。所有处理指令应该遵循下面格式:
<?处理指令名 处理指令信息?>
由于XML声明的处理指令名是“xml”,因此其他处理指令名不能再用“xml”。例如,针对下面这条指令:
<?xml-stylesheet type="text/xsl" href="xsl.xsl" ?>
具体解释如下:
☑ <?……?>表示该行是一条指令。
☑ xml-stylesheet表示该指令用于设定文档所使用的样式表单文件。
☑ type="text/xsl"属性设置文档所使用的样式表单为XSL类型。如果要设置为CSS样式表,则该属性应该为type="text/css"。
☑ href="xsl.xsl"属性设置样式表单文件的地址。
21.2 XML文档对象模型
XML文档对象模型(XML DOM)定义了访问和操作XML文档的标准方法。DOM将XML文档作为一个树形结构,而树叶被定义为节点。
21.2.1 认识XML DOM
XML DOM通过对XML文档的分析,把整个XML文档以一棵树的形式存储在内存中,不管这个文档多么复杂,文档中的信息都会被转换成一棵对象节点树。在这棵节点树中,有一个根节点,所有其他节点都是根节点的后代节点(子节点)。
【示例】在XML DOM中,文档的逻辑结构类似一棵树,文档、文档中的根、元素、元素的内容、属性、属性值等都是以对象模型的形式表示的。例如,针对下面XML文档结构:
其对应的DOM树状结构如图21-2所示。
图21-2 XML DOM文档树形结构
DOM节点树生成之后,即可通过DOM接口访问、修改、添加、删除、创建树中的节点和内容。
提示:节点树中的节点彼此之间都有等级关系。父、子和同级节点用于描述这种关系。父节点拥有子节点,位于相同层级上的子节点称为同级节点(兄弟或姐妹):
☑ 在节点树中,顶端的节点称为根节点。
☑ 根节点之外的每个节点都有一个父节点。
☑ 节点可以有任何数量的子节点。
☑ 叶子是没有子节点的节点。
☑ 同级节点是拥有相同父节点的节点。
21.2.2 读取XML
当在浏览器加载XML数据之后,就可以借助XML DOM定义的属性和方法来读取全部或部分数据。其中Node对象是整个DOM的主要数据类型。根据DOM文档对象模型规定,XML文档中的每个对象都是一个节点,具体说明如下:
☑ 整个文档是一个文档节点。
☑ 每个XML标签是一个元素节点。
☑ 包含在XML元素中的文本是文本节点。
☑ 每一个XML属性是一个属性节点。
☑ 注释属于注释节点。
Node对象包含的常用属性如表21-1所示,常用方法如表21-2所示。
表21-1 Node对象包含的常用属性
表21-2 Node对象包含的常用方法
不同类型的XML DOM对象可能包含不同的属性和方法,具体说明请参考XML DOM参考手册。
【示例】下面示例在IE浏览器中加载XML文件,并以字符串的形式显示在网页中:
21.3 在IE中操作XML
IE 8及以下版本浏览器对于XML的支持主要通过ActiveX扩展的形式来实现。微软在JavaScript中引入了用于创建ActiveX扩展对象的ActiveXObject类,并开发了基于ActiveX的MSXML组件,该组件支持用户在客户端操作XML数据。
ActiveXObject()构造函数只有一个参数,该参数设置要实例化ActiveX对象的字符串。例如,XML DOM对象的第一个版本为Microsoft.XmlDom,要创建该对象实例,可以使用下面代码:
var xmlDom=new ActiveXObject("Microsoft.XmlDom");
然后,就可以调用XML DOM对象的属性和方法,执行对XML数据的操作。注意,XML对象操作与DOM文档对象模型的操作完全一样。
21.3.1 创建XML DOM对象
创建XML DOM对象是非常简单的,但是由于IE在不同版本的MSXML组件中创建了不同的XML DOM对象,它们的名称也各不相同。所以在创建XML DOM对象时,就应该考虑版本兼容问题。MSXML组件的不同版本中XML DOM对象的名称如下:
☑ Microsoft.XmlDom。
☑ MSXML2.DOMDocument。
☑ MSXML2.DOMDocument.3.0。
☑ MSXML2.DOMDocument.4.0。
☑ MSXML2.DOMDocument.5.0。MSXML组件的最新版本为DOMDocument 5.0,不同版本的XML DOM对象基本功能相同,且都能够相互兼容,但是新版本一般都比旧版本有更高的执行效率,且附加了一些新功能。
【示例】如果没有特殊的需求(如XML数据验证等),不用担心新旧版本的功能缺失问题。实现兼容的代码如下:
上面的函数将遍历MSXML版本字符串数组,并尝试从最新版本的MSXM组件开始创建,如果创建成功,则返回创建的实例,否则继续尝试,直到成功为止。如果所有版本都不能够创建,则抛出异常。然后就可以调用该函数创建XML DOM对象:
var o=xmlDom(); //创建XML DOM对象
21.3.2 加载XML数据
XML DOM对象加载XML数据的方法有两种:loadXML()和load()。
【示例1】loadXML()方法能够把XML数据字符串转换为XML DOM对象。
【示例2】load()方法能够加载XML数据文件。
load()方法的参数值可以是相对路径或绝对路径等。但是为了安全考虑,load()方法不能够实现跨域访问,它只能够加载同一服务器上的XML文件。
load()方法在加载数据时,也有两种模式:同步加载和异步加载。在同步模式下,XML文件被完全加载之后才能够执行其他操作;而异步加载时,用户不用等待,可以执行其他操作,也可以跟踪加载过程并决定下一步的操作。
1.设置加载模式
load()方法默认加载模式为异步加载,也可以使用async属性来设置加载模式,该属性值为布尔值,取值为false表示同步加载,取值为true表示异步加载。
【示例3】下面使用load()方法加载XML文档。
2.跟踪异步加载状态
与XMLHttpRequest对象异步通信状态一样,XML DOM对象使用readyState属性跟踪加载进程。readyState属性取值共有5个,具体说明如表21-3所示。
表21-3 readyState属性取值
同时,XML DOM对象定义了onreadystatechange属性,每当readyState属性值发生变化时,就会触发readystatechange事件,**onreadystatechange事件处理函数。
【示例4】可以使用下面的方法,判断XML文件是否被完全加载到XML DOM对象。
在回调函数中,不能够使用this关键字,因为JavaScript中的ActiveX对象比较特殊,this关键字可能会发生错误指代,为安全起见,一般直接引用XML DOM对象的实例名称。
21.3.3 错误处理
在使用loadXML()或者load()方法时,都可能会出现各种异常,为此XML DOM对象还定义了parseError对象属性,利用该对象可以获取发生错误的具体原因。parseError对象包含很多属性,这些属性可以显示错误的具体原因,具体说明如表21-4所示。
表21-4 parseError对象的属性
【示例】使用parseError对象的属性可以获取加载XML数据时发生错误的详细信息。
呈现效果如图21-3所示。
图21-3 提示错误信息
当直接为parseError对象取值时,它会返回errorCode属性的值,因此表达式o.parseError != 0也是正确的。同时由于错误代码的值可能为负值,此时应该检测它是否不等于0,作为监测是否发生错误的条件。这个监测条件一般应在XML DOM对象加载完毕时执行,即放在load()或laodXML()方法后面。
21.4 使用DOM2操作XML
DOM 2提供了比IE更加标准的XML DOM技术支持,并把它作为JavaScript语言的一部分,而不是以浏览器插件形式进行支持,因此它具有跨平台和浏览器类型的特点。
21.4.1 创建XML DOM对象
在DOM 2模型中,Document对象包含一个Implementation对象,使用该对象的createDocument()方法可以创建符合标准的XML DOM对象。createDocument()方法包含以下3个参数。
☑ 第一个参数是包含文档所使用的命名空间URI的字符串。
☑ 第二个参数是包含文档根元素名称的字符串。
☑ 第三个参数是要创建的文档类型(即doctype)。
【示例】下面代码将创建一个空的XML DOM文档对象:
var xmlDom=document.implementation.createDocument("","", null);
前两个参数是空字符串,第三个参数为null,这样就可以确保生成一个空XML文档。事实上,现在Mozilla并不提供针对文档类型的JavaScript支持,所以第三个参数总是为null。
如果要创建包含文档元素的XML DOM,那么可以在第二个参数中指定根元素的名称:
var xmlDom=document.implementation.createDocument("","root", null);
上面的代码创建了一个XML DOM文档对象,其中documentElement是<root/>。
如果要创建包含指定命名空间的DOM,可以在第一个参数中指定命名空间URI:
当在createDocument()方法中指定命名空间时,Mozilla会自动附上前缀a0以表示命名空间URI:
<a0:root xmlns:a0=" http://www.mysite.cn/" />
下面就可以在其中填充XML文档,当然也可以在空的XML DOM对象中加载指定的XML文档。
21.4.2 加载XML数据
DOM 2仅支持使用load()方法加载外部XML数据。
【示例1】使用load()方法加载XML数据。
async属性可以设置是同步加载,还是异步加载。如果将async属性设置为false,表示以同步模式加载文档;否则,以异步模式加载文档。
XML DOM对象不支持readyState属性和onreadystatechange事件处理函数。但是可以借助load事件和onload事件处理函数来监测XML文档加载是否完毕,当XML文档完全加载后将触发load事件。
【示例2】跟踪加载状态。
【示例3】DOM的XML DOM对象不支持loadXML()方法,不过可以通过DOMParser对象来模拟loadXML()的功能。该对象包含有parseFromString()的方法,用来加载字符串并解析成文档。
在上面代码中,创建了一个XML字符串,并作为参数传递给DOMParser的parseFromString()方法。parseFromString()方法包含两个参数,分别是XML字符串和数据的内容类型。要解析XML代码,内容类型应该是"text/xml"或者"application/xml",任何其他内容类型都被忽略。parseFromString()方法返回XML DOM对象,因此上面的代码所生成的XML DOM对象与使用implementation.createDocument()方法创建XML DOM对象功能相同。
21.4.3 读取XML数据
XML DOM严格遵循DOM 2标准模型,因此可以使用DOM标准模型中大部分属性和方法来读取XML数据。例如,使用documentElement属性来获取根元素,另外还可以使用childNodes、firstChild、lastChild、nextSibling、nodeName、nodeType、nodeValue、ownerDocument、parentNode和previousSibling等属性获取不同位置的节点,以及节点类型、值包含内容等。但是Mozilla不支持IE的xml和text属性,不过可以自定义方法来模拟这两个属性的功能。
【示例1】自定义方法模拟text属性的功能。
在IE浏览器中,text属性可以读取当前节点的内容,或者是当前节点及其子节点的内容。这不仅仅返回当前节点的文本,还有所有子节点的文本。可以使用下面的方法来进行模拟:
上面的函数使用for循环遍历指定节点的所有子节点,检查每个子节点是否包含子节点。如果有子节点,那么就将其childNode传给text()函数,并进行递归处理。如果没有子节点,那么将当前节点的nodeValue加到字符串中。处理完所有子节点后,该函数返回变量s。例如,在下面的示例中利用text()方法可以读取每个节点包含的内容:
【示例2】自定义方法模拟xml属性的功能。
在IE浏览器中,xml属性将存放对当前节点包含的所有XML字符串结果。虽然DOM不支持该属性,但是它提供了可以实现相同目的的XMLSerializer对象来完成这一功能。该对象定义了serializeToString()方法,使用该方法可以把XML数据转换为字符串。
xml()函数以XML节点作为参数,创建一个XMLSerializer对象,并将该节点传给serializeToString()方法。该方法将向调用者返回XML数据的字符串表示。例如:
当IE在加载和解析XML文档时,如果发生错误,它会把错误信息存储在parseError对象中,通过读取该对象的属性来获取信息。不过对于Mozilla来说,它将创建XML文档来存储错误信息,并将包含错误信息的XML文档加载到XML DOM对象中。
21.5 使用XPath
XPath(XML Path)是一门在XML数据中查找特定信息的语言,于1999年7月首次在XSL(可扩展样式表语言)中作为一种在XML文档中查找任意节点的解决方案被提出。XPath可用来在XML文档中对元素和属性进行遍历,它是W3C XSLT标准的主要元素,并且XQuery和XPointer都是基于XPath构建的,因此,学习和掌握XPath语言是很多高级XML应用的基础。
21.5.1 熟悉XPath基本语法
XPath语法非常类似于文件系统的路径语法,主要由位置路径、表达式,以及一些有助于获取特定数据的函数等组成。XPath语言使用路径表达式来选取XML文档中的节点或者节点集。也许读者已经非常熟悉这些表达式了,因为此前我们经常使用它们,常用路径表达式如表21-5所示。
表21-5 XPath常用路径表达式
XPath路径表达式一般都包括两部分:上下文节点和节点模式。上下文节点提供了节点模式起始的位置。节点模式是由一个或多个节点选择器组成的字符串。
【示例】构建一个XML文档,保存为xpath.xml,代码如下:
对于下面这个路径表达式:
book/title
book和title都表示XML元素名,按照它们在上下文节点中出现的顺序,斜杠表示从父节点到子节点的关系。这个XPath表达式表示从<bookstore/>起,匹配位于book元素下的子节点title元素。其中bookstore表示上下文节点,而book/title表示bookstore节点的匹配模式。还有以下一些情况:
☑ 如果要选取book元素的所有子节点,则可以使用book路径表达式来表示。
☑ 如果要选取根元素,则可以使用/bookstore路径表达式来表示。
☑ 如果要选取属于bookstore的所有book子元素,则可以使用bookstore/book路径表达式来表示。
☑ 如果选取所有book子元素,而不管它们在文档中的位置,则可以使用// book路径表达式来表示。但是bookstore//book表达式可以选取所有属于bookstore元素后代的book元素。
☑ 如果选取名称为lang的所有属性,则可以使用//@lang表达式来表示。
路径表达式中的位置路径可以是绝对的,也可以是相对的。绝对路径起始于正斜杠(/),而相对路径将根据上下文节点来确定,这与URL中的相对路径和绝对路径是相通的。
在XPath语言中,方括号用来为某个节点提供更加确切的信息。例如,如果选取book元素的第一个title元素,则可以这样设计:
book[position()=1]/title
上面的代码使用了XPath的position()函数,该函数可以返回元素在父节点下的位置,所以将position()函数设置为1,就可以匹配第一个book元素。然后,使用斜杠和title匹配在第一个book元素下的title元素。还有如下一些情况:
☑ /bookstore/book[1]表达式可以选取属于bookstore子元素的第一个book元素。
☑ /bookstore/book[last()]表达式可以选取属于bookstore子元素的最后一个book元素。
☑ /bookstore/book[last()-2]表达式可以选取属于bookstore子元素的倒数第三个book元素。
☑ /bookstore/book[position()<4]表达式可以选取最前面的3个属于bookstore元素的子元素的book元素。
除了位置和名称外,还可以使用属性进行匹配。例如,如果选择属性lang等于cn的title元素,则可以设计表达式为title[@lang ="cn"]。
☑ //title[@lang]表达式可以选取所有拥有名为lang的属性的title元素。
☑ /bookstore/book[price>30.00]表达式可以选取所有bookstore元素的book元素,且其中的price元素的值必须大于30.00。
☑ /bookstore/book[price>30.00]/title表达式可以选取所有bookstore元素中的book元素的title元素,且其中的price元素的值必须大于30.00。
另外,使用“|”可以选取多个路径,这与JavaScript中的逻辑运算符“|”有点类似。例如,表达式//book/title | // book/price可以选取所有book元素的title和price元素,而表达式//title | // price可以选取文档中所有title和price元素。
XPath语言还包含众多表达式及100多个内置函数,灵活使用这些函数可以精确选择XML文档中特定位置的信息,如果想深入学习XPath语言,建议读者参阅更专业的XPath技术书籍进行学习。
21.5.2 IE中的XPath
IE 9及其以下版本浏览器通过为每一个节点都定义了selectNodes()和selectSingleNode()方法来实现对XPath语言的支持。这两个方法的参数都是XPath匹配模式,即XPath语言的路径表达式,以字符串形式传递。然后分别返回与指定XPath模式相匹配的节点集合和第一个节点。
【示例】在本示例中,使用前面介绍的IE方法导入外部XML文档xpath.xml,然后设置根节点(documentElement)为上下文节点,并为该上下文节点调用selectNodes()方法获取book元素下的title元素:
上面示例是以根节点(o.documentElement)为上下文节点的,此时也可以使用绝对路径匹配指定元素:
如果以第一个book元素为上下文节点,则可以使用下面的XPath匹配模式获取其中的title元素,注意,此时返回的是一个元素集合,虽然仅匹配一个title元素:
var xpath ="title";
var nodes=o.documentElement.firstChild.selectNodes(xpath);
如果没有匹配指定的节点,selectNodes()方法还会返回一个NodeList,当为空时,则它的length属性值为0。
如果仅匹配模式中第一个节点,则可以使用selectSingleNode()方法。例如:
var xpath ="book/title";
var nodes=o.documentElement.selectSingleNode(xpath);
alert(nodes.xml);
21.5.3 DOM 3中的XPath
DOM 3.0附加标准定义了XPath接口,利用这个接口可以在XML DOM中实现对XPath模式的匹配操作。Mozilla支持这个标准,不过这个附加标准比较复杂,读者可以先掌握其中两个重要对象:XPathEvaluator和XPathResult。
XPathEvaluator对象定义了evaluate()方法,该方法可以计算XPath匹配模式。其用法如下:
evaluate(expression, contextNode, resolver, type, result)
evaluate()方法包含5个参数,每个参数的说明如表21-6所示。
表21-6 evaluate()方法的参数
【示例1】本示例实现在Mozilla浏览器中能够正常执行。
在上面的示例中,evaluate()方法的返回结果类型为XPathResult.ORDERED_NODE_ ITERATOR_ TYPE,它表示返回的节点将按文档中的顺序进行排列。
如果返回集合中可以允许包含XPath查询获得的任何类型的结果,可以设置type参数是XPathResult.ANY_TYPE。
【示例2】如果只需获取第一个元素,即模仿IE中selectSingleNode()方法的功能,则需要将type参数设置为XPathResult.FIRST_ORDERED_NODE_TYPE,不过其读取方法就与上面不同了,如下所示:
除了以上常用类型之外,type参数还支持其他7种类型。所有常数说明如表21-7所示。
表21-7 type参数类型
21.6 案例实战
下面通过几个案例演示如何使用JavaScript操作XML数据。
21.6.1 在网页中显示XML数据
在制作网站时,有时需要在页面显示信息列表。这时,如果是动态网站可以将列表信息保存到数据库中,但如果是静态网站,制作和维护起来就很麻烦。解决方法是,将要显示的信息保存到XML文件中,然后再通过JavaScript读取并显示该XML文件中的内容。
【操作步骤】
第1步,新建XML文档,保存为goodss.xml。在其中输入需要在页面显示的列表信息,代码如下:
第2步,新建网页文件,保存为index.html。在头部标签(<head>)内插入<script type="text/ JavaScript">标签。
第3步,自定义JavaScript函数createTable(),用于将载入到DOM中的XML取出来,并以表格的形式显示在页面中。该函数只包括一个参数xmldoc,用于指定载入到DOM中的XML,无返回值。代码如下:
第4步,自定义JavaScript函数readXML(),用于读取XML文件并显示在页面中。在该函数中,首先实现在IE或Mozilla浏览器中创建DOM,然后把指定的XML文件载入到DOM中,最后调用createTable()在页面的指定位置显示XML文件的内容,代码如下:
第5步,将用于显示新创建表格的单元格的id属性设置为parentTd:
<td valign="top" id="parentTd"> </td>
第6步,在页面的onload事件中调用自定义函数readXML()读取XML文件并显示在页面中。
第7步,使用CSS美化表格,然后在浏览器中预览,则显示效果如图21-4所示。
图21-4 XML数据在网页中显示
21.6.2 异步加载XML数据
本实例将应用Ajax技术实现XMl数据的异步加载,通过XMLHttpRequest对象完成XML数据的无缝交互,而不需要每次请求都刷新整个页面,也不需要将每次的数据操作都交付给服务器去完成。
【操作步骤】
第1步,在服务器端设计一个XML格式的数据文件,保存为customers.xml,位于服务器根目录下。该文件的数据如下:
第2步,新建网页文件,保存为index.html,放置于服务器根目录下。在头部标签(<head>)内插入<script type="text/JavaScript">标签。
第3步,自定义JavaScript函数createTable(),用于将载入到DOM中的XML取出来并以表格的形式显示在页面中。该函数只包括一个参数xmldoc,用于指定载入到DOM中的XML,无返回值。代码如下:
第4步,搭建Ajax技术框架,具体代码如下:
第5步,编写函数dealresult(),用于处理服务器返回的信息。在该函数中,将调用函数createTable()在页面的指定位置显示XML文件的内容,代码如下:
第6步,在页面指定位置单元格中设置该标签的id属性值为parentTd,定义一个钩子,以便JavaScript脚本抓取并显示信息,关键代码如下:
<td width="96%" id="parentTd"></td>
第7步,在页面的onload事件中调用自定义函数createRequest()读取服务器端的XML文件并显示在页面中。
第8步,使用CSS美化表格,然后在浏览器中预览,则显示效果如图21-5所示。
图21-5 异步加载XML数据效果