使用PowerShell解析XML命名空间

时间:2017-08-14 14:21:52

标签: xml powershell xpath

我需要一点帮助来理解PowerShell中的XML。 我有几个像这样的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 1和对象1)?

  2. 是否可以以“对象”方式选择节点?像这样:

    $product = $xml.catalog.product |
               Where-Object {$_."product-id".value -eq "50610"}
    
  3. 如果我有多个文件,选择文件名,全局在线标志(没有属性),特定在线标志的最佳方式是什么?

3 个答案:

答案 0 :(得分:1)

我能够通过&#34;对象&#34;获得我需要的数据。方式:

$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 :(得分: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']
    
  3. 您可以通过使XPath表达式相对于当前节点(.)来选择相对于已选定节点的节点:

    $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)
    

    如果您需要包含文件名和两个节点值的结果数据,我建议您构建自定义对象:

    $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效率更高。

答案 2 :(得分: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
    

    结果:

    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:

    ...
    $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
    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