内容


用 XQuery 制作指示板

通过一个基于 Web 的指示板展示业务数据

Comments

几年前,我为一个客户制作了大量与体育比赛有关的数据提要。我们需要寻求一种方法来体现此客户的数据服务产品的广度和深度,我们于是决定设计一个类似记分板似的东西放在客户网站上。我们当时的想法是通过显示关键的体育比赛数据,比如 “足球比赛得分”、“赛马” 或 “板球比赛的次数” — 所有实时记录的数据 — 来达到理想的效果并将此客户数据服务的质量有效地传达给潜在的顾客。

至于这个特定的记分板如何开发并非本文的目标:我之所以重新把它拿出来是因为在这个小部件部署后发生了一件特殊的事情。

这个记分板投入使用后,我的这个客户的高级管理层开始就所生成的数据提出问题。最初,这些问题围绕着数据合计的准确性,随后的问题愈发发人深省,比如 “我们应该显示更多的足球得分” 或 “为何赛事多的时候更新速度要比平常的时候慢”。我们所生成的各种数字最终成为了衡量此客户与体育比赛相关的数据提要的质量和总体状况的有效基准数据。

由于这个计分板应用程序差不多每秒要更新一次,这样就将此公司的运转情况及时地传递给了高级管理层以供其快速地发现任何不正常之处。当然,我们所做的没什么新鲜的 — 只不过是开发 KPI 指示板理念的再现 — 但是创建它时加入了一些市场意识。

指示板概览

在企业开发界,一段时期内,指示板曾经是人们大肆宣扬的一个主题,所以让我先来给出指示板的一个非正式的定义,不过只限在本文中使用:

指示板 指的是实现一个或多个目标所需的重要信息的可视展示,这些信息经过整理并被恰当地摆放在屏幕上以便于人们一眼就能理解这些信息。

数据的目标和类型可以分为如下三个类别:

  • 战略:战略目标常常是公司的主管和决策者感兴趣的,他们总是试图更好地了解公司生产第一线的情况以便对公司的整体状况有一个直观的把握。
  • 分析:指的是推断和辨别带有分析性质的趋势的目标。
  • 操作:在直接监视过程状态的时候会出现操作数据;这类数据的例子很多,比如 “Web 站点是否正在运行?” 或 “我们的联系人电子邮件表单是否工作?”。

除了上述这些类别,我所遇到或见过的指示板还常常具有一些共性,比如它们多久更新一次,它们展示的是定量数据、定性数据还是二者都展示 — 有一些颇值得我们进行细致研究。

好的 KPI 是什么样的?

指示板倾向于显示可度量的一些指标,在商界通常就是指所谓的关键绩效指标(KPI)。一个 KPI 指的是能表明公司业务处理状况的任何可测指标。因此,一个好的 KPI 应该能够很好地度量公司内的任何战略、分析或操作目标的成功或失败。

侧栏 “KPI 的例子” 给出了您的公司可以采用的几个 KPI 例子。实际上,这些 KPI 对很多公司都是适用的:我鼓励寻找(正如在之前的记分板示例中那样)能简便地展现业务处理状况特征的一个 KPI。KPI 越是接近您公司的具体业务,指标就越合适。

请务必注意 KPI 的定义有可能会不断变化,因为随着时间和经验的积累您会掌握更合适的定义它们的方法,以及用它们所想要回答的问题。毕竟,使用 KPI 的最终目的还是提出并解答有关您业务情况的这些问题。

除此之外,无须害怕 KPI:它可以很高深,也可以很简单,这取决于您的选择,而且现在 Web 上有大量的参考资源可帮助您打造出自己的 KPI。我认为在创建 KPI 过程中最重要的一件事情就是确保有数据可用。

好的指示板是什么样的?

一个好的 Web 指示板总是力求以最简明的方式呈现这些 KPI,它最大化数据,并尽可能地减少与信息表达无关的图形 “噪音”。按照战略、操作和分析目标来组织这些显示元素是个很好的做法。在对想要进行对比的条目进行分组时,需要检查对比是否相关、有效并且这个对比提供了对业务的一些有用反馈,而不是解释过去发生的事情。虽然解释过去发生的某个事情可以算是一种服务,但是指示板的任务是给出事情现在和当前的状态。如果需要历史记录来更好地了解现状,可以让所构建的指示板或其数据能够定期地被保存。

