引用嵌入式哈希表元素的哈希表键语法

Hashtable key syntax to refer to embedded hashtable element

假设我有一个哈希表:

$tokens = @{
    Id=9999; 
    Title="Lorem ipsum dolor sit amet";
    Author=@{Name="John Doe"; Email='john.doe@foo.xyz'};
    Analyst=@{Name="Jane Doe"; Email='jane.doe@foo.xyz'}
}

还有一个我想要填充的模板,将标记(例如 __Title__)替换为相应哈希表的值:

/*
Author:     __Author.Name__ <__Author.Email__>
Analyst:    __Analyst.Name__ <__Analyst.Email__>
Request:    __Title__ [__Id__]
*/
...

应该变成:

/*
Author:     John Doe <john.doe@foo.xyz>
Analyst:    Jane Doe <jane.doe@foo.xyz>
Request:    Lorem ipsum dolor sit amet [9999]
*/

有没有办法在 'parent' 哈希表中引用嵌入式哈希表的元素? $tokens['Author.Email'],例如,不起作用。

代码:

...
return [regex]::Replace( $template, '__(?<tokenName>\w+)__', {
  # __TOKEN__
  param($match)

  $tokenName = $match.Groups['tokenName'].Value

  if ($tokens[$tokenName]) {
    # matching token returns value from hashtable;
    works for simple keys `$tokens['Title']`, not complex keys `$tokens['Author.Name']`
    return $tokens[$tokenName]
  } 
  else {
    # non-matching token returns token
    return $match
  }
})

您可以只用点符号引用元素

$tokens.author.email

如果你想检查名称是否为空,你也可以这样做。 注意有一个警告:作者应该存在才能完全按预期工作。)

If(!$tokens.author.name){$tokens.author.name = "Awesome Sauce"; }
Write-Host ("Author Name: {0}" -f $tokens.author.name)

您也可以按照 briantist

的建议使用哈希表表示法
$tokens['Author']['Email']

动态替换

你使用了动态这个词,但我不确定你想把它讲到什么程度。现在让我们假设 $tokens 元素都存在,我们将替换 here-string 中的文本。

$text = @"
/*
Author:     __Author.Name__ <__Author.Email__>
Analyst:    __Analyst.Name__ <__Analyst.Email__>
Request:    __Title__ [__Id__]
*/
"@

$text -replace "__Author\.Name__",$tokens.Author.Name -replace "__Author\.Email__",$tokens.Author.Email `
        -replace "__Analyst\.Name__",$tokens.Analyst.Name -replace "__Analyst\.Email__",$tokens.Analyst.Email `
        -replace "__Title__",$tokens.Title -replace "__Id__",$tokens.Id

但我觉得您的意思是 更多 动态,因为所有这些都需要了解有关 $Tokens 和源字符串的信息。让我知道我们现在的立场。我们可以更深入地了解这一点。

让我们疯狂起来

假设您知道哈希表 $tokens 和源 $text 具有共同的值,但您不知道它们的名称。这将根据哈希表上的键名动态填充文本。目前这只适用于只有一个哈希表深度的情况。

ForEach($childKey in $tokens.Keys){ 
    If($tokens[$childKey] -is [System.Collections.Hashtable]){
        ForEach($grandChildKey in $tokens[$childKey].Keys){ 
            Write-Host "GrandChildKey = $childKey"
            $text = $text -replace "__$childKey\.$($grandChildKey)__", $tokens.$childKey.$grandChildKey
        }
    } Else {
        $text = $text -replace "__$($childKey)__", $tokens.$childKey
    }
}

$text

其他

这借鉴了 mike z 关于 Invoke-Expression 的建议,因为它减少了猜测工作。

$output = $text
$placeHolders = $text | Select-String '__([\w.]+)__' -AllMatches | ForEach-Object{$_.matches} | ForEach-Object{$_.Value}
$placeHolders.count
$placeHolders | ForEach-Object {
    $output = $output -replace [regex]::Escape($_), (Invoke-Expression "`$tokens.$($_ -replace "_")")
}
$output

$text 中搜索所有字符串,例如 something。对于每个匹配项,将该文本替换为等效的点符号。

任一示例的输出都应与您所拥有的相匹配应变为:

几件事:

  1. 您需要修复正则表达式以实际匹配嵌套属性。现在你不需要它是 __(?<tokenName>[\w\.]+)__

  2. 使用Invoke-Expression动态展开嵌套属性。只需构建一个代表您要评估的表达式的字符串。这很好,因为它不依赖于模型对象 $tokens 及其属性,根本不是哈希表。它所需要的只是让属性解析那里的对象。

下面是一个简短的例子。注意:如果模板来自不安全的来源,请小心并首先清理输入:

$tokens = @{
    Id=9999; 
    Title="Lorem ipsum dolor sit amet";
    Author=@{Name="John Doe"; Email='john.doe@foo.xyz'};
    Analyst=@{Name="Jane Doe"; Email='jane.doe@foo.xyz'};
    '3PTY' = "A";
    Test=@{'Name with space' = 'x' }
}

$template = @"
/*
Author:     __Author.Name__ <__Author.Email__>
Analyst:    __Analyst.Name__ <__Analyst.Email__>
Request:    __Title__ [__Id__]
3PTY: __"3PTY"__
Name:__Test.'Name with space'__
*/
"@

function Replace-Template {

    param ([string]$template, $model)

    [regex]::Replace( $template, '__(?<tokenName>[\w .\''\"]+)__', {
      # __TOKEN__
      # Note that TOKEN should be a valid PS property name. It may need to be enclosed in quotes
      # if it starts with a number or has spaces in the name. See the example above for usage.
      param($match)

      $tokenName = $match.Groups['tokenName'].Value
      Write-Verbose "Replacing '$tokenName'"
      $tokenValue = Invoke-Expression "`$model.$tokenName" -ErrorAction SilentlyContinue

      if ($tokenValue) {
        # there was a value. return it.
        return $tokenValue
      } 
      else {
        # non-matching token returns token
        return $match
      }
    })
}

Replace-Template $template $tokens

输出:

/*
Author: John Doe
Analyst: Jane Doe
Request: Lorem ipsum dolor sit amet [9999]
3PTY: A
Name:x
*/