您好,脚本专家!临别感言

Microsoft 脚本专家

著名的希腊哲学家苏格拉底(死于公元前 399 年,在“脚本编辑器”问世之前不久)很可能是因为说过“浑浑噩噩的生活不值得过”而出名的。很多人也都知道这句名言。但值得一提的是,脚本专家们最近发现苏格拉底根本就没有说过“浑浑噩噩的生活不值得过”这句话。事实证明,这句名言只是数百年前某位中世纪抄写员的误译。苏格拉底的原话其实是:“未经检验的 XML 文件根本不值得存在”。

现在看来,这句哲学名言确实很有道理!XML 最近变得越来越流行;快速搜索一下某位脚本专家的测试计算机,最少也能马上找出由各种系统或应用程序使用的 500 个 XML 文件。

另外,现在很多非常重要的数据也都以 XML 的格式存储。这样做很正确。当然,如果这些数据未经检验则不属正确之列。但让人沮丧的是,这种情况时有发生,如果没有其他原因,那只能说明:许多人对如何检验 XML 文件一无所知。特别是他们不了解如何查询和搜索 XML 文件。

注意可能您对希腊哲学家不太熟悉,苏格拉底非常喜欢四处高谈阔论,但却不愿意做一些实事,事实上,他的妻子赞西佩就把他称为“无聊的闲人”。现在您应该知道为什么脚本专家会在内心对苏格拉底充满好感了吧。

当然,你们当中肯定有人会想,“等一下:脚本专家不是已经在以前的专栏(technet.microsoft.com/magazine/cc162506“追汽车……和 XML”)中讨论过如何搜索 XML 文件了吗?既然如此,为什么他们现在还要再讨论这个话题呢,是不是脚本专家们已经懒惰到一遍又一遍地重复撰写相同专栏的地步啦?”

不管您是不是相信,我们实际上比您想象的还要懒。但这并不是我们重新讨论这个话题的主要原因。在以前的专栏中,我们讨论过结构类似于图 1 中所示代码的 XML 文件,其中每个属性值都代表文件中的单个节点。

图 1 仅包含节点的 XML 文件

<?xml version='1.0'?> 
  <INVENTORY> 
    <COMPUTER>
      <os>Windows XP</os>
      <department>Human Resources
      </department>
      <name>atl-ws-001</name>
    </COMPUTER> 
    <COMPUTER>
      <os>Windows XP</os>
      <department>Finance</department>
      <name>atl-ws-002</name>
    </COMPUTER> 
    <COMPUTER>
      <os>Windows Vista</os>
      <department>Finance</department>
      <name>atl-ws-003</name>
    </COMPUTER> 
  </INVENTORY>

这个文件很不错,但除了便于写入和指示外,它并不是 XML 文件的唯一组织方式。在前面的示例中,文件中的每个计算机都有其自身的节点;而其中的每个节点又包含许多子节点(os、department 和 name)。但是,也可以构造一个各节点都不包含子节点的 XML 文件;附加属性值将被配置为属性。类似于图 2 中的文件所示。

图 2 包含属性的 XML 文件

<?xml version='1.0'?> 
  <HARDWARE> 
      <COMPUTER os="Windows XP" department="Human Resources">atl-ws-001</COMPUTER> 
      <COMPUTER os="Windows XP" department="Finance">atl-ws-002</COMPUTER> 
      <COMPUTER os="Windows Server 2003" department="IT">atl-fs-003</COMPUTER> 
      <COMPUTER os="Windows Vista" department="IT">atl-ws-004</COMPUTER> 
      <COMPUTER os="Windows Vista" department="Human Resources">atl-ws-005</COMPUTER> 
      <COMPUTER os="Windows Vista" department="Finance">atl-ws-006</COMPUTER> 
      <COMPUTER os="Windows XP" department="Sales">atl-ws-007</COMPUTER> 
      <COMPUTER os="Windows Server 2008" department="IT">atl-fs-008</COMPUTER> 
      <COMPUTER os="Windows XP" department="Human Resources">atl-ws-009</COMPUTER> 
      <COMPUTER os="Windows Vista" department="Sales">atl-ws-010</COMPUTER> 
  </HARDWARE>

