IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  XML | Web development  >

在 Ajax 中进行 XML 处理,第 1 部分: 四种方法

在 Ajax 程序中解析和转换 XML

developerWorks
文档选项

未显示需要 JavaScript 的文档选项

样例代码

英文原文

英文原文


级别: 中级

Mark Pruett (mark.l.pruett@dom.com), 系统构架师, Dominion

2008 年 3 月 24 日

任何编程问题都可以通过多种正确的方法解决。本系列共考察了四种创建一个 Asynchronous JavaScript + XML (Ajax) 天气预报面板(weather badge)的方法,这是一种小型可重用部件,可以轻松嵌入到任何 Web 页面。本文是第一篇文章,主要介绍一些基础内容,同时审视第一种方法 —— 遍历 DOM 树。

希腊哲学家亚里士多德曾经写到:“通往失败的道路有许多条……,但通往成功的道路仅有一条。” 遗憾的是,亚里士多德并不是一名计算机程序员。

本系列的其他文章

虽然亚里士多德的第一个论断符合编程的特点 — “通往失败的道路有许多条” — 他的第二个推测却没有一点依据。

本系列文章针对同一问题运用了四种方法。每种方法都被证明是正确的 — 每种方法都有各自的优缺点。它们要解决的问题并不复杂,同样,解决方案也不复杂。尽管如此,这些方法涉及了大量 “权衡”,可以包含到非常简单的解决方案中。

问题:创建一个可重用的 Ajax 天气预报面板

常用缩写词
  • DOM:文档对象模型(Document Object Model)
  • HTML:超文本标记语言(Hypertext Markup Language)
  • HTTP:超文本传输协议(Hypertext Transfer Protocol)
  • RSS:富站点摘要(Rich Site Summary)
  • XML:可扩展标记语言(Extensible Markup Language)
  • XSLT:可扩展样式表语言变换(Extensible Stylesheet Language Transformation)

要定义需要解决的问题,我列出了以下的问题说明:

构建一个 Ajax 库读取从国家气象服务(National Weather Service)获得的最新观测数据,然后提取一部分数据并转换为 HTML 格式,从而创建一个天气预报面板。

许多 Web 站点喜欢在 Web 页面上显示当地的天气状况。为此,他们需要访问最新的气象信息。那么如何获得这些数据呢?

在美国,National Weather Service (NWS) 提供大量的天气信息。其中包括美国数百个城市的当前天气观测数据。该数据可使用 RSS 或 XML 格式。

在 Ajax 中,字母 X 代表 XML,所以 NWS 数据似乎非常适合 Ajax 方法。

什么是面板(badge)?
面板部件 是一个较小的、自包含的 JavaScript 代码块,可以包含到 Web 站点中用于显示特殊的(通常属于第三方)内容。面板通常在页面内的一小块区域显示内容。面板可以显示的内容包括新闻提要、日历、时钟或标记作者希望显示的任何其他内容。

四种可能的解决方案

本系列文章共考察了四种不同的方法,用于构建一个 Ajax 天气预报面板 — 一个小的信息框,包含 NWS 监测的任何城市或城镇的气象信息。其设计目标是

  • 简单性
  • 易于重用

我将利用这四种方法来展示各个方法内在的 “权衡” 问题。任何一种方法都不存在绝对的对与错。

本质上讲,每种方法的实现截然不同,如 表 1 所描述。


表 1. 天气预报面板库的四种版本
方法描述
1:遍历 DOM 树服务器上简单的 Web 代理从 NWS 服务器拉出数据并发送到浏览器。 在浏览器内,JavaScript 解释器从返回的 responseXML DOM 树提取部分数据,添加一些 HTML 格式,然后将其插入到页面中的 DIV 标记。
2:服务器上的 XSLT 一个服务器端脚本从 NWS 服务器拉出数据,使用 XSLT 将数据由 XML 转化成 HTML 格式,然后将 HTML 代码片段发回浏览器。 浏览器随后将代码片段插入到一个 DIV 标记。
3:客户端 XSLT 该方法使用一个简单的 Web 代理(同方法 1)将 XML 数据发送回浏览器。与方法 1 不同的是, 使用客户端 XSLT 将 XML 转换为 HTML,并将其插入到一个 DIV 标记。
4:JSON 和动态脚本标记一个外部服务(Yahoo! Pipes)将 NWS 数据从 XML 转换为 JavaScript Object Notation (JSON)。天气预报面板库利用 JSON 的特殊能力和 JavaScript 语言将转换后的数据拉回到浏览器 — 避免了对代理的需求。

