使用 PowerShell 排序和删除 XML 个文档元素
Sorting and deleting XML document elements using PowerShell
我正在尝试组织一个包含驱动程序信息的 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>
我需要按以下顺序对每个包的元素进行排序:
- 包名
- PackageFeedName
- 版本
- 风味
有些包的元素已经按照正确的顺序排列,有些则没有,如我的示例 XML 代码所示。此外,每个包都需要根据 PackageName 按字母顺序排序。我是在 PowerShell 中使用 XML 的新手,我终其一生都不知道如何完成这项工作。
另一个要求是找到并删除所有 <CabName>
元素。我有点明白了。不幸的是,我在下面的代码删除了 <Package>
元素的所有子元素,如果它的子元素之一是 <CabName>
。我似乎无法弄清楚 select 的语法并仅删除 <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 数据以包含名称空间,因为我使用的一些文档包含它们。下面的代码处理名称空间。我希望这可以帮助其他有类似 problem/questions!
的人
[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
XML 此作业不需要转换:
$xml = @"
<?xml version="1.0" encoding="utf-8"?>
<Drivers>
<Driver>
<Component>
<Package>
<PackageName>Intel.Display.Driver</PackageName>
<PackageFeedName>Feed</PackageFeedName>
<Version>10.24.0.1638</Version>
<Flavor>release</Flavor>
</Package>
</Component>
</Driver>
<Driver>
<Component>
<Package>
<PackageName>Intel.Audio.Driver</PackageName>
<PackageFeedName>Feed</PackageFeedName>
<Flavor>release</Flavor>
<Version>10.24.0.1638</Version>
<CabName>Intel.Audio.cab</CabName>
</Package>
</Component>
</Driver>
</Drivers>
"@
$XMLSorted = [System.Text.StringBuilder]::new()
$packageName = ''
$packageFeedName = ''
$version = ''
$flavor = ''
foreach( $line in @($xml -split [Environment]::NewLine) ) {
if( $line -like '*<PackageName>*' ) {
$packageName = $line
}
elseif( $line -like '*<PackageFeedName>*' ) {
$packageFeedName = $line
}
elseif( $line -like '*<Version>*' ) {
$version = $line
}
elseif( $line -like '*<Flavor>*' ) {
$flavor = $line
}
elseif( $line -like '*<CabName>*' ) {
# nothing to do
}
elseif( $line -like '*</Package>*' ) {
[void]$XMLSorted.AppendLine( $packageName )
[void]$XMLSorted.AppendLine( $packageFeedName )
[void]$XMLSorted.AppendLine( $version )
[void]$XMLSorted.AppendLine( $flavor )
[void]$XMLSorted.AppendLine( $line )
}
else {
[void]$XMLSorted.AppendLine( $line )
}
}
#Result:
$XMLSorted.ToString()
您的代码会删除所有 <Package>
个具有子元素 <CabName>
的节点,而不仅仅是这些节点的所有子元素。这是因为 //Package[CabName]
匹配包含 <CabName>
个子节点的所有 <Package>
个节点。您真正想要匹配的是所有具有 <Package>
个父节点的 <CabName>
个节点。
$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>
节点 from/to <Drivers>
节点。在这种情况下,您需要必须使用过滤模式,尽管
"./Driver[Component/Package/PackageName/text()='$_']"
我正在尝试组织一个包含驱动程序信息的 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>
我需要按以下顺序对每个包的元素进行排序:
- 包名
- PackageFeedName
- 版本
- 风味
有些包的元素已经按照正确的顺序排列,有些则没有,如我的示例 XML 代码所示。此外,每个包都需要根据 PackageName 按字母顺序排序。我是在 PowerShell 中使用 XML 的新手,我终其一生都不知道如何完成这项工作。
另一个要求是找到并删除所有 <CabName>
元素。我有点明白了。不幸的是,我在下面的代码删除了 <Package>
元素的所有子元素,如果它的子元素之一是 <CabName>
。我似乎无法弄清楚 select 的语法并仅删除 <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 数据以包含名称空间,因为我使用的一些文档包含它们。下面的代码处理名称空间。我希望这可以帮助其他有类似 problem/questions!
的人[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
XML 此作业不需要转换:
$xml = @"
<?xml version="1.0" encoding="utf-8"?>
<Drivers>
<Driver>
<Component>
<Package>
<PackageName>Intel.Display.Driver</PackageName>
<PackageFeedName>Feed</PackageFeedName>
<Version>10.24.0.1638</Version>
<Flavor>release</Flavor>
</Package>
</Component>
</Driver>
<Driver>
<Component>
<Package>
<PackageName>Intel.Audio.Driver</PackageName>
<PackageFeedName>Feed</PackageFeedName>
<Flavor>release</Flavor>
<Version>10.24.0.1638</Version>
<CabName>Intel.Audio.cab</CabName>
</Package>
</Component>
</Driver>
</Drivers>
"@
$XMLSorted = [System.Text.StringBuilder]::new()
$packageName = ''
$packageFeedName = ''
$version = ''
$flavor = ''
foreach( $line in @($xml -split [Environment]::NewLine) ) {
if( $line -like '*<PackageName>*' ) {
$packageName = $line
}
elseif( $line -like '*<PackageFeedName>*' ) {
$packageFeedName = $line
}
elseif( $line -like '*<Version>*' ) {
$version = $line
}
elseif( $line -like '*<Flavor>*' ) {
$flavor = $line
}
elseif( $line -like '*<CabName>*' ) {
# nothing to do
}
elseif( $line -like '*</Package>*' ) {
[void]$XMLSorted.AppendLine( $packageName )
[void]$XMLSorted.AppendLine( $packageFeedName )
[void]$XMLSorted.AppendLine( $version )
[void]$XMLSorted.AppendLine( $flavor )
[void]$XMLSorted.AppendLine( $line )
}
else {
[void]$XMLSorted.AppendLine( $line )
}
}
#Result:
$XMLSorted.ToString()
您的代码会删除所有 <Package>
个具有子元素 <CabName>
的节点,而不仅仅是这些节点的所有子元素。这是因为 //Package[CabName]
匹配包含 <CabName>
个子节点的所有 <Package>
个节点。您真正想要匹配的是所有具有 <Package>
个父节点的 <CabName>
个节点。
$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>
节点 from/to <Drivers>
节点。在这种情况下,您需要必须使用过滤模式,尽管
"./Driver[Component/Package/PackageName/text()='$_']"