Home > Mobile >  Powershell: Order list in custom format
Powershell: Order list in custom format

Time:10-19

I have been attempting to dynamically create a list in a .xml file based on a list of all the files in a folder. However, the list in the .xml file needs to be in a certain order and have various information with it.

I have successfully made a list, I just haven't figured out how to order it the way that I want.

Example: List of files in the D:\ folder

BG.pdf
CS.pdf
EN.pdf
17.pdf

This folder can have any number of PDF files in it and the list created needs to consistently have the same/similar order. Essentially 17 needs to be first and EN needs to be second, everything else can be put in alphabetical order.

Here is what I have so far:

$filePath = "D:\"

$list = Get-ChildItem -Path $filePath -Recurse | `
        Where-Object { $_.PSIsContainer -eq $false -and $_.Extension -ne '.srt' }

New-Item $filePath’\.PDFList.txt'

$var = "<PDFList>" | Out-File -Append $filePath’\.PDFList.txt'

ForEach($n in $list){
    if($n.name.Contains('EN')) {
        $var = "    <PDF><language>English</language><filename>"  $n.Name  "</filename></PDF>" | Out-File -Append $filePath’\.PDFList.txt'
    } elseif($n.name.Contains('BG')) {
        $var = "    <PDF><language>Bulgarian</language><filename>"  $n.Name  "</filename></PDF>" | Out-File -Append $filePath’\.PDFList.txt'
    } elseif($n.name.Contains('CS')) {
        $var = "    <PDF><language>Czech</language><filename>"  $n.Name  "</filename></PDF>" | Out-File -Append $filePath’\.PDFList.txt'
    } elseif($n.name.Contains('17')) {
        $var = "    <PDF><language>17</language><filename>"  $n.Name  "</filename></PDF>" | Out-File -Append $filePath’\.PDFList.txt'
    }

$var = "<PDFList>" | Out-File -Append $filePath’\.PDFList.txt'

Rename-Item -Path $filePath’\.PDFList.txt' -NewName $filePath’\.PDFList.xml'

This is what the current result is:

<PDFList>
     <PDF><language>Bulgarian</language><filename>BG.pdf</filename></PDF>
     <PDF><language>Czech</language><filename>CS.pdf</filename></PDF>
     <PDF><language>English</language><filename>EN.PDF</filename></PDF>
     <PDF><language>17</language><filename>17.pdf</filename></PDF>
<PDFList>

I want the list put in the .xml file to be this:

<PDFList>
     <PDF><language>17</language><filename>17.pdf</filename></PDF>
     <PDF><language>English</language><filename>EN.PDF</filename></PDF>
     <PDF><language>Bulgarian</language><filename>BG.pdf</filename></PDF>
     <PDF><language>Czech</language><filename>CS.pdf</filename></PDF>
<PDFList>

CodePudding user response:

You could also use the Array IndexOf() method combined with Sort-Object for this:

$sorted = 'GB','CS','EN','17' | Sort-Object { ('17','EN','GB','CS').IndexOf($_) }
$sorted
17
EN
GB
CS

CodePudding user response:

Use PowerShell's ability to define custom .NET classes to implement a custom IComparer[string], which you can then use with methods like Array.Sort() and List.Sort():

# Define our custom string comparer
class SevenTeenAndEnglishComparer : System.Collections.Generic.IComparer[string]
{
    [int]
    Compare([string]$a, [string]$b){
        # Handle 17* first
        if($a -like '17*' -and $b -notlike '17*'){
            return -1
        }
        elseif($b -like '17*' -and $a -notlike '17*'){
            return 1
        }

        # Handle EN* next
        if($a -like 'EN*' -and $b -notlike 'EN*'){
            return -1
        }
        elseif($b -like 'EN*' -and $a -notlike 'EN*'){
            return 1
        }

        # Handle everything else as we normally would
        return [System.StringComparer]::CurrentCultureIgnoreCase.Compare($a,$b)
    }
}

# Prepare string array as input for `Array.Sort()`
$strings = -split @'
BG.pdf
CS.pdf
EN.pdf
17.pdf
'@ -as [string[]]

# Sort strings, but do it according to the logic of our new custom comparer
[array]::Sort($strings, [SevenTeenAndEnglishComparer]::new())

# Observe that strings are now ordered correctly
$strings

CodePudding user response:

A pragmatic solution is to use two sort criteria with Sort-Object:

# Simulate the output from your Get-ChildItem call
$list = [System.IO.FileInfo[]] (
  'CS.pdf',
  'BG.pdf',
  'EN.pdf',
  '17.pdf'
)

$customSortedNames = (
  $list | Sort-Object { $_.BaseName -notin '17', 'EN' }, BaseName
).Name

# Output the sort result
$customSortedNames

The above yields:

17.pdf
EN.pdf
BG.pdf
CS.pdf

Note that this relatively simple solution relies on the fact that the desired custom order of the two exceptions - 17 first, then EN - happens to reflect the lexical sort order among them too.

With a fixed number of known exceptions with known relative sort order, you could also do something like (note that .IndexOf() is case-sensitive; for a case-insensitive alternative, see this answer):

# Note that the desired order among the exceptions must be stated *in reverse*.
$list | Sort-Object { -('EN', '17').IndexOf($_.BaseName) }, BaseName

If you need more sophisticated logic than that, consider Mathias R. Jessen's helpful answer.

  • Related