共享元素

这四种构建可重用 Ajax 天气预报面板的方法均共享以下元素:

  • 管道(pipeline)方法
  • 一个简单的 Ajax 库
  • weather_badge() JavaScript 函数
  • National Weather Service 数据

管道方法

数据管道的概念至少可以回溯到 UNIX® 的早期。在这个模型中,数据进入管道,然后在其中经历一系列过滤器。每个过滤器以某种方法转换数据。经转换后的数据被送回到管道,也有可能进行进一步的转换,直至完成所有转换为止。管道的终点可能是一个用户终端、重定向到一个文件或另一个程序。

这种方法能够有效处理从 Web 上获得的基于 XML 的数据和服务。 程序能够从 Web 获取 XML 数据,发送至管道,并且将一系列转换链接以提取数据并重新格式化。

不同于 UNIX 命令行中的管道和过滤器,这种用于 Ajax 应用程序的方法要求管道横跨网络内多台计算机。 XML 数据可以源自一台 Web 服务器,然后被传送到另一个域内的不同服务器, 最终到达目的地:用户的 Web 浏览器。

一个简单的 Ajax 库

有关 Ajax 的介绍超出了本文的范围,但是可以获取优秀的初级读本(参见 参考资料)。

为使本系列尽可能涵盖更多的读者, 本文的示例使用了一个小型 Ajax 库,如 清单 1 所示。这个库围绕 XMLHttpRequest 对象只提供了最少的修饰 — 刚好消除各个主要浏览器中的 XMLHttpRequest 差异。


清单 1. ajax-simple.js — 示例使用的最小的 Ajax/XMLHttpRequest
                
function Ajax (url, parms, method, callback) {

    this.url = url;
    this.parms = parms;
    this.method = method;
    this.callback = callback;
    this.async = true;

    this.create ();

    this.req.onreadystatechange = this.dispatch (this);
}

Ajax.prototype.dispatch = function (ajax) {

    function funcRef()
    {
        if (ajax.req.readyState == 4) {
            if (ajax.callback) {
                ajax.callback (ajax.req);
            }
        }
    }

    return funcRef;
}

Ajax.prototype.request = function () {

    if (this.method == "POST") {
        this.req.open("POST", this.url, this.async);
        this.req.send (this.parms);
    }
    else if (this.method == "GET") {
        this.req.open("GET", this.url + this.parms, this.async);
        this.req.send (null);
    }

}

Ajax.prototype.setAsync = function (async) {

    this.async = async;
}

Ajax.prototype.create = function () {

    var xmlhttp;
    /*@cc_on
    @if (@_jscript_version >= 5)

    try {
        xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
    }
    catch (e) {
        try {
            xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
        }
        catch (E) {
            xmlhttp = false;
        }
    }

    @else

    xmlhttp = false;

    @end @*/

    if (!xmlhttp && typeof XMLHttpRequest != 'undefined') {
        try {
            xmlhttp = new XMLHttpRequest();
        } catch (e) {
            xmlhttp = false;
        }
    }

    this.req = xmlhttp;
}

weather_badge() JavaScript 函数

四种方法都具有一个在 Web 页面添加天气预报面板的接口。 该接口是一个单一的 JavaScript 函数:weather_badge()。该函数需要 2 个参数:一个用于识别相关城市和城镇的 NWS 站点 ID 和一个 HTML DIV 标记的元素 ID。 这个 DIV 标记是呈现天气预报面板的目标。图 1 是一个 Ajax 天气预报面板的示例。


图 1. 一个 Ajax 天气预报面板
一个 Ajax 天气预报面板

天气预报面板是使用 HTML 呈现的,但是你可以使用级联样式表(CSS)控制它的许多外观元素,包括字体、背景颜色和边框。

清单 2 演示如何在 Web 页面中嵌入天气预报面板。这里从一个 JavaScript onload 事件处理程序中调用 weather_badge() 函数。


清单 2. 在 Web 页面嵌入天气预报面板
                
<html>
  <head>
    <title>Apache Proxy Example</title>
    <link rel="stylesheet" type="text/css" href="weather.css" />
    <script language="Javascript" src="ajax-simple.js"></script>
    <script language="Javascript" src="weather_badge_apache_proxy.js">
    </script>
    <script>
      function init () {
        weather_badge ("KAKQ", "target1");
      }
    </script>
  </head>

  <body onload="init();">
    <h3>Apache Proxy Example</h3>

    <div class="wbadge" id="target1">
      Loading...
    </div>
  </body>
