Кодирование выражений XPath как одинарными, так и двойными кавычками
XPath (v1) не содержит способа кодирования выражений.
Если у вас есть только одинарные или двойные кавычки, то вы можете использовать такие выражения, как
//review[@name="Bob's Pizza"]
//review[@name='"Pizza" Pam']
Но если у вас есть и то, и другое, например [Fred's "Fancy Pizza"], то вы должны использовать что-то вроде этого экранирующие строки в XPath (C++) для генерации
//review[@name=Concat("Fred's ",'"Fancy Pizza"')]
У кого-нибудь есть функция в c# для этого?
Некоторые близкие ссылки
- используйте MVP.XML библиотека и XPathVariable (очень хороший решение, но немного тяжеловесное для моих нужд).
- не кодирует, где присутствуют оба " и "
- добавляет больше аргументов к операции Конкат, чем необходимо например, вернется // review[@name=concat ('Fred',''", ' s ',''', 'Fancy Pizza', '"', ")]
EDIT: несколько ответов предложили экранировать "с '
и" с "
, но хотя это имеет смысл, это не работает; попробуйте использовать фрагмент XML:
<review name="Bob's Pizza"/>
И xpath
//review[@name='Bob's Pizza']
9 ответов:
Вау, вы все, конечно, усложняете это. Почему бы просто не сделать это?
public static string XpathExpression(string value) { if (!value.Contains("'")) return '\'' + value + '\''; else if (!value.Contains("\"")) return '"' + value + '"'; else return "concat('" + value.Replace("'", "',\"'\",'") + "')"; }
Хотя это, конечно, не будет работать во всех обстоятельствах, вот способ обойти проблему:
doc.DocumentElement.SetAttribute("searchName", name); XmlNode n = doc.SelectNodes("//review[@name=/*/@searchName]");
Я нуждался в этом, поэтому я создал это решение для C#.
/// <summary> /// Returns a valid XPath statement to use for searching attribute values regardless of 's or "s /// </summary> /// <param name="attributeValue">Attribute value to parse</param> /// <returns>Parsed attribute value in concat() if needed</returns> public static string GetXpathStringForAttributeValue(string attributeValue) { bool hasApos = attributeValue.Contains("'"); bool hasQuote = attributeValue.Contains("\""); if (!hasApos) { return "'" + attributeValue + "'"; } if (!hasQuote) { return "\"" + attributeValue + "\""; } StringBuilder result = new StringBuilder("concat("); StringBuilder currentArgument = new StringBuilder(); for (int pos = 0; pos < attributeValue.Length; pos++) { switch (attributeValue[pos]) { case '\'': result.Append('\"'); result.Append(currentArgument.ToString()); result.Append("'\","); currentArgument.Length = 0; break; case '\"': result.Append('\''); result.Append(currentArgument.ToString()); result.Append("\"\',"); currentArgument.Length = 0; break; default: currentArgument.Append(attributeValue[pos]); break; } } if (currentArgument.Length == 0) { result[result.Length - 1] = ')'; } else { result.Append("'"); result.Append(currentArgument.ToString()); result.Append("')"); } return result.ToString(); }
Вот что я придумал
public static string EncaseXpathString(string input) { // If we don't have any " then encase string in " if (!input.Contains("\"")) return String.Format("\"{0}\"", input); // If we have some " but no ' then encase in ' if (!input.Contains("'")) return String.Format("'{0}'", input); // If we get here we have both " and ' in the string so must use Concat StringBuilder sb = new StringBuilder("concat("); // Going to look for " as they are LESS likely than ' in our data so will minimise // number of arguments to concat. int lastPos = 0; int nextPos = input.IndexOf("\""); while (nextPos != -1) { // If this is not the first time through the loop then seperate arguments with , if (lastPos != 0) sb.Append(","); sb.AppendFormat("\"{0}\",'\"'", input.Substring(lastPos, nextPos - lastPos)); lastPos = ++nextPos; // Find next occurance nextPos = input.IndexOf("\"", lastPos); } sb.Append(")"); return sb.ToString(); }
Вызывается с помощью чего-то вроде
XmlNode node = doc.SelectSingleNode("//review[@name=" + EncaseXpathString("Fred's \"Fancy Pizza\"" + "]")
Таким образом, мы получаем следующие результаты
EncaseXpathString("Pizza Shed") == "'Pizza Shed'"; EncaseXpathString("Bob's pizza") == "\"Bob's Pizza\""; EncaseXpathString("\"Pizza\" Pam" == "'\"Pizza\" Pam'"; EncaseXpathString("Fred's \"Fancy Pizza\"") == "concat(\"Fred's \",'\"',\"Fancy Pizza\",'\"')";
Таким образом, он использует concat только тогда, когда это необходимо (оба " и ' в строке)
Последний результат показывает, что операция конката не так коротка, как могла бы быть (см. вопрос), но она достаточно близка, и все более оптимальное было бы очень сложным, поскольку вам пришлось бы искать соответствующие пары "или".
До сих пор у меня были проблемы со всеми решениями. У одного есть дополнительные текстовые разделы (например, ""или""), которые ломают то, что вы ищете. Один отбрасывает весь текст после последней цитаты/dblquote, которая также ломается.
Это тупое и быстрое решение от тупого разработчика vb:
Function ParseXpathString(ByVal input As String) As String input = Replace(input, "'", Chr(1)) input = Replace(input, """", Chr(2)) input = Replace(input, Chr(1), "',""'"",'") input = Replace(input, Chr(2), "','""','") input = "concat('','" + input + "')" Return input End Function
Использование (как и в предыдущих примерах):
x.SelectNodes("/path[@attr=" & ParseXpathString(attrvalue) & "]")
Мне нужно было сделать это в самом XSLT, поэтому я придумал следующее, основываясь на ответах на этой странице:
<xsl:template name="escape-string"> <xsl:param name="string"/> <xsl:param name="concat" select="true()"/> <xsl:variable name="quote">"</xsl:variable> <xsl:variable name="apos">'</xsl:variable> <xsl:choose> <xsl:when test="not(contains($string, $apos))">'<xsl:value-of select="$string"/>'</xsl:when> <xsl:when test="not(contains($string, $quote))">"<xsl:value-of select="$string"/>"</xsl:when> <xsl:otherwise> <xsl:if test="$concat">concat(</xsl:if> <xsl:call-template name="escape-string"> <xsl:with-param name="string" select="substring-before($string, $apos)"/> <xsl:with-param name="concat" select="false()"/> </xsl:call-template> <xsl:text>, "'", </xsl:text> <xsl:call-template name="escape-string"> <xsl:with-param name="string" select="substring-after($string, $apos)"/> <xsl:with-param name="concat" select="false()"/> </xsl:call-template> <xsl:if test="$concat">)</xsl:if> </xsl:otherwise> </xsl:choose> </xsl:template>
Еще один variation...my часть concat () немного ленива, но, по крайней мере, она использует все значение.
/// <summary> /// Returns an XPath string literal to use for searching attribute values (wraped in apostrophes, quotes, or as a concat function). /// </summary> /// <param name="attributeValue">Attribute value to encode and wrap.</param> public static string CreateXpathLiteral(string attributeValue) { if (!attributeValue.Contains("\"")) { // if we don't have any quotes, then wrap string in quotes... return string.Format("\"{0}\"", attributeValue); } else if (!attributeValue.Contains("'")) { // if we have some quotes, but no apostrophes, then wrap in apostrophes... return string.Format("'{0}'", attributeValue); } else { // must use concat so the literal in the XPath will find a match... return string.Format("concat(\"{0}\")", attributeValue.Replace("\"", "\",'\"',\"")); } }
Присоединяйтесь к веселью
public string XPathLiteral(string text) { const string APOS = "'"; const string QUOTE = @""""; int pos = 0; int posApos; int posQuote; posQuote = text.IndexOf(QUOTE, pos); if (posQuote < 0) { return QUOTE + text + QUOTE; }//if posApos = text.IndexOf(APOS, pos); if (posApos < 0) { return APOS + text + APOS; }//if bool containsApos = posApos < posQuote; StringBuilder sb = new StringBuilder("concat(", text.Length * 2); bool loop = true; bool comma = false; while (loop) { if (posApos < 0) { posApos = text.Length; loop = false; }//if if (posQuote < 0) { posQuote = text.Length; loop = false; }//if if (comma) { sb.Append(","); } else { comma = true; }//if if (containsApos) { sb.Append(QUOTE); sb.Append(text.Substring(pos, posQuote - pos)); sb.Append(QUOTE); pos = posQuote; if (loop) posApos = text.IndexOf(APOS, pos + 1); } else { sb.Append(APOS); sb.Append(text.Substring(pos, posApos - pos)); sb.Append(APOS); pos = posApos; if (loop) posQuote = text.IndexOf(QUOTE, pos + 1); }//if // Toggle containsApos = !containsApos; }//while sb.Append(")"); return sb.ToString(); }//method
Цитата:
public static string GetXPathString(string input) { string[] fragments = input.Split(new char[] { ‘\” }); string result = “”; result += “concat(””; for (int i = 0; i < fragments.Length; i++) { result += “, ‘” + fragments[i] + “‘”; if (i < fragments.Length - 1) { result += “, \”‘\”"; } } result += “)”; return result; }
И вот как вы модифицируете приведенный выше код, чтобы использовать нашу новую функцию: // не забудьте убрать одинарные кавычки после = и]
XmlNode n = doc.SelectSingleNode(“/root/emp[@lname=" + GetXPathString(ln) + "]“);
Или просто сделайте так, как предлагалось в предыдущем посте. сделайте простой func, заменив ' и ' на & apos; и & quot;