¿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

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!"

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.


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('\''));

    }
}

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)];
    }
}

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);

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();
    }

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;
        }
    }
}

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();
    }
}

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?


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.


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 + "\"");
    }
}

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();
  }
}

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;
}

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;

-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();
  }
}

-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?


Tengo someString1, pero se lee de un archivo. Quiero que aparezca como someString2 después de llamar a algún método.
Hallgrim
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.