Кодирование выражений XPath с одинарными и двойными кавычками - PullRequest
25 голосов
/ 13 марта 2009

XPath (v1) не содержит способа кодирования выражений.

Если у вас есть только одинарные ИЛИ двойные кавычки, вы можете использовать такие выражения, как

//review[@name="Bob's Pizza"]
//review[@name='"Pizza" Pam']

Но если у вас есть ОБА, например, [Fancy's "Fancy Pizza"], вы должны использовать что-то вроде этого Escapeing Strings в XPath (C ++) для генерации

//review[@name=Concat("Fred's ",'"Fancy Pizza"')]

У кого-нибудь есть функция в c # для этого?

Некоторые ссылки, которые закрываются

РЕДАКТИРОВАТЬ: В нескольких ответах предлагалось экранировать 'с ' и "с ", но, хотя это имеет смысл, это не работает; попробуйте использовать фрагмент XML:

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

и xpath

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

Ответы [ 9 ]

10 голосов
/ 13 марта 2009

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

doc.DocumentElement.SetAttribute("searchName", name);
XmlNode n = doc.SelectNodes("//review[@name=/*/@searchName]");
9 голосов
/ 07 июля 2016

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

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

7 голосов
/ 18 марта 2009

Я нуждался в этом, поэтому создал это решение для 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();
    }
4 голосов
/ 11 июля 2011

У меня до сих пор были проблемы со всеми решениями. У одного есть дополнительные текстовые разделы (например, "" "или" '"), которые ломают то, что вы ищете. Один сбрасывает весь текст после последней цитаты / 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 голосов
/ 13 марта 2009

Это то, что я придумал

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 не так коротка, как могла бы (см. Вопрос), но достаточно близка и что-либо более оптимальное было бы очень сложным, так как вам пришлось бы искать соответствующие пары "или".

2 голосов
/ 26 апреля 2011

Мне нужно было сделать это в самом 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>
1 голос
/ 29 сентября 2010

Присоединяйтесь к веселью

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 голос
/ 03 июня 2009

Еще один вариант ... моя часть 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("\"", "\",'\"',\""));
        }
    }
0 голосов
/ 13 марта 2009

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

Или просто сделайте, как предложено в предыдущем посте. сделать простой функционал, заменив 'и' на 'и' и '

...