Hey, Scripting Guy!有名な最後の言葉

Microsoft Scripting Guys

有名なギリシャの哲学者であるソクラテス (編集者が生まれる直前の紀元前 399 年に亡くなりました) についておそらく最もよく知られているのは、「吟味されない人生など、生きる価値がない」と述べたことです。この格言は、ほとんどの人が知っています。ただし、驚いたことに、最近 Scripting Guys は、ソクラテスが「吟味されない人生など、生きる価値がない」とは述べていなかったことを知りました。実は、この格言は何百年も前の中世の書記による誤訳だったのです。ソクラテスが実際に言ったのは、次のような言葉でした。「吟味されない XML ファイルなど、持っている価値がない」

これで本当に意味のある哲学的な格言になりましたね。最近は、XML がますます普及しています。Scripting Guys のテスト コンピュータを簡単に検索してみたところ、さまざまなシステムやアプリケーションに使用されている XML ファイルが 500 個以上も見つかりました。

言うまでもありませんが、多くの非常に重要なデータが XML 形式で保存されるようになっています。それはそれで問題ありません。もちろん、そのデータが吟味されていればの話ですが。残念ながらほとんどの場合、多くの人は、XML ファイルを吟味する方法を知らないというだけの理由で、データを吟味していません。特に、クエリを実行してこのような XML ファイルを検索する方法は知られていません。

注 ギリシャの哲学者についてあまりご存じない方のために説明すると、ソクラテスはあちこちでさまざまな物事について語ることを非常に好みましたが、あちこちでさまざまなことを行うのはそれほど好きではありませんでした。実際、ソクラテスの妻のクサンティッペは、彼のことを「役立たずの怠け者」と呼びました。Scripting Guys がソクラテスを大好きな理由がわかったでしょう。

もちろん、このコラムの読者の中には次のように思う方もいらっしゃるでしょう。「ちょっと待って。Scripting Guys は少し前のコラム (technet.microsoft.com/magazine/cc162506 で公開されている「車を追いかけるのと、XML を追求するのと」) で XML ファイルの検索について説明したはずなのに、どうしてまたこのトピックを取り上げるんだろう。Scripting Guys は、同じコラムを何度も繰り返し執筆するほど怠け者なんだろうか」

信じられないかもしれませんが、私たちはそれに輪を掛けた怠け者です。でも、それは私たちがこのトピックを再び取り上げた最大の理由ではありません。以前のコラムでは、図 1 のコードのような構造を持つ XML ファイルの操作方法を説明しました。このファイルでは、それぞれのプロパティ値が 1 つのノードを表しています。

図 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 ファイルを読み取ることができる必要があるので、これは間違いなく問題になります。

質問してくださればよかったのに。質問して、私たちが最終的にこの問題を解決するまで 1 年半待つだけでよかったのです。

説明を続ける前に、この XML ファイルを詳しく見てみましょう。このファイルには、HARDWARE という名前のプライマリ ノードがあり、個々のコンピュータはすべて HARDWARE の子ノードとして存在しています。さらに、これらのノードにはそれぞれ 2 つの属性があります。その属性は、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 に設定します。これで、非同期ではなく同期を取ってドキュメントを読み込むことがスクリプトに伝えられます。なぜスクリプトがこれを気にするのでしょうか。正直言って、気にはしないでしょう。スクリプトは (Scripting Guys と同様) 生物ではありませんからね。

ただし、皆さんは気にする必要があります。非同期にドキュメントを読み込むと、ドキュメントが完全に読み込まれていない場合でもスクリプトは続行されます。これは適切ではありませんね。まだ存在しないドキュメントを使用して作業しようとしたときに発生する可能性がある問題を想像してみてください。同期を取って XML ファイルを読み込むと、ファイルが完全に読み込まれてからスクリプトが続行されます。

偶然にも、次の処理は XML ファイルの読み込みです。これを行うには、Load メソッドを呼び出して C:\Scripts\HARDWARE.xml ファイルを開きます。続いて、次のコードを実行します。

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

ここでは SelectNodes メソッドを使用して、XML ファイルに対してクエリを実行し、取得する XML ノードをスクリプトに指示しています。構文に注目してください。確かに、少し変わった構文です。

まず、XML ファイル内のパスを指定する必要があります。今回の XML ファイルには、最も上のレベルに HARDWARE という名前のノードがあり、続いて第 2 レベルに COMPUTER という名前の一連のノードがあります。第 2 レベルにあるこれらのノードは、それぞれ XML データ ファイルの 1 つのレコードを表しています。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

この場合、getNamedItem メソッドを使用して 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']")

上記のスクリプトでは、どのような処理を実行しているのでしょうか。1 組の角かっこの中に 2 つの条件 (@os='Windows XP'、または @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

なぜレポートの項目が 1 つだけなのでしょうか。そのとおりです。財務部門には Windows XP を実行しているコンピュータが 1 台しかないからです。

皆さんがこのような XML ファイルを操作するうえで、上記の説明がお役に立つことを願っています。こっそり他の種類の XML ファイルが存在していた場合は、健闘を祈ります。

今月のコラムを有名な格言で始めたので、同じような調子でコラムを終えるのがふさわしいと思います。そこで、皇帝ヴィルヘルム 2 世の次の言葉で締めくくります。Scripting Guys はこの言葉を大切にしています。「ここに、予はプロイセン王位、およびそれに伴うドイツ帝国の帝位を主張する権利を、永久に放棄する」

Dr. Scripto のスクリプト パズル

パズルを解く能力だけでなく、スクリプト作成スキルもテストする月に 1 度の課題です。

2008 年 10 月 : VBScript の穴埋め

このパズルを解くには、各行に VBScript 関数の名前を当てはめるだけです。完成したら青いマス目の文字を解読して、関数名をもう 1 つ見つけてください。

fig10.gif

解答 :

Dr. Scripto のスクリプト パズル

解答 : 2008 年 10 月 : VBScript の穴埋め

puzzle_answer.gif

Scripting Guys の Greg Stemp と Jean Ross は、マイクロソフトに勤務しています。