2015年7月25日土曜日

gaucheでxmlから特定の要素を抜き出す

1. 背景

windosw updateをオフラインで実行する過程で、
オフラインのwindowsマシンは、windows updateの状況が書かれたXMLファイルを出力する。
出力されるファイルは次のようなものである。
<XMLOut>
  <CatalogInfo>
    <CreationDate>2015-07-14T04:38:16Z    </CreationDate>
  </CatalogInfo>
  <Check ID="500" Grade="2" Type="5" Cat="1" Rank="1" Name="Developer Tools, Runtimes, and Redistributables のセキュリティ更新プログラム" URL1="Help/Check5311.html" URL2="Help/Check5311fix.html" GroupID="48ce8c86-6850-4f68-8e9d-7dc8535ced60" GroupName="Developer Tools, Runtimes, and Redistributables">
    <Advice>不足しているセキュリティ更新プログラムが 2 個あります。
    </Advice>
    <Detail>
      <UpdateData ID="MS11-025" GUID="bb49cc19-8847-4986-aa93-5e905421e55a" BulletinID="MS11-025" KBID="2538242" Type="1" IsInstalled="false" Severity="3" RestartRequired="false">
    <Title>Microsoft Visual C++ 2005 Service Pack 1 再頒布可能パッケージのセキュリティ更新プログラム (KB2538242)
    </Title>
    <References>
      <BulletinURL>http://www.microsoft.com/technet/security/bulletin/MS11-025.mspx
      </BulletinURL>
      <InformationURL>http://go.microsoft.com/fwlink/?LinkId=216804
      </InformationURL>
      <DownloadURL>http://download.windowsupdate.com/msdownload/update/software/secu/2011/06/vcredist_x86_b8fab0bb7f62a24ddfe77b19cd9a1451abd7b847.exe
      </DownloadURL>
    </References>
      </UpdateData>
      <UpdateData ID="MS11-025" GUID="729a0dcb-df9e-4d02-b603-ed1aee074428" BulletinID="MS11-025" KBID="2538243" Type="1" IsInstalled="false" Severity="3" RestartRequired="false">
    <Title>Microsoft Visual C++ 2008 Service Pack 1 再頒布可能パッケージのセキュリティ更新プログラム (KB2538243)
    </Title>
    <References>
      <BulletinURL>http://www.microsoft.com/technet/security/bulletin/MS11-025.mspx
      </BulletinURL>
      <InformationURL>http://go.microsoft.com/fwlink/?LinkId=216803
      </InformationURL>
      <DownloadURL>http://download.windowsupdate.com/msdownload/update/software/secu/2011/05/vcredist_x86_470640aa4bb7db8e69196b5edb0010933569e98d.exe
      </DownloadURL>
    </References>
      </UpdateData>
    </Detail>
  </Check>
  (中略)
</XMLOut>

(もともとのファイルは改行があまりないので、blogに掲載するためにemacsのsgml-modeでsgml-pretty-printで整形した。)

ここで、各UpdateDataにおいて、IsInstalled="false"になっている項目を、DownloadURLからダウンロードしてくる必要がある。
さて、どうやろうか。手動でやるのは問題外である。
いろいろ方法はありそうだが、XMLのライブラリがあるscript言語を使ってみよう。
私が一番親しいscript言語は、Gaucheである。

2. Gaucheのxmlモジュール

GaucheにはXMLのためのモジュールがいくつかある。
今回は、sxml.ssax、sxml.sxpathでやりたいことがやれた。
詳しくは、Referenceを参照のこと。[1]

まず、ssax:xml->sxmlでファイルから読み込んだXMLをS式(sxml形式と読んでいる)に変換する。

(define (read-xml file-name)
  (call-with-input-file file-name
    (lambda (port)
      (ssax:xml->sxml port '() ))) )
は、XMLファイルを引数として受け取り、ファイル内のXMLと同じ構造のS式を返す関数を定義する。
ssax:xml->sxml の第3引数namespace-prefix-assigは
'()としたが問題は起きなかった。

次に、このS式を処理して必要なURLだけをファイルに書き出したい。
それにはsxpathが使える。
(sxpath '(XMLOut Check))は、(ルートから)XMLOut -> Checkとたどった時の要素のリスト(Checkがたくさんあることが想定されるから)を取り出すClosureを返す。

したがって、sxmlであるsxml-wholeに対して、((sxpath '(XMLOut Check)) sxml-whole)でXMLOut -> Checkとたどった時の要素のリストが返る。

3. まとめ

これらを組み合わせると、windowsの出力したXMLを食べて、必要なDownloadURLの一覧を出力するscriptができる。
#!/usr/local/bin/gosh 
(use sxml.ssax)
(use sxml.sxpath)

(define (read-xml file-name)
  (call-with-input-file file-name
    (lambda (port)
      (ssax:xml->sxml port '() ))) )
(define (get-url-unless-installed update-data)
  (if (string=? "false"
  (cadar ((sxpath '(|@| IsInstalled)) update-data)))
      (cadar
       ((sxpath '(References DownloadURL)) update-data))
      #f))

(define (main argv)
  (let ((sxml-whole (read-xml (list-ref argv 1))))    
    (map (lambda (y) (print  y))
  
  (filter (lambda (x) x) 
   (apply append
   (map (lambda (check)
          (map get-url-unless-installed
        ((sxpath '(Detail UpdateData)) check)))
        ((sxpath '(XMLOut Check)) sxml-whole))
   )
   ))
    ))

これで、gaucheでxmlから特定の要素を抜き出すことができた。

[1] Gauche ユーザリファレンス: 12. ライブラリモジュール - ユーティリティ
http://practical-scheme.net/gauche/man/gauche-refj_149.html#g_t_00e3_0083_00a9_00e3_0082_00a4_00e3_0083_0096_00e3_0083_00a9_00e3_0083_00aa_00e3_0083_00a2_00e3_0082_00b8_00e3_0083_00a5_00e3_0083_00bc_00e3_0083_00ab-_002d-_00e3_0083_00a6_00e3_0083_00bc_00e3_0083_0086_00e3_0082_00a3_00e3_0083_00aa_00e3_0083_0086_00e3_0082_00a3