XPATH查询中的特殊字符

我使用以下XPATH Query来列出站点下的对象。 ListObject[@Title='SomeValue'] 。 SomeValue是dynamic的。 只要SomeValue没有撇号('),此查询就可以工作。 尝试使用转义序列也。 没有工作。

我究竟做错了什么?

这是令人惊讶的难以做到的。

看一下XPath推荐 ,你会看到它定义了一个文字:

 Literal ::= '"' [^"]* '"' | "'" [^']* "'" 

也就是说,XPathexpression式中的string文字可以包含撇号或双引号,但不能同时包含两个引号。

你不能使用转义来解决这个问题。 像这样的文字:

 'Some'Value' 

将匹配这个XML文本:

 Some'Value 

这确实意味着可能有一段XML文本不能生成匹配的XPath文本,例如:

 <elm att="&quot;&apos"/> 

但是,这并不意味着将该文本与XPath匹配是不可能的,这只是一个棘手的问题。 在任何情况下,您尝试匹配的值都包含单引号和双引号,您可以构build一个使用concat生成要匹配的文本的expression式:

 elm[@att=concat('"', "'")] 

所以这就把我们引向了这一点,这比我想要的要复杂得多:

 /// <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, eg: // // 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(); } 

是的,我testing了所有的边缘情况。 这就是为什么逻辑如此愚蠢复杂:

  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(); } 

编辑:经过一个沉重的unit testing会议,并检查XPath标准 ,我已经修改我的function如下:

 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; } } 

看来XPath根本没有一个字符转义系统,它确实是相当原始的。 显然我的原始代码只是偶然的工作。 我很抱歉误导任何人!

下面的原始答案仅供参考 – 请忽略

为了安全起见,请确保您的XPathstring中的所有5个预定义XML实体的任何出现都已转义,例如

 public static string ToXPath(string value) { return "'" + XmlEncode(value) + "'"; } public static string XmlEncode(string value) { StringBuilder text = new StringBuilder(value); text.Replace("&", "&amp;"); text.Replace("'", "&apos;"); text.Replace(@"""", "&quot;"); text.Replace("<", "&lt;"); text.Replace(">", "&gt;"); return text.ToString(); } 

我以前做过这个,它工作正常。 如果它不适合你,也许有一些额外的问题,你需要让我们意识到的上下文。

我移植了Robert对Java的回答(在1.6中进行了testing):

 /// <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, eg: // // 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; } 

希望这有助于某人!

到目前为止,解决此问题的最佳方法是使用XPath库提供的工具来声明可在expression式中引用的XPath级variables。 variables值可以是主机编程语言中的任何string,并且不受XPathstring文字的限制。 例如,在使用javax.xml.xpath Java中:

 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); 

对于C# XPathNavigator您将定义一个自定义的XsltContext ,如本MSDN文章中所述 (您只需要本示例的variables相关部分,而不是扩展函数)。

这里的大多数答案都着重于如何使用string操作来凑齐使用string分隔符的XPath。

我认为最好的做法是不要依赖这种复杂和潜在脆弱的方法。

以下内容适用于.NET,因为此问题使用C#进行标记。 Ian Roberts提供了我认为在Java中使用XPath的最佳解决scheme。

现在,您可以使用Linq-to-Xml查询XML文档,这样可以直接在查询中使用variables。 这不是XPath,但目的是一样的。

对于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); 

或者使用查询理解语法:

 IEnumerable<XElement> nodes = from lo in doc.Descendants("ListObject") where (string)lo.Attribute("Title") == value select lo; 

.NET还提供了在XPath查询中使用XPathvariables的方法。 不幸的是,这样做并不容易,但是在这个其他的答案中提供了一个简单的帮助类,这很容易。

你可以像这样使用它:

 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); 

这是罗伯特·罗斯尼(Robert Rossney)的StringBuilder方法的另一种select,也许更直观:

  /// <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, eg: // // 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) + ")"; } 

您可以使用search和replace引用XPathstring。

在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 ("'", "', \"'\", '") + "')" 

我没有广泛的testing,但似乎工作。

如果您在SomeValue中不会有任何双引号,则可以使用转义的双引号指定您在XPathsearchstring中search的值。

 ListObject[@Title=\"SomeValue\"] 

您可以通过在XPathexpression式中使用double quotes而不是single quotes来解决此问题。

例如:

 element.XPathSelectElements(String.Format("//group[@title=\"{0}\"]", "Man's")); 

我曾经有过这个问题,看起来最简单,但不是最快的解决scheme是,您将一个新的节点添加到具有值“SomeValue”的属性的XML文档中,然后使用简单的xpathsearch来查找该属性值。 在完成操作之后,可以从XML文档中删除“临时节点”。

这样,整个比较发生在“内部”,所以你不必构造奇怪的XPath查询。

我似乎记得,为了加快速度,您应该将temp值添加到根节点。

祝你好运…