若一个指示板在一开始就以最为简要的格式显示数据,而后又大肆显示更多的信息,它很容易就会变成一个浏览信息的工具。即便您的确提供了像数据向下钻取或导航 UI 之类的元素,还是需要将此指示板与特定的用户角色相匹配。最佳的做法是让一个指示板对应于一个特定的角色。

最后,一个好的 Web 指示板会避开过于精细的图片。典型的例子是使用类似三维 (3D) 饼图这样的图形,其实,额外的深度反倒会让可视地决定各个块在整个图形中所占的比例变得更为困难。更甚者,现在,已经有很多讨论(更多信息,请参见 参考资料)共同反对使用这种饼图。

如果与我上面刚刚给出的这些建议背道而驰,那么得到的就是一个糟糕的指示板。这里,我列出了在设计得很糟糕的指示板应用程序中常见的几个显著特征:

  • 过度使用颜色:基于颜色组织数据固然好,但是很多人还是有点走极端。
  • 要求用户滚动或使用选项卡。让用户遍阅整页的数据会让用户感到麻烦。
  • 过于详细。不对信息进行总结会让人无法综合数据以采取实质的行动。
  • 过于精确。比如,数字内小数点位数太多或图片上数据标签太多。
  • 关键的异常遭忽视。不突出异常(比如偏离了某个目标或没有达到某个目地)会消弱指示板在当前时间框架内调用动作的效用。
  • 部署的指标不全面或不完善。确保 KPI 得到恰当且准确地部署似乎很简单,但 KPI 问题与之前的那些坏的特征一样时有发生。在将其安置到指示板应用程序之前,必须手动生成此指标或使用一个电子表格来进行一些示例计算的测试。

数据墨水(data ink)的基本理念蕴含着描述指示板好坏的另一种方式。作为信息和数据设计的一个基本原则,数据墨水 实际上是被打印页面上代表数据(比如,数值或图片上的数据线)的任何内容所用到的所有的墨水。计算机屏幕也是同样的道理,其中数据像素类似于数据墨水,而其他的事情则不是。最大化信息设计内的数据像素意味着需要删除尽量多的无关的图形元素、使用低调的颜色模式并避免花哨的字体。

安装示例指示板

在我向您展示如何构建这个示例指示板前,可以从 下载 部分获得源文件并安装这个指示板。为此,解压缩 .zip 文件并遵循其中所包含的 README 文件的指导。

在安装完毕后,应该能够使用一个 Web 浏览器和如下的 URL 从 eXist 数据库访问 dashboard.xq 来查看这个示例指示板:

http://localhost:8080/exist/rest/db/dashboards_with_xquery/xquery/dashboard.xq

如果 XQuery 文件安装在 eXist 的其他地方,可以相应调整这个 URL。

同样应该注意这个示例指示板使用了很多测试数据和在线 Web 服务的凭证,所有这些均存在于 Internet 上。这些资源应该还可用,但是如果您遇到任何问题,我建议您用您自己的凭证和测试数据代替示例数据。

构建一个公司指示板

了解了指示板的原理,也知道了其主要目标就是以一种动人的方式展示 KPI 来帮助您提出并解答重要的业务问题,现在就可以开始创建这个示例指示板了。使用像 eXist XML 数据库和 XQuery 这类 XML 技术就可以聚集和使用 XML 数据,然后再创建指示板的一个 HTML 表示。

图 1 显示了这个示例指示板的最终外观。

图 1. 示例指示板
示例指示板
示例指示板

这个 Web 页面少有新奇的设计元素来吸引眼球,这是我遵循前面所提到的有关最大化数据像素建议的结果。这个 CSS 文件定义了一个颜色一般的背景色和一种很便于阅读的字体。

我选择了几个现成的 KPI 来展示 XQuery 能够多么好地集成数据:

  • Aging Invoices:当一个公司收取其服务的费用时,费用通常是依照一套标准条款(例如,客户付款的天数)收取的。
  • Sales Pipeline:一个公司常常在一系列阶段之间对销售机会进行分类,这定义了能够最终成交并为此开帐单的可能性有多大。
  • Weekly Timesheets:显示员工的周工作时间表,并能够标示出每周工作时间超过 40 小时的员工。
  • Website Operation:这个监视指标表明 Web 站点是否在运转。
  • Reminders:这是公司的工作任务的日程表。
  • Competitor Websites:这个图表显示了与竞争对手相比 Web 站点的性能如何。

