XPath(v1)には、式をエンコードする方法が含まれていません。
一重引用符のみがある場合OR二重引用符)次のような式を使用できます
//review[@name="Bob's Pizza"]
//review[@name='"Pizza" Pam']
ただし、[Fred's "Fancy Pizza"]のように両方がある場合は、次のようなものを使用する必要があります XPath(C++)での文字列のエスケープ
//review[@name=Concat("Fred's ",'"Fancy Pizza"')]
誰かがこれを行うためにC#に関数を持っていますか?
近いいくつかのリンク
編集:いくつかの回答は、 'を'
で、 "を"
でエスケープすることを提案していますが、これは理にかなっていますが、機能しません。XMLフラグメントを使用して試してください。
<review name="Bob's Pizza"/>
およびxpath
//review[@name='Bob's Pizza']
うわー、あなたは確かにこれを複雑にしている。なぜこれをしないのですか?
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を使用します(文字列の「」と「」の両方)
最後の結果は、concat操作が可能な限り短くないことを示しています(質問を参照)が、十分に近く、より最適なものは、「または」の一致するペアを探す必要があるため、非常に複雑になります。
これまでのところ、すべての解決策で問題が発生しています。 1つには、探しているものを壊す余分なテキストセクション(例: '"'または" '")があります。1つは、最後の引用符/ 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>
楽しみに参加する
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
別のバリエーション...私の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("\"", "\",'\"',\""));
}
}