2016-05-03 23 views
1

XMLファイルには、さまざまな要素のある&属性があります。いくつかはすべてに共通していますが、すべてのノードにすべての(または同じ)ノードがあるわけではありません。次のようにサンプルXMLは次のとおりです。ダイナミックXMLをCSV /テキストに変換する

<?xml version='1.0' encoding='UTF-8'?> 
<index> 
    <doc id='0'> 
     <field name='IDTREE' norm='124' flags='Idfp--S--Ni08--------'> 
      <val>-</val> 
     </field> 
     <field name='role' norm='114' flags='Idfp--S--Ni08--------'> 
      <val>administrators</val> 
     </field> 
     <field name='internalid' norm='117' flags='Idfp--S--Ni08--------'> 
      <val>123456</val> 
     </field> 
     <field name='version' norm='124' flags='Idfp--S--Ni08--------'> 
      <val>test</val> 
     </field> 
     <field name='id' norm='124' flags='Idfp--S--Ni08--------'> 
      <val>myname-123456-test</val> 
     </field> 
     <field name='siteId' norm='124' flags='Idfp--S--Ni08--------'> 
      <val>myname</val> 
     </field> 
    </doc> 
    <doc id='1'> 
     <field name='internalid' norm='117' flags='Idfp--S--Ni08--------'> 
      <val>98765</val> 
     </field> 
     <field name='version' norm='124' flags='Idfp--S--Ni08--------'> 
      <val>dev</val> 
     </field> 
     <field name='category' norm='113' flags='Idfp--S--Ni08--------'> 
      <val>biography</val> 
     </field> 
     <field name='display' norm='120' flags='Idfp--S--Ni08--------'> 
      <val>false</val> 
     </field> 
     <field name='publisher' norm='124' flags='Idfp--S--Ni08--------'> 
      <val>-</val> 
     </field> 
     <field name='id' norm='124' flags='Idfp--S--Ni08--------'> 
      <val>myname-98765-dev</val> 
     </field> 
     <field name='siteId' norm='124' flags='Idfp--S--Ni08--------'> 
      <val>myname</val> 
     </field> 
    </doc> 
</index> 

私が何をしたいのか、私はエクセル(またはSQL)にインポートできるテキストファイル(パイプ区切り)にこの(非常に大きい)XMLファイルを変換することです。私は、列名を取得するための最初の1、および第2の適切なフィールドにデータを追加するために、XMLデータに2回のパスを作成する必要があると思い

id|siteId|version|internalid|role|IDTREE|category|display|publisher 
myname-123456-test|myname|test|123456|administrators|-||| 
myname-98765-dev|myname|dev|98765|||biography|false|-

:ように私は出力になりたいですテキストファイルに出力する。

私は、各ドキュメントに同じフィールドノードが少なくとも4つあることを知っています:id、siteId、versionおよびinternalid。他のすべてはさまざまです。

私の最初の考えは、XMLの1パスを行い、フィールドの名前属性をハッシュテーブルに追加することでした。 2番目のパスでは、ハッシュテーブルを使用して、&をループして、出力上の適切な場所に各フィールドを割り当てます。

私は今XMLファイルを読むためにこれを使用しています。

$f = [System.Xml.XmlReader]::Create("C:\Test\MyXMLFile.xml") 

while ($f.read()) { 
    switch ($f.NodeType) { 
     ([System.Xml.XmlNodeType]::Element) { 
      if ($f.Name -eq "doc") { 
       $e = [System.Xml.Linq.XElement]::ReadFrom($f)    
       $nbr = [String] $e.Attribute("id").Value 
       $fields = $e.Descendants("field") 
       foreach ($fld in $fields) { 
        $z = $fld.FirstAttribute.Value 
        $z1 = $fld.Element("val").Value 
       } 
       # write output 
      } 
     } 
    } 
} 

私が検討している方法よりも良い方法がありますか?

+1

それはあなたに良い意味であるかどうかによって異なります –

+0

@ MathiasR.Jessen - 残念なことに、「より良い」は必ずしもw帽子それは言う。この場合、「より良い」というのは簡単だと思います。しかし現実の世界では、「より良い」とはしばしば悪臭を放つオプションを意味します。 ;) –

答えて

3
あなた自身を指摘してきたように

、およびAnsgar has already shownとして、ここでは最良の手順は、以下のとおりです。

  1. 反復をファイルを使用して、一度可能なすべての列名を検索する
  2. ファイルを再度繰り返し、1に基づいて構造化オブジェクトを作成する。

