第18章 JavaScript 与 XML
1. 浏览器对 XML DOM 的支持
(1) DOM2 级核心
* 支持 DOM2 级的浏览器中使用以下语法来创建一个空白的 XML 文档:
var xmldom = document.implementation.createDocument(namespaceUri, root, doctype);
创建一个新的、文档元素为
var xmldom = document.implementation.createDocument("", "root", null);
alert(xmldom.documentElement.tagName);//"root"
var child = xmldom.createElement("child");
xmldom.documentElement.appendChild(child);
* 要检测浏览器是否支持 DOM2 级 XML,可以使用下面这行代码:
var hasXmlDom = document.implementation.hasFeature("XML", "2.0");
(2) DOMParser类型
- 在解析 XML 之前,首先必须创建一个
DOMParser 的实例
,然后再调用parseFromString()方法
。这个方法接受两个参数
:要解析的 XML 字符串和内容类型(内容类型始 终都应该是"text/xml")。返回的值是一个 Document 的实例。
var parser = new DOMParser();
var xmldom = parser.parseFromString(" ", "text/xml");
alert(xmldom.documentElement.tagName); //"root"
alert(xmldom.documentElement.firstChild.tagName);
var anotherChild = xmldom.createElement("child");
xmldom.documentElement.appendChild(anotherChild);
var children = xmldom.getElementsByTagName("child");
alert(children.length); //2
DOMParser 只能解析格式良好的 XML
,因而不能把 HTML 解析为 HTML 文档。在发生解析错误 时,仍然会从 parseFromString()中返回一个 Document 对象,但这个对象的文档元素是,而文档元素的内容是对解析错误的描述。
(3)XMLSerializer类型
XMLSerializer 类型
将 DOM 文档序列化为 XML 字符串。
要序列化 DOM 文档,首先必须创建 XMLSerializer 的实例,然后将文档传入其 serializeToString ()方法
。
var serializer = new XMLSerializer();
var xml = serializer.serializeToString(xmldom);
alert(xml);
(4) IE8 及之前版本中的XML
- IE 是第一个原生支持 XML 的浏览器,而这一支持是通过
ActiveX 对象
实现的。ActiveXObject 类型
可以在 JavaScript 中创建 ActiveX 对象的实例。创建一个 XML 文档的实例,也要使用 ActiveXObject 构造函数并为其传入一个表示XML 文档版本的字符串
。
- 通过尝试创建每个版本的实例并观察是否有错误发生,可以确定哪个版本可用。
function createDocument(){
if (typeof arguments.callee.activeXString != "string"){
var versions = ["MSXML2.DOMDocument.6.0", "MSXML2.DOMDocument.3.0", "MSXML2.DOMDocument"],i, len;
for (i=0,len=versions.length; i < len; i++){
try {
new ActiveXObject(versions[i]);
arguments.callee.activeXString = versions[i];
break;
} catch (ex){
//跳过
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
}
- 要解析 XML 字符串,首先必须创建一个 DOM 文档,然后调用
loadXML()方法
。
var xmldom = createDocument();
xmldom.loadXML(" ");
alert(xmldom.documentElement.tagName); //"root"
alert(xmldom.documentElement.firstChild.tagName);
var anotherChild = xmldom.createElement("child");
xmldom.documentElement.appendChild(anotherChild);
var children = xmldom.getElementsByTagName("child");
alert(children.length); //2
- 如果解析过程中出错,可以在
parseError 属性
中找到错误消息。
(1) errorCode:错误类型的数值编码;在没有发生错误时值为 0。
(2) filePos:文件中导致错误发生的位置。
(3) line:发生错误的行。
(4) linepos:发生错误的行中的字符。
(5) reason:对错误的文本解释。
(6) srcText:导致错误的代码。
(7) url:导致错误的文件的 URL(如果有这个文件的话)
if (xmldom.parseError != 0){
alert("An error occurred:\nError Code: "
+ xmldom.parseError.errorCode + "\n"
+ "Line: " + xmldom.parseError.line + "\n"
+ "Line Pos: " + xmldom.parseError.linepos + "\n"
+ "Reason: " + xmldom.parseError.reason);
}
* 序列化 XML
每个 DOM 节点都有一个 xml 属性,其中保存着
表示该节点的 XML 字符串。
alert(xmldom.xml);
* 加载 XML 文件
- IE 中的 XML 文档对象也可以加载来自服务器的文件。加载文档的方式也可以分为
同步和异步
两种。要指定加载文档的方式,可以设置async 属性
,true 表示异步,false 表示同步(默认值为 true)。- 在确定了加载 XML 文档的方式后,调用
load()
可以启动下载过程。这个方法接受一个参数,即要加载的 XML 文件的 URL。
- 在
同步方式
下,调用 load()后可以立即检测解析错误并执行相关的 XML 处理。
var xmldom = createDocument();
xmldom.async = false;
xmldom.load("example.xml");
if (xmldom.parseError != 0){
//处理错误
} else {
alert(xmldom.documentElement.tagName); //"root"
alert(xmldom.documentElement.firstChild.tagName); //"child"
var anotherChild = xmldom.createElement("child");
xmldom.documentElement.appendChild(anotherChild);
var children = xmldom.getElementsByTagName("child");
alert(children.length); //2
alert(xmldom.xml);
}
在
异步
加载 XML 文件的情况下,需要为 XML DOM 文档的 onreadystatechange 事件指定处理程序。
4 个就绪状态(ready state):
1:DOM 正在加载数据。
2:DOM 已经加载完数据。
3:DOM 已经可以使用,但某些部分可能还无法访问。
4:DOM 已经完全可以使用。
XML 文档的
readyState 属性
可以取得其就绪状态。
var xmldom = createDocument();
xmldom.async = true;
xmldom.onreadystatechange = function(){
if (xmldom.readyState == 4){
if (xmldom.parseError != 0){
alert("An error occurred:\nError Code: "
+ xmldom.parseError.errorCode + "\n"
+ "Line: " + xmldom.parseError.line + "\n"
+ "Line Pos: " + xmldom.parseError.linepos + "\n"
+ "Reason: " + xmldom.parseError.reason);
} else {
alert(xmldom.documentElement.tagName); //"root"
alert(xmldom.documentElement.firstChild.tagName); //"child"
var anotherChild = xmldom.createElement("child");
xmldom.documentElement.appendChild(anotherChild);
var children = xmldom.getElementsByTagName("child");
alert(children.length); //2
alert(xmldom.xml);
}
}
};
xmldom.load("example.xml");
(5) 跨浏览器处理XML
*解析 XML
function parseXml(xml){
var xmldom = null;
if (typeof DOMParser != "undefined"){
xmldom = (new DOMParser()).parseFromString(xml, "text/xml");
var errors = xmldom.getElementsByTagName("parsererror");
if (errors.length){
throw new Error("XML parsing error:" + errors[0].textContent);
}
} else if (typeof ActiveXObject != "undefined"){
xmldom = createDocument();
xmldom.loadXML(xml);
if (xmldom.parseError != 0){
throw new Error("XML parsing error: " + xmldom.parseError.reason);
}
} else {
throw new Error("No XML parser available.");
}
return xmldom;
}
//在使用这个函数解析 XML 字符串时,应该将它放在 try-catch 语句当中,以防发生错误。
var xmldom = null;
try {
xmldom = parseXml(" ");
} catch (ex){
alert(ex.message);
}
//进一步处理
*序列化 XML
function serializeXml(xmldom){
if (typeof XMLSerializer != "undefined"){
return (new XMLSerializer()).serializeToString(xmldom);
} else if (typeof xmldom.xml != "undefined"){
return xmldom.xml;
} else {
throw new Error("Could not serialize XML DOM.");
}
}
var xml = serializeXml(xmldom);
2. 浏览器对 XPath 的支持
XPath 是设计用来在 DOM 文档中查找节点的一种手段。
(1) DOM3 级XPath
- 确定某浏览器是否支持 DOM3级 XPath:
var supportsXPath = document.implementation.hasFeature("XPath", "3.0");
*XPathEvaluator
用于在特定的上下文中对 XPath 表达式求值。这个类型有下列 3 个方法。
- createExpression(expression, nsresolver):将 XPath 表达式及相应的命名空间信息转 换成一个 XPathExpression,这是查询的编译版。在多次使用同一个查询时很有用。
- createNSResolver(node):根据 node 的命名空间信息创建一个新的 XPathNSResolver 对 象。在基于使用命名空间的 XML 文档求值时,需要使用 XPathNSResolver 对象。
- evaluate(expression, context, nsresolver, type, result):在给定的上下文中, 基于特定的命名空间信息来对 XPath 表达式求值。剩下的参数指定如何返回结果。
-
evaluate()
这个方法接收5 个参数
:XPath 表达式、上下文节点、命名空间求解器、返回结果的类型和保存结果的 XPathResult 对象(通常是 null,因为结果 也会以函数值的形式返回)。
(1) 第三个参数
(命名空间求解器)只在 XML 代码中使用了 XML 命名空间时有必要指定;如果 XML 代码中没有使用命名空间,则这个参数应该指定为 null。
(2) 第四个参数
(返回结果的类型)的取值范围是下列常量之一。
- XPathResult.ANY_TYPE:返回与 XPath 表达式匹配的数据类型。
- XPathResult.NUMBER_TYPE:返回数值。
- XPathResult.STRING_TYPE:返回字符串值。
- XPathResult.BOOLEAN_TYPE:返回布尔值。
- XPathResult.UNORDERED_NODE_ITERATOR_TYPE:返回匹配的节点集合,但集合中节点的次 序不一定与它们在文档中的次序一致。
- XPathResult.ORDERED_NODE_ITERATOR_TYPE:返回匹配的节点集合,集合中节点的次序与 它们在文档中的次序一致。这是最常用的结果类型。
- XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE:返回节点集合的快照,由于是在文档外部 捕获节点,因此对文档的后续操作不会影响到这个节点集合。集合中节点的次序不一定与它们 在文档中的次序一致。
- XPathResult.ORDERED_NODE_SNAPSHOT_TYPE:返回节点集合的快照,由于是在文档外部捕 获节点,因此对文档的后续操作不会影响到这个节点集合。集合中节点的次序与它们在文档中 的次序一致。
- XPathResult.ANY_UNORDERED_NODE_TYPE:返回匹配的节点集合,但集合中节点的次序不 一定与它们在文档中的次序一致。
- XPathResult.FIRST_ORDERED_NODE_TYPE:返回只包含一个节点的节点集合,包含的这个 节点就是文档中第一个匹配的节点。
在 Firefox、Safari、Chrome 和 Opera 中,
Document 类型通常都是与 XPathEvaluator 接口一起实现的
。换句话说,在这些浏览器中,既可以创建 XPathEvaluator 的新实例,也可以使用 Document 实例中的方法(XML 或 HTML 文档均是如此)。
var result = xmldom.evaluate("employee/name", xmldom.documentElement, null,XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
if (result !== null) {
var node = result.iterateNext();
while(node) {
alert(node.tagName);
node = node.iterateNext();
}
}
如果指定的是
快照结果类型
(不管是次序一致还是次序不一致的),就必须使用snapshotItem() 方法和 snapshotLength 属性
。
var result = xmldom.evaluate("employee/name", xmldom.documentElement, null,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
if (result !== null) {
for (var i=0, len=result.snapshotLength; i < len; i++) {
alert(result.snapshotItem(i).tagName);
}
}
*单节点结果
指定常量XPathResult.FIRST_ORDERED_NODE_TYPE会返回第一个匹配的节点,可以通过结果的
singleNodeValue 属性
来访问该节点。
var result = xmldom.evaluate("employee/name", xmldom.documentElement, null,XPathResult.FIRST_ORDERED_NODE_TYPE, null);
if (result !== null) {
alert(result.singleNodeValue.tagName);
}
*简单类型结果
XPathResult
的布尔值、数值和 字符串类型。这几个结果类型分别会通过 booleanValue、numberValue 和 stringValue 属性
返回一个值。
- 对于
布尔值类型
,如果至少有一个节点与 XPath 表达式匹配,则求值结果返回 true,否则 返回 false。
var result = xmldom.evaluate("employee/name", xmldom.documentElement, null,XPathResult.BOOLEAN_TYPE, null);
alert(result.booleanValue);
数值类型
,必须在 XPath 表达式参数的位置上指定一个能够返回数值的 XPath 函数,如果使用这个方法的时候没有指定 的 XPath 函数,那么 numberValue 的值将等于 NaN。
var result = xmldom.evaluate("count(employee/name)", xmldom.documentElement,null, XPathResult.NUMBER_TYPE, null);
alert(result.numberValue);
- 对于字符串类型,evaluate()方法会查找与 XPath 表达式匹配的第一个节点,然后返回其第一个 子节点的值(实际上是假设第一个子节点为文本节点)。如果没有匹配的节点,结果就是一个空字符串。
var result = xmldom.evaluate("employee/name", xmldom.documentElement, null, XPathResult.STRING_TYPE, null);
alert(result.stringValue);
*默认类型结果
使用
XPathResult.ANY_TYPE 常量
可以自动确定返回结果的类型。要确定返回的是什么结 果类型,可以检测结果的resultType 属性
。
var result = xmldom.evaluate("employee/name", xmldom.documentElement, null,
XPathResult.ANY_TYPE, null);
if (result !== null) {
switch(result.resultType) {
case XPathResult.STRING_TYPE:
//处理字符串类型
break;
case XPathResult.NUMBER_TYPE:
//处理数值类型
break;
case XPathResult.BOOLEAN_TYPE:
//处理布尔值类型
break;
case XPathResult.UNORDERED_NODE_ITERATOR_TYPE:
//处理次序不一致的节点迭代器类型
break;
default:
//处理其他可能的结果类型
}
}
*命名空间支持
- 对于利用了命名空间的 XML 文档,XPathEvaluator 必须知道命名空间信息,然后才能正确地进行求值。
Professional JavaScript for Web Developers Nicholas C. Zakas
Professional Ajax
Nicholas C. Zakas
Jeremy McPeak
Joe Fawcett
在这个 XML 文档中,所有元素定义都来自 http://www.wrox.com/命名空间,以前缀 wrox 标识。 如果要对这个文档使用 XPath,就需要定义要使用的命名空间;否则求值将会失败。
处理命名空间的方法:
- 通过
createNSResolver()
来创建 XPathNSResolver 对象。这个 方法接受一个参数,即文档中包含命名空间定义的节点。
var nsresolver = xmldom.createNSResolver(xmldom.documentElement);
var result = xmldom.evaluate("wrox:book/wrox:author",
xmldom.documentElement, nsresolver,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
alert(result.snapshotLength);
- 定义一个函数,让它接收一个命名空间前缀,返回关联的 URI。
var nsresolver = function(prefix){
switch(prefix){
case "wrox": return "http://www.wrox.com/";
//其他前缀
}
};
var result = xmldom.evaluate("count(wrox:book/wrox:author)", xmldom.documentElement, nsresolver, XPathResult.NUMBER_TYPE, null);
alert(result.numberValue);
(2) IE中的XPath
-
selectSingleNode()方法
接受一个 XPath 模式,在找到匹配节点时返回第一个匹配的节点,如果没有 找到匹配的节点就返回 null。
var element = xmldom.documentElement.selectSingleNode("employee/name");
if (element !== null){
alert(element.xml);
}
-
selectNodes()
也接收一个 XPath 模式作为参数,但它返回与模式匹配的所有节点的 NodeList(如果没有匹配的节点,则返回一个包含零项的 NodeList)。
var elements = xmldom.documentElement.selectNodes("employee/name");
alert(elements.length);
-
IE 对命名空间的支持
setProperty()
,这个方法接 收两个参数:要设置的属性名和属性值
xmldom.setProperty("SelectionNamespaces", "xmlns:wrox=’http://www.wrox.com/’");
var result = xmldom.documentElement.selectNodes("wrox:book/wrox:author");
alert(result.length);
(3) 跨浏览器使用XPath
- 重新创建
selectSingleNode()
,它接收三个参数:上下文节点、XPath 表达式和可选的命名空间对象。
命名空间对象应该是下面这种字面量的形式:
{
prefix1: "uri1",
prefix2: "uri2",
prefix3: "uri3"
}
function selectSingleNode(context, expression, namespaces){
var doc = (context.nodeType != 9 ? context.ownerDocument : context);
if (typeof doc.evaluate != "undefined"){
var nsresolver = null;
if (namespaces instanceof Object){
nsresolver = function(prefix){
return namespaces[prefix];
};
}
var result = doc.evaluate(expression, context, nsresolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
return (result !== null ? result.singleNodeValue : null);
} else if (typeof context.selectSingleNode != "undefined"){
//创建命名空间字符串
if (namespaces instanceof Object){
var ns = "";
for (var prefix in namespaces){
if (namespaces.hasOwnProperty(prefix)){
ns += "xmlns:" + prefix + "='" + namespaces[prefix] + "' ";
}
}
doc.setProperty("SelectionNamespaces", ns);
}
return context.selectSingleNode(expression);
} else {
throw new Error("No XPath engine found.");
}
}
var result = selectSingleNode(xmldom.documentElement, "wrox:book/wrox:author", { wrox: "http://www.wrox.com/" });
alert(serializeXml(result));
- 重新创建
selectNodes()函数
。这个函数接收与 selectSingle- Node()相同的三个参数。
function selectSingleNode(context, expression, namespaces){
var doc = (context.nodeType != 9 ? context.ownerDocument : context);
if (typeof doc.evaluate != "undefined"){
var nsresolver = null;
if (namespaces instanceof Object){
nsresolver = function(prefix){
return namespaces[prefix];
};
}
var result = doc.evaluate(expression, context, nsresolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
var nodes = new Array();
if (result !== null){
for (var i=0, len=result.snapshotLength; i < len; i++) {
nodes.push(result.snapshotItem(i));
}
}
return nodes;
} else if (typeof context.selectSingleNode != "undefined"){
//创建命名空间字符串
if (namespaces instanceof Object){
var ns = "";
for (var prefix in namespaces){
if (namespaces.hasOwnProperty(prefix)){
ns += "xmlns:" + prefix + "='" + namespaces[prefix] + "' ";
}
}
doc.setProperty("SelectionNamespaces", ns);
}
var result = context.selectNodes(expression);
var nodes = new Array();
for (var i=0,len=result.length; i < len; i++){
nodes.push(result[i]);
}
return nodes;
} else {
throw new Error("No XPath engine found.");
}
}
var result = selectNodes(xmldom.documentElement, "wrox:book/wrox:author", { wrox: "http://www.wrox.com/" });
alert(result.length);
3. 浏览器对 XSLT 的支持
(1) IE中的XSLT
*简单的 XSLT 转换
使用 XSLT 样式表转换 XML 文档
,将它们分别加到一个 DOM 文档中,然后再使用transformNode()方法
。这个方法存在于文档的所有节点中
,它接受一个参数,即包含 XSLT 样
式表的文档。调用 transformNode()方法会返回一个包含转换信息的字符串。
XSLT 转换可以在文档的任何级别上进行。
//加载 XML 和 XSLT(仅限于 IE)
xmldom.load("employees.xml");
xsltdom.load("employees.xslt");
//转换
var result = xmldom.transformNode(xsltdom);
result = xmldom.documentElement.childNodes[1].transformNode(xsltdom);
result = xmldom.getElementsByTagName("name")[0].transformNode(xsltdom);
result = xmldom.documentElement.firstChild.lastChild.transformNode(xsltdom);
*复杂的 XSLT 转换
使用 XSL 模板
和 XSL 处理器
- 第一步是要把
XSLT 样式表
加载到一个线程安全的 XML 文档中。而这可以通过使用 ActiveX 对象 MSXML2.FreeThreadedDOMDocument 来做到。
function createThreadSafeDocument(){
if (typeof arguments.callee.activeXString != "string"){
var versions = ["MSXML2.FreeThreadedDOMDocument.6.0","MSXML2.FreeThreadedDOMDocument.3.0","MSXML2.FreeThreadedDOMDocument"],i, len;
for (i=0,len=versions.length; i < len; i++){
try {
new ActiveXObject(versions[i]);
arguments.callee.activeXString = versions[i];
break;
} catch (ex){
//跳过
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
}
xsltdom.createThreadSafeDocument()
xsltdom.async = false;
xsltdom.load("employees.xslt");
- 在创建并加载了自由线程的 DOM 文档之后,必须将它指定给一个 XSL 模板,这也是一个 ActiveX 对象。而这个模板是用来创建 XSL 处理器对象的。
function createXSLTemplate(){
if (typeof arguments.callee.activeXString != "string"){
var versions = ["MSXML2.XSLTemplate.6.0",
"MSXML2.XSLTemplate.3.0",
"MSXML2.XSLTemplate"],
i, len;
for (i=0,len=versions.length; i < len; i++){
try {
new ActiveXObject(versions[i]);
arguments.callee.activeXString = versions[i];
break;
} catch (ex){
//跳过
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
}
- 在创建了 XSL 处理器之后,必须将要转换的节点指定给 input 属性。这个值可以是一个文档,也 可以是文档中的任何节点。然后,调用 transform()方法即可执行转换并将结果作为字符串保存在 output 属性中。
var template = createXSLTemplate();
template.stylesheet = xsltdom;
var processor = template.createProcessor();
processor.input = xmldom;
processor.transform();
var result = processor.output;
(2) XSLTProcessor类型
通过 XSLTProcessor 类型
使用 XSLT 转换 XML 文档。
- 第一步也是加载两个 DOM 文档,一个基于 XML,另一个基于 XSLT。
- 然后, 创建一个新 XSLTProcessor 对象,并使用
importStylesheet()方法
为其指定一个 XSLT
var processor = new XSLTProcessor()
processor.importStylesheet(xsltdom);
- 最后一步就是执行转换。
(1) 调用transformToDocument()
返回一个完整的 DOM 文档。
(2)调用 transformToFragment()则可以得到一个文档片段对象。
- 在使用
transformToDocument()
时,只要传入 XML DOM,就可以将结果作为一个完全不同的 DOM 文档来使用。
var result = processor.transformToDocument(xmldom);
alert(serializeXml(result));
-
transformToFragment()
方法接收两个参数:要转换的 XML DOM 和应该拥有结果片段的文 档。
var fragment = processor.transformToDocument(xmldom, document); var div = document.getElementById("divResult"); div.appendChild(fragment);