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

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

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.


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

    }
}

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

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

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

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

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

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?


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.


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

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

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

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;

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

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


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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.