Dear Powershell fellows,
i have a (maybe) very simple problem, but i have no idea how to solve it. I want to use a String Variable, that contains a single quote, within a XPath notation. I use the CMDlet Select-Xml. If there is no single quote within the String, Select-Xml is working completly fine. But if there is one single quote (for example in: don't) it crashes my script. Let me show you in detail.
Problem
$Name = "Dont display" ##is working completly fine
(Select-Xml -Path "C:\SomePath\Somexml.xml" -XPath "//q1:Options[q1:Name = '$Name']" -Namespace $namespace).Node.InnerText ##is working completly fine
$Name = "Don't display" ##crashes script
(Select-Xml -Path "C:\SomePath\Somexml.xml" -XPath "//q1:Options[q1:Name = '$Name']" -Namespace $namespace).Node.InnerText ##crashes script
The error output of powershell is:
Select-Xml : '//q1:Options[q1:Name = 'Don't display']' has an invalid token.
At line:251 char:41
... ing_Name = (Select-Xml -Path "C:\SomePath\Somexml.xml" -XPath ...
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CategoryInfo : NotSpecified: (:) [Select-Xml], XPathException
FullyQualifiedErrorId : System.Xml.XPath.XPathException,Microsoft.PowerShell.Commands.SelectXmlCommand
What i tried so far
Of course i tried different quotations, such as:
$Name = """Don't display"""
$Name = '"Don't display"'
$Name = "'Don't display'"
$Name = "'Don't display'"
$Name = 'Don't display'
$Name = ''Don't display''
It seems like, there might be a problem with powershell quotation rules and XPath notation.
But maybe anyone of you guys have an idea on how to solve it.
Thank you very much
CodePudding user response:
It seems there's no escape character in XPath string literals, so you can't use your string delimiter inside a literal string as it terminates the string - i.e. this:
$name = "Don't display"
# single quotes:
# //q1:Options[q1:Name = 'Don't display']
# ^ terminates string literal
A quick (but naive) solution to your specific issue would be to just use double-quotes as delimiters instead:
$name = "Don't display"
# double quotes:
# //q1:Options[q1:Name = "Don't display"]
# ^ *doesn't* terminate string literal
but what if your data contains double quotes? Then you're back at square one..
$name = "Don""t display"
# double quotes:
# //q1:Options[q1:Name = "Don"t display"]
# ^ terminates string literal again
And in a pathological case, if your literal contains both single and double quotes then you can't use either as delimters:
$name = "Don't d""isplay"
# single quotes:
# //q1:Options[q1:Name = 'Don't d"isplay']
# ^ terminates string literal
# double quotes:
# //q1:Options[q1:Name = "Don't d"isplay"]
# ^ also terminates string literal
In that case, you could resort to this answer which suggests converting your string literal into a concat
expression so that you get:
$name = "Don't d""isplay"
# //q1:Options[q1:Name = concat('Don', "'", 't d"isplay')]
# ^^^^^ use single quotes
# ^^^ use double quotes
# ^^^^^^^^^^^^ use single quotes
which you could generate with this:
$name = "Don't d""isplay"
$squote = "', `"'`", '"
$expr = "concat('{0}')" -f $name.Replace("'", $squote)
# Select-Xml -Xml $xml -XPath "//q1:Options[q1:Name = $expr]"
# ->
# //q1:Options[q1:Name = concat('Don', "'", 't d"isplay')]
and then the parts of your data that contain double-quotes are delimited with single quotes, and vice versa so they all terminate properly.
Note - you could probably optimise this for literals without one type of quote or the other and for consecutive single quotes, and it'll need some error handling added for $null
and other edge cases, but it basically does the job...
Update
Here's a full code sample to show it in action...
$xml = [xml] "<root><child Name=`"my'name`" /></root>"
$name = "my'name"
$squote = "', `"'`", '"
$expr = "concat('{0}')" -f $name.Replace("'", $squote)
Select-Xml -Xml $xml -XPath "//child[@Name = $expr]"
# Node Path Pattern
# ---- ---- -------
# child InputStream //child[@Name = concat('my', "'", 'name')]