Кодирование выражений 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# для этого?

Некоторые близкие ссылки

EDIT: несколько ответов предложили экранировать "с ' и" с " , но хотя это имеет смысл, это не работает; попробуйте использовать фрагмент XML:

<review name="Bob's Pizza"/>

И xpath

//review[@name='Bob&apos;s Pizza']
9 23

9 ответов:

Вау, вы все, конечно, усложняете это. Почему бы просто не сделать это?

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

Хотя это, конечно, не будет работать во всех обстоятельствах, вот способ обойти проблему:

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

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) + "]“);

Или просто сделайте так, как предлагалось в предыдущем посте. сделайте простой func, заменив ' и ' на & apos; и & quot;