Сортировка и удаление элементов документа XML с помощью PowerShell - PullRequest
0 голосов
/ 29 июня 2018

Я пытаюсь организовать 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>

Мне нужно отсортировать элементы каждого пакета в следующем порядке:

  1. PackageName
  2. PackageFeedName
  3. Версия
  4. Ароматизатор

Некоторые элементы пакетов уже находятся в правильном порядке, некоторые нет, как в моем примере XML-кода. Также каждый пакет должен быть отсортирован в алфавитном порядке на основе PackageName. Я новичок в работе с XML в PowerShell и не могу понять, как этого добиться.

Другое требование - найти и удалить все элементы <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-данных, добавив в него пространство имен, поскольку некоторые документы, с которыми я работаю, содержат их. Код ниже обрабатывает пространства имен. Я надеюсь, что это поможет кому-то еще с подобной проблемой / вопросами!

[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

Ответы [ 2 ]

0 голосов
/ 29 июня 2018

Код, который вы удалили, удаляет все <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>. В этом случае вам необходимо использовать шаблон фильтра, хотя

"./Driver[Component/Package/PackageName/text()='$_']"
0 голосов
/ 29 июня 2018

Преобразование 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()
...