【问题标题】:Parsing XML with namespace with PowerShell使用 PowerShell 解析带有命名空间的 XML
【发布时间】:2017-08-15 16:56:43
【问题描述】:

我需要一些帮助来理解 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 中获取两个元素的值:

  • &lt;online-flag&gt;(不带 site-id 属性)
  • &lt;online-flag site-id="ru"&gt;

对于带有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. 如果我有多个文件,选择文件名、全局在线标志(无属性)、特定在线标志的最佳方法是什么?

【问题讨论】:

    标签: xml powershell xpath


    【解决方案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 表达式相对于当前节点 (.) 来选择相对于已选择节点的节点:

    $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 的效率要高得多。

    【讨论】:

    • 你好安斯加尔!感谢您的回答。我已经提到点符号是有效的,我同意它不方便。您的示例的问题是我的 xml 文件很大,选择两个节点需要时间。是否可以像我的示例一样先选择产品,然后使用 XPath 选择在线标志元素的值?在这种情况下,XPath 是什么?
    • 我尝试了以下所有方法,但没有成功:$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("/online-flag[@site-id='ru']")。这个 $product.GetElementsByTagName("online-flag") 有效。但结果不是单个值,而是值列表。
    • 在这里找到答案:stackoverflow.com/questions/2238201/…
    • 需要使用 $product.SelectSingleNode("ns:online-flag[not(@site-id)]", $ns) 或 $product.SelectSingleNode("./ns:online -flag[not(@site-id)]", $ns) 在当前节点中搜索。非常感谢!
    • 我认为命名空间是&lt;xml&gt;&lt;ns:node&gt;val&lt;/ns:node&gt;?我在xmlfile 中看不到它
    【解决方案2】:

    我能够通过“对象”的方式获得我需要的数据:

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

    但我不喜欢我设法做到这一点的方式。有没有更方便的解决方案?

    第二个问题的答案是肯定的——见第一行。

    【讨论】:

      【解决方案3】:

      完成这个话题。我测量了 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 假 假 2017-08-11_190203.xml 假真 36269,535 PS> .\ParseXML2.ps1 2017-08-10_190159.xml 假 假 2017-08-11_190203.xml 假真 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 假 假 2017-08-11_190203.xml 假真 36129,1368 PS> .\ParseXML2.ps1 2017-08-10_190159.xml 假 假 2017-08-11_190203.xml 假真 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 假 假 2017-08-11_190203.xml 假真 33477,1708 PS> .\ParseXML2.ps1 2017-08-10_190159.xml 假 假 2017-08-11_190203.xml 假真 34116,7626

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-02-01
        • 2012-06-09
        • 2010-09-12
        • 2014-07-28
        • 2021-10-24
        • 2019-03-17
        • 2014-01-30
        • 2023-04-03
        相关资源
        最近更新 更多