C# XPATH 查询中的特殊字符
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1341847/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Special Character in XPATH Query
提问by Prabhu
I use the following XPATH Query
to list the object under a site. ListObject[@Title='SomeValue']
. SomeValue is dynamic. This query works as long as SomeValue does not have an apostrophe ('). Tried using escape sequence also. Didn't work.
我使用以下内容XPATH Query
列出站点下的对象。ListObject[@Title='SomeValue']
. SomeValue 是动态的。只要 SomeValue 没有撇号 ('),此查询就有效。也尝试使用转义序列。没用。
What am I doing wrong?
我究竟做错了什么?
采纳答案by Robert Rossney
This is surprisingly difficult to do.
这出乎意料地难以做到。
Take a look at the XPath Recommendation, and you'll see that it defines a literal as:
看看XPath Recommendation,你会看到它定义了一个文字:
Literal ::= '"' [^"]* '"'
| "'" [^']* "'"
Which is to say, string literals in XPath expressions can contain apostrophes or double quotes but not both.
也就是说,XPath 表达式中的字符串文字可以包含撇号或双引号,但不能同时包含两者。
You can't use escaping to get around this. A literal like this:
你不能使用转义来解决这个问题。像这样的文字:
'Some'Value'
will match this XML text:
将匹配此 XML 文本:
Some'Value
This does mean that it's possible for there to be a piece of XML text that you can't generate an XPath literal to match, e.g.:
这确实意味着可能会有一段 XML 文本无法生成 XPath 文字来匹配,例如:
<elm att=""&apos"/>
But that doesn't mean it's impossible to match that text with XPath, it's just tricky. In any case where the value you're trying to match contains both single and double quotes, you can construct an expression that uses concat
to produce the text that it's going to match:
但这并不意味着不可能将文本与 XPath 匹配,这只是棘手。在您尝试匹配的值同时包含单引号和双引号的任何情况下,您都可以构建一个表达式,用于concat
生成要匹配的文本:
elm[@att=concat('"', "'")]
So that leads us to this, which is a lot more complicated than I'd like it to be:
所以这导致我们这样做,这比我希望的要复杂得多:
/// <summary>
/// Produce an XPath literal equal to the value if possible; if not, produce
/// an XPath expression that will match the value.
///
/// Note that this function will produce very long XPath expressions if a value
/// contains a long run of double quotes.
/// </summary>
/// <param name="value">The value to match.</param>
/// <returns>If the value contains only single or double quotes, an XPath
/// literal equal to the value. If it contains both, an XPath expression,
/// using concat(), that evaluates to the value.</returns>
static string XPathLiteral(string value)
{
// if the value contains only single or double quotes, construct
// an XPath literal
if (!value.Contains("\""))
{
return "\"" + value + "\"";
}
if (!value.Contains("'"))
{
return "'" + value + "'";
}
// if the value contains both single and double quotes, construct an
// expression that concatenates all non-double-quote substrings with
// the quotes, e.g.:
//
// concat("foo", '"', "bar")
StringBuilder sb = new StringBuilder();
sb.Append("concat(");
string[] substrings = value.Split('\"');
for (int i = 0; i < substrings.Length; i++ )
{
bool needComma = (i>0);
if (substrings[i] != "")
{
if (i > 0)
{
sb.Append(", ");
}
sb.Append("\"");
sb.Append(substrings[i]);
sb.Append("\"");
needComma = true;
}
if (i < substrings.Length - 1)
{
if (needComma)
{
sb.Append(", ");
}
sb.Append("'\"'");
}
}
sb.Append(")");
return sb.ToString();
}
And yes, I tested it with all the edge cases. That's why the logic is so stupidly complex:
是的,我用所有边缘情况对其进行了测试。这就是为什么逻辑如此愚蠢复杂的原因:
foreach (string s in new[]
{
"foo", // no quotes
"\"foo", // double quotes only
"'foo", // single quotes only
"'foo\"bar", // both; double quotes in mid-string
"'foo\"bar\"baz", // multiple double quotes in mid-string
"'foo\"", // string ends with double quotes
"'foo\"\"", // string ends with run of double quotes
"\"'foo", // string begins with double quotes
"\"\"'foo", // string begins with run of double quotes
"'foo\"\"bar" // run of double quotes in mid-string
})
{
Console.Write(s);
Console.Write(" = ");
Console.WriteLine(XPathLiteral(s));
XmlElement elm = d.CreateElement("test");
d.DocumentElement.AppendChild(elm);
elm.SetAttribute("value", s);
string xpath = "/root/test[@value = " + XPathLiteral(s) + "]";
if (d.SelectSingleNode(xpath) == elm)
{
Console.WriteLine("OK");
}
else
{
Console.WriteLine("Should have found a match for {0}, and didn't.", s);
}
}
Console.ReadKey();
}
回答by 48klocs
If you're not going to have any double-quotes in SomeValue, you can use escaped double-quotes to specify the value you're searching for in your XPath search string.
如果您不打算在 SomeValue 中使用任何双引号,则可以使用转义双引号来指定您在 XPath 搜索字符串中搜索的值。
ListObject[@Title=\"SomeValue\"]
回答by Christian Hayter
EDIT:After a heavy unit testing session, and checking the XPath Standards, I have revised my function as follows:
编辑:经过大量的单元测试会话并检查XPath Standards 后,我修改了我的函数如下:
public static string ToXPath(string value) {
const string apostrophe = "'";
const string quote = "\"";
if(value.Contains(quote)) {
if(value.Contains(apostrophe)) {
throw new XPathException("Illegal XPath string literal.");
} else {
return apostrophe + value + apostrophe;
}
} else {
return quote + value + quote;
}
}
It appears that XPath doesn't have a character escaping system at all, it's quite primitive really. Evidently my original code only worked by coincidence. My apologies for misleading anyone!
看起来 XPath 根本没有字符转义系统,它真的很原始。显然,我的原始代码只是巧合。我为误导任何人而道歉!
Original answer below for reference only - please ignore
以下原始答案仅供参考 - 请忽略
For safety, make sure that any occurrence of all 5 predefined XML entities in your XPath string are escaped, e.g.
为了安全起见,请确保对 XPath 字符串中出现的所有 5 个预定义 XML 实体进行转义,例如
public static string ToXPath(string value) {
return "'" + XmlEncode(value) + "'";
}
public static string XmlEncode(string value) {
StringBuilder text = new StringBuilder(value);
text.Replace("&", "&");
text.Replace("'", "'");
text.Replace(@"""", """);
text.Replace("<", "<");
text.Replace(">", ">");
return text.ToString();
}
I have done this before and it works fine. If it doesn't work for you, maybe there is some additional context to the problem that you need to make us aware of.
我以前做过这个,效果很好。如果它对您不起作用,也许您需要让我们意识到该问题的一些其他背景。
回答by Gyuri
I had this problem a while back and seemingly the simplest, but not the fastest solution is that you add a new node into the XML document that has an attribute with the value 'SomeValue', then look for that attribute value using a simple xpath search. After the you're finished with the operation, you can delete the "temporary node" from the XML document.
不久前我遇到了这个问题,看似最简单但不是最快的解决方案是将一个新节点添加到 XML 文档中,该节点的属性值为“SomeValue”,然后使用简单的 xpath 搜索查找该属性值. 完成操作后,您可以从 XML 文档中删除“临时节点”。
This way, the whole comparison happens "inside", so you don't have to construct the weird XPath query.
这样,整个比较就发生在“内部”,因此您不必构造奇怪的 XPath 查询。
I seem to remember that in order to speed things up, you should be adding the temp value to the root node.
我似乎记得为了加快速度,您应该将临时值添加到根节点。
Good luck...
祝你好运...
回答by Cody S
I ported Robert's answer to Java (tested in 1.6):
我将 Robert 的答案移植到 Java(在 1.6 中测试):
/// <summary>
/// Produce an XPath literal equal to the value if possible; if not, produce
/// an XPath expression that will match the value.
///
/// Note that this function will produce very long XPath expressions if a value
/// contains a long run of double quotes.
/// </summary>
/// <param name="value">The value to match.</param>
/// <returns>If the value contains only single or double quotes, an XPath
/// literal equal to the value. If it contains both, an XPath expression,
/// using concat(), that evaluates to the value.</returns>
public static String XPathLiteral(String value) {
if(!value.contains("\"") && !value.contains("'")) {
return "'" + value + "'";
}
// if the value contains only single or double quotes, construct
// an XPath literal
if (!value.contains("\"")) {
System.out.println("Doesn't contain Quotes");
String s = "\"" + value + "\"";
System.out.println(s);
return s;
}
if (!value.contains("'")) {
System.out.println("Doesn't contain apostophes");
String s = "'" + value + "'";
System.out.println(s);
return s;
}
// if the value contains both single and double quotes, construct an
// expression that concatenates all non-double-quote substrings with
// the quotes, e.g.:
//
// concat("foo", '"', "bar")
StringBuilder sb = new StringBuilder();
sb.append("concat(");
String[] substrings = value.split("\"");
for (int i = 0; i < substrings.length; i++) {
boolean needComma = (i > 0);
if (!substrings[i].equals("")) {
if (i > 0) {
sb.append(", ");
}
sb.append("\"");
sb.append(substrings[i]);
sb.append("\"");
needComma = true;
}
if (i < substrings.length - 1) {
if (needComma) {
sb.append(", ");
}
sb.append("'\"'");
}
System.out.println("Step " + i + ": " + sb.toString());
}
//This stuff is because Java is being stupid about splitting strings
if(value.endsWith("\"")) {
sb.append(", '\"'");
}
//The code works if the string ends in a apos
/*else if(value.endsWith("'")) {
sb.append(", \"'\"");
}*/
sb.append(")");
String s = sb.toString();
System.out.println(s);
return s;
}
Hope this helps somebody!
希望这对某人有帮助!
回答by Jonathan Gilbert
Here is an alternative to Robert Rossney's StringBuilder approach, perhaps more intuitive:
这是 Robert Rossney 的 StringBuilder 方法的替代方法,也许更直观:
/// <summary>
/// Produce an XPath literal equal to the value if possible; if not, produce
/// an XPath expression that will match the value.
///
/// Note that this function will produce very long XPath expressions if a value
/// contains a long run of double quotes.
///
/// From: http://stackoverflow.com/questions/1341847/special-character-in-xpath-query
/// </summary>
/// <param name="value">The value to match.</param>
/// <returns>If the value contains only single or double quotes, an XPath
/// literal equal to the value. If it contains both, an XPath expression,
/// using concat(), that evaluates to the value.</returns>
public static string XPathLiteral(string value)
{
// If the value contains only single or double quotes, construct
// an XPath literal
if (!value.Contains("\""))
return "\"" + value + "\"";
if (!value.Contains("'"))
return "'" + value + "'";
// If the value contains both single and double quotes, construct an
// expression that concatenates all non-double-quote substrings with
// the quotes, e.g.:
//
// concat("foo",'"',"bar")
List<string> parts = new List<string>();
// First, put a '"' after each component in the string.
foreach (var str in value.Split('"'))
{
if (!string.IsNullOrEmpty(str))
parts.Add('"' + str + '"'); // (edited -- thanks Daniel :-)
parts.Add("'\"'");
}
// Then remove the extra '"' after the last component.
parts.RemoveAt(parts.Count - 1);
// Finally, put it together into a concat() function call.
return "concat(" + string.Join(",", parts) + ")";
}
回答by Ian Roberts
By far the best approach to this problem is to use the facilities provided by your XPath library to declare an XPath-level variable that you can reference in the expression. The variable value can then be any string in the host programming language, and isn't subject to the restrictions of XPath string literals. For example, in Java with javax.xml.xpath
:
到目前为止,解决此问题的最佳方法是使用 XPath 库提供的工具来声明您可以在表达式中引用的 XPath 级变量。变量值可以是宿主编程语言中的任何字符串,并且不受 XPath 字符串文字的限制。例如,在 Java 中javax.xml.xpath
:
XPathFactory xpf = XPathFactory.newInstance();
final Map<String, Object> variables = new HashMap<>();
xpf.setXPathVariableResolver(new XPathVariableResolver() {
public Object resolveVariable(QName name) {
return variables.get(name.getLocalPart());
}
});
XPath xpath = xpf.newXPath();
XPathExpression expr = xpath.compile("ListObject[@Title=$val]");
variables.put("val", someValue);
NodeList nodes = (NodeList)expr.evaluate(someNode, XPathConstants.NODESET);
For C# XPathNavigator
you would define a custom XsltContext
as described in this MSDN article(you'd only need the variable-related parts of this example, not the extension functions).
对于 C#,XPathNavigator
您将XsltContext
按照此 MSDN 文章中的描述定义自定义(您只需要此示例中与变量相关的部分,而不需要扩展函数)。
回答by Shivanand
You can fix this issue by using double quotes
instead of single quotes
in the XPath
expression.
您可以通过在表达式中使用double quotes
代替来解决此问题。single quotes
XPath
For ex:
例如:
element.XPathSelectElements(String.Format("//group[@title=\"{0}\"]", "Man's"));
回答by Fortune
You can quote an XPath string by using search and replace.
您可以使用搜索和替换来引用 XPath 字符串。
In F#
在 F#
let quoteString (s : string) =
if not (s.Contains "'" ) then sprintf "'%s'" s
else if not (s.Contains "\"") then sprintf "\"%s\"" s
else "concat('" + s.Replace ("'", "', \"'\", '") + "')"
I haven't tested it extensively, but seems to work.
我还没有对其进行广泛的测试,但似乎有效。
回答by JLRishe
Most of the answers here focus on how to use string manipulation to cobble together an XPath that uses string delimiters in a valid way.
这里的大多数答案都集中在如何使用字符串操作来拼凑一个以有效方式使用字符串分隔符的 XPath。
I would say the best practice is not to rely on such complicated and potentially fragile methods.
我会说最好的做法是不要依赖这种复杂且可能脆弱的方法。
The following applies to .NET since this question is tagged with C#. Ian Roberts has provided what I think is the best solution for when you're using XPath in Java.
以下适用于 .NET,因为此问题用 C# 标记。当您在 Java 中使用 XPath 时,Ian Roberts 提供了我认为最好的解决方案。
Nowadays, you can use Linq-to-Xml to query XML documents in a way that allows you to use your variables in the query directly. This is not XPath, but the purpose is the same.
现在,您可以使用 Linq-to-Xml 以允许您直接在查询中使用变量的方式查询 XML 文档。这不是 XPath,但目的是相同的。
For the example given in OP, you could query the nodes you want like this:
对于 OP 中给出的示例,您可以像这样查询所需的节点:
var value = "Some value with 'apostrophes' and \"quotes\"";
// doc is an instance of XElement or XDocument
IEnumerable<XElement> nodes =
doc.Descendants("ListObject")
.Where(lo => (string)lo.Attribute("Title") == value);
or to use the query comprehension syntax:
或使用查询理解语法:
IEnumerable<XElement> nodes = from lo in doc.Descendants("ListObject")
where (string)lo.Attribute("Title") == value
select lo;
.NET also provides a way to use XPath variables in your XPath queries. Sadly, it's not easy to do this out of the box, but with a simple helper class that I provide in this other SO answer, it's quite easy.
.NET 还提供了一种在 XPath 查询中使用 XPath 变量的方法。可悲的是,开箱即用并不容易,但是通过我在另一个 SO 答案中提供的简单助手类,这很容易。
You can use it like this:
你可以这样使用它:
var value = "Some value with 'apostrophes' and \"quotes\"";
var variableContext = new VariableContext { { "matchValue", value } };
// ixn is an instance of IXPathNavigable
XPathNodeIterator nodes = ixn.CreateNavigator()
.SelectNodes("ListObject[@Title = $matchValue]",
variableContext);