我正在尝试组织一个包含驱动程序信息的XML文档。这是我正在使用的示例:

<?xml version="1.0" encoding="utf-8"?>
<IncludeFragment xmlns:p="http://schemas.microsoft.com/someschema">>
  <FFUDriver>
    <Component>
      <Package>
        <p:PackageName>Intel.Display.Driver</PackageName>
        <p:PackageFeedName>Feed</PackageFeedName>
        <p:Version>10.24.0.1638</Version>
        <p:Flavor>release</Flavor>
      </Package>
    </Component>
  </FFUDriver>
  <FFUDriver>
    <Component>
      <Package>
        <p:PackageName>Intel.Audio.Driver</PackageName>
        <p:PackageFeedName>Feed</PackageFeedName>
        <p:Flavor>release</Flavor>
        <p:Version>10.24.0.1638</Version>
        <p:CabName>Intel.Audio.cab</CabName>
      </Package>
    </Component>
  </FFUDriver>
</IncludeFragment>

我需要按以下顺序对每个Packages的元素进行排序:
  • PackageName
  • PackageFeedName
  • 版本
  • 风味

  • 某些Packages元素已经按照正确的顺序排列,而有些则没有,例如在我的示例XML代码中。同样,每个Package都需要基于PackageName按字母顺序排序。我是在PowerShell中使用XML的新手,我一生都无法弄清楚如何实现这一点。

    另一个要求是找到并删除所有<CabName>元素。我有点想通了。我下面的代码不幸地删除了<Package>元素的所有子元素,如果其子元素之一是<CabName>。我似乎无法弄清楚仅选择和删除<CabName>的语法。

    $Path = 'C:\Drivers.xml'
    $xml = New-Object -TypeName XML
    $xml.Load($Path)
    
    $xml.SelectNodes('//Package[CabName]') | ForEach-Object {
        $_.ParentNode.RemoveChild($_)
    }
    
    $xml.Save('C:\Test.xml')
    

    更新:在Ansgar Wiechers的帮助下,这是完成的代码。我更新了示例XML数据以包含 namespace ,因为我使用的某些文档包含它们。下面的代码处理 namespace 。希望这对其他有类似问题的人有所帮助!
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory = $True, Position = 0)]
        [ValidateScript({
            $_ = $_ -replace '"', ""
            if (-Not (Test-Path -Path $_ -PathType Leaf))
            {
                Throw "`n `n$_ `n `nThe specified file or path does not exist. Check the file name and path, and then try again."
            }
            return $True
        })]
        [System.String]$XMLPath,
    
        [Parameter(Mandatory = $False, Position = 1)]
        [System.String]$nsPrefix = "p",
    
        [Parameter(Mandatory = $False, Position = 2)]
        [System.String]$nsURI = "http://schemas.microsoft.com/someschema"
    )
    
    
    # Remove quotes from full path name, if they are present
    $XMLPath = $XMLPath -replace '"', ""
    
    
    $xml = New-Object -TypeName XML
    $xml.Load($XMLPath)
    $ns = New-Object System.Xml.XmlNamespaceManager($xml.NameTable)
    $ns.AddNamespace($nsPrefix, $nsURI)
    
    
    # Delete all CabName elements
    $xml.SelectNodes('//p:CabName', $ns) | ForEach-Object {
    
        $_.ParentNode.RemoveChild($_) | Out-Null
    }
    
    
    # Sort each Package element's child nodes based on custom order
    $SortList = 'p:PackageName', 'p:PackageFeedName', 'p:Version', 'p:Flavor'
    
    $xml.SelectNodes('//Package') | ForEach-Object {
    
        $parent = $_
    
        $SortList | ForEach-Object {
    
            $child = $parent.RemoveChild($parent.SelectSingleNode("./$_", $ns))
            $parent.AppendChild($child)
        }
    } | Out-Null
    
    
    # Sort each Package element in alphabetical order based on its child node PackageName
    $PackageNameList = $xml.SelectNodes('//p:PackageName', $ns) | Select-Object -Expand '#text' | Sort-Object
    
    $xml.SelectNodes('//IncludeFragment') | ForEach-Object {
    
        $parent = $_
    
        $PackageNameList | ForEach-Object {
    
            $child = $parent.RemoveChild($parent.SelectSingleNode("./FFUDriver[Component/Package/p:PackageName/text()='$_']", $ns))
            $parent.AppendChild($child)
        }
    } | Out-Null
    
    
    $XMLPath = $XMLPath -replace ".xml", "_sorted.xml"
    
    $xml.Save($XMLPath)
    
    Write-Host "`nSorting complete. Sorted XML document saved under $XMLPath" -ForegroundColor Green
    

    最佳答案

    您拥有的代码将删除所有具有子元素<Package><CabName>节点,而不仅仅是此类节点的所有子元素。这是因为//Package[CabName]与包含<Package>子节点的所有<CabName>节点匹配。您实际要匹配的是所有具有<CabName>父节点的<Package>节点。

    $xml.SelectNodes('./Package/CabName') | ForEach-Object {
        $_.ParentNode.RemoveChild($_) | Out-Null
    }
    

    而且,通常XML中元素的顺序无关紧要,因此对元素进行排序是毫无意义的。但是,如果由于某种原因必须按特定顺序设置子节点,则可以通过按所需顺序删除和附加元素来对元素进行排序。
    # names of the child nodes in the desired order
    $nodenames = 'PackageName', 'PackageFeedName', 'Version', 'Flavor'
    
    $xml.SelectNodes('//Package') | ForEach-Object {
        $parent = $_
    
        $nodenames | ForEach-Object {
            $child = $parent.RemoveChild($parent.SelectSingleNode("./$_"))
            $parent.AppendChild($child)
        }
    }
    

    如果还希望按包名称对<Driver>节点进行排序,则首先需要构建包名称的排序列表:
    $xml.SelectNodes('//PackageName') | Select-Object -Expand '#text' | Sort-Object
    

    然后使用与上述相同的技术从<Driver>节点中删除<Drivers>节点并将其附加到ojit_code节点。在这种情况下,您必须使用过滤器模式
    "./Driver[Component/Package/PackageName/text()='$_']"
    

    08-16 18:52