Home > Mobile >  PowerShell - Complex replacement in a string
PowerShell - Complex replacement in a string

Time:05-28

I have a very complex question regarding a complex replacement in a file name, I will try to explain it

I am reading a file name that has an agreed signs in our company such as: , _ that representing a different signs.

' ' represent a space 
'_' represent '/'

According to those signs I am searching the file in the right place in the TFS and its working fine when '' is really represent '/' but what happen when '' is part of a name?

Lets see an easy example while there is only one '_' in the file name mail_sp.sql It could be converted to : mail/sp.sql or mail_sp.sql Here is the code I wrote (I don't know if its working or not, feel free to correct me please)

CheckFileExistsTfs is a function that return True or False depend if the file exist in the TFS

function CheckFileExistsTfs {
    param(
        [Parameter(Mandatory)]
        [string] $tfsFilePath,
        [string] $userName,
        [string] $userPassword,
        [string] $userDomain
    )

    Add-Type -AssemblyName 'System.Net.Http'

    $clientHandler = New-Object System.Net.Http.HttpClientHandler

    if ($userName) {
        $clientHandler.Credentials = new-object System.Net.NetworkCredential($userName, $userPassword, $userDomain)
    }
    else {
        $clientHandler.UseDefaultCredentials = 1
    }
    
    $client = New-Object System.Net.Http.HttpClient($clientHandler)

    $tfsBranchPath = "https://tfs.bandit.com/DefaultCollection/Idu Client-Server/_apis/tfvc/items?scopePath=$tfsFilePath"

    #Write-Host "Sending get request (items api) to url: " $tfsBranchPath
    $response = $client.GetAsync($tfsBranchPath).Result
    $contentObjects = $response.Content.ReadAsStringAsync().Result | ConvertFrom-Json

    $exists = $contentObjects.count -eq 1
    
    $fileSize = 0
    
    if ($exists) {
        $fileSize = $contentObjects.value[0].size / 1024
    }   

    $result = [PSCustomObject]@{
        FileExists      = $exists
        FileSizeInBytes = $fileSize
    }

    return $result
}

$PatchBranch is the path in the TFS like $/Idu Client-Server/Branches/V6.4/Releases/v8.6.24-CU/Database/

$sql = mail_sp.sql
$newname = $sql.Name.Replace(' ', ' ').replace('_', '/')
$filePathInTfs = $PatchBranch   $new
$fileExist = CheckFileExistsTfs $filePathInTfs

if ($fileExist)
{ Write-Host "TFS file existance is: [$fileExist] by path [$filePathInTfs]" }
                         
elseif (!$fileExist) {
    $new = $sql.Name.Replace(' ', ' ')
    $filePathInTfs = $PatchBranch   $new
    $fileExist = CheckFileExistsTfs $filePathInTfs

    if ($fileExist) 
    { Write-Host "TFS file existance is: [$fileExist] by path [$filePathInTfs]" }

}
else { Write-Host "TFS file not exist : [$fileExist] by path [$filePathInTfs] " }
                                                        

But what if the name is mail_sp_sp.sql? or even more '_' will be many combinations:

mail/sp_sp.sql
mail/sp/sql.sql
mail_sp/sql.sql
mail_sp_sql.sql
mail/sp_sql.sql

Additional example:

AD mail_sp.sql should be converted to:

  AD mail_sp.sql
  AD mail/sp.sql

The goal is to check each combination and if one of them is true stop the iteration and display a message, only when all the tries failed then return false or give a proper message.

What I am asking is to check if the first code is working (when there is only one '_') and help to check the complex combinations Thanks

CodePudding user response:

Preamble

Per comments, to paraphrase your question to make sure I understand it, you have a file path which you've encoded by replacing / with _ and (space) with . You want to reverse the encoding to get back to the original file path, but your encoding is ambiguous:

  • _ in your encoded string could mean / or _ in the original file path
  • in your encoded string could mean or (space) in the original file path

As a result, you need to generate all possible original values and then find the one that exists on your file system.

As an aside, this is why a lot of encodings have "escape sequences" so you know whether some part of the string is a literal or a token - e.g. in C# var x = "line1\nline2\nline3"; - the \n unambiguously represents a new line character. If you wanted the string literal \n instead you'd have to escape the \ like this: var x = "line1\\nline2\\nline3";. This way, there's only one original value that any encoded string can represent.

If you're able to, you might want to revisit your encoding rules by adding an escape character to make the encoded strings unambiguous.

Answer

I wrote an answer a while ago for a different question about transliterating words with Cyrillic characters into Latin characters that you could repurpose - see variable transliteration (powershell).

All you need to do with that answer is change the lookup table like this:

function ConvertTo-DecodedPath
{
    param(
        [string] $InputString
    )

    $lookups = [ordered] @{
        # single character substitutions
        # (we need to use the [char] cast to force case sensitivity for keys)
        [char] "_" = @( "_", "/" )
        # multi-character substitutions
        [string] " " = @( " ", " " )
    }

    # if the input is empty then there's no work to do,
    # so just return an empty string
    if( [string]::IsNullOrEmpty($InputString) )
    {
        return [string]::Empty;
    }

    # find all the lookups that can be applied at the start of the string
    $keys = @( $lookups.Keys | where-object { $InputString.StartsWith($_) } );

    # if there are no lookups found at the start of the string we'll keep
    # the first character as-is and prefix it to all the transliterations
    # for the remainder of the string
    if( $keys.Length -eq 0 )
    {
        $results = @();
        $head    = $InputString[0];
        $rest    = $InputString.Substring(1);
        $tails   = ConvertTo-DecodedPath -InputString $rest;
        foreach( $tail in $tails )
        {
            $results  = $head   $tail;
        }
        return $results;
    }

    # if we found any lookups at the start of the string we need to "multiply"
    # them with all the transliterations for the remainder of the string
    $results = @();
    foreach( $key in $keys )
    {
        if( $InputString.StartsWith($key) )
        {
            $heads = $lookups[$key];
            $rest  = $InputString.Substring(([string] $key).Length);
            $tails = ConvertTo-DecodedPath -InputString $rest;
            foreach( $head in $heads )
            {
                foreach( $tail in $tails )
                {
                    $results  = $head   $tail;
                }
            }

        }
    }

    return $results;

}

Example

Here's an example based on your sample string. Note the values are different to the ones you've listed, but I think your list might be wrong because I can't see how mail_sp_sp.sql could become any of the strings you've given that end with sql.sql :-).

PS> ConvertTo-DecodedPath "mail_sp_sp.sql"
mail_sp_sp.sql
mail_sp/sp.sql
mail/sp_sp.sql
mail/sp/sp.sql

Question

What happens next if more than one of the decoded paths exists?

  • Related