</html>

National Weather Service 数据

National Weather Service 站点使用一个站点 ID 用于识别读取天气数据的 城市、城镇或其他位置。站点 ID 是一个由四个字符组成的惟一编码。

所有 NWS 当前观测数据的基 URL 是:

http://www.nws.noaa.gov/data/current_obs/

基 URL 加上由四个字符组成的站点 ID,提供了一个气象数据 URL。 例如 Richmond, Virginia 的站点 ID 是 KRIC。Richmond 的天气数据的 URL 就是:

http://www.nws.noaa.gov/data/cuurent_obs/KRIC.xml

一个简单的 XML 格式定义了当前观测数据,如 清单 3 所示。


清单 3. Richmond, Virginia 的 National Weather Service XML 数据
                
<current_observation version="1.0" 
 xsi:noNamespaceSchemaLocation=
 "http://www.weather.gov/data/current_obs/current_observation.xsd">
  <credit>NOAA's National Weather Service</credit>
  <credit_URL>http://weather.gov/</credit_URL>
  <image>
    <url>http://weather.gov/images/xml_logo.gif</url>
    <title>NOAA's National Weather Service</title>
    <link>http://weather.gov</link>
  </image>
  <suggested_pickup>15 minutes after the hour</suggested_pickup>
  <suggested_pickup_period>60</suggested_pickup_period>
  <location>Richmond International Airport, VA</location>
  <station_id>KRIC</station_id>
  <latitude>37.51</latitude>
  <longitude>-77.31</longitude>
  <observation_time>
    Last Updated on Dec 11, 12:54 pm EST
  </observation_time>
  <observation_time_rfc822>
    Tue, 11 Dec 2007 12:54:00 -0500 EST
  </observation_time_rfc822>
  <weather>Overcast</weather>
                <temperature_string>54 F (12 C)</temperature_string>
  <temp_f>54</temp_f>
  <temp_c>12</temp_c>
  <relative_humidity>80</relative_humidity>
                <wind_string>From the South at 5 MPH</wind_string>
  <wind_dir>South</wind_dir>
  <wind_degrees>180</wind_degrees>
  <wind_mph>4.6</wind_mph>
  <wind_gust_mph>NA</wind_gust_mph>
  <pressure_string>30.31" (1026.7 mb)</pressure_string>
  <pressure_mb>1026.7</pressure_mb>
  <pressure_in>30.31</pressure_in>
  <dewpoint_string>48 F (9 C)</dewpoint_string>
  <dewpoint_f>48</dewpoint_f>
  <dewpoint_c>9</dewpoint_c>
  <heat_index_string>NA</heat_index_string>
  <heat_index_f>NA</heat_index_f>
  <heat_index_c>NA</heat_index_c>
  <windchill_string>53 F (12 C)</windchill_string>
  <windchill_f>53</windchill_f>
  <windchill_c>12</windchill_c>
  <visibility_mi>7.00</visibility_mi>
                <icon_url_base>
    http://weather.gov/weather/images/fcicons/
  </icon_url_base>
  <icon_url_name>ovc.jpg</icon_url_name>
  <two_day_history_url>
    http://www.weather.gov/data/obhistory/KRIC.html
  </two_day_history_url>
  <ob_url>http://www.nws.noaa.gov/data/METAR/KRIC.1.txt</ob_url>
  <disclaimer_url>http://weather.gov/disclaimer.html</disclaimer_url>
  <copyright_url>http://weather.gov/disclaimer.html</copyright_url>
  <privacy_policy_url>http://weather.gov/notice.html</privacy_policy_url>
</current_observation>

天气预报面板仅需要这些数据的一小部分。我将使用的值位于 locationweathericon_url_baseicon_url_nametemperature_stringwind_stringrelative_humidityvisibility_miobservation_time 元素内。

方法 1:遍历 DOM 树

使用这种方法必须首先解决一个对 XMLHttpRequest 对象(由 Ajax 程序使用)的基本限制:域相同的问题。

出于安全考虑,一个 XMLHttpRequest 调用只能够发起传递初始 Web 页面的服务器请求。 除非我为 National Weather Service 工作,否则我的服务器一定在他们的域之外(www.nws.noaa.gov)。图 2 展示了第一种方法的数据管道。


图 2. 方法 1 中用于天气预报面板的数据管道
方法 1 的数据管道

如果可以访问 Web 服务器的配置,有一个简单的解决方法:使用一个 Web 代理。

Web 代理 指将一个服务器请求重定向到另一台服务器。 我需要 Ajax 程序在我的服务器上请求一个资源,然后将该请求转换为在 NWS 上的请求。 这样就解决了 “相同域” 的问题:Ajax 程序与它所在的服务器通信,后者将这个请求秘密地重定向到 NWS 服务器。

在 Apache Web 服务器上,代理是通过一个 ProxyPass 规则实现的。该规则的语法非常简单:

ProxyPass our_directory their_url

第一个选项引用的是我的服务器上一个不存在的位置,而第二个选项是一个远程服务器上的 URL。当一个请求 进入 our_directory,请求被 Apache 重定向到 their_url。请求者(我的 Ajax 程序)永远不会察觉到这个重定向。

这就是用于实现 National Weather Service 数据访问的代理规则:

ProxyPass /nws_currobs/ http://www.nws.noaa.gov/data/current_obs/

若要得到关于 Richmond,Virginia 的数据,则请求这个 URL:

/nws_currobs/KRIC.xml

Apache 将请求转换成对 NWS 的请求:

http://www.nws.noaa.gov/data/current_obs/KRIC.xml

在浏览器中解析 XML

在 Ajax 应用程序中,如果一个请求 XML 数据的服务器请求是成功的,responseXML 属性将被初始化。 这个对象属性包含检索后的 XML,被解析为一个 XMLDocument 类型的 DOM 树(如果服务器数据不是有效的 XML,或者,在某些浏览器上, 如果返回的数据没有伴随一个 text/xml 或 application/xml HTTP Content-type 报头,则不会创建 responseXML 属性。在这些情况下,responseText 属性包含由服务器返回的未处理过的文本) 。

利用 responseXML,我可以遍历 DOM,从返回的 XML 中提取值。清单 4 显示对返回的 XML 进行删减后的版本:


清单 4. 从 NWS 服务器返回的经过删减的 XML

                    
<current_observation version="1.0" 
 xsi:noNamespaceSchemaLocation=
 "http://www.weather.gov/data/current_obs/current_observation.xsd">
  <location>Richmond International Airport, VA</location>
                    <observation_time>
    Last Updated on Dec 11, 12:54 pm EST
  </observation_time>
                    <weather>Overcast</weather>
                    <temperature_string>54 F (12 C)</temperature_string>
                    <relative_humidity>80</relative_humidity>
                    <wind_string>From the South at 5 MPH</wind_string>
                    <visibility_mi>7.00</visibility_mi>
                    <icon_url_base>
                    http://weather.gov/weather/images/fcicons/
                    </icon_url_base>
                    <icon_url_name>ovc.jpg</icon_url_name>
</current_observation>

现在我可以从 responseXML 提取 wind_stringresponseXML 属性是一个 XMLDocument 类型。XMLDocument 的 documentElement 属性返回我的 XML DOM 树的顶层元素。要在程序中进行检验,在代码中插入一个 alert() 函数:

alert ("tagName: " + req.responseXML.documentElement.tagName);

在执行时,alert() 弹出一个窗口,包含下面的内容:

tagName: current_observation

若访问 current_observation 下的各个元素,使用 getElementsByTagName()。这个 Element 方法接受一个标记名参数, 并返回一个包含所有子 Element 节点的数组,其名称与元素名相同。在一个 JavaScript 程序中,我可以编写以下代码

var elements = req.responseXML.documentElement.getElementsByTagName("wind_string");

NWS XML 数据仅包括一个 wind_string 元素,因此完全可以认为我需要的数据位于第一个元素中。wind_string 元素标记内的实际文本可以按以下方式访问:

elements[0].firstChild.data

从一个 XML 文档中提取单一值非常困难,尤其是当文档拥有一个简单的结构时。很明显,这种从 XML 提取数据的方法很快会变得难以处理。如果我把上述提到的所有步骤组合到一个 DOM 引用中,它看起来类似下面的内容:

req.responseXML.documentElement.getElementsByTagName("wind_string")[0].firstChild.data

对于这个应用程序,我可以定义一个 JavaScript helper 函数来提取值,使代码更具可读性,如 清单 5 所示。


清单 5. 简化 DOM 访问的函数
                
function get_element (doc_el, name, idx) {
    var element = doc_el.getElementsByTagName (name);
    return element[idx].firstChild.data;
}

有了该函数后,weather_badge() 函数变得更易于管理,如 清单 6 所示。


清单 6. weather_badge() 函数使用一个 Apache 代理检索 XML
                
function weather_badge (nws_id, div_name) {
    var ajax = new Ajax
        ("/nws_currobs/" + nws_id + ".xml",
         "",
         "GET",
         function (req) {
            var doc_el = req.responseXML.documentElement;

            // Extract values from XML structure returned by
            // by Ajax (XMLHttpRequest) call.

            var location = get_element (doc_el, "location", 0);
            var temperature_string = get_element (doc_el, "temperature_string", 0);
            var weather = get_element (doc_el, "weather", 0);
            var icon_url_base = get_element (doc_el, "icon_url_base", 0);
            var icon_url_name = get_element (doc_el, "icon_url_name", 0);
            var wind_string = get_element (doc_el, "wind_string", 0);
            var relative_humidity = get_element (doc_el, "relative_humidity", 0);
            var visibility_mi = get_element (doc_el, "visibility_mi", 0);
            var observation_time = get_element (doc_el, "observation_time", 0);

            var div = document.getElementById ("target1");
            div.innerHTML =
                "<center>\n"
                + "<b>" + location + "</b><br>\n"
                + weather + "<br>"
                + "<img border='0' src='"
                + icon_url_base + icon_url_name + "'/><br>\n"
                + temperature_string + "<br>\n"
                + "Wind: " + wind_string + "<br>\n"
                + "Humidity: " + relative_humidity + "<br>\n"
                + "Visibility: " + visibility_mi + "<br>\n"
                + "<br><span style='font-size: 0.8em; font-weight: bold;'>"
                + observation_time + "</span><br>\n"
                + "</center>\n";
          }
         );
    ajax.request ();
}

代码创建了一个 Ajax 对象(请回忆一下,这仅是一个围绕 XMLHttpRequest 对象的小型包装器)。Ajax 构造函数采用四个参数, 如 表 2 所描述。


表 2. Ajax 构造函数采用的四个参数
参数描述
url用于远程资源的 URL — 在本文示例中,National Weather Service XML 文件通过我的服务器进行代理。
parms一个包含任意 URL 参数的字符串。我请求的是一个静态的 XML 文档,而不是一个服务器端脚本,所以不需要任何参数。
method该参数通知 Ajax 生成一个 HTTP GET 请求。
callback这个参数定义在 XML 文档返回到浏览器时由 Ajax 调用的 callback 函数。在本文中,我 从 XML 提取值,然后使用一些 HTML 格式化标记将它们拼接到一起,创建了一个 HTML 代码片段。 这个代码片段定义了我的天气预报面板。

我使用 innerHTML 属性将 HTML 代码片段填入到 Web 页面中。目标 DIV 标记已经被作为 div_name 参数传入 weather_badge()。现在,可以非常轻松地插入我的 HTML 代码片段:

var div = document.getElementById (div_name);
div.innerHTML = html_snippet;

优点和缺点

解决编程问题的任何方法都需要进行权衡。表 3 列举了这种方法的优缺点。


表 3. 方法 1 的优点和缺点
优点缺点
适用于所有主要的浏览器。

不要求附加的库或第三方工具。
语法不规则使得难于访问 XML 元素,即使对简单的 XML 文档也是如此。

结束语

在本系列的 第 2 部分 中,我将介绍直接 DOM 访问的替代方法。我将使用 XSLT 将 XML 转换成 HTML。第二个和第三个实现都使用 XSLT,不同处在于发生转换的数据管道位置 — 浏览器或服务器。










回页首


下载

描述名字大小下载方法
本系列的样例代码x-xmlajax.zip194KBHTTP
关于下载方法的信息


参考资料

学习

获得产品和技术
  • IBM 试用版软件:使用可直接从 developerworks 下载的试用版软件构建您的下一个开发项目。


讨论


关于作者

Mark Pruett 是 Dominion 的一名系统架构师。他是 O'Reilly Media 出版的 Ajax Hacks 丛书的作者,并且是 O'Reilly Shortcuts Ajax 和 Web ServicesYahoo! Pipes 的作者。




对本文的评价










回页首


UNIX 是 The Open Group 在美国和其他国家的注册商标。 其他公司、产品、或服务名称可能是其他公司的商标或服务标记。 其他公司、产品或服务的名称可能是其他公司的商标或服务标志。

IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款