web-dev-qa-db-ja.com

一重引用符と二重引用符の両方を使用したXPath式のエンコード

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&apos;s Pizza']
25
Ryan

うわー、あなたは確かにこれを複雑にしている。なぜこれをしないのですか?

public static string XpathExpression(string value)
{
    if (!value.Contains("'"))
        return '\'' + value + '\'';

    else if (!value.Contains("\""))
        return '"' + value + '"';

    else
        return "concat('" + value.Replace("'", "',\"'\",'") + "')";
}

。NET Fiddle&test

9
Kaleb

確かにすべての状況で機能するわけではありませんが、問題を回避する方法は次のとおりです。

doc.DocumentElement.SetAttribute("searchName", name);
XmlNode n = doc.SelectNodes("//review[@name=/*/@searchName]");
10
Robert Rossney

私はこれを必要としていたので、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();
    }
7
Jay

これは私が思いついたものです

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操作が可能な限り短くないことを示しています(質問を参照)が、十分に近く、より最適なものは、「または」の一致するペアを探す必要があるため、非常に複雑になります。

4
Ryan

これまでのところ、すべての解決策で問題が発生しています。 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) & "]")
4
Eric

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>
2
Laurence Rowe

楽しみに参加する

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
1
Jack

別のバリエーション...私の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("\"", "\",'\"',\""));
        }
    }
1
tiwahu