XML DOM文档的遍历与HTML DOM的遍历非常类似,因为它们都是节点层次的结构。节点树的最顶部是documentElement属性,包含文档的根元素。使用表4-1中所列出的属性,可以访问文档中任何元素或属性。
表4-1 XML DOM属性
属 性 |
描 述 |
attributes |
包含当前节点属性的数组 |
childNodes |
包含子节点数组 |
firstChild |
指向当前节点的第一个子节点 |
lastChild |
指向当前节点的最后一个子节点 |
nextSibling |
返回当前节点的下一个邻居节点 |
nodeName |
返回当前节点的名字 |
nodeType |
指定当前节点的XML DOM节点类型 |
nodeValue |
包含当前节点的文本 |
ownerDocument |
返回文档的根元素 |
parentNode |
指向当前节点的父节点 |
previousSibling |
返回当前节点的前一个邻居节点 |
text |
返回当前节点的内容或当前节点及其子节点的文本(只有IE才支持的属性) |
xml |
以字符串返回当前节点及其子节点的XML(只有IE才支持的属性) |
遍历DOM文档并获取数据,是一个很直观的过程。让我们看看下面的XML文档:
<?xml version="1.0" encoding="utf-8"?>
<books>
<book isbn="0471777781">Professional Ajax</book>
<book isbn="0764579088">Professional JavaScript for Web Developers</book>
<book isbn="0764557599">Professional C#</book>
<book isbn="1861002025">Professional Visual Basic 6 Databases</book>
</books>
这是一个简单的XML文档,包含一个根元素<books/>以及四个子元素<book/>。以该文档为例,我们可以研究DOM的细节。DOM树是基于节点之间的关系构造的。一个节点可能包含其他节点或者子节点。另一个节点可能与其他节点拥有相同的父节点,我们称之为邻居节点。
如果要获取文档中第一个<book/>元素,那么只需简单通过访问firstChild属性就可以达到目的:
var oRoot = oXmlDom.documentElement;
var oFirstBook = oRoot.firstChild;
将documentElement赋给变量oRoot,可以节省程序空间和输入的内容,尽管这并不是必需的。使用firstChild属性可以引用根元素<books/>的第一个子元素<books/>的引用,并将其赋值给变量oFirstBook。
使用childNodes集合也可以达到相同的目的:
var oFirstBook2 = oRoot.childNodes[0];
选择childNodes集合中的第一项将返回根节点的第一个子节点。因为childNodes是JavaScript中的NodeList类型,所以使用length属性可以得到子节点的数量,如下:
var iChildren = oRoot.childNodes.length;
本示例中,因为文档元素有四个子节点,所以iChildren值为4。
正如前面所述,节点可以有子节点,也就意味着它可以有父节点。通过parentNode属性可以选择当前节点的父节点:
var oParent = oFirstBook.parentNode;
在本小节前面已经提到变量oFirstBook,不过很快,它现在已经是文档中第一个<book/>元素,所以其parentNode属性就是指DOM的documentElement属性,也就是<books/>元素。
如果当前节点是book元素,那么如何选择另一个book元素呢?因为<book/>元素有共同的父节点,所以它们互为邻居关系。通过nextSibling和previousSibling属性可以选择当前节点的临近节点。nextSibling属性指向下一个邻居,而previousSibling属性指向前一个邻居:
var oSecondBook = oFirstBook.nextSibling;
oFirstBook2 = oSecondBook.previousSibling;
这段代码引用第二个<book/>元素,并将其赋值给oSecondBook。通过oSecondBook邻居节点对变量oFirstBook2重新赋值,oFirstBook2的值不变。如果节点没有下一个邻居节点,那么nextSibling为null。对于previousSibling也是同样的,如果当前节点没有前一个邻居节点,那么previousSibling也为null。
现在我们知道了如何遍历文档结构,接下来要了解的是如何从树的节点获取数据。例如,使用text属性可以得到包含第三个<book/>元素的文本,代码如下:
var sText = oRoot.childNodes[2].text;
text属性(微软特有的属性)可以得到该节点包含的所有文本节点,该属性相当有用。如果没有text属性,访问文本节点必须:
var sText = oRoot.childNodes[2].firstChild.nodeValue;
这段代码与前面使用text属性的代码一样得到同样的结果。类似上一个例子,使用childNodes集合引用第三个<book/>元素,而使用firstChild指向<book/>元素的文本节点,因为文本节点在DOM中仍是一个节点。使用nodeValue属性获取当前节点的值,就可以获取文本。
这两个示例所产生的结果是相同的,然而使用text属性和使用文本节点的nodeValue属性之间存在一个主要的区别。text属性将得到包含当前元素及其子节点的所有文本节点的值,而nodeValue属性只能得到当前节点的值。它虽然是个有用的属性,但可能会返回比预期值更多的内容。例如,假设我们将XML文档修改成:
<?xml version="1.0" encoding="utf-8"?>
<books>
<book isbn="0471777781">
<title>Professional Ajax</title>
<author>Nicholas C. Zakas, Jeremy McPeak, Joe Fawcett</author>
</book>
<book isbn="0764579088">Professional JavaScript for Web Developers</book>
<book isbn="0764557599">Professional C#</book>
<book isbn="1861002025">Professional Visual Basic 6 Databases</book>
</books>
新的XML文档在第一个<book/>元素中添加了两个新的子节点:<title/>元素(书名),<author/>元素(作者)。我们再一次使用text属性:
alert(oFirstChild.text);
请注意,这时我们将获得 <title/> 和 <author/> 元素的文本节点,并将其连接在一起。这就是 text 与 nodeValue 的不同之处。 nodeValue 属性只能得到当前节点的值,而 text 属性则将得到包含当前节点及其子节点的所有文本节点。
MSXML还提供其他一些获取特定节点或数值的方法,最常用的方法是getAttribute()和getElementsByTagName()。
getAttribute()方法将接受一个包含属性名称的字符串型参数,并返回属性值。如果指定的属性不存在,那么返回的值为null。我们还将使用本小节前面提到的那个XML文档,请看下列代码:
var sAttribute = oFirstChild.getAttribute("isbn");
alert(sAttribute);
这段代码获取第一个<book/>元素的isbn属性值,并将其赋值给变量sAttribute,然后使用Alert()方法显示该值。
getElementsByTagName()方法根据其参数所指定的名字,返回子元素的NodeList。该方法只搜索给定的节点中的元素,所以返回的NodeList不包含任何外部元素。例如:
var cBooks = oRoot.getElementsByTagName("book");
alert(cBooks.length);
这段代码获取文档中所有的<book/>元素,并将返回的NodeList赋值给变量cBooks。对于前面那个XML文档例子而言,警告框将显示找到的四个<book/>元素。如果要获取所有子节点,那么必须用“*”作为getElementsByTagName()方法的参数,其代码如下所示:
var cElements = oRoot.getElementsByTagName("*");
因为前面的XML文档例子中只包含<book/>元素,所以这段代码的结果与上一个示例相同。
在IE中获取XML数据
要获取XML数据只需使用一个属性,即xml。该属性将对当前节点的XML数据进行序列化。序列化(serialization)是将对象转换成简单的可存储或可传输格式的过程。xml属性将XML转换成字符串形式,包括完整的标签名称、属性和文本:
var sXml = oRoot.xml;
alert(sXml);
这段代码从文档元素开始序列化XML数据,并将其作为参数传递给alert()方法。下面就是部分已序列化的XML:
<books><book isbn="0471777781">Professional Ajax</book></books>
已序列化的数据可以载入到另一个XML DOM对象,发送到服务器,或者传给另一个页面。通过xml属性返回的已序列化XML数据,取决于当前节点。如果是在documentElement节点使用xml属性,那么将返回整个文档的XML数据;如果只是在<book/>元素上使用它,那么将返回该<book/>元素所包含的XML数据。
xml属性是只读属性。如果希望往文档中添加元素,那么必须使用DOM方法来实现。
在IE中操作DOM
现在为止,我们已经学习如何遍历DOM,从DOM中提取信息,将XML转换成字符串格式。接下来学习的是如何在DOM中添加、删除和替换节点。
l 创建节点
使用DOM方法可以创建多种不同的节点。第一种就是用createElement()方法创建的元素。向该方法传入一个参数,指明要创建的元素标签名称,并返回一个对XMLDOMElement的引用:
var oNewBook = oXmlDom.createElement("book");
oXmlDom.documentElement.appendChild(oNewBook);
这段代码创建一个新的<book/>元素,并通过appendChild()方法把它添加到documentElement中。appendChild()方法添加由其参数指定的新元素,并且将其作为最后一个子节点。但在该例子中,添加到该文档中的是一个空的<book/>元素,因而还需要为该元素添加一些文本:
var oNewBook = oXmlDom.createElement("book");
var oNewBookText = oXmlDom.createTextNode("Professional .NET 2.0 Generics");
oNewBook.appendChild(oNewBookText);
oXmlDom.documentElement.appendChild(oNewBook);
这段代码通过createTextNode()方法创建一个文本节点,并通过appendChild()方法把它添加到新创建的<book/>元素中。createTextNode()方法只有一个字符串参数,用来指定文本节点的值。
现在已经通过程序创建了新的<book/>元素,为其提供了一个文本节点,并将它添加到文档中。对于这个新元素而言,还需要像其他邻居节点一样,为其设置isbn属性。这很简单,只要通过setAttribute()方法就可以创建属性,该方法适用于所有元素节点。
var oNewBook = oXmlDom.createElement("book");
var oNewBookText = oXmlDom.createTextNode("Professional .NET 2.0 Generics");
oNewBook.appendChild(oNewBookText);
oNewBook.setAttribute("isbn","0764559885");
oXmlDom.documentElement.appendChild(oNewBook);
上面这段代码中,新添加的一行是用来创建isbn属性的,并将其值赋为0764559885。setAttribute()方法有两个参数:第一个参数是属性名,第二个参数则是赋给该属性的值。对于向元素添加属性,IE还提供其他一些方法,不过它们实际上并不比setAttribute()更好用,而且还需要更多的编码。
l 删除、替换和插入节点
如果能够往文档中添加节点,那么同样意味着可以删除节点。removeChild()方法正是用来实现该功能的。该方法包含一个参数:要删除的节点。例如,要从文档中删除第一个<book/>元素,则可以使用以下代码:
var oRemovedChild = oRoot.removeChild(oRoot.firstChild);
removeChild()方法返回被删除的子节点,因而oRemoveChild变量将指向已删除的<book/>元素。当拥有对旧节点的引用时,就可以将其放置在文档的任何地方。
如果想用oRemovedChild指向的元素来替换第三个<book/>元素,那么可以通过replaceChild()方法来实现,该方法返回被替换的节点:
var oReplacedChild = oRoot.replaceChild(oRemovedChild, oRoot.childNodes[2]);
replaceChild()方法接受两个参数:新添加的节点和将被替换的节点。在这段代码中,将用oRemovedChild变量引用的节点替换第三个<book/>元素,而被替换节点的引用将存在oReplacedChild变量中。
由于oReplaceChild变量是被替换节点的引用,因而可以容易地将其插入到文档中。使用appendChild()方法可以该其添加到子节点列表的最后,也可以使用insertBefore()方法将该节点插入到某个节点之前:
oRoot.insertBefore(oReplacedChild, oRoot.lastChild);
这段代码将之前被替换的节点插入到最后一个<book/>元素的前面。lastChild属性的用法与firstChild选择第一个子节点非常相似,通过该属性可以获取最后一个子节点。insertBefore()方法接受两个参数:要插入的节点和表示插入点的节点(插入点在该节点之前)。该方法也将返回插入节点的值,但上述例子中并不需要。
如你所见,DOM是一个相当强大的接口,通过它可以实现数据的获取、删除和添加等操作。
5. 在IE中处理错误
在XML数据的载入过程中,可能会由于不同的原因而抛出错误。例如,外部的XML文件找不到,或者XML的格式不正确。为了处理这些情况,MSXML提供了一个包含错误信息的parseError对象。对于每个由MSXML创建的XML DOM文档对象而言,该对象都是其所属的属性值之一。
我们可以通过parseError对象公开的与整数0进行比较的errorCode属性来检查错误。如果errorCode不等于0,则表示有错误发生。下面的例子故意设计出现一个错误。
var sXml = "<root><person><name>Jeremy McPeak</name></root>";
var oXmlDom = createDocument();
oXmlDom.loadXML(sXml);
if (oXmlDom.parseError.errorCode != 0) {
alert("An Error Occurred: " + oXmlDom.parseError.reason);
} else {
//当XML载入成功后的操作
}
大家会注意到,在突出显示的代码行中,<person>元素是不完整的(没有相应的</person>标签)。由于要载入的XML的格式不正确,因此将产生一个错误。然后errorCode与0进行比较,如果不相等(在本例中就不相等),那么将显示发生错误的警告。要实现该功能,可以使用parseError对象的reason属性来获取错误出现的原因。
parseError对象提供了以下属性,能够帮助你更好地了解错误:
q errorCode:错误代码(长整型);
q filePos:在文件中发生错误的位置(长整型);
q line:包含错误的代码行的行号(长整型);
q linePos:在特定行中发生错误的位置(长整型);
q reason: 错误的原因(字符串型);
q srcText: 发生错误的代码行内容(字符串型);
q url: XML文档的URL(字符串型)。
尽管这些属性提供了每种错误的信息,但是应该使用哪个属性,则取决于你的需要。