指示板不太像是一个十分成熟的应用程序,它充分利用了 XQuery 的 eXist XML 数据库实现中的特定功能。我们将要创建的这个指示板仅使用三个 XQuery 文件就可以实现,其中一个是供其他两个文件使用的 XQuery 模块,参见 图 2

图 2. 指示板 XQuery 组件
指示板 XQuery 组件
指示板 XQuery 组件

utility.xqm XQuery 模块包含 data.xq 与 dashboard.xq 所使用的一些函数。data.xq XQuery 文件的作用是生成一个包含所有与 KPI 相关的数据的 XML 文档。dashboard.xq 则用来显示数据,创建一个可以用浏览器查看的 HTML 文件。

首先是数据源

当我开发软件时,首先要做的就是充实应用程序的数据层:如果一个有用的应用程序 依靠数据,未免太不正常。在创建一个用来展示数据的指示板时,首先要做的自然就是确定数据源。

指示板的目标就是显示 KPI,所以需要集成数据源,这些数据源可以是 KPI 本身,或者是与其计算相关的其他数据。不管 KPI 本质上是定性的还是定量的,所有的 KPI 都需要能够展现出一些有关业务运转的有用信息。以这个指示板示例为例,我使用了公开可用的数据来说明 XQuery 是如何处理集成场景的。此外,我还选用了与在内部、外部及业务周边生成的 XML 数据有关的一系列流行的 Web 服务。

图 3 中的图表显示了一组数据源,它们可以从 Google™ 之类流行的 Web 服务得到,也可以通过诸如关系数据库这样的常见技术获得。

图 3. 可用数据源的图表
可用数据源的图表
可用数据源的图表

每种数据源都需要与一个 Web 服务、XML 数据或 XQuery 生成的数据相集成:

  • Website Operation:显示 XQuery 本身就是一个易于扩展的语言
  • Aging Invoices:说明了如何从 XQuery 访问 MySQL
  • Sales Pipeline:显示了如何从 XQuery 访问 Google Docs 电子数据表
  • Weekly Timesheets:显示如何访问一个本地的 Microsoft® Office Excel® 电子数据表
  • Reminders:与流行的 Backpack Web 服务集成
  • Competitor Web sites:一个图表,它显示了与竞争对手相比 Web 站点的性能如何

data.xq 的处理结果应是包含了所有所需数据的一个 XML 文档。在 XQuery 内进行数据集成的最简单的一个方法就是使用 doc() 函数,它可以从 Web URL 指向的站点检索 XML。如果这个 URL 指向的是一个返回的数据结构不合法(从 XML 的角度来说)的资源,这时候再使用此函数就会得到一个错误。

清单 1 显示了 data.xq 中的处理过程。

清单 1. data.xq 中的处理
<sales_pipeline desc="example accessing published Google spreadsheet">
{doc("http://spreadsheets.google.com/feeds/list/
pZDPqHJcLzxKntsQv2tuIMQ/1/public/basic")}
</sales_pipeline>

...

<backpack desc="example accessing simple web service">
{doc("http://dashboardwithxquery.backpackit.com/
69babcbce8aa212fc83464088b45f7a97a9f3dce/reminders.xml")}
</backpack>
...

<timesheet desc="example accessing local MS Excel file">
doc("file:///Users/jimfuller/Source/Writing/1_articles/1_ibm/
3_dashboards_with_xquery/working/src/data/MSOFFICE-timesheet.xls")
</timesheet>

sales_pipeline 元素访问我发布的并可通过 URL 访问的一个 Google 文档。用这种方式,Google 文档就可以作为 Atom XML 提要(相关链接,参见 参考资料)公开。Backpack URL 返回一个 XML 提要,它代表的是我在 URL http://www.backpackit.com 处设置的提醒(reminder)。timesheet 元素则不同,它负责访问一个本地的 Excel 文件。几年前,使用以特定于 Microsoft 的 XML 格式生成的 Excel 电子数据表也是可以的。

数据集成的下一个步骤就是使用 XQuery 创建一套帮助函数,它们均位于 utility.xqm XQuery 模块中。以下是对这些函数的一个简短介绍,并突出显示了相关代码:

  • utility:check_site():这一小段 XQuery 代码用来检查一个 Web URL 是否可被访问以及 Web 服务器做出响应要多长时间。清单 2 给出了这个函数。
    清单 2. utility:check_site() 函数
    declare function utility:check_site($uri) { 
    let $start := util:system-time() 
    let $response := httpclient:get(xs:anyURI($uri),false(),()) 
    let $end := util:system-time() 
    let $response-time := (($end - $start) div xs:dayTimeDuration('PT1S')) * 1000 
    let $status-code := string($response/@statusCode) 
    return <test xmlns=""
            ts="{current-dateTime()}"
            status="{$status-code}"
            response="{$response-time}" /> 
    };

    此函数返回的是一个测试元素,dashboard.xq 使用这个测试元素来显示 Web 站点的状态。

  • utility:get-aged-invoices():由于被编码和存储在关系数据库中的数据的数量巨大,我们不妨来看一个集成 eXist XML 数据库以便与 MySQL ® 社区服务器交互的例子。XQuery 没有内置的关系数据库管理系统 (RDBMS) 的功能,但很多常见的 XQuery 处理器均有一些扩展函数。eXist XML 数据库有 Structured Query Language (SQL) 扩展函数,这些函数以可选模块的形式存在,所以必须通过 uncomment eXist conf.xml 文件内的恰当部分来启用此模块(参阅示例代码内所包含的 README 文件)。清单 3 给出了这个 utility:get-aged-invoices() 函数。
    清单 3. utility:get-aged-invoices() 函数
    declare function utility:get-aged-invoices(){ 
    let $connection := sql:get-connection("com.mysql.jdbc.Driver",
    "jdbc:mysql://localhost/test", "root",
    "") 
    let $data := sql:execute($connection, "select * from invoices;", fn:true()) 
    return <invoices>
    <total>{sum($data/sql:row/sql:invoice_amount)}</total>
    <age	amt="15">
        {sum($data/sql:row/sql:invoice_amount[../sql:invoice_terms='15'])}</age>
    <age
    amt="30">
        {sum($data/sql:row/sql:invoice_amount[../sql:invoice_terms='30'])}</age>
    <age
    amt="60">
        {sum($data/sql:row/sql:invoice_amount[../sql:invoice_terms='60'])}</age>
    </invoices> 
    };

    此函数先是使用 eXist SQL 扩展模块的 sql:get-connection() 函数建立一个 Java™ Database Connectivity (JDBC)-风格的连接。然后,定义一个 SQL 语句来从这个数据库选择数据,此数据库用 sql:execute 执行。此函数的结果保存在 $data 变量内,之后再使用它来通过 XPath 挑选相关数据。

如下所列的是一系列与各种 Google 的 Web 服务相集成的函数(更多信息,请参见 参考资料)。Google 的很多 Web 服务使用类似的技术来公开和连接它们,所以我特意给出了其中的几个例子。(这些例子并没有用在我们的这个示例指示板中,但它们可能对您很有启发)。

  • utility:get-google-token($Email,$Passwd,$accountType,$source,$service):根据想要集成的深度,Google Web 服务提供了几个不同的身份验证机制。我选择了它的 ClientToken 版本(如 清单 4 所示)来创建一次性的身份验证 token,这适合于本例的目的。
    清单 4. Google utility:get-google-token($Email,$Passwd,$accountType,$source,$service) 服务
    declare function 
            utility:get-google-token($Email,$Passwd,$accountType,$source,$service){
    let $params := concat("Email=",$Email,
                          "&amp;Passwd=",$Passwd,
                              "&amp;source=",$source,
                              "&amp;accountType=",$accountType,
                              "&amp;service=",$service)
    let $uri := concat("https://www.google.com/accounts/ClientLogin?",$params) 
    let $response := httpclient:get(xs:anyURI($uri),false(),()) 
    let $token := substring-after(xmldb:decode($response),"Auth=") 
    return
            string($token) 
    };

    请注意此函数返回的是一个字符串,而非 XML。在 XQuery,可以限制函数的输入和输出并给它们赋予一个数据类型。

  • utility:get-google-spreadsheet-feed($Email,$Passwd):此函数(如 清单 5 所示)从您自己(或他人)的 Google 文档作为一个 Atom 提要返回 Google 文档的一个列表。由于我使用的是一个公开可用的 Google 电子数据表,我猜想有些开发人员会非常想知道如何通过 XQuery 连接和使用 Google 文档。
    清单 5. utility:get-google-spreadsheet-feed($Email,$Passwd) 函数
    declare function utility:get-google-spreadsheet-feed($Email,$Passwd){ 
    let $accountType :=  "HOSTED_OR_GOOGLE" 
    let $source := "Dashboards-XQUERY-Example" 
    let $service := "wise" 
    let $token := utility:get-google-token($Email,$Passwd,$accountType,$source,$service) 
    let $headers := <headers> <header name="Authorization"
    value="GoogleLogin auth={$token}"/>
    </headers> 
    let $uri := 
            xs:anyURI('http://spreadsheets.google.com/feeds/spreadsheets/private/full')
    return 
            httpclient:get($uri, false(), $headers) 
    };

    此函数制作了一个恰当的 HTTP 请求来检索 Google 文档内的文档列表,这之后可被用来打开这些文档。

  • utility:get-google-atom-feed():另一个有趣的数据源是 Google 的电子邮件地址。数据在 Gmail 内被作为 Atom 数据提要公开,如 清单 6 所示。(这个示例指示板并没有真正地使用此数据源,但是在这里将其作为处理电子邮件的数据源的例子给出,还是很有用的)。
    清单 6. utility:get-google-atom-feed() 函数
    declare function utility:get-google-atom-feed($user,$pass,$label){ 
    let $token := util:string-to-binary(concat($user,':',$pass)) 
    let $headers := <headers> 
    <header name="Authorization" value="Basic {$token}"/> 
    </headers> 
    let $uri := xs:anyURI(concat('https://mail.google.com/mail/feed/atom/',$label))
    return 
    httpclient:get($uri, false(), $headers) 
    };
  • utility:get-google-cal-feed($Email,$Passwd):另一个十分流行的 Google 服务是 Google Calendar(没有用在 data.xq 内),用来显示日历事件数据。清单 7 给出了这个对应的函数。
    清单 7. utility:get-google-cal-feed($Email,$Passwd) 函数
    declare function utility:get-google-cal-feed($Email,$Passwd){ 
    let $accountType := "HOSTED_OR_GOOGLE" 
    let $source := "Dashboards-XQUERY-Example" 
    let $service := "cl" 
    let $token := utility:get-google-token($Email,$Passwd,$accountType,$source,$service)
    let $headers := <headers> 
    <header
            name="GData-Version" 
            value="2"/>
    <header name="Authorization" 
            value="GoogleLogin
            auth={$token}"/> 
    </headers> 
    let $uri := xs:anyURI(
            concat('http://www.google.com/calendar/feeds/',
                            string($Email),
                            '/private/full')
            )
    return 
    httpclient:get($uri, false(), $headers) 
    };

这些函数被 data.xq 用来创建一个大型的 XML 文档,该文档就是这个数字指示板的源文档。如果您已经安装了代码,我建议您通过浏览器访问 data.xq,您应该能够看到类似 图 4 的结果。

图 4. 检索 data.xq
检索 data.xq
检索 data.xq

这个 markup 的结构是任意的,您可以自由选择其他的方式。

指示板的表示

dashboard.xq XQuery 文件所生成的结果 — 一个 HTML 文档 — 基于特定于 eXist XML 数据库的序列化选项来表示这个指示板:

declare option exist:serialize 
        "method=xhtml media-type=text/html indent=yes omit-xml-declaration=no";

要创建这个示例指示板,用聚合后的这个 XML 文档和 utility.xqm 内定义的这些 XQuery 函数构造这个 Web 页面的各个特定区域,如 图 5 所示。

图 5. 将各函数对应到这个指示板
将各函数对应到这个指示板
将各函数对应到这个指示板

KPI 是混合使用条形图和 HTML 来显示的,以给出特定指标的状态。在使用条形图的区域,我选择了部署这个 Google Charting API。这个 API 创建了在 sales pipeline、aging invoices 和 time sheets 区域内用到的那些图表:

  • Aging Invoices:通过使用代表标准付款期限(15、30 和 60 天)的三个独立的条形图显示未付订单的状态。
  • Sales Pipeline:使用六个条形图来表明在销售流程的每个状态各存在多少个潜在客户。条形图的范围代表的是此渠道的各个阶段的预期目标。
  • Timesheets:针对每个员工生成一个条形图,如果员工每周工作超过 40 小时,条形就变成红色。

为了便于创建这些图表,我创建了一个 XQuery 函数 utility:graph(),此函数在 utility.xqm 内定义(参见 清单 8)。

清单 8. utility:graph() 函数
(: Wrap up Google Charting API :) 
declare function utility:graph($type,$colors,$size,$markers,$data,$alt){ 
let $src 
        :=concat('http://chart.apis.google.com/chart?chf=bg,s,F7F5E6&amp;chco=',$colors,
'&amp;chs=',$size,
'&amp;cht=',$type,
'&amp;chl=',$markers,
'&amp;chd=t:',$data
)
return <img alt="{$alt}"src="{$src}"/> 
};

utility:graph() 函数构造这个 URL,返回结果为 img 元素。有关 Google Charting API 选项的更多信息,请参见 参考资料

这个示例指示板需要数据才能完成特定的功能,所以接受了使用 doc() 函数由 data.xq 生成的聚合数据,并将结果保存在 $data 变量内:

let $data := 
doc('http://localhost:8080/exist/rest/db/dashboards_with_xquery/xquery/data.xq')

将这个 XML 文档放入 $data 变量意味着这些指标全部是同时测量的。这虽然不是很必要,但是它意味着所有的指标都是同步的 — 如希望定期保存结果和查看历史性能的话,这一点十分有用。

随后的清单描述了每个显示区域并解释了 XQuery 的主要工作原理。我用粗体 突出显示了 XQuery 代码以便让其能够从 HTML 标记中明显地区分出来。

Aging Invoices

清单 9 中的 XQuery 代码显示了三个条形图,但它们必须要统一以便于比较。由于所使用的范围是 10,000,这意味着除数值也是这个值,然后再乘以 100 获得在条形图中所用的百分比。

清单 9. Aging Invoices 的条形图代码
<h3>Aging Invoices<sup>(mysql)</sup></h3>
<table> 
<tr>
<td>£{$data/data/strategic/financial/invoices/age[@amt='15']}</td>
<td>
{ let $amt :=(xs:float($data/data/strategic/financial/invoices/age[@amt='15']) 
        div 10000) * 100 
return 
        utility:graph("bhs","4D89F9", 
                "150x30", "0|10000",$amt, '15 day')
} 
</td> 
<td>15 Day</td> </tr> 
<tr>
<td>£{$data/data/strategic/financial/invoices/age[@amt='30']}</td>
        <td>{ let $amt :=
(xs:float($data/data/strategic/financial/invoices/age[@amt='30'])
div 10000) * 100 
return 
        utility:graph("bhs",
        "4D89F9", 
        "150x30", 
        "0|10000",
        $amt, 
        '30 day') 
} </td> 
<td>30 Day</td> </tr> 
<tr>
<td>£{$data/data/strategic/financial/invoices/age[@amt='60']}</td>
<td>{ let $amt := (xs:float($data/data/strategic/financial/invoices/age[@amt='60']) 
        div 10000) * 100 
return 
utility:graph("bhs","4D89F9", "150x30", 
        "0|10000", $amt, '60 day') } </td>
<td>60 Day</td> </tr> 
</table>

每个表行都需要选择正确的数据,这可以通过选择 amt 属性实现,该属性代表的是总的数量。请记住此数据最初从 MySQL 数据库生成,所以需要确保 MySQL 服务器运行并由示例数据集加载。此外,还需要取消注释这个 data.xq 代码,正如 README 安装指导文件内解释的那样。

Sales Pipeline

sales pipeline 数据从一个可公开查看的 Google Docs 电子数据表获得。这意味着数据是从一个 Atom 提要中选择 得到的。在呈现数据时惟一一个有些奇怪的地方是这个 Atom 提要(来自 Google 电子数据表)按行提供的数据内包含分隔符,如 清单 10 所示。

清单 10. Sales Pipeline 代码
<h3>Sales Pipeline
<sup>(google spreadsheet)</sup></h3>
<table> 
{for $item in $data/data/strategic/sales_pipeline/atom:feed/atom:entry/atom:content 
return 
        let $cols := tokenize($item,',') 
        return 
<tr>
<td>£{substring-after($cols[2],':')}</td>
<td>{ 
let $amt := (xs:float(substring-after($cols[2],':')) 
       div 10000) * 100 return
utility:graph("bhs", 
"4D89F9,C6D9FD",
"150x30", 
"0|10000", 
$amt,
substring-after($cols[1],':')) 
}</td>
<td>{
substring-after($cols[1],':')
}</td>
</tr> }
</table>

XML 内包含分隔了的数据多少有些怪异,但 XQuery 提供了很多解析它的方法。我使用了 tokenize() 函数来分隔每列,然后再用 substring-after() 函数获得这个数据值。

Weekly Timesheets

有的时候,获得 timesheet 数据最好的方式就是打开一个本地的电子数据表。现在,Excel 已经支持 XML 格式,所以只需知道其结构就可以开始使用它了,如 清单 11 所示。

清单 11. 处理 Excel 电子数据表数据
<h3>Weekly Timesheets <sup>(MS Excel)</sup></h3>
<table> {
for $item in $data/data/operational/project/timesheet/
ss:Workbook/ss:Worksheet/ss:Table/ss:Row[not(position()=1)]
return 
<tr> <td>{$item/ss:Cell[1]}</td>
<td>{$item/ss:Cell[2]}</td> <td> 
{ 
let $hours := xs:decimal(($item/ss:Cell[3] div 40 ) * 100) 
return
        if ($hours &lt; 100) then 
                utility:graph("bhs",
                "4D89F9,C6D9FD", 
                "150x30",
                "0|40", 
                $hours, 
                $item/ss:Cell[3]) 
                else
                utility:graph("bhs", 
                "FF5858,C6D9FD",
                "150x30", 
                "0|40", 
                $hours, 
                $item/ss:Cell[3]) }
</td> </tr> 
}
</table>

XQuery 让您避免使用第一行,因为该行包含了列标签。另外一个需要实现的功能是当员工每周的工作时间超过 40 小时时,能让条形图显示为红色。 XQuery 内的 if|then|else 语句的作用是根据 $hours 变量的值是否大于 40 来选择一个条形。

Microsoft 也开始提供基于开放标准的 XML 源代码格式,这打破了很多年来一直使用二进制格式的局面。这些格式确实很巧妙,但并不开放,因此很难实现本文所示的这种集成。代表电子数据表的这个 Excel XML 格式是顺理成章的,不过,有关其特定结构的优缺点,我留给别人去讨论吧。

站点运行

由 data.xq 生成的测试元素能够展现此 Web 站点是否在线以及需要多久 Web 服务器才会响应。为了简便起见,我只对 status 属性运行了一个测试,如 清单 12 所示。

清单 12. 测试这个 Web 站点
<h3>Website Operation <sup>(xquery logic)</sup></h3>
<table> {
for $item in $data//website_ok/website 
return 
        <tr> 
        <td>{$item/@title/string()}</td>
<td> 
{ 
if ( $item/test/@status = '200' ) then
        attribute style{'background-color:green'} 
else 
attribute style{'background-color:red'
}
} &#160;</td>
<td>{

$item/test/@response/string()

} ms</td>
</tr> 
}
</table>

使用类似的函数不仅能进行存在与否的测试,而且还能更近一步。比如,可以测试联系人表单是否工作。

Reminders

有了来自另外一个 Web 应用程序(http://www.backpackit.com/)的数据,我使用 XQuery FLWR(for、let、where 和 return)表达式对一个 XML Web 服务进行迭代,如 清单 13 所示。

清单 13. 对 XML Web 服务进行迭代
<h3>Reminders <sup>(simple webservice)</sup></h3> <table> {
for $item in $data/data/operational/project/backpack/reminders/reminder 
	return
<tr>
<td>{
string($item/creator/@name)
}:</td>
<td>&#160;{
$item/content
}</td>
</tr> 
}
</table>

Competitor Website

为了启发读者,我还包括了一些额外的小部件。在这种意义上,一个指示板更像是由多种技术混合搭建起来的。compete.com Web 站点能很好地对比显示几个 Web 站点的性能。

结束语

在本文中,我构建了一个示例指示板,只是为了展示如何使用 XQuery,因此没有创建一些快捷或高度可用的东西。如下是一些优化建议:

  • 通过调度 dashboard.xq 的执行来生成 dashboard.html, 使用 cron(和 wgetcurl)。
  • 也可以使用 eXist XML 数据库的调度程序来安排 dashboard.html 的创建。
  • 将来自每个数据源的结果单独存储于 XML 数据库内。
  • 模块化每个 KPI — 可以使用 XSLT 来管理数据的表示。

有了这三个直白的 XQuery 文件,您就可以实现一个能彰显 XQuery 和 XML 在聚集和显示数据方面的威力的 Web 指示板了。


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML, Open source
ArticleID=389923
ArticleTitle=用 XQuery 制作指示板
publish-date=05182009