Nikita Kobtsev
Nikita Kobtsev

Reputation: 153

Variable inside regex expression

There are controls on the form that need to be searched inside the function, for this I decided to use Controls.Find, the input of the function is $name. In this case, the search works among the TextBox and add to the array for further work. TextBox names are represented as IPTextBox1, IPTextBox2, etc. As I wrote and how it does not work (NetworkForm is a form that contains all controls):

$TextBoxes = $NetworkForm.Controls.Find('/^([regex]::escape($name))[A-Z]{1}[a-z]{3}[A-Z]{1}[a-z]{2}.{1}$/', 1)

Upvotes: 2

Views: 87

Answers (2)

mklement0
mklement0

Reputation: 437278

To answer the generic question in the title:

The safest way to embed an arbitrary variable value in a regex is:

  • to first escape the value with [regex]::Escape($var), which ensures that the value is treated as a literal (regex metacharacters such as . are \-escaped).

  • and then embed it in a single-quoted string via -f, the (string) format operator, which allows embedding of RHS operands via indexed placeholders in the LHS format string; e.g., {0} is the 1st RHS operand, {1} the 2nd, and so on; use {{ and }} to escape literal { and }.

For example, to construct a regex that matches an arbitrary value $var if preceded by one ore more digits (\d+) at a word boundary (\b) and if positioned at the end of the string ($)

# The value to embed in the regex, to be interpreted as a *literal*.
$var = '$'  

# Embed the escaped value in the regex.
# This results in the following regex - note the \-escaped $
#         \b\d+\$$
$regex = '\b\d+{0}$' -f [regex]::Escape($var)

# Perform matching:
'Cost: 20$' -match $regex  # -> $true

As for your specific WinForm problem:

.Controls.Find() on a WinForm form / control only allows searching for controls by their full, literal name, not by a regex.

Therefore you must enumerate all controls recursively and match their .Name property values individually.
Note that controls aren't required to have names.

Given that there is no built-in way to perform recursive enumeration of the controls contained inside a form / control, you must first implement that yourself, then filter by -match with a regex:

# Example:
#  Get-ChildControl -Recurse $form
function Get-ChildControl { 
  param([System.Windows.Forms.Control] $Control, [Switch] $Recurse) 
  foreach ($child in $Control.Controls) { 
    $child 
    if ($Recurse) { Get-ChildControl -Recurse:$Recurse $child } 
  } 
}

$regex = '^{0}[A-Z]{1}[a-z]{3}[A-Z]{1}[a-z]{2}.{1}$' -f [regex]::Escape($name)

$TextBoxes = Get-ChildControl -Recurse $NetworkForm | Where { $_.Name -cmatch $regex }

Note the use of -cmatch to perform case-sensitive matching.
-match (and its alias -imatch) are case-insensitive by default, as is PowerShell in general.


As for the problems with your original regex:

  • Don't use '...' (literal strings) if you want to embed expressions such as [regex]::escape($name) in it.

    • To do so, you must use an expandable string ("...") and embed the expression inside $(...), not (...) - as shown in @TobyU's answer.

    • The alternative is to use -f, the string-formatting operator, as shown above.

  • Generally, PowerShell has no regex-literal syntax, it just uses strings, so don't use /.../ inside a string representing a regex.

Upvotes: 1

TobyU
TobyU

Reputation: 3908

You can build a string of the pattern before and use it afterwards.

$pattern = "/^($([regex]::escape($name)))[A-Z]{1}[a-z]{3}[A-Z]{1}[a-z]{2}.{1}$/"
$TextBoxes = $NetworkForm.Controls.Find($pattern, 1)

Upvotes: 1

Related Questions