# PowershellのXPathNavigatorでXMLパース

2018/04/30

仕事でやったことのメモ。XMLファイルで記述された文書をPowerhsellでパースし、必要な情報を取り出す方法です。

# XMLとは

Extensible Markup Languageの略。html同様マークアップ言語ですが、htmlとは違い属性を自由に定義できるため、アプリケーションのコンフィグファイル等によく利用されています。Tomcatの設定なんかもXML形式。

ファイルの先頭にはXML宣言を書きます。この文書はXMLですよという宣言。

<?xml version="1.0" encoding="UTF-8" standalone="yes">
1

versionというのはXMLのバージョン。現在は1.0以外ありません。encodingはその名の通り文字のエンコーディング形式。standaloneというのはこの文書がスタンドアロン文書かどうか。XMLは外部の文書を読み込むこともできるそうです。

んで中身。例えばこんな行があったとします。

<Linux distributor="Red Hat">CentOS</Linux>
1

Linuxの位置にあるものは「要素」。Linux要素の開始タグと終了タグで囲まれたCentOSが「コンテンツ」。distributorの位置にあるものはLinux要素が持つ「属性」。こんな感じで、XMLは「要素」が「コンテンツ」を持ち、要素の付加的な情報を「属性」で記述していく文章です。

マークアップ言語なので、要素は子要素を持つことができます。こんな感じ。htmlでよく見るやつですね。

<OS>
  <Linux distributor="Red Hat">CentOS</Linux>
  <Windows distributor="Microsoft">Windows10</Windows>
</OS>
1
2
3
4

一つの要素は属性を複数持つこともできます。こんな感じ。属性の順番は意味をなしません。

<Linux distributor="Red Hat" desktopmanager="GNOME">CentOS</Linux>
1

コンテンツを含まない要素もあります。その場合、htmlと同様にスラッシュを開始タグの末尾につけることで、終了タグを省略できます。以下二つは同じ意味。省略した書き方を「空要素タグ」と呼んだりします。

<Linux distributor="Red Hat"></Linux>
<Linux distributor="Red Hat"/>
1
2

# PowershellとXML

仕事でXMLから情報を取り出さなあかんくなったので、やっぱどんな環境でも使えるツールが一番いいんだろうなぁと思ってPowerhsellとXMLについて調べました。

PowershellにはクラスとしてXML型が存在します。例えばこんな文書をtest.xmlとして作成します。

<?xml version="1.0" encoding="UTF-8" standalone="yes">
<OS>
 <Linux distributor="Red Hat" desktopmanager="GNOME">CentOS</Linux>
 <Windows distributor="Microsoft">Windows10</Windows>
</OS>
1
2
3
4
5

そしてこのファイルの中身をGet-Contentで取得し、変数に代入します。その際、データ型としてXMLと指定します。

PS C:\work> $MYXML = [xml](Get-Content .\test.xml)
1

こうすることで、要素をドット区切りで取得できるようになります。オブジェクトのプロパティを取得していくイメージですね。

PS C:\work> ${MYXML}.OS

Linux Windows
----- -------
Linux Windows

PS C:\work> ${MYXML}.OS.Linux

distributor desktopmanager #text
----------- -------------- -----
Red Hat     GNOME          CentOS
1
2
3
4
5
6
7
8
9
10
11

属性は、XMLの要素(XmlElement)クラスのGetAttributeメソッドを利用します。

PS C:\work> ${MYXML}.OS.Linux.GetAttribute("distributor")
Red Hat
1
2

属性の単純なgetとsetは可能なのですが、検索だとか件数カウントだとかが微妙に使いづらい。例えばある属性を持つ要素だけを絞り込んでそのコンテンツを取得する、なんてことはちょっとしたロジックを書いてやらなきゃいけない。foreach文回して全部書き出してそこから選択する、とかね。そこで登場するのがPowershellのXPathNavigatorオブジェクト。

# PowershellとXPathNavigator

XMLクラスの持つCreateNavigator()メソッドを利用すると、XMLオブジェクトをXPathNavigatorオブジェクトに変換することができます。

PS C:\work> $MYXML = [xml](Get-Content .\test.xml)
PS C:\work> $MYXNAVI = ${MYXML}.CreateNavigator()
1
2

これ何かというと、XPathExpressionクラスが提供するXPath式という表記方法と併用することで、XML文書をパス区切りのディレクトリのような木構造として扱えるオブジェクトです。

基本的な使い方は、XPathNavigatorクラスの持つSelect()メソッドにてXPath式を記述し、XML文書を検索していきます。XPath式の基本的な書き方はMS公式のここを見れば大体書いてあります。

# 例1.OS要素の子要素のうち、distributorがMicrosoftの全要素を出力する

こんな感じ。

PS C:\work> ${MYXNAVI}.Select("/OS/*[@distributor = 'Microsoft']")

NameTable : System.Xml.NameTable
NodeType : Element
LocalName : Windows
NamespaceURI :
Name : Windows
Prefix :
Value : Windows10
BaseURI :
IsEmptyElement : False
XmlLang :
UnderlyingObject : Windows
HasAttributes : True
HasChildren : True
SchemaInfo : System.Xml.XmlName
CanEdit : True
IsNode : True
XmlType :
TypedValue : Windows10
ValueType : System.String
ValueAsBoolean :
ValueAsDateTime :
ValueAsDouble :
ValueAsInt :
ValueAsLong :
OuterXml : <Windows distributor="Microsoft">Windows10</Windows>
InnerXml : Windows10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

オブジェクトなのでいろんな情報がひっついてきます。コンテンツだけでよければ、Select()した結果の.Valueプロパティを取得すればOK。

PS C:\work> ${MYXNAVI}.Select("/OS/*[@distributor = 'Microsoft']").Value
Windows10
1
2

# 例2.desktopmanager属性がGNOMEである要素のdistributor属性を出力する

こんな感じ。2つ目のスラッシュ区切りの部分は、全て(*)の要素から角括弧でくくった部分で属性条件を指定して要素集合(属性、コンテンツを含む)を絞りこむという意味。その絞り込んだ結果のdistributor属性を表示しています。

PS C:\work> ${MYXNAVI}.Select("/OS/*[@desktopmanager = 'GNOME']/@distributor").Value
Red Hat
1
2

# 使用例

成果物です。何かっつうとNmapの-oXオプションが吐くXMLファイルから各スキャン(UDP、Connect、サービススキャン、etc)にかかった時間を出力するスクリプト。GitHub始めました。

https://github.com/uda-cha/CalcTimeNmapXML

おわり。