Puis-je convertir une valeur de chaîne C # en un littéral de chaîne d'échappement

195

En C #, puis-je convertir une valeur de chaîne en un littéral de chaîne, comme je le verrais dans le code? Je voudrais remplacer les tabulations, les nouvelles lignes, etc. par leurs séquences d'échappement.

Si ce code:

Console.WriteLine(someString);

produit:

Hello
World!

Je veux ce code:

Console.WriteLine(ToLiteral(someString));

produire:

\tHello\r\n\tWorld!\r\n
Hallgrim
la source

Réponses:

180

J'ai trouvé ça:

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

Ce code:

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

Produit:

    Hello
    World!
"\tHello\r\n\tWorld!"
Hallgrim
la source
1
Je viens de trouver cela sur google sur le sujet. Cela doit être le mieux, inutile de réinventer des choses que .net peut faire pour nous
Andy Morris
16
Bien, mais sachez que pour les chaînes plus longues, cela insérera des opérateurs "+", des sauts de ligne et un retrait. Je ne pouvais pas trouver un moyen de désactiver cela.
Timwi
2
Et l'inverse? Si vous avez un fichier contenant du texte contenant des séquences d'échappement, y compris un caractère spécial échappé avec son code ascii? Comment produire une version brute?
Luciano
1
Si vous exécutez: void Main () {Console.WriteLine (ToLiteral ("test \" \ '\\\ 0 \ a \ b \ f \ n \ r \ t \ v \ uaaaa \\\ blah "));} vous remarquerez que cela ne prend pas en charge quelques évasions. Ronnie Overby a souligné \ f, les autres sont \ a et \ b
costa
4
Existe-t-il un moyen de le faire sortir des @"..."littéraux verbatim ( )?
rookie1024
38

Qu'en est-il de Regex.Escape (String) ?

Regex.Escape échappe un ensemble minimal de caractères (\, *, +,?, |, {, [, (,), ^, $,., # Et espace blanc) en les remplaçant par leurs codes d'échappement.

Shqdooow
la source
6
+1 aucune idée pourquoi c'est bien en dessous. D'autres réponses sont tout simplement trop verbeuses et ressemblent à réinventer des roues
Adriano Carneiro
39
Ce n'est pas ce que demande OP. Il ne renvoie pas de chaîne littérale, il renvoie une chaîne avec des caractères spéciaux Regex échappés. Cela se transformerait Hello World?en Hello World\?, mais c'est un littéral de chaîne invalide.
atheaos
1
Je suis d'accord avec @atheaos, c'est une excellente réponse à une question très différente.
hypehuman
5
+1 même si cela ne répond pas tout à fait à la question du PO, c'est ce que je cherchais (et je soupçonne donc peut-être d'autres) lorsque je suis tombé sur cette question. :)
GazB
Cela ne fonctionnera pas comme nécessaire. Les caractères spéciaux regex ne sont pas les mêmes. Cela fonctionnera pour \ n par exemple, mais quand vous aurez un espace, il sera converti en "\" ce qui n'est pas ce que ferait C # ...
Ernesto
24

EDIT: Une approche plus structurée, incluant toutes les séquences d'échappement pour strings et chars.
Ne remplace pas les caractères unicode par leur équivalent littéral. Ne fait pas cuire les œufs non plus.

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
la source
Ce ne sont pas toutes des séquences d'échappement;)
TcKs
1
Fonctionne mieux que la solution ci-dessus - et d'autres séquences d'échappement peuvent facilement être ajoutées.
Arno Peters
Verbatim dans la réponse acceptée me rendait fou. Cela fonctionne à 100% pour mon but. Regex remplacé par @"[\a\b\f\n\r\t\v\\""/]"et ajouté m_replaceDict.Add("/", @"\/");pour JSON.
nom-intéressant-ici
De plus, vous devez ajouter les citations ci-jointes si vous le souhaitez.
nom-intéressant-ici
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
la source
1
Pourquoi y a-t-il 3 barres obliques inverses et deux marques vocales dans la première valeur du dictionnaire?
James Yeoman
Bonne réponse, @JamesYeoman, c'est parce que le motif regex doit être échappé.
Ali Mousavi Kherad
18

essayer:

var t = HttpUtility.JavaScriptStringEncode(s);
Arsen Zahray
la source
Ne marche pas. Si j'ai "abc \ n123" (sans guillemets, 8 caractères), je veux "abc" + \ n + "123" (7 caractères). Au lieu de cela, il produit "abc" + "\\" + "\ n123" (9 caractères). Notez que la barre oblique a été doublée et qu'elle contient toujours une chaîne littérale de "\ n" en deux caractères, pas le caractère échappé.
Paul
2
@Paul Ce que vous voulez est l'opposé de ce que la question vous demande. Ce qui , selon votre description, répond à la question, et donc fait le travail.
Fund Monica's Lawsuit
J'ai trouvé cela utile pour échapper aux noms de répertoires actifs dans le frontend
chakeda
18

Implémentation pleinement opérationnelle, y compris l'échappement des caractères non imprimables Unicode et ASCII. N'insère pas de signe "+" comme la réponse d'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
la source
2
Vous devriez utiliser Char.GetUnicodeCategory(c) == UnicodeCategory.Controlpour décider si vous voulez y échapper, ou les personnes qui ne parlent pas ASCII ne seront pas très heureuses.
deerchao
Cela dépend de la situation si votre chaîne résultante sera utilisée ou non dans l'environnement prenant en charge Unicode.
Smilediver
J'ai ajouté input = input ?? string.Empty;comme première ligne de la méthode afin que je puisse passer nullet récupérer au ""lieu d'une exception de référence nulle.
Andy
Agréable. Modifiez les guillemets fermants 'et vous avez maintenant ce que Python vous offre avec repr(a_string):).
z33k
17

La réponse de Hallgrim est excellente, mais les ajouts "+", la nouvelle ligne et le retrait étaient une rupture de fonctionnalité pour moi. Un moyen simple de le contourner est:

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
la source
Fonctionne très bien. J'ai également ajouté une ligne avant le return literalpour le rendre plus lisible: literal = literal.Replace("\\r\\n", "\\r\\n\"+\r\n\"");
Bob
Ajouté ceci literal = literal.Replace("/", @"\/");pour la JSONfonctionnalité.
nom-intéressant-ici
C'est 100% simple et la seule bonne réponse! Toutes les autres réponses n'ont pas compris la question ou ont réinventé la roue.
bytecode77
Malheureusement, cela ne peut pas fonctionner sous DOTNET CORE. Quelqu'un a une meilleure réponse?
sk
8

Voici une petite amélioration pour la réponse de Smilediver, elle n'échappera pas à tous les caractères sans ASCII mais seuls ceux-ci sont vraiment nécessaires.

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();
    }
}
deerchao
la source
8

Question interessante.

Si vous ne trouvez pas de meilleure méthode, vous pouvez toujours la remplacer.
Si vous optez pour cette option, vous pouvez utiliser cette liste de séquences d'échappement C # :

  • \ '- guillemet simple, nécessaire pour les littéraux de caractères
  • \ "- guillemet double, nécessaire pour les littéraux de chaîne
  • \ - barre oblique inverse
  • \ 0 - Caractère Unicode 0
  • \ a - Alerte (caractère 7)
  • \ b - Retour arrière (caractère 8)
  • \ f - Flux de formulaire (caractère 12)
  • \ n - Nouvelle ligne (caractère 10)
  • \ r - Retour chariot (caractère 13)
  • \ t - Onglet horizontal (caractère 9)
  • \ v - Citation verticale (caractère 11)
  • \ uxxxx - Séquence d'échappement Unicode pour un caractère de valeur hexadécimale xxxx
  • \ xn [n] [n] [n] - Séquence d'échappement Unicode pour le caractère de valeur hexadécimale nnnn (version à longueur variable de \ uxxxx)
  • \ Uxxxxxxxx - Séquence d'échappement Unicode pour un caractère de valeur hexadécimale xxxxxxxx (pour générer des substituts)

Cette liste se trouve dans la foire aux questions C # Quelles séquences d'échappement de caractères sont disponibles?

Nelson Reis
la source
2
Ce lien ne fonctionne plus, un exemple de manuel expliquant pourquoi les réponses de lien uniquement sont découragées.
James
Très vrai, @James, mais grâce à Jamie Twells, les informations sont à nouveau disponibles: +1:
Nelson Reis
5

Il existe une méthode pour cela dans le package Microsoft.CodeAnalysis.CSharp de Roslyn sur nuget:

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

Évidemment, cela n'existait pas au moment de la question d'origine, mais pourrait aider les personnes qui se retrouvent ici de Google.

Graham
la source
3

Si les conventions JSON sont suffisantes pour les chaînes non échappées que vous souhaitez obtenir échappées et que vous utilisez déjà Newtonsoft.Jsondans votre projet (il a une surcharge assez importante), vous pouvez utiliser ce package comme suit:

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
la source
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
la source
2

Ma tentative d'ajouter ToVerbatim à la réponse acceptée de Hallgrim ci-dessus:

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
la source
1

La réponse d'Hallgrim était excellente. Voici un petit ajustement au cas où vous auriez besoin d'analyser des espaces et des sauts de ligne supplémentaires avec une expression régulière ac #. J'en avais besoin dans le cas d'une valeur Json sérialisée pour l'insertion dans des feuilles Google et j'ai rencontré des problèmes car le code insérait des tabulations, +, des espaces, 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
la source
-1

Je soumets ma propre implémentation, qui gère les nullvaleurs et devrait être plus performante en raison de l'utilisation des tables de recherche de tableaux, de la conversion hexadécimale manuelle et de l'évitement des switchinstructions.

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
la source
-7

Code:

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

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

Production:

    Hello
    World!

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

c'est ce que tu veux?

rfgamaral
la source
J'ai someString1, mais il est lu à partir d'un fichier. Je veux qu'il apparaisse comme someString2 après avoir appelé une méthode.
Hallgrim