JavaScript 高级程序设计(第18章 JavaScript 与 XML)

第18章 JavaScript 与 XML

1. 浏览器对 XML DOM 的支持

(1) DOM2 级核心

* 支持 DOM2 级的浏览器中使用以下语法来创建一个空白的 XML 文档:

var xmldom = document.implementation.createDocument(namespaceUri, root, doctype);

创建一个新的、文档元素为的 XML 文档,可以使用如下代码:

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类型

  1. 在解析 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
  1. 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

  1. IE 是第一个原生支持 XML 的浏览器,而这一支持是通过 ActiveX 对象实现的。
  2. 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 文件
  1. IE 中的 XML 文档对象也可以加载来自服务器的文件。加载文档的方式也可以分为同步和异步两种。要指定加载文档的方式,可以设置async 属性,true 表示异步,false 表示同步(默认值为 true)。
  2. 在确定了加载 XML 文档的方式后,调用load()可以启动下载过程。这个方法接受一个参数,即要加载的 XML 文件的 URL。
  1. 同步方式下,调用 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 个方法。
  1. createExpression(expression, nsresolver):将 XPath 表达式及相应的命名空间信息转 换成一个 XPathExpression,这是查询的编译版。在多次使用同一个查询时很有用。
  2. createNSResolver(node):根据 node 的命名空间信息创建一个新的 XPathNSResolver 对 象。在基于使用命名空间的 XML 文档求值时,需要使用 XPathNSResolver 对象。
  3. evaluate(expression, context, nsresolver, type, result):在给定的上下文中, 基于特定的命名空间信息来对 XPath 表达式求值。剩下的参数指定如何返回结果。
  1. 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 属性返回一个值。

  1. 对于布尔值类型,如果至少有一个节点与 XPath 表达式匹配,则求值结果返回 true,否则 返回 false。
var result = xmldom.evaluate("employee/name", xmldom.documentElement, null,XPathResult.BOOLEAN_TYPE, null);
alert(result.booleanValue);
  1. 数值类型,必须在 XPath 表达式参数的位置上指定一个能够返回数值的 XPath 函数,如果使用这个方法的时候没有指定 的 XPath 函数,那么 numberValue 的值将等于 NaN。
var result = xmldom.evaluate("count(employee/name)", xmldom.documentElement,null, XPathResult.NUMBER_TYPE, null);
alert(result.numberValue);
  1. 对于字符串类型,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,就需要定义要使用的命名空间;否则求值将会失败。

处理命名空间的方法:

  1. 通过 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);
  1. 定义一个函数,让它接收一个命名空间前缀,返回关联的 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

  1. selectSingleNode()方法接受一个 XPath 模式,在找到匹配节点时返回第一个匹配的节点,如果没有 找到匹配的节点就返回 null。
var element = xmldom.documentElement.selectSingleNode("employee/name");
if (element !== null){
    alert(element.xml);
}
  1. selectNodes()也接收一个 XPath 模式作为参数,但它返回与模式匹配的所有节点的 NodeList(如果没有匹配的节点,则返回一个包含零项的 NodeList)。
var elements = xmldom.documentElement.selectNodes("employee/name");
alert(elements.length);
  1. 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

  1. 重新创建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));
  1. 重新创建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 处理器
  1. 第一步是要把 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");
  1. 在创建并加载了自由线程的 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);
}
  1. 在创建了 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 文档。

  1. 第一步也是加载两个 DOM 文档,一个基于 XML,另一个基于 XSLT。
  2. 然后, 创建一个新 XSLTProcessor 对象,并使用 importStylesheet()方法为其指定一个 XSLT
var processor = new XSLTProcessor()
processor.importStylesheet(xsltdom);
  1. 最后一步就是执行转换。
    (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);

你可能感兴趣的:(JavaScript 高级程序设计(第18章 JavaScript 与 XML))