XML是一种语义描述语言。一般来说,包含在给定XML文档中的元素描述该文档的数据,因此对于静态信息或是不常改变的信息,这是一种不错的数据存储方法。
假设有一个在线书店,它有一个保存在XML文档books.xml中的“最佳选择”列表。你需要将这些信息显示给用户,但是又不希望使用服务器组件来实现,因此只能采用基于JavaScript的解决方案。使用zXml库就可以编写这样的JavaScript解决方案,载入、解析XML文件,并使用DOM方法在Web页面中显示这些信息。
books.xml文件中包含下列XML数据:
<?xml version="1.0" encoding="utf-8"?>
<bookList>
<book isbn="0471777781">
<title>Professional Ajax</title>
<author>Nicholas C. Zakas, Jeremy McPeak, Joe Fawcett</author>
<publisher>Wrox</publisher>
</book>
<book isbn="0764579088">
<title>Professional JavaScript for Web Developers</title>
<author>Nicholas C. Zakas</author>
<publisher>Wrox</publisher>
</book>
<book isbn="0764557599">
<title>Professional C#</title>
<author>Simon Robinson, et al</author>
<publisher>Wrox</publisher>
</book>
<book isbn="1861006314">
<title>GDI+ Programming: Creating Custom Controls Using C#</title>
<author>Eric White</author>
<publisher>Wrox</publisher>
</book>
<book isbn="1861002025">
<title>Professional Visual Basic 6 Databases</title>
<author>Charles Williams</author>
<publisher>Wrox</publisher>
</book>
</bookList>
正如你所见,文档元素<bookList/>中包含一系列的<book/>元素,每个<book/>元素包含一本特定书籍的信息。
1. 载入XML数据
实现的第一步是创建XML DOM文档,并载入XML数据。因为books.xml是以异步模式载入的,因此需要设置onreadystatechange事件处理函数:
var oXmlDom = zXmlDom.createDocument();
oXmlDom.onreadystatechange = function () {
if (oXmlDom.readyState == 4) {
}
};
当readystatechange事件触发并调用相应的事件处理函数时,将对readyState属性进行检查,如果该属性的值为4,就表明文档已完全载入,可以使用DOM了。
接下来一步是检查错误,因为即使文档已经完全载入,但并不表示一切正常:
var oXmlDom = zXmlDom.createDocument();
oXmlDom.onreadystatechange = function () {
if (oXmlDom.readyState == 4) {
if (oXmlDom.parseError.errorCode == 0) {
parseBookInfo(oXmlDom);
} else {
var str = "An error occurred!!\n" +
"Description: " + oXmlDom.parseError.reason + "\n" +
"File: " + oXmlDom.parseError.url + "\n" +
"Line: " + oXmlDom.parseError.line + "\n" +
"Line Position: " + oXmlDom.parseError.linePos + "\n" +
"Source Code: " + oXmlDom.parseError.srcText;
alert(str);
}
}
};
如果没有错误发生,那么XML DOM文档将作为参数传给parseBookInfo()函数,该函数负责解析这个书籍列表。如果发生了错误,那么将通过alert()方法在警告框中来显示parseError对象中的错误信息。
当完成了onreadystatechange事件处理函数的编写之后,就将使用load()方法载入XML数据:
oXmlDom.load("books.xml");
现在XML文档已经载入。接下来则是对XML数据进行解析。
2. 解析书籍列表
parseBookInfo()函数负责对DOM文档进行解析。该函数接受一个参数,即DOM文档自身:
function parseBookInfo(oXmlDom) {
var oRoot = oXmlDom.documentElement;
var oFragment = document.createDocumentFragment();
oRoot变量将赋值为XML文档的documentElement。这样做仅仅是为了方便,毕竟输入oRoot要比输入oXmlDom.documentElement简单、迅速得多。你还需要创建一个文档片断。parseBookInfo()函数将生成许多HTML元素,并将其转换成HTML DOM载入浏览器。往HTML DOM中添加每个元素是一个大开销的过程,因为显示这些变化会花费不少时间。所以我们采用的方法是,先将每个元素添加到文档片断,当所有HTML元素创建后,再将文档片断添加到文档中。这样做只需要对HTML DOM进行一次更新,从而可以更快地呈现。
你知道,文档元素的子节点只有<book/>元素,因此可以通过childNodes集合来遍历该文档:
var aBooks = oRoot.getElementsByTagName("book");
for (var i = 0; i < aBooks.length; i++) {
var sIsbn = aBooks[i].getAttribute("isbn");
var sAuthor, sTitle, sPublisher;
在for循环中,实际的解析工作才开始。首先,通过getAttribute()方法获取<book/>元素的isbn属性,并保存在变量sIsbn中。该值用来向用户显示书籍封面及其实际的ISBN值。这里还声明了变量sAuthor、sTitle和sPublisher,它们分别用来保存<author/>、<title/>和<publisher/>元素的值。
接下来必须获取书籍的相关数据,这可以通过多种不同的方法来实现。例如可以使用childNodes集合并遍历子节点,但这个例子中采用了另一种不同的方法。它通过一个使用firstChild和nextSibling属性的do...while循环来达到相同的目的:
var oCurrentChild = aBooks[i].firstChild;
do {
switch (oCurrentChild.tagName) {
case "title":
sTitle = oCurrentChild.text;
break;
case "author":
sAuthor = oCurrentChild.text;
break;
case "publisher":
sPublisher = oCurrentChild.text;
break;
default:
break;
}
oCurrentChild = oCurrentChild.nextSibling;
} while (oCurrentChild = oCurrentChild.nextSibling);
第一行代码将oCurrentChild变量的值设置为当前<book/>元素的第一个子节点(记住,这将发生在for循环中)。子节点的tagName属性用在switch语句中来判断如何进行数据的处理。switch语句将根据当前节点的相应tagName来将其值赋给相应的变量。为了获取这个数据,可以使用节点的text属性,该属性可以获取元素内的所有文本节点。使用nextSibling属性,将当前节点的下一个节点赋给变量oCurrentChild。如果下一个邻居存在,那么继续循环,否则oCurrentChild设为null并退出循环。
当所有数据变量包含所需数据时,就可以开始生成HTML元素来显示数据。通过程序创建的HTML元素结构可能如下所示:
<div class="bookContainer">
<img class="bookCover" alt="Professional Ajax" src="0471777781.png" />
<div class="bookContent">
<h3>Professional Ajax</h3>
Written by: Nicholas C. Zakas, Jeremy McPeak, Joe Fawcett<br />
ISBN #0471777781
<div class="bookPublisher">Published by Wrox</div>
</div>
</div>
为了提高列表的可读性,可以通过<div/>元素来实现交替背景色的效果。奇数行的书籍信息的背景色为浅灰,由类名为bookContainer-odd(CSS)类定义;偶数行的书籍信息的背景色为白色,由bookContainer(CSS)类定义。
通过DOM方法来生成这些代码,虽然很简单但也十分冗长。第一步是通过createElement()的DOM方法来创建容器<div/>、<img/>和内容<div/>元素。
var divContainer = document.createElement("div");
var imgBookCover = document.createElement("img");
var divContent = document.createElement("div");
var sOdd = (i % 2)?"":"-odd";
divContainer.className = "bookContainer" + sOdd;
在创建元素的同时,将为其指定相应的CSS类。通过使用取余操作符(%)判断当前书籍是位于奇数行还是偶数行。根据不同情况对sOdd变量赋予相应的值,偶数是空字符串,奇数则是“-odd”,并用在className的赋值上。
接下来对书籍封面图像的属性进行赋值。这些PNG图像使用ISBN号作为文件名:
imgBookCover.src = "images/" + sIsbn + ".png";
imgBookCover.className = "bookCover";
divContainer.appendChild(imgBookCover);
在这里,将对该图像的src和className属性进行赋值,并将其添加到divContainer中。完成图像处理后,就可以添加内容。首先要添加的信息是书籍标题,这是一个3级标题元素<h3/>。该元素也通过createElement()方法创建。
var h3Title = document.createElement("h3");
h3Title.appendChild(document.createTextNode(sTitle));
divContent.appendChild(h3Title);
如果要创建包含标题的文本节点,那么要使用createTextNode()方法,并将其添加到<h3/>元素中,然后将完整标题添加到divContent中。
接下来添加作者和ISBN的信息。这两部分信息都是文本节点,且除了divContent不存在父元素。不过在这两个文本节点间还将加入一个换行元素。
divContent.appendChild(document.createTextNode("Written by: " + sAuthor));
divContent.appendChild(document.createElement("br"));
divContent.appendChild(document.createTextNode("ISBN: #" + sIsbn));
这段代码将创建这些信息。首先,将包含作者信息的文本节点添加到divContent中,接着创建并添加换行元素<br/>,最后添加包含ISBN信息的文本节点。
最后添加的是出版社信息:
var divPublisher = document.createElement("div");
divPublisher.className = "bookPublisher";
divPublisher.appendChild(document.createTextNode("Published by: " + sPublisher));
divContent.appendChild(divPublisher);
出版社信息在<div/>元素中显示。<div/>元素创建后,其className设为“bookPublisher”,包含出版社名称的文本节点添加到该元素中。这样就完成了divPublisher元素,可以将其添加到divContent中。
到此为止,所有的数据操作就全部完成了。但是,divContent还没有指定CSS类名,而且必须添加到divContainer中,而divContainer必须依次地添加到文档片段中。下面三行代码将完成这个功能:
divContent.className = "bookContent";
divContainer.appendChild(divContent);
oFragment.appendChild(divContainer);
最后一步是,当通过下述代码遍历了所有的书籍节点之后,则将文档片段添加到页面主体中。
document.body.appendChild(oFragment);
这段代码并没有真正地添加文档片段本身,实际上只是添加文档片段的所有子节点,并立刻将其转换成HTML DOM。有了这最后一行代码,parseBookInfo()函数也就完成了。
3. 整合
该Web页面的主体部分完全是由JavaScript生成的。正因如此,所以必须在文档载入后才能执行元素的创建和代码的插入。请记住,在载入books.xml后才调用parseBookInfo(),所以创建XML DOM对象的代码必须在页面载入时执行。创建一个名为init()的函数,用来放置创建XML DOM对象的代码。
function init() {
var oXmlDom = zXmlDom.createDocument();
oXmlDom.onreadystatechange = function () {
if (oXmlDom.readyState == 4) {
if (oXmlDom.parseError.errorCode == 0) {
parseBookInfo(oXmlDom);
} else {
alert("An Error Occurred: " + oXmlDom.parseError.reason);
}
}
};
oXmlDom.load("book.xml");
}
init()函数用来处理window.onload事件。这有助于确保JavaScript生成的元素添加到页面中,而不引起任何错误。
这个小应用程序就在一个HTML文档中。所需的只是两个<script/>元素,一个为CSS提供的<link/>元素,以及为onload事件处理函数赋值。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>Book XML Exercise</title>
<link rel="stylesheet" type="text/css" href="books.css" />
<script type="text/javascript" src="zxml.js"></script>
<script type="text/javascript" src="books.js"></script>
</head>
<body onload="init()">
</body>
</html>
当运行这段代码时,将看到如图4-2所示的结果。

图 4-2