HUGE xmlファイルを使用している場合、XmlReaderを使用するアプローチは、おそらくファイル全体を解析するよりも高速で、メモリの消費量は少なくなります。

私はあなたの現在のコードを簡素化し、それを2つの似たような別個の操作に分割します。

$objectTemplate = Get-FieldNames -AsHashTable 
:今、私たちは後で New-Object -Propertyで使用できるプロパティテーブル用のテンプレートを作成するために、最初の関数を使用することができます

# Import the XElement-to-XML linq assembly 
Add-Type -AssemblyName System.Xml.Linq |Out-Null 

function Get-FieldNames 
{ 
    param(
     [string]$Path = "C:\Test\MyXMLFile.xml", 
     [switch]$AsHashTable 
    ) 

    # Create reader 
    $xmlReader = [System.Xml.xmlReader]::Create($Path) 

    # Set up a dictionary 
    $hashTable = [ordered]@{} 

    # Read through the file 
    while ($xmlReader.Read()) 
    { 
     # Only interested in the <doc> elements 
     if($xmlReader.NodeType -eq [System.Xml.XmlNodeType]::Element -and $xmlReader.Name -eq "doc") 
     { 
      $docElement = [System.Xml.Linq.XElement]::ReadFrom($xmlReader) 
      foreach ($field in $docElement.Descendants("field")) 
      { 
       # Grab name of each field entry and set dictionary entry 
       $fieldName = $field.Attribute("name").Value 
       $hashTable[$fieldName] = $null 
      } 
     } 
    } 

    if($AsHashTable) 
    { 
     return $hashTable 
    } 
    else 
    { 
     return $hashTable.Keys 
    } 
} 

のは、フィールド名を収集、ステップ1から始めましょう

素晴らしい!すべて実際の値を解析するように設定されています。前とほとんど同じ戦略:

function Get-XMLFieldValues 
{ 
    param(
     [string]$Path = "C:\dev\test\huge.xml", 
     [hashtable]$Template 
    ) 

    # Create reader 
    $xmlReader = [System.Xml.xmlReader]::Create($Path) 

    # Read through the file 
    while ($xmlReader.Read()) 
    { 
     # Only interested in the <doc> elements 
     if($xmlReader.NodeType -eq [System.Xml.XmlNodeType]::Element -and $xmlReader.Name -eq "doc") 
     { 
      $docElement = [System.Xml.Linq.XElement]::ReadFrom($xmlReader) 

      # This is important - clone the template HashTable, don't reuse it 
      $objectProperties = $Template.Clone() 

      foreach ($field in $docElement.Descendants("field")) 
      { 
       # Grab name of the current field entry 
       $fieldName = $field.Attribute("name").Value 
       # Assign the value from the <val> child node 
       $objectProperties[$fieldName] = $($field.Descendants("val")|Select-Object -First 1).Value 
      } 
      # Create and emit a psobject 
      New-Object psobject -Property $objectProperties 
     } 
    } 
} 

は、ステップ1からハッシュテーブルとそれを組み合わせるら出来上がり:

Get-XMLFieldValues -Template $objectTemplate |ft -AutoSize 

Ready to Export!

今、すべてを行う必要があるExport-Csv -Delimite '|'に出力パイプであります代わりにFormat-Table

+0

これは完全に機能します!機能をありがとう。そして笑いのためにも....;) –

1

は、私はおそらくこのような何かをしたい:

[xml]$xml = Get-Content 'C:\Test\MyXMLFile.xml' 

# transform XML to list of custom objects 
$docs = $xml.SelectNodes('//doc') | ForEach-Object { 
    $props = @{} 
    $_.Field | ForEach-Object { $props[$_.name] = $_.val } 
    New-Object -Type PSObject -Property $props 
} 

# get list of unique property names 
$props = $docs | ForEach-Object { 
    Get-Member -InputObject $_ -Type NoteProperty 
} | Select-Object -Expand Name -Unique 

# add missing properties to objects 
$docs | ForEach-Object { 
    $doc = $_ 
    $props | Where-Object { 
     $_.PSObject.Properties.Name -notcontains $_ 
    } | ForEach-Object { 
     $doc | Add-Member -Type NoteProperty -Name $_ -Value '' 
    } 
} 

# export object list to CSV 
$docs | Export-Csv 'C:\Test\MyXMLFile.csv' -Delimiter '|' -NoType 
+1

'-Delimiter" | "'スイッチを最後の行に置いていますか? –

関連する問題