HOWTO · PowerShell
PowerShell を使用して XML ファイルを解析する方法
この記事では、Windows PowerShell を使用して XML ファイルを適切に解析する方法について説明します。
このページの内容
PowerShell で XML から値を抽出するには、簡単な検索なら XPath 式と Select-Xml、読み込んだ文書内を移動する必要があるなら System.Xml.XmlDocument、ストリームとして前方に読み進めるなら System.Xml.XmlTextReader を使います。以下の例は XML の読み取りのみを行います。小さなコンピューター一覧を使い、要素テキストと属性値の違いを示します。
Select-Xml で XML の要素と属性を解析する
Select-Xml は XPath 式を評価し、一致したオブジェクトを返します。各一致オブジェクトの Node プロパティから、選択された XML ノードにアクセスできます。要素のテキストを読むには InnerText を使います。InnerXml は要素内に含まれるマークアップを返すため、その内容を XML として扱う必要がある場合に便利です。
次の例を実行します。
$path = Join-Path ([System.IO.Path]::GetTempPath()) "computers-$([guid]::NewGuid()).xml"
@'
<Computers>
<Computer name="WINPC-01" ip="127.0.0.1"><Name>WINPC-01</Name></Computer>
<Computer name="WINPC-02" ip="192.168.1.105"><Name>WINPC-02</Name></Computer>
</Computers>
'@ | Set-Content -Path $path -Encoding utf8
Write-Output 'Element names:'
Select-Xml -Path $path -XPath '/Computers/Computer/Name' |
ForEach-Object { $_.Node.InnerText }
Write-Output 'Attribute names:'
Select-Xml -Path $path -XPath '/Computers/Computer' |
ForEach-Object { $_.Node.GetAttribute('name') }
Remove-Item -Path $path
Element names:
WINPC-01
WINPC-02
Attribute names:
WINPC-01
WINPC-02
XPath /Computers/Computer/Name は、ルートから各 Name 要素までの階層をたどります。属性を選択するには、Computer 要素を選び、一致した各ノードから name 属性を読み取ります。XPath は XML の構造に合わせて変更してください。要素名と大文字・小文字も文書内の表記に一致させる必要があります。データがすでに文字列にある場合、Select-Xml は -Content パラメーターで XML コンテンツを受け取ることもできます。
出力例の「Element names」グループには各要素のテキストが、「Attribute names」グループには各 name 属性の値が表示されます。この2つのグループ見出しは英語のままです。
System.Xml.XmlDocument で XML を読み込み、検索する
System.Xml.XmlDocument は文書を移動可能なツリーとして読み込みます。スクリプトで関連する複数の検索を行う場合や、親ノードと子ノードの間を移動する場合に便利です。一方、文書全体をメモリ上に保持するため、非常に大きなファイルではストリーム読み取りのほうが適することがあります。
この例では、一時 XML ファイルを読み込み、各コンピューターを選択してから、Name 子要素があるか確認して読み取ります。XML スキーマ上、その要素がない場合があるなら、この確認が重要です。一致するノードがない場合、SelectSingleNode() は $null を返します。
$path = Join-Path ([System.IO.Path]::GetTempPath()) "computers-$([guid]::NewGuid()).xml"
@'
<Computers>
<Computer><Name>WINPC-01</Name></Computer>
<Computer><Ip>192.168.1.105</Ip></Computer>
</Computers>
'@ | Set-Content -Path $path -Encoding utf8
$document = [System.Xml.XmlDocument]::new()
$document.Load($path)
foreach ($computer in $document.SelectNodes('/Computers/Computer')) {
$nameNode = $computer.SelectSingleNode('Name')
if ($null -ne $nameNode) {
Write-Output $nameNode.InnerText
}
else {
Write-Output 'No Name element'
}
}
Remove-Item -Path $path
要素内のテキスト値には InnerXml ではなく InnerText を使います。InnerXml は要素内に入れ子になったマークアップを返します。文書に既定の XML 名前空間があると、接頭辞のない XPath 式では要素に一致しないことがあります。その場合は XmlNamespaceManager を作成し、クエリで接頭辞を使います。
System.Xml.XmlTextReader で XML をストリームとして読む
System.Xml.XmlTextReader は、前方にのみ進み、読み取り専用のリーダーを通じて XML ノードを文書順に公開します。文書全体のツリーを作成しないため、大きな入力ではメモリ使用量を抑えられる場合があります。後ろに戻って移動できないので、対象のノードが現れた時点で値を抽出します。処理が途中で終了した場合もファイル ハンドルを解放できるよう、finally ブロックで閉じてください。
次の例では、各 Computer 開始要素から name 属性を読み取ります。例の中で XML ファイルを作成し、処理後にリーダーを破棄します。
$path = Join-Path ([System.IO.Path]::GetTempPath()) "computers-$([guid]::NewGuid()).xml"
@'
<Computers>
<Computer name="WINPC-01" ip="127.0.0.1" />
<Computer name="WINPC-02" ip="192.168.1.105" />
</Computers>
'@ | Set-Content -Path $path -Encoding utf8
$reader = [System.Xml.XmlTextReader]::new($path)
try {
while ($reader.Read()) {
if ($reader.NodeType -eq [System.Xml.XmlNodeType]::Element -and
$reader.Name -eq 'Computer') {
Write-Output $reader.GetAttribute('name')
}
}
}
finally {
$reader.Dispose()
Remove-Item -Path $path
}
属性ではなく要素のテキストを読むには、リーダーが該当要素に位置している間に要素名を確認し、そのテキストを読み取ります。ReadString() はテキスト内容を読み取ってリーダーを進めるため、より複雑なループではその移動を考慮してください。処理が終わったらリーダーを破棄します。開いたままにすると、入力ファイルがロックされ続けることがあります。
PowerShell で使う XML 解析方法を選ぶ
ファイルまたは文字列から少数のノードを取得する際、短い XPath クエリが最も明快なら Select-Xml を選びます。ツリーを使った移動、複数の検索、関連ノードへのアクセスが必要なら System.Xml.XmlDocument を選びます。ノードを順に処理でき、ツリー全体をメモリに読み込まずに済ませたい場合は System.Xml.XmlTextReader を選びます。
3つの方法はいずれも要素と属性を読み取れます。主な違いは文書へのアクセス方法です。Select-Xml は XPath の一致結果を返し、XmlDocument は読み込んだツリー内の移動を可能にし、XmlTextReader はストリームを前方に読み進めます。文書のサイズと検索方法に合うものを選び、入力に含まれない可能性がある要素や属性は存在を確認してください。