Кодирование выражений XPath с одиночными и двойными кавычками
XPath (v1) не содержит возможности кодирования выражений.
Если у вас есть только двойные кавычки ИЛИ, вы можете использовать выражения типа
//review[@name="Bob Pizza"]
//review[@name='"Pizza" Pam']
Но если у вас есть BOTH, например [Fred "Fancy Pizza" ], тогда вы должны использовать что-то вроде этого Escaping Strings в XPath (С++) для генерации
//review[@name=Concat("Fred ",'"Fancy Pizza"')]
Кто-нибудь имеет функцию в С# для этого?
Некоторые близкие ссылки
EDIT: несколько ответов предложили экранировать "с помощью '
и" с помощью "
, но, хотя это имеет смысл, он не работает, попробуйте его с помощью фрагмента XML:
<review name="Bob Pizza"/>
и xpath
//review[@name='Bob's Pizza']
Ответы
Ответ 1
Ничего себе, вы все уверены, что это сложно. Почему бы просто не сделать это?
public static string XpathExpression(string value)
{
if (!value.Contains("'"))
return '\'' + value + '\'';
else if (!value.Contains("\""))
return '"' + value + '"';
else
return "concat('" + value.Replace("'", "',\"'\",'") + "')";
}
.NET Fiddle и тест
Ответ 2
Хотя это, безусловно, не будет работать при любых обстоятельствах, вот способ обойти проблему:
doc.DocumentElement.SetAttribute("searchName", name);
XmlNode n = doc.SelectNodes("//review[@name=/*/@searchName]");
Ответ 3
Мне это нужно, поэтому я создал это решение для С#.
/// <summary>
/// Returns a valid XPath statement to use for searching attribute values regardless of 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();
}
Ответ 4
Вот что я придумал
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 \"Fancy Pizza\"" + "]")
Итак, мы получаем следующие результаты
EncaseXpathString("Pizza Shed") == "'Pizza Shed'";
EncaseXpathString("Bob pizza") == "\"Bob Pizza\"";
EncaseXpathString("\"Pizza\" Pam" == "'\"Pizza\" Pam'";
EncaseXpathString("Fred \"Fancy Pizza\"") == "concat(\"Fred \",'\"',\"Fancy Pizza\",'\"')";
Поэтому он использует только concat, когда необходимо (и "и" в строке)
Последний результат показывает, что операция concat не так коротка, как могла бы быть (см. вопрос), но ее достаточно близко и что-то более оптимальное будет очень сложным, так как вам придется искать соответствующие пары "или".
Ответ 5
У меня были проблемы со всеми решениями. У одного есть дополнительные текстовые разделы (например, "" или "" ), которые ломают то, что вы ищете. Отбрасывается весь текст после последнего котировки /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) & "]")
Ответ 6
Мне нужно было сделать это в самом 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>
Ответ 7
Другая вариация... моя часть 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("\"", "\",'\"',\""));
}
}
Ответ 8
Присоединяйтесь к веселью
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
Ответ 9
URL: http://vaibhavgaikwad.wordpress.com/2007/10/04/handling-apostrophe-single-quote-in-xpath-expressions-in-net/
цитата:
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) + "]");
Или просто сделайте так, как предлагалось предыдущее сообщение. сделайте легкую функциональность, заменяя "и" словами "и" и "