2017-08-14 100 views
1

我需要一些幫助來理解PowerShell中的XML。 我有幾個XML文件是這樣的:使用PowerShell解析帶名稱空間的XML

<?xml version="1.0" encoding="UTF-8"?> 
<catalog xmlns="http://www.example.com/xml/catalog/2006-10-31"> 
    <product product-id="11210"> 
     ... 
     <available-flag>true</available-flag> 
     <online-flag>false</online-flag> 
     <online-flag site-id="ru">true</online-flag> 
     <online-flag site-id="fr">true</online-flag> 
     <online-flag site-id="uk">false</online-flag> 
     <online-flag site-id="de">true</online-flag> 
     ... 
    </product> 
    <product product-id="50610"> 
     ... 
     <available-flag>true</available-flag> 
     <online-flag>true</online-flag> 
     <online-flag site-id="ru">false</online-flag> 
     <online-flag site-id="fr">true</online-flag> 
     <online-flag site-id="uk">false</online-flag> 
     <online-flag site-id="de">fasle</online-flag> 
     ... 
    </product> 
    <product product-id="82929"> 
     ... 
     <available-flag>true</available-flag> 
     <online-flag>true</online-flag> 
     <online-flag site-id="ru">false</online-flag> 
     <online-flag site-id="fr">true</online-flag> 
     <online-flag site-id="uk">false</online-flag> 
     <online-flag site-id="de">true</online-flag> 
     ... 
    </product> 
</catalog> 

我需要兩個元素的值在PowerShell中:

  • <online-flag>(不site-id屬性)
  • <online-flag site-id="ru">

對於帶有product-id="50610"的產品。

我有以下代碼:

$Path = "C:\Temp\0\2017-08-12_190211.xml" 
$XPath = "/ns:catalog/ns:product[@product-id='50610']" 

$files = Get-ChildItem $Path | Where {-not $_.PSIsContainer} 

if ($files -eq $null) { 
    return 
} 

foreach ($file in $files) { 
    [xml]$xml = Get-Content $file 
    $namespace = $xml.DocumentElement.NamespaceURI 
    $ns = New-Object System.Xml.XmlNamespaceManager($xml.NameTable) 
    $ns.AddNamespace("ns", $namespace) 
    $product = $xml.SelectSingleNode($XPath, $ns) 
} 

幾個問題:

  1. 有了這個代碼,我可以選擇所需要的產品節點。 PowerShell中顯示:

    online-flag  : {true, online-flag, online-flag, online-flag...} 
    

    但如何然後我可以選擇所需要的online-flag元素的值(如果有可能兩種方式:一個的XPath和對象之一)?

  2. 是否可以用「對象」方式選擇一個節點?就像這樣:

    $product = $xml.catalog.product | 
          Where-Object {$_."product-id".value -eq "50610"} 
    
  3. 如果我有幾個文件,究竟是選擇文件名,全球在線標誌(無屬性),具體的網上旗的最佳方式?

回答

1

使用兩個不同的XPath表達式:

  1. 用於選擇節點,而無需特定的屬性:

    //ns:product[@product-id='50610']/ns:online-flag[not(@site-id)] 
    
  2. 用於選擇節點具有特定屬性值:

    //ns:product[@product-id='50610']/ns:online-flag[@site-id='ru'] 
    
$XPath = "/ns:catalog/ns:product[@product-id='50610']" 
... 
$product = $xml.SelectSingleNode($XPath, $ns) 
$product.SelectSingleNode("./ns:online-flag[not(@site-id)]", $ns) 
$product.SelectSingleNode("./ns:online-flag[@site-id='ru']", $ns) 

如果你需要得到包括文件名的數據和兩個節點的值,我建議:您可以通過相對XPath表達式當前節點(.)選擇相對於已經選擇的節點的節點建設自定義對象:

$files | ForEach-Object { 
    [xml]$xml = Get-Content $_ 
    ... 
    New-Object -Type PSObject -Property @{ 
     'Filename' = $_ 
     'online' = $product.SelectSingleNode("./ns:online-flag[not(@site-id)]", $ns).'#text' 
     'ru_online' = $product.SelectSingleNode("./ns:online-flag[@site-id='ru']", $ns).'#text' 
    } 
} 

使用點符號和過濾通過Where-Object應該是可能的,但我不會推薦它。我發現XPath更加高效。

+0

安斯加爾您好!感謝您的回答。我已經提到這個點符號正在工作,我同意這不方便。你的例子的問題是我的XML文件很大,選擇兩個節點需要時間。首先可以像我的示例中那樣選擇產品,然後使用XPath選擇聯機標記元素的值?在這種情況下,XPath會是什麼? – Alterant

+0

