Posso convertire un valore di stringa C # in un valore letterale di stringa con escape


196

In C #, posso convertire un valore di stringa in un valore letterale di stringa, come lo vedrei nel codice? Vorrei sostituire schede, newline, ecc. Con le loro sequenze di escape.

Se questo codice:

Console.WriteLine(someString);

produce:

Hello
World!

Voglio questo codice:

Console.WriteLine(ToLiteral(someString));

produrre:

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

Risposte:


181

Ho trovato questo:

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

Questo codice:

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

produce:

    Hello
    World!
"\tHello\r\n\tWorld!"

1
Ho appena trovato questo da Google l'argomento. Questo deve essere il migliore, inutile reinventare cose che .net può fare per noi
Andy Morris,

16
Bello, ma tieni presente che per stringhe più lunghe, verranno inseriti operatori "+", newline e rientri. Non sono riuscito a trovare un modo per disattivarlo.
Timwi,

2
Che dire dell'inverso? Se hai un file con testo contenente sequenze di escape che includono caratteri speciali sfuggiti al suo codice ASCII? Come produrre una versione non elaborata?
Luciano,

1
Se esegui: void Main () {Console.WriteLine (ToLiteral ("test \" \ '\\\ 0 \ a \ b \ f \ n \ r \ t \ v \ uaaaa \\\ blah "));} noterai che questo non si occupa di alcune fughe. Ronnie Overby ha indicato \ f, le altre sono \ a e \ b
costa

4
C'è un modo per renderlo in output verbatim ( @"...") letterali?
rookie1024

39

Che dire di Regex.Escape (String) ?

Regex.Escape sfugge a un set minimo di caratteri (\, *, +,?, |, {, [, (,), ^, $,., # E spazio bianco) sostituendoli con i loro codici di escape.


6
+1 non ho idea del perché di seguito. Altre risposte sono troppo dettagliate e sembrano reinventare le ruote
Adriano Carneiro,

40
Questo non è ciò che l'OP chiede. Non restituisce una stringa letterale, restituisce una stringa con caratteri speciali Regex con escape. Ciò si trasformerebbe Hello World?in Hello World\?, ma si tratta di una stringa non valida letterale.
Atene,

2
Sono d'accordo con @atheaos, questa è un'ottima risposta a una domanda molto diversa.
hypehuman,

5
+1 anche se non risponde completamente alla domanda del PO, era quello che io (e quindi sospetto forse altri) stavo cercando quando mi sono imbattuto in questa domanda. :)
GazB,

Questo non funzionerà come necessario. I caratteri speciali regex non sono gli stessi. Funzionerà per \ n per esempio, ma quando hai uno spazio, sarà convertito in "\" che non è quello che farebbe C # ...
Ernesto

25

EDIT: un approccio più strutturato, comprese tutte le sequenze di escape per strings e chars.
Non sostituisce i caratteri unicode con il loro equivalente letterale. Neanche cuocere le uova.

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

    }
}

Non sono tutte sequenze di escape;)
TcKs,

1
Funziona meglio della soluzione sopra e altre sequenze di escape possono essere facilmente aggiunte.
Arno Peters,

Verbatim nella risposta accettata mi stava facendo impazzire. Questo funziona al 100% per il mio scopo. Sostituito regex con @"[\a\b\f\n\r\t\v\\""/]"e aggiunto m_replaceDict.Add("/", @"\/");per JSON.
interessante-nome-qui

Inoltre, è necessario aggiungere le citazioni allegate a questo se si desidera quelli.
interessante-nome-qui

19

provare:

var t = HttpUtility.JavaScriptStringEncode(s);

Non funziona. Se ho "abc \ n123" (senza virgolette, 8 caratteri), voglio "abc" + \ n + "123" (7 caratteri). Invece produce "abc" + "\\" + "\ n123" (9 caratteri). Si noti che la barra è stata raddoppiata e contiene ancora una stringa letterale di "\ n" come due caratteri, non il carattere di escape.
Paul

2
@Paul Quello che vuoi è il contrario di quello che la domanda sta ponendo, però. Questo, secondo la tua descrizione, risponde alla domanda, e quindi fa il lavoro.
Finanzia la causa di Monica il

L'ho trovato utile per sfuggire ai nomi delle directory attive nel frontend
chakeda,

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
Perché nel primo valore del dizionario sono presenti 3 barre rovesciate e due segni vocali?
James Yeoman,

Bella risposta, @JamesYeoman è perché il pattern regex deve essere evitato.
Ali Mousavi Kherad,

18

Implementazione completamente funzionante, inclusa la fuga di caratteri Unicode e ASCII non stampabili. Non inserisce segni "+" come la risposta di 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
Dovresti usare Char.GetUnicodeCategory(c) == UnicodeCategory.Controlper decidere se fuggire, o le persone che non parlano ASCII non saranno molto felici.
Deerchao,

Ciò dipende dalla situazione se la stringa risultante verrà utilizzata nell'ambiente che supporta Unicode o meno.
Smilediver,

Ho aggiunto input = input ?? string.Empty;come prima riga del metodo in modo da poter passare nulle tornare ""invece di un'eccezione di riferimento null.
Andy,

Bello. Cambia le virgolette allegate a 'e ora hai ciò che Python ti dà fuori dalla scatola con repr(a_string):).
z33k,

17

La risposta di Hallgrim è eccellente, ma le aggiunte "+", newline e rientro mi hanno rotto la funzionalità. Un modo semplice per aggirare è:

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

Funziona alla grande. Ho anche aggiunto una riga prima del return literalper renderlo più leggibile: literal = literal.Replace("\\r\\n", "\\r\\n\"+\r\n\"");
Bob

Aggiunto questo literal = literal.Replace("/", @"\/");per JSONfunzionalità.
interessante-nome-qui

Questo è al 100% semplice e l'unica risposta corretta! Tutte le altre risposte o non capivano la domanda o reinventavano la ruota.
bytecode77

Triste, non riesco a farlo funzionare sotto DOTNET CORE. Qualcuno ha una risposta migliore?
sk

8

Ecco un piccolo miglioramento per la risposta di Smilediver, non sfuggirà a tutti i caratteri senza ASCII ma solo questi sono davvero necessari.

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

Domanda interessante.

Se non riesci a trovare un metodo migliore, puoi sempre sostituirlo.
Nel caso tu stia optando per questo, puoi usare questo Elenco sequenze di escape C # :

  • \ '- virgoletta singola, necessaria per i letterali dei personaggi
  • \ "- virgoletta doppia, necessaria per i letterali stringa
  • \ - barra rovesciata
  • \ 0 - Carattere Unicode 0
  • \ a - Avviso (carattere 7)
  • \ b - Backspace (carattere 8)
  • \ f - Form feed (carattere 12)
  • \ n - Nuova riga (carattere 10)
  • - Ritorno a capo (carattere 13)
  • \ t - Scheda orizzontale (carattere 9)
  • \ v - Virgoletta verticale (carattere 11)
  • \ uxxxx - Sequenza di escape Unicode per carattere con valore esadecimale xxxx
  • \ xn [n] [n] [n] - Sequenza di escape Unicode per carattere con valore esadecimale nnnn (versione a lunghezza variabile di \ uxxxx)
  • \ Uxxxxxxxx - Sequenza di escape Unicode per carattere con valore esadecimale xxxxxxxx (per la generazione di surrogati)

Questo elenco è disponibile nelle Domande frequenti su C # Quali sequenze di escape caratteri sono disponibili?


2
Questo collegamento non funziona più, un esempio da manuale del perché le risposte solo al collegamento sono scoraggiate.
James,

Molto vero, @James, ma grazie a Jamie Twells le informazioni sono di nuovo disponibili: +1:
Nelson Reis,

5

C'è un metodo per questo nel pacchetto Microsoft.CodeAnalysis.CSharp di Roslyn su nuget:

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

Ovviamente questo non esisteva al momento della domanda originale, ma potrebbe aiutare le persone che finiscono qui da Google.


3

Se le convenzioni JSON sono sufficienti per le stringhe senza caratteri di escape che si desidera sfuggire e si utilizza già Newtonsoft.Jsonnel progetto (ha un sovraccarico piuttosto ampio) è possibile utilizzare questo pacchetto come il seguente:

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

Il mio tentativo di aggiungere ToVerbatim alla risposta accettata di Hallgrim sopra:

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 risposta di Hallgrim è stata eccellente. Ecco una piccola modifica nel caso in cui sia necessario analizzare ulteriori caratteri di spazi bianchi e interruzioni di riga con espressione regolare ac #. Ne avevo bisogno nel caso di un valore Json serializzato per l'inserimento in fogli di Google e ho avuto problemi mentre il codice inseriva schede, +, spazi, ecc.

  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

Inoltro la mia implementazione, che gestisce i nullvalori e dovrebbe essere più performante a causa dell'utilizzo di tabelle di ricerca di array, conversione esadecimale manuale ed evitare switchistruzioni.

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

Codice:

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

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

Produzione:

    Hello
    World!

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

È questo che vuoi?


Ho someString1, ma è letto da un file. Voglio che appaia come someString2 dopo aver chiamato un metodo.
Hallgrim,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.