52489

Hashtable key syntax to refer to embedded hashtable element

Question:

Assuming that I have a hashtable:

$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'} }

And a template that I would like to populate, replacing the tokens (e.g. __Title__) with the corresponding hashtable's value:

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

Should become:

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

Is there a way to refer to an embedded hashtable's elements in the 'parent' hashtable? $tokens['Author.Email'], for example, doesn't work.

The code:

... 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 } })

Answer1:

A couple things:

<ol><li>

You need to fix the regular expression to actually match the nested properties. Right now it doesn't you need it to be __(?<tokenName>[\w\.]+)__

</li> <li>

Use Invoke-Expression to dynamically expand the nested properties. Just build a string the represents the expression you want to evaluate. This is good because it doesn't rely on the model objects, $tokens and its properties, being hashtables at all. All it needs is for the properties to resolve on the objects that are there.

</li> </ol>

A short example is below. Note: if the template is coming from an unsecure source, be careful with this and sanitize the input first:

$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

Output:

<blockquote>

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

</blockquote>

Answer2:

You can just reference the element with dot notation

<pre class="lang-powershell prettyprint-override">$tokens.author.email

Then you could do things like this as well if you wanted to check if the name was empty for example. <strong>Note</strong> that there is a caveat: Author should exist for this to work exactly as intended.)

<pre class="lang-powershell prettyprint-override">If(!$tokens.author.name){$tokens.author.name = "Awesome Sauce"; } Write-Host ("Author Name: {0}" -f $tokens.author.name)

You can also use hashtable notation as suggested by <a href="https://stackoverflow.com/users/3905079/briantist" rel="nofollow">briantist</a>

<pre class="lang-powershell prettyprint-override">$tokens['Author']['Email']

<strong>Dynamic replacement</strong>

You use the word dynamic but I am not sure how far you want to take that. For now lets assume that the $tokens elements all exist and we are going to replace the text from a here-string.

<pre class="lang-powershell prettyprint-override">$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

But I feel you mean <em>more</em> dynamic since all of this requires knowing information about the $Tokens and the the source string. Let me know how we stand now. We could get deeper with this.

<strong>Lets get freaky</strong>

Let say you know that the hashtable $tokens and the source $text have values in common but you don't know the names of them. This will dynamically populate text based on the key names on the hashtables. Currently this only works if there is only one hashtable depth.

<pre class="lang-powershell prettyprint-override">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

<strong>Something else</strong>

This borrows from <a href="https://stackoverflow.com/users/517852/mike-z" rel="nofollow">mike z</a> suggestion about Invoke-Expression as it makes less guess work involved.

<pre class="lang-powershell prettyprint-override">$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

Search the $text for all strings like <strong>something</strong>. For every match replace that text with its dot notation equivalent.

Output from either samples should match what you have for <em>Should become:</em>

Recommend

  • How to execute 2 Observables in parallel, ignoring their results and execute next Observable
  • download all file formats using angular and express JS
  • Displaying pdf files using the PDFKit interface
  • WPF Run Animation in a separate Thread
  • Google Bigquery Command Line Return Limit
  • How can I determine if process is 32 or 64Bit from a handle?
  • findObjectsInBackgroundWithBlock block signature not correct
  • Raphael.js function getBBox give back NAN/NAN/NAN in IE8
  • matching similar elements in between two lists
  • Wrap C++ function using Boost Reflect or another C++ reflection library
  • What is the undocumented SessionIdInterface in PHP 5.5?
  • Embedded Glassfish JPA Datasource connection fail
  • IE10 strips out hashtag from the URL
  • Create a link to a web page that runs a Javascript function on the page
  • What causes the runtime difference in this trivial fortran code?
  • NUnit 3.0 TestCase const custom object arguments
  • Plotting line graph with factors in R
  • Suppressing passwd when calling sqlplus from shell script
  • Laravel: Getting Session ID oddly truncates when using foreach
  • How to disable all widgets inside Panel or inside Composite?
  • WPF ICommand CanExecute(): RaiseCanExecuteChanged() or automatic handling via DispatchTimer?
  • Reduction and collapse clauses in OMP have some confusing points
  • gspread or such: help me get cell coordinates (not value)
  • How do I exclude a dependency in provided scope when running in Maven test scope?
  • JQuery Internet Explorer and ajaxstop
  • Can you perform a UNION without a subquery in SQLAlchemy?
  • Use of this Javascript
  • FFmpeg Conversion Error
  • Q promise. Difference between .when and .then
  • Initializer list vs. initialization method
  • Nant, Vault & Windows Integrated Authentication
  • Bug in WPF DataGrid
  • Can Jackson SerializationFeature be overridden per field or class?
  • TFS: Get latest causes slow project reloading
  • Javascript Callbacks with Object constructor
  • How to make Safari send if-modified-since header?
  • ExecuteAsync RestSharp to allow backgroundWorker CancellationPending c#
  • C# - Getting references of reference
  • -fvisibility=hidden not passed by compiler for Debug builds
  • Net Present Value in Excel for Grouped Recurring CF