我嘗試了所有以下沒有運氣: $ product.SelectSingleNode(「/ ns:product/ns:online-flag [@ site-id ='ru']」,$ ns), $ product.SelectSingleNode(「/ns:online-flag [@ site-id ='ru']「,$ ns), $,product_SelectSingleNode(」/ product/online-flag [@ site-id ='ru']「), $ product.SelectSingleNode( 「/在線標誌[@站點-ID = 'RU']」)。 This $ product。GetElementsByTagName(「在線標誌」)的作品。但結果不是一個單一的價值,而是一個價值清單。 – Alterant

+0

在這裏找到答案:https://stackoverflow.com/questions/2238201/xmlnode-selectsinglenode-returns-element-outside-current – Alterant

0

我能得到我需要的「對象」的方式中的數據:

$product = $xml.catalog.product | Where-Object {$_."product-id" -eq "50610"} 
$of = $product."online-flag" 
$glblsid = $of | Where-Object {$_ -is [System.String]} 
$specsid = ($of | Where-Object {$_."site-id" -eq "ru"})."#text" 

但我不喜歡,我設法做到這一點的方式。有更方便的解決方案嗎?

第二個問題的答案是肯定的 - 請參閱第一行。

1

要完成此主題。我測量了3種方法的性能:點樣式,文件上的XPath和節點上的XPath。他們之間沒有顯着差異。 以下是詳細信息。

我解析了2次2個文件,每個60MB。

  1. 對象樣式(點樣式)

    ... 
    $StartTime = Get-Date 
    foreach ($file in $files) { 
        [xml]$xml = Get-Content $file 
    
        #Object style 
        $product = $xml.catalog.product | Where-Object {$_."product-id" -eq "50610"} 
        $of = $product."online-flag" 
        $glblsid = $of | Where-Object {$_ -is [System.String]} 
        $specsid = ($of | Where-Object {$_."site-id" -eq "ru"})."#text" 
        Write-Output "$($file.Name) $glblsid $specsid" 
    } 
    $EndTime = Get-Date 
    $TimeSpan = New-TimeSpan -Start $StartTime -End $EndTime 
    Write-Output $TimeSpan.TotalMilliseconds 
    

    結果:

    ... 
    $StartTime = Get-Date 
    foreach ($file in $files) { 
        [xml]$xml = Get-Content $file 
    
        #XPath on the file 
        $namespace = $xml.DocumentElement.NamespaceURI 
        $ns = New-Object System.Xml.XmlNamespaceManager($xml.NameTable) 
        $ns.AddNamespace("ns", $namespace) 
        $glblsid = $xml.SelectSingleNode("/ns:catalog/ns:product[@product-id='50610']/ns:online-flag[not(@site-id)]", $ns).'#text' 
        $specsid = $xml.SelectSingleNode("/ns:catalog/ns:product[@product-id='50610']/ns:online-flag[@site-id='ru']", $ns).'#text' 
        Write-Output "$($file.Name) $glblsid $specsid" 
    } 
    $EndTime = Get-Date 
    $TimeSpan = New-TimeSpan -Start $StartTime -End $EndTime 
    Write-Output $TimeSpan.TotalMilliseconds 
    

    結果:

     
    PS> .\ParseXML2.ps1 
    2017-08-10_190159.xml false false 
    2017-08-11_190203.xml false true 
    36269,535 
    PS> .\ParseXML2.ps1 
    2017-08-10_190159.xml false false 
    2017-08-11_190203.xml false true 
    36628,3304 
    
  2. 上的文件的XPath:

     
    PS> .\ParseXML2.ps1 
    2017-08-10_190159.xml false false 
    2017-08-11_190203.xml false true 
    36129,1368 
    PS> .\ParseXML2.ps1 
    2017-08-10_190159.xml false false 
    2017-08-11_190203.xml false true 
    38890,3014 
    
  3. 的XPath的節點上:

    ... 
    $StartTime = Get-Date 
    foreach ($file in $files) { 
        [xml]$xml = Get-Content $file 
    
        #XPath on the node 
        $namespace = $xml.DocumentElement.NamespaceURI 
        $ns = New-Object System.Xml.XmlNamespaceManager($xml.NameTable) 
        $ns.AddNamespace("ns", $namespace) 
        $product = $xml.SelectSingleNode("/ns:catalog/ns:product[@product-id='50610']", $ns) 
        $glblsid = $product.SelectSingleNode("ns:online-flag[not(@site-id)]", $ns).'#text' 
        $specsid = $product.SelectSingleNode("ns:online-flag[@site-id='ru']", $ns).'#text' 
        Write-Output "$($file.Name) $glblsid $specsid" 
    } 
    $EndTime = Get-Date 
    $TimeSpan = New-TimeSpan -Start $StartTime -End $EndTime 
    Write-Output $TimeSpan.TotalMilliseconds 
    

    結果:

     
    PS> .\ParseXML2.ps1 
    2017-08-10_190159.xml false false 
    2017-08-11_190203.xml false true 
    33477,1708 
    PS> .\ParseXML2.ps1 
    2017-08-10_190159.xml false false 
    2017-08-11_190203.xml false true 
    34116,7626