这有什么问题吗?事实上,确实有问题。我们在很久以前在一个很久远的问题中为您展示的脚本就无法使用图 2 所示的 XML 文件的结构。那是因为当时没有发生这种情况。但这确实是一个不容回避的问题,因为你们当中的许多人都需要读取这种类型的 XML 文件。

有问题请尽管提问。不过在我们最终回过头来解决这个问题之前可能还得等上一年半载。

在继续讨论之前,让我们仔细看一看我们的 XML 文件。在本例中,文件的主要节点名为 HARDWARE;每个单独的计算机都作为 HARDWARE 的子节点存在。除此以外,其中的每个节点还都包含一对属性:os(用于存储计算机中所安装的操作系统的名称)和 department(用于存储拥有该计算机的部门的名称)。

例如,假设我们希望获得所有运行 Windows XP 的计算机的列表。使用脚本能做到这一点吗?当然可以:

Set xmlDoc = _
  CreateObject("Microsoft.XMLDOM")

xmlDoc.Async = "False"
xmlDoc.Load("HARDWARE.xml")

Set colNodes=xmlDoc.selectNodes _
  ("//HARDWARE/COMPUTER[@os='Windows XP']")

For Each objNode in colNodes
  Wscript.Echo objNode.Text 
Next

让我们看一下这个脚本。首先我们创建一个 Microsoft.XMLDOM 对象实例;顾名思义,这个对象可使我们能够对 XML 文件进行处理。创建完对象后,我们将 Async 属性设置为 False;这可以告诉脚本我们希望同步加载文档而不是异步加载。为什么脚本会关心这一点呢?说句实话,可能它根本就不关心;毕竟脚本(像脚本专家一样)都是没有生命死气沉沉的。

但您却必须关心这一点。如果异步加载文档,即使文档未完全加载,脚本也会继续运行下去。这就有点糟糕了:想象一下如果试图对并不存在的文档执行任务可能会出现什么情况。同步加载 XML 文件可确保在脚本继续执行前文件已完全加载。

真是太巧了,我们现在刚好到了加载 XML 文件的步骤。我们通过调用 Load 方法并打开文件 C:\Scripts\HARDWARE.xml 进行加载,代码如下所示:

Set colNodes=xmlDoc.selectNodes _
  ("//HARDWARE/COMPUTER[@os='Windows XP']")

在这里我们采用了 SelectNodes 方法来查询 XML 文件,并告诉脚本我们希望检索哪些 XML 节点。请注意这种确实有点不同寻常的语法。

首先,我们需要指定在 XML 文件中的路径。我们的 XML 文件有一个名为 HARDWARE 的顶级节点,后跟一系列名为 COMPUTER 的二级节点。其中每个二级节点都代表 XML 数据文件中的一条单独记录。这正是 selectNodes 查询以如下方式开始执行的原因:

//HARDWARE/COMPUTER

在这里,我们遇到了这种结构:

[@os='Windows XP']

这部分查询与标准 SQL 查询中的 WHERE 子句类似。在 SQL 中,我们可能会使用类似如下所示的数据库查询来检索运行 Windows XP 操作系统的所有计算机的列表:

SELECT Name FROM Hardware 
    WHERE OS = 'Windows XP'

使用 SelectNodes 方法时也与此类似:我们希望获取所有 os 属性 (@os) 等于 Windows XP 的计算机的列表。并且为了表明这是一个 WHERE 子句,我们将整个子句都括在方括号内。

注意这种类型的查询被称为 XPath 查询。有关 XPath 的更多信息,请查看 msdn.microsoft.com/library/ms256115.aspx

如前所述,这虽然有点怪异,但确实有效。如果希望得到属于财务部门的所有计算机的列表应该怎样做呢?小菜一碟;我们可以按照如下方式调用 selectNodes:

Set colNodes=xmlDoc.selectNodes _
  ("//HARDWARE/COMPUTER" & _
   "[@department='Finance']")

同样,WHERE 子句包含在方括号中,我们在属性名称 (department) 前面加上符号 (@),然后指明我们要获取的值。

简单吧?

调用了 selectNodes 方法后,我们只需通过循环返回值的集合并回显 Text 属性即可回显计算机名称,如下所示:

For Each objNode in colNodes
  Wscript.Echo objNode.Text 
Next

我们能够得到哪类信息呢?实际上,我们希望得到类似如下所示的信息:

atl-ws-001
atl-ws-002
atl-ws-007
atl-ws-009

换句话说,它将返回当前运行 Windows XP 的所有计算机的名称。

顺便说一下,这可以并不局限于仅返回计算机名称;因为计算机名称是每个节点的“默认”值,所以这只是我们引用 Text 属性得到的结果。

我们也可以明确指出需要返回哪个属性。例如,看一看修改后的 For Each 循环:

For Each objNode in colNodes
    Wscript.Echo objNode.Text 
    Wscript.Echo objNode.Attributes. _
      getNamedItem("department").Text
    Wscript.Echo
Next

如您所见,在此循环中我们仍回显 Text 属性的值;但我们还追加了下面这行代码:

Wscript.Echo objNode.Attributes. _
  getNamedItem("department").Text

在这种情况下,我们使用 get­NamedItem 方法来检索 department 属性的值;然后回显该属性的 Text 属性。这就是我们如何指定希望(以及不希望)回显到屏幕上的属性值的方法。(还要注意,我们还加入了 Wscript.Echo 命令,以便在两条记录之间插入一个空行。)当运行此脚本时,我们会得到如下所示的结果:

atl-ws-001
Human Resources

atl-ws-002
Finance

atl-ws-007
Sales

atl-ws-009
Human Resources

毫无疑问,如果需要,您还可以编写更为复杂的查询。例如,假设您希望获取所有运行 Windows XP 或 Windows Vista 的计算机的列表。在 SQL 中,您可以通过编写 OR 查询来实现。猜到了吗?在查询 XML 文件时,您可以使用完全相同的方法:

Set colNodes=xmlDoc.selectNodes _
    ("//HARDWARE/COMPUTER" & _
     "[@os='Windows XP' or " & _
     "@os='Windows Vista']")

看明白这段脚本的含义了吗?在一对方括号中,我们提供了两个条件:@os='Windows XP' or @os='Windows Vista'。这样就可以返回类似图 3 所示的报告。

图 3 OR 查询的结果

atl-ws-001
Human Resources

atl-ws-002
Finance

atl-ws-004
IT

atl-ws-005
Human Resources

atl-ws-006
Finance

atl-ws-007
Sales

atl-ws-009
Human Resources

atl-ws-010
Sales

如果仔细检查该 XML 文件,您会发现输出结果中包含运行 Windows XP 或 Windows Vista 的计算机;而运行 Windows Server 2003 或 Windows Server 2008 的计算机却未包含在其中。确实不应该包含它们。

有什么问题吗?您是否希望编写更为严格的查询,比如仅返回属于财务部门且运行 Windows XP 的计算机?正如您猜想的那样,这非常简单;您只需编写类似下面所示的 AND 查询即可:

Set colNodes=xmlDoc.selectNodes _
    ("//HARDWARE/COMPUTER" & _
     "[@os='Windows XP' and " & _
     "@department='Finance']")

执行后将会返回以下数据集:

atl-ws-002
Finance

为什么报告中只有一个结果?没错:因为财务部门的确只有一台运行 Windows XP 的计算机。

我们希望这些能够帮助大家处理这种特殊类型的 XML 文件。如果还有其他类型的 XML 文件让您给遇上了,那就只能祝您好运了。

因为我们是以一句名言作为本月专栏开场白的,所以我们想引用类似的警句作为本专栏的结束语。我们脚本专家都非常喜欢德皇威廉二世的一句名言,就把它作为结束语吧:“我由此永远放弃对普鲁士王位以及与之伴随而来的德意志帝国王位的主张。”

Scripto 博士的脚本谜题

每月挑战不仅测试您解决谜题的能力,还测试您的脚本编写技能。

2008 年 10 月:VBScript 填充

要解开此谜题,只需在每行填入一个 VBScript 函数名称即可。完成后,通过组合蓝色方框中的字母还会得出另一个函数名称。

fig10.gif

答案:

Scripto 博士的脚本谜题

答案:2008 年 10 月:VBScript 填充

puzzle_answer.gif

脚本专家—Greg Stemp 和 Jean Ross—为 Microsoft 工作。