UML软件工程组织

 

 

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

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

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

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

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

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

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

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

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

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

四种可能的解决方案

本系列文章共考察了四种不同的方法,用于构建一个 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>

天气预报面板仅需要这些数据的一小部分。我将使用的值位于 location、weather、icon_url_base、icon_url_name、temperature_string、wind_string、relative_humidity、visibility_mi 和 observation_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_string。 responseXML 属性是一个 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.zip
194KB

参考资料

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

组织简介 | 联系我们 |   Copyright 2002 ®  UML软件工程组织 京ICP备10020922号

京公海网安备110108001071号