c# - escapar - Codificación de expresiones XPath con comillas simples y dobles
replace comillas dobles c# (9)
XPath (v1) no contiene forma de codificar expresiones.
Si solo tiene comillas simples o dobles, puede usar expresiones como
//review[@name="Bob''s Pizza"]
//review[@name=''"Pizza" Pam'']
Pero si tiene AMBAS, por ejemplo, "Fancy Pizza" de Fred, entonces tiene que usar algo como esto Escaping Strings en XPath (C ++) para generar
//review[@name=Concat("Fred''s ",''"Fancy Pizza"'')]
¿Alguien tiene una función en c # para hacer esto?
Algunos enlaces que están cerca
- Use la biblioteca MVP.XML y XPathVariable (una muy buena solución pero un poco pesada para mis necesidades).
- No codifica donde están presentes "y"
- Agrega más argumentos a la operación Concat de los necesarios, por ejemplo, devolvería // review [@ name = concat (''Fred'', "''",'' s '','' "'','' Fancy Pizza '',''" '','' '') ]
EDITAR: Algunas respuestas han sugerido escapar ''con '
y "con "
pero aunque esto tiene sentido, no funciona; pruébelo con el fragmento XML:
<review name="Bob''s Pizza"/>
y el xpath
//review[@name=''Bob's Pizza'']
Únete a la diversión
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
Aunque ciertamente no funcionará en todas las circunstancias, aquí hay una manera de soslayar el problema:
doc.DocumentElement.SetAttribute("searchName", name);
XmlNode n = doc.SelectNodes("//review[@name=/*/@searchName]");
Esto es lo que se me ocurrió
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();
}
Llamado usando algo como
XmlNode node = doc.SelectSingleNode("//review[@name=" + EncaseXpathString("Fred''s /"Fancy Pizza/"" + "]")
Entonces obtenemos los siguientes resultados
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/",''/"'')";
Entonces solo usa concat cuando es necesario (tanto "como" en la cadena)
El último resultado muestra que la operación de concat no es tan corta como podría ser (vea la pregunta) pero lo suficientemente cerca y lo más óptimo sería muy complejo, ya que tendría que buscar pares de "o".
Guau, seguro que están complicando esto. ¿Por qué no solo hacer esto?
public static string XpathExpression(string value)
{
if (!value.Contains("''"))
return ''/''' + value + ''/''';
else if (!value.Contains("/""))
return ''"'' + value + ''"'';
else
return "concat(''" + value.Replace("''", "'',/"''/",''") + "'')";
}
He tenido problemas con todas las soluciones hasta ahora. Uno tiene secciones de texto adicionales (por ejemplo, "" o "" "") que rompe lo que estás buscando. Uno descarta todo el texto después de la última cita / dblquote, que también se rompe.
Esta es una solución tonta y rápida de un desarrollador de vb tonto:
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
Uso (igual que en los ejemplos anteriores):
x.SelectNodes("/path[@attr=" & ParseXpathString(attrvalue) & "]")
Necesitaba esto, así que creé esta solución para 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();
}
Necesitaba hacer esto en XSLT, así que se me ocurrió lo siguiente en base a las respuestas en esta página:
<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>
Otra variación ... mi parte concat () es un poco floja, pero al menos usa todo el valor.
/// <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("/"", "/",''/"'',/""));
}
}
citar:
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;
}
Y así es como modificas el código anterior para usar nuestra nueva función: // recuerda eliminar las comillas simples después de = y]
XmlNode n = doc.SelectSingleNode(“/root/emp[@lname=" + GetXPathString(ln) + "]“);
O simplemente haz clic en la publicación anterior sugerida. hacer un func fácil reemplazando el ''y "con & apos; & & quot;