1

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>

3 Answers3

1

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
Theo
  • 57,719
  • 8
  • 24
  • 41
  • Using a script block for custom sorting is promising, but I don't think that hard-coding all values to sort by is a practical solution. – mklement0 Oct 18 '21 at 22:19
1

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.

mklement0
  • 382,024
  • 64
  • 607
  • 775
0

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
Mathias R. Jessen
  • 157,619
  • 12
  • 148
  • 206
  • How would I dynamically prepare the $strings array as input for 'Array.Sort()' if the list that I have is comprised of 30+ pdfs from a folder where new pdfs might be added or names changed in the future? for instance: if the EN pdf was later changed to 12-EN-55.pdf and 20 similarly named (not EN) pdfs were added and I didn't want to copy the names by hand? Thank you for your help. – Don Cummins Oct 18 '21 at 16:39
  • The same way you'd otherwise programmatically discover the files in the folde, `$strings = Get-ChildItem -Filter .pdf |% Name` or similar. I'm not sure what you mean by "changed in the future" - I'm afraid I don't know how to implement precognition :-) – Mathias R. Jessen Oct 18 '21 at 16:40