XML などのプロパティの中の「name=”value”」みたいなやつの値をCSV とかに抽出したい。
Contents
例えばこんなやつの「name=」の値を取り出して一覧にしたい。
例えばこんなやつ
<Configuration xmlns:xlink="http://www.w3.org/1999/xlink">
<Property name="ie.ldap.serverHostName" overridable="true"
targetFile="codebase/WEB-INF/ieStructProperties.txt"
value="test.bbtec.net"/>
<Property name="installedProduct.location.Apache" overridable="true"
targetFile="codebase/WEB-INF/ieWebServerLocation.txt"
value="C:\ptc\HTTPServer"/>
<Property name="ie.ldap.managerPw" overridable="true"
targetFile="codebase/WEB-INF/ieStructProperties.txt"
value="encrypted.ie.ldap.managerPw"/>
<Property name="wt.webapp.name" overridable="true" targetFile="codebase/wt.properties"
value="Windchill"/>
<Property name="wt.rmi.server.hostname" overridable="true"
targetFile="codebase/wt.properties"
value="test.bbtec.net"/>
</Configuration>
Bing のサンプルコード
Bing 氏によると、Get-Content でXML をプロパティ単位で取得できるとのこと。
最初、意味がよくわからなかったので、あきらめて正規表現を使って抜き出ししようと思ってちょっと頑張りましたが、理解するとこちらの方がはるかに楽であることに気づきました。
# XMLファイルのパス
$xmlFilePath = "path\to\your\file.xml"
# CSVファイルのパス
$csvFilePath = "path\to\your\output.csv"
# XMLファイルを読み込む
[xml]$xml = Get-Content -Path $xmlFilePath
# 必要なプロパティを抽出する
$data = foreach ($item in $xml.Root.Element) {
[PSCustomObject]@{
Property1 = $item.Property1
Property2 = $item.Property2
# 他のプロパティも追加できます
}
}
# データをCSVファイルにエクスポートする
$data | Export-Csv -Path $csvFilePath -NoTypeInformation
$xml.Root.Element が下記のXML を前提とた場合の値になっています。
<Root>
<Element>
<Property1>Value1</Property1>
<Property2>Value2</Property2>
</Element>
<Element>
<Property1>Value3</Property1>
<Property2>Value4</Property2>
</Element>
</Root>
実行結果がこんな感じになります。
Property1 Property2
--------- ---------
Value1 Value2
Value3 Value4
試してみた結果
上記の例ではプロパティがタグで挟んでありましたが、name=**** でもプロパティとして取得されていたので、「例えばこんなやつ」の場合、下記のように書けます。
※「$item」を参照している、「$xml.Configuration.Property」がxml の構造に対応。
# XMLファイルを読み込む
[xml]$xml = Get-Content -Path $xmlFilePath;
# 各Element要素をループしてプロパティを表示
# xml の構造に合わせて変更
foreach ($item in $xml.Configuration.Property) {
$data += @(
[PSCustomObject]@{
# 取得するパラメーター名に合わせて変更
Name=$item.name;
Override=$item.overridable;
Value=$item.value;
}
)
}
# 書き出し
$data | ForEach-Object { $_ } | Export-Csv -Path $csvFilePath -NoTypeInformation;
「[xml]$xml = Get-Content -Path $xmlFilePath;」のところは、拡張子がXML でないと読み込まないみたいです。
今回、ファイルの中身はXML なのに拡張子が違っており、読み込んでくれなかったので、下記のようなコードを挟んで、リネームするようにしました。
リネーム後にファイルパスを示す変数も更新する必要があるので、フルネームも取得して置換してます。
$xmlPath = (Read-Host xmlPath);
$test = Get-ChildItem -File $xmlPath
if( $test.Extension -ne ".xml" ){
$temp_name = $test.Name -replace $test.Extension ,'.xml'
$temp_fullname = $test.FullName -replace $test.Extension ,'.xml'
$csv_fullname = $test.FullName -replace $test.Extension ,'.csv'
Rename-Item -Path $test -NewName $temp_name
$xmlPath = $temp_fullname
}
ついでに出力先のCSV ファイルを示すパスも作成して、同じファイル名を使って同じ場所にCSV を残すように設定する。
$data | Export-Csv -Path $csv_fullname -NoTypeInformation
一括で変換バージョン
変換するファイルが結構あって面倒になったので、指定したファイルからフォルダパスを取り出し、
フォルダ内のファイルを一括で変換するように変更。
「拡張子がXML でないと読み込まないみたいです。」は勘違いだったようで、拡張子をXML へ変更するくだりは消しました。
ファイル名にスペースとか入っていると、うまくいかないです。
$xmlPath = (Read-Host xmlPath);
$test = Get-ChildItem -File $xmlPath
Get-ChildItem -Path $test.Directory -File | ForEach-Object {
$xmlPath = $_.FullName
$csv_fullname = $_.FullName -replace $_.Extension ,'.csv'
#Get-ChildItem -File $xmlPath
[xml]$xml = Get-Content -Path $xmlPath;
# xml の構造に合わせて変更
foreach ($item in $xml.MfgParamValuesDocument.MfgParamCollection.MfgParam) {
$data += @(
[PSCustomObject]@{
# 取得するパラメーター名に合わせて変更
Name=$item.name;
Value=$item.value;
}
)
}
$data | Export-Csv -Path $csv_fullname -NoTypeInformation
}
シートにまとめる。
先に、エクセルのモジュールをインストールする必要があります。
Install-Module -Name ImportExcel -Scope CurrentUser
コパイロット(旧 Bing…?)さんからのコードを組み込んで、まとめる処理も追加。
# CSVファイルのパスを取得
$csvFiles = Get-ChildItem -Path . -Filter *.csv
# データを格納するためのリストを作成
$data = @()
# 各CSVファイルを読み込み、データをリストに追加
foreach ($file in $csvFiles) {
$csvData = Import-Csv -Path $file.FullName
$data += $csvData
}
# データをExcelファイルに書き出し
$data | Export-Excel -Path "combined.xlsx" -WorksheetName "Sheet1"
# xml 的な構文を読み込んで、エクセルに出力する。
$xmlPath = (Read-Host xmlPath);
$test = Get-ChildItem -File $xmlPath
Get-ChildItem -Path $test.Directory -File | ForEach-Object {
$data = @()
$xmlPath = $_.FullName
$csv_fullname = $_.FullName -replace $_.Name ,'output.xlsx'
[xml]$xml = Get-Content -Path $xmlPath;
# xml の構造に合わせて変更
foreach ($item in $xml.MfgParamValuesDocument.MfgParamCollection.MfgParam) {
$data += @(
[PSCustomObject]@{
# 取得するパラメーター名に合わせて変更
Name=$item.name;
Value=$item.value;
}
)
}
$data | Export-Excel -Path $csv_fullname -WorksheetName $_.Name
}
正規表現で頑張ってみた!
参考までに、無駄な思考錯誤を置いておきます。
最初「$xml.Root.Element」が理解できていなくて、Bing 氏の提案をあきらめて、
正規表現で頑張ってました。
正規表現でダブルクォーテーションで囲まれた値は「”([^”]*)”」
Bing 氏による解説
正規表現の構造
":
ダブルクォーテーションを表します。
これはリテラル文字として、文字列の開始と終了を示します。
([^"]*):
これはキャプチャグループです。キャプチャグループは、括弧 () で囲まれた部分で、マッチした内容を後で参照するために使用されます。
[^"]:
これは否定文字クラスです。[] 内の ^ は否定を意味し、" 以外の任意の文字を表します。
つまり、ダブルクォーテーション以外の任意の文字にマッチします。
*:
これは量指定子で、直前の要素(この場合は [^"])が0回以上繰り返されることを意味します。
つまり、ダブルクォーテーション以外の任意の文字が0回以上続く部分にマッチします。
全体の意味
"([^"]*)":
最初の " はダブルクォーテーションの開始を示します。
([^"]*) はダブルクォーテーション以外の任意の文字が0回以上続く部分をキャプチャします。
最後の " はダブルクォーテーションの終了を示します。
例
例えば、文字列 This is a "test" string with "multiple" quoted "words". に対してこの正規表現を適用すると、以下の部分がマッチします:
"test"
"multiple"
"words"
そして、キャプチャグループ ([^"]*) によって、以下の文字列が抽出されます:
test
multiple
words
あれこれした結果こんな感じになりました。
コメントアウトしてあるような感じで書くと対象の値が複数の時、全部出力することもできます。
今回は「name」属性だけに絞り込む感じなので、2段階でマッチを評価しています。
配列をCSV に変換する部分は、文字列のlength をCSV に出力してしまう現象を回避した結果こんな感じになりました。
[PSCustomObject] の定義が配列の定義に入れ子になっている感じです。
$xmlFilePath = "site.xconf";
$csvFilePath = "output.csv";
$pattern = 'name="([^"]*)"';
$pattern2 = '"([^"]*)"';
$data = @();
Get-Content -Path $xmlFilePath | ForEach-Object{
if(($_ -match "name=" )){
$matches = [regex]::Matches($_, $pattern);
$matches = [regex]::Matches($matches.Value, $pattern2);
$data += @( [PSCustomObject]@{ name = $matches.Value.Replace('"','') } );
#foreach ($match in $matches) {
# $match.Groups.Value
#}
}
}
$data | ForEach-Object { $_ } | Export-Csv -Path $csvFilePath -NoTypeInformation;