¿Puedo convertir un valor de cadena C # en un literal de cadena escapado?

195

En C #, ¿puedo convertir un valor de cadena en un literal de cadena, como lo vería en el código? Me gustaría reemplazar pestañas, nuevas líneas, etc. con sus secuencias de escape.

Si este código:

Console.WriteLine(someString);

produce:

Hello
World!

Quiero este código:

Console.WriteLine(ToLiteral(someString));

para producir:

\tHello\r\n\tWorld!\r\n
Hallgrim
fuente

Respuestas:

180

Encontré esto:

private static string ToLiteral(string input)
{
    using (var writer = new StringWriter())
    {
        using (var provider = CodeDomProvider.CreateProvider("CSharp"))
        {
            provider.GenerateCodeFromExpression(new CodePrimitiveExpression(input), writer, null);
            return writer.ToString();
        }
    }
}

Este código:

var input = "\tHello\r\n\tWorld!";
Console.WriteLine(input);
Console.WriteLine(ToLiteral(input));

Produce:

    Hello
    World!
"\tHello\r\n\tWorld!"
Hallgrim
fuente
1
Acabo de encontrar esto en google el tema. Esto tiene que ser lo mejor, no tiene sentido reinventar cosas que .net puede hacer por nosotros
Andy Morris
16
Buena, pero tenga en cuenta que para cadenas más largas, esto insertará operadores "+", nuevas líneas y sangría. No pude encontrar una manera de apagar eso.
Timwi
2
¿Qué pasa con el inverso? Si tiene un archivo con texto que contiene secuencias de escape que incluyen caracteres especiales escapados con su código ASCII? ¿Cómo producir una versión en bruto?
Luciano
1
Si ejecuta: void Main () {Console.WriteLine (ToLiteral ("test \" \ '\\\ 0 \ a \ b \ f \ n \ r \ t \ v \ uaaaa \\\ blah "));} notarás que esto no resuelve algunos escapes. Ronnie Overby señaló \ f, los otros son \ a y \ b
costa
44
¿Hay alguna manera de hacer que genere @"..."literales verbatim ( )?
rookie1024
38

¿Qué pasa con Regex.Escape (String) ?

Regex.Escape escapa de un conjunto mínimo de caracteres (\, *, +,?, |, {, [, (,), ^, $,., # Y espacios en blanco) al reemplazarlos con sus códigos de escape.

Shqdooow
fuente
66
No tengo idea de por qué esto está muy por debajo. Otras respuestas son demasiado detalladas y parecen reinventar las ruedas
Adriano Carneiro
39
Esto no es lo que está pidiendo OP. No devuelve una cadena literal, devuelve una cadena con caracteres especiales Regex escapados. Esto se convertiría Hello World?en Hello World\?, pero ese es un literal de cadena no válido.
ateos
1
Estoy de acuerdo con @atheaos, esta es una gran respuesta a una pregunta muy diferente.
hypehuman
55
+1 a pesar de que no responde a la pregunta del OP, era lo que yo (y sospecho que tal vez otros) estaban buscando cuando me encontré con esta pregunta. :)
GazB
Esto no funcionará según sea necesario. Los caracteres especiales regex no son lo mismo. Funcionará para \ n por ejemplo, pero cuando tenga un espacio, se convertirá a "\", que no es lo que C # haría ...
Ernesto
24

EDITAR: Un enfoque más estructurado, que incluye todas las secuencias de escape para stringsy chars.
No reemplaza los caracteres unicode con su equivalente literal. Tampoco cocina huevos.

public class ReplaceString
{
    static readonly IDictionary<string, string> m_replaceDict 
        = new Dictionary<string, string>();

    const string ms_regexEscapes = @"[\a\b\f\n\r\t\v\\""]";

    public static string StringLiteral(string i_string)
    {
        return Regex.Replace(i_string, ms_regexEscapes, match);
    }

    public static string CharLiteral(char c)
    {
        return c == '\'' ? @"'\''" : string.Format("'{0}'", c);
    }

    private static string match(Match m)
    {
        string match = m.ToString();
        if (m_replaceDict.ContainsKey(match))
        {
            return m_replaceDict[match];
        }

        throw new NotSupportedException();
    }

    static ReplaceString()
    {
        m_replaceDict.Add("\a", @"\a");
        m_replaceDict.Add("\b", @"\b");
        m_replaceDict.Add("\f", @"\f");
        m_replaceDict.Add("\n", @"\n");
        m_replaceDict.Add("\r", @"\r");
        m_replaceDict.Add("\t", @"\t");
        m_replaceDict.Add("\v", @"\v");

        m_replaceDict.Add("\\", @"\\");
        m_replaceDict.Add("\0", @"\0");

        //The SO parser gets fooled by the verbatim version 
        //of the string to replace - @"\"""
        //so use the 'regular' version
        m_replaceDict.Add("\"", "\\\""); 
    }

    static void Main(string[] args){

        string s = "here's a \"\n\tstring\" to test";
        Console.WriteLine(ReplaceString.StringLiteral(s));
        Console.WriteLine(ReplaceString.CharLiteral('c'));
        Console.WriteLine(ReplaceString.CharLiteral('\''));

    }
}
Cristian Diaconescu
fuente
Estas no son todas las secuencias de escape;)
TcKs
1
Funciona mejor que la solución anterior, y se pueden agregar fácilmente otras secuencias de escape.
Arno Peters
Verbatim en la respuesta aceptada me estaba volviendo loco. Esto funciona 100% para mi propósito. Reemplazado regex con @"[\a\b\f\n\r\t\v\\""/]"y agregado m_replaceDict.Add("/", @"\/");para JSON.
interesante-nombre-aquí
Además, debe agregar las citas adjuntas a esto si lo desea.
interesante-nombre-aquí
19
public static class StringHelpers
{
    private static Dictionary<string, string> escapeMapping = new Dictionary<string, string>()
    {
        {"\"", @"\\\"""},
        {"\\\\", @"\\"},
        {"\a", @"\a"},
        {"\b", @"\b"},
        {"\f", @"\f"},
        {"\n", @"\n"},
        {"\r", @"\r"},
        {"\t", @"\t"},
        {"\v", @"\v"},
        {"\0", @"\0"},
    };

    private static Regex escapeRegex = new Regex(string.Join("|", escapeMapping.Keys.ToArray()));

    public static string Escape(this string s)
    {
        return escapeRegex.Replace(s, EscapeMatchEval);
    }

    private static string EscapeMatchEval(Match m)
    {
        if (escapeMapping.ContainsKey(m.Value))
        {
            return escapeMapping[m.Value];
        }
        return escapeMapping[Regex.Escape(m.Value)];
    }
}
ICR
fuente
1
¿Por qué hay 3 barras invertidas y dos marcas de voz en el primer valor del diccionario?
James Yeoman
Buena respuesta, @JamesYeoman, eso se debe a que se debe escapar el patrón de expresiones regulares.
Ali Mousavi Kherad
18

tratar:

var t = HttpUtility.JavaScriptStringEncode(s);
Arsen Zahray
fuente
No funciona. Si tengo "abc \ n123" (sin comillas, 8 caracteres), quiero "abc" + \ n + "123" (7 caracteres). En su lugar, produce "abc" + "\\" + "\ n123" (9 caracteres). Observe que la barra diagonal se duplicó y todavía contiene una cadena literal de "\ n" como dos caracteres, no el carácter escapado.
Paul
2
@Paul Sin embargo, lo que quieres es lo opuesto a lo que está haciendo la pregunta. Esto, de acuerdo con su descripción, responde a la pregunta y, por lo tanto , funciona.
Financia la demanda de Mónica el
Encontré esto útil para escapar de los nombres de directorio activo en la interfaz
chakeda
18

Implementación totalmente funcional, incluido el escape de caracteres no imprimibles Unicode y ASCII. No inserta signos "+" como la respuesta de Hallgrim .

    static string ToLiteral(string input) {
        StringBuilder literal = new StringBuilder(input.Length + 2);
        literal.Append("\"");
        foreach (var c in input) {
            switch (c) {
                case '\'': literal.Append(@"\'"); break;
                case '\"': literal.Append("\\\""); break;
                case '\\': literal.Append(@"\\"); break;
                case '\0': literal.Append(@"\0"); break;
                case '\a': literal.Append(@"\a"); break;
                case '\b': literal.Append(@"\b"); break;
                case '\f': literal.Append(@"\f"); break;
                case '\n': literal.Append(@"\n"); break;
                case '\r': literal.Append(@"\r"); break;
                case '\t': literal.Append(@"\t"); break;
                case '\v': literal.Append(@"\v"); break;
                default:
                    // ASCII printable character
                    if (c >= 0x20 && c <= 0x7e) {
                        literal.Append(c);
                    // As UTF16 escaped character
                    } else {
                        literal.Append(@"\u");
                        literal.Append(((int)c).ToString("x4"));
                    }
                    break;
            }
        }
        literal.Append("\"");
        return literal.ToString();
    }
Smilediver
fuente
2
Deberías Char.GetUnicodeCategory(c) == UnicodeCategory.Controldecidir si escapar o las personas que no hablan ASCII no estarán muy contentas.
Ciervo
Esto depende de la situación si la cadena resultante se usará en el entorno compatible con Unicode o no.
Smilediver
Agregué input = input ?? string.Empty;como la primera línea del método para poder pasar nully volver en ""lugar de una excepción de referencia nula.
Andy
Agradable. Cambie las comillas adjuntas a 'y ahora tiene lo que Python le da de fábrica con repr(a_string):).
z33k
17

La respuesta de Hallgrim es excelente, pero las adiciones de "+", nueva línea y sangría me estaban rompiendo la funcionalidad. Una forma fácil de evitarlo es:

private static string ToLiteral(string input)
{
    using (var writer = new StringWriter())
    {
        using (var provider = CodeDomProvider.CreateProvider("CSharp"))
        {
            provider.GenerateCodeFromExpression(new CodePrimitiveExpression(input), writer, new CodeGeneratorOptions {IndentString = "\t"});
            var literal = writer.ToString();
            literal = literal.Replace(string.Format("\" +{0}\t\"", Environment.NewLine), "");
            return literal;
        }
    }
}
lesur
fuente
Funciona genial. También agregué una línea antes return literalpara hacerla más legible: literal = literal.Replace("\\r\\n", "\\r\\n\"+\r\n\"");
Bob
Se agregó esto literal = literal.Replace("/", @"\/");para la JSONfuncionalidad.
interesante-nombre-aquí
¡Esto es 100% sencillo y la única respuesta correcta! Todas las demás respuestas no entendieron la pregunta o reinventaron la rueda.
bytecode77
Triste, no puedo hacer que esto funcione bajo DOTNET CORE. Alguien tiene una mejor respuesta?
sk
8

Aquí hay una pequeña mejora para la respuesta de Smilediver, no escapará a todos los caracteres sin ASCII, pero solo estos son realmente necesarios.

using System;
using System.Globalization;
using System.Text;

public static class CodeHelper
{
    public static string ToLiteral(this string input)
    {
        var literal = new StringBuilder(input.Length + 2);
        literal.Append("\"");
        foreach (var c in input)
        {
            switch (c)
            {
                case '\'': literal.Append(@"\'"); break;
                case '\"': literal.Append("\\\""); break;
                case '\\': literal.Append(@"\\"); break;
                case '\0': literal.Append(@"\0"); break;
                case '\a': literal.Append(@"\a"); break;
                case '\b': literal.Append(@"\b"); break;
                case '\f': literal.Append(@"\f"); break;
                case '\n': literal.Append(@"\n"); break;
                case '\r': literal.Append(@"\r"); break;
                case '\t': literal.Append(@"\t"); break;
                case '\v': literal.Append(@"\v"); break;
                default:
                    if (Char.GetUnicodeCategory(c) != UnicodeCategory.Control)
                    {
                        literal.Append(c);
                    }
                    else
                    {
                        literal.Append(@"\u");
                        literal.Append(((ushort)c).ToString("x4"));
                    }
                    break;
            }
        }
        literal.Append("\"");
        return literal.ToString();
    }
}
ciervo
fuente
8

Interesante pregunta.

Si no puede encontrar un método mejor, siempre puede reemplazarlo.
En caso de que esté optando por él, puede usar esta Lista de secuencia de escape de C # :

  • \ '- comilla simple, necesaria para literales de caracteres
  • \ "- comillas dobles, necesarias para literales de cadena
  • \ - barra invertida
  • \ 0 - Carácter Unicode 0
  • \ a - Alerta (personaje 7)
  • \ b - Retroceso (carácter 8)
  • \ f - Alimentación de formulario (carácter 12)
  • \ n - Nueva línea (carácter 10)
  • \ r - Retorno de carro (personaje 13)
  • \ t - Pestaña horizontal (carácter 9)
  • \ v - Cita vertical (carácter 11)
  • \ uxxxx - Secuencia de escape Unicode para caracteres con valor hexadecimal xxxx
  • \ xn [n] [n] [n] - Secuencia de escape Unicode para caracteres con valor hexadecimal nnnn (versión de longitud variable de \ uxxxx)
  • \ Uxxxxxxxx: secuencia de escape Unicode para el personaje con valor hexadecimal xxxxxxxx (para generar sustitutos)

Esta lista se puede encontrar en C # Preguntas frecuentes ¿Qué secuencias de escape de caracteres están disponibles?

Nelson Reis
fuente
2
Este enlace ya no funciona, un ejemplo de libro de texto de por qué se desaconsejan las respuestas de solo enlace.
James
Muy cierto, @James, pero gracias a Jamie Twells la información está disponible nuevamente: +1:
Nelson Reis
5

Hay un método para esto en el paquete Microsoft.CodeAnalysis.CSharp de Roslyn en nuget:

    private static string ToLiteral(string valueTextForCompiler)
    {
        return Microsoft.CodeAnalysis.CSharp.SymbolDisplay.FormatLiteral(valueTextForCompiler, false);
    }

Obviamente, esto no existía en el momento de la pregunta original, pero podría ayudar a las personas que terminan aquí desde Google.

Graham
fuente
3

Si las convenciones JSON son suficientes para las cadenas sin escape que desea escapar y que ya usa Newtonsoft.Jsonen su proyecto (tiene una sobrecarga bastante grande), puede usar este paquete de la siguiente manera:

using System;
using Newtonsoft.Json;

public class Program
{
    public static void Main()
    {
    Console.WriteLine(ToLiteral( @"abc\n123") );
    }

    private static string ToLiteral(string input){
        return JsonConvert.DeserializeObject<string>("\"" + input + "\"");
    }
}
Ehsan88
fuente
2
public static class StringEscape
{
  static char[] toEscape = "\0\x1\x2\x3\x4\x5\x6\a\b\t\n\v\f\r\xe\xf\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\"\\".ToCharArray();
  static string[] literals = @"\0,\x0001,\x0002,\x0003,\x0004,\x0005,\x0006,\a,\b,\t,\n,\v,\f,\r,\x000e,\x000f,\x0010,\x0011,\x0012,\x0013,\x0014,\x0015,\x0016,\x0017,\x0018,\x0019,\x001a,\x001b,\x001c,\x001d,\x001e,\x001f".Split(new char[] { ',' });

  public static string Escape(this string input)
  {
    int i = input.IndexOfAny(toEscape);
    if (i < 0) return input;

    var sb = new System.Text.StringBuilder(input.Length + 5);
    int j = 0;
    do
    {
      sb.Append(input, j, i - j);
      var c = input[i];
      if (c < 0x20) sb.Append(literals[c]); else sb.Append(@"\").Append(c);
    } while ((i = input.IndexOfAny(toEscape, j = ++i)) > 0);

    return sb.Append(input, j, input.Length - j).ToString();
  }
}
Serge N
fuente
2

Mi intento de agregar ToVerbatim a la respuesta aceptada de Hallgrim arriba:

private static string ToLiteral(string input)
{
    using (var writer = new StringWriter())
    {
        using (var provider = CodeDomProvider.CreateProvider("CSharp"))
        {
            provider.GenerateCodeFromExpression(new CodePrimitiveExpression(input), writer, new CodeGeneratorOptions { IndentString = "\t" });
            var literal = writer.ToString();
            literal = literal.Replace(string.Format("\" +{0}\t\"", Environment.NewLine), "");           
            return literal;
        }
    }
}

private static string ToVerbatim( string input )
{
    string literal = ToLiteral( input );
    string verbatim = "@" + literal.Replace( @"\r\n", Environment.NewLine );
    return verbatim;
}
Derek
fuente
1

La respuesta de Hallgrim fue excelente. Aquí hay un pequeño ajuste en caso de que necesite analizar caracteres de espacio en blanco adicionales y saltos de línea con una expresión regular ac #. Necesitaba esto en el caso de un valor Json serializado para la inserción en las hojas de Google y tuve problemas ya que el código estaba insertando pestañas, +, espacios, etc.

  provider.GenerateCodeFromExpression(new CodePrimitiveExpression(input), writer, null);
  var literal = writer.ToString();
  var r2 = new Regex(@"\"" \+.\n[\s]+\""", RegexOptions.ECMAScript);
  literal = r2.Replace(literal, "");
  return literal;
Alexander Yoshi
fuente
-1

Presento mi propia implementación, que maneja los nullvalores y debería ser más eficiente debido al uso de tablas de búsqueda de matriz, conversión hexadecimal manual y evitar switchdeclaraciones.

using System;
using System.Text;
using System.Linq;

public static class StringLiteralEncoding {
  private static readonly char[] HEX_DIGIT_LOWER = "0123456789abcdef".ToCharArray();
  private static readonly char[] LITERALENCODE_ESCAPE_CHARS;

  static StringLiteralEncoding() {
    // Per http://msdn.microsoft.com/en-us/library/h21280bw.aspx
    var escapes = new string[] { "\aa", "\bb", "\ff", "\nn", "\rr", "\tt", "\vv", "\"\"", "\\\\", "??", "\00" };
    LITERALENCODE_ESCAPE_CHARS = new char[escapes.Max(e => e[0]) + 1];
    foreach(var escape in escapes)
      LITERALENCODE_ESCAPE_CHARS[escape[0]] = escape[1];
  }

  /// <summary>
  /// Convert the string to the equivalent C# string literal, enclosing the string in double quotes and inserting
  /// escape sequences as necessary.
  /// </summary>
  /// <param name="s">The string to be converted to a C# string literal.</param>
  /// <returns><paramref name="s"/> represented as a C# string literal.</returns>
  public static string Encode(string s) {
    if(null == s) return "null";

    var sb = new StringBuilder(s.Length + 2).Append('"');
    for(var rp = 0; rp < s.Length; rp++) {
      var c = s[rp];
      if(c < LITERALENCODE_ESCAPE_CHARS.Length && '\0' != LITERALENCODE_ESCAPE_CHARS[c])
        sb.Append('\\').Append(LITERALENCODE_ESCAPE_CHARS[c]);
      else if('~' >= c && c >= ' ')
        sb.Append(c);
      else
        sb.Append(@"\x")
          .Append(HEX_DIGIT_LOWER[c >> 12 & 0x0F])
          .Append(HEX_DIGIT_LOWER[c >>  8 & 0x0F])
          .Append(HEX_DIGIT_LOWER[c >>  4 & 0x0F])
          .Append(HEX_DIGIT_LOWER[c       & 0x0F]);
    }

    return sb.Append('"').ToString();
  }
}
J Cracknell
fuente
-7

Código:

string someString1 = "\tHello\r\n\tWorld!\r\n";
string someString2 = @"\tHello\r\n\tWorld!\r\n";

Console.WriteLine(someString1);
Console.WriteLine(someString2);

Salida:

    Hello
    World!

\tHello\r\n\tWorld!\r\n

¿Es esto lo que quieres?

rfgamaral
fuente
Tengo someString1, pero se lee de un archivo. Quiero que aparezca como someString2 después de llamar a algún método.
Hallgrim