XPATH查询中的特殊字符
我使用以下XPATH Query
来列出站点下的对象。 ListObject[@Title='SomeValue']
。 SomeValue是dynamic的。 只要SomeValue没有撇号('),此查询就可以工作。 尝试使用转义序列也。 没有工作。
我究竟做错了什么?
这是令人惊讶的难以做到的。
看一下XPath推荐 ,你会看到它定义了一个文字:
Literal ::= '"' [^"]* '"' | "'" [^']* "'"
也就是说,XPathexpression式中的string文字可以包含撇号或双引号,但不能同时包含两个引号。
你不能使用转义来解决这个问题。 像这样的文字:
'Some'Value'
将匹配这个XML文本:
Some'Value
这确实意味着可能有一段XML文本不能生成匹配的XPath文本,例如:
<elm att=""&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("&", "&"); text.Replace("'", "'"); text.Replace(@"""", """); text.Replace("<", "<"); text.Replace(">", ">"); 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\"]
您可以通过在XPath
expression式中使用double quotes
而不是single quotes
来解决此问题。
例如:
element.XPathSelectElements(String.Format("//group[@title=\"{0}\"]", "Man's"));
我曾经有过这个问题,看起来最简单,但不是最快的解决scheme是,您将一个新的节点添加到具有值“SomeValue”的属性的XML文档中,然后使用简单的xpathsearch来查找该属性值。 在完成操作之后,可以从XML文档中删除“临时节点”。
这样,整个比较发生在“内部”,所以你不必构造奇怪的XPath查询。
我似乎记得,为了加快速度,您应该将temp值添加到根节点。
祝你好运…