Czy mogę przekonwertować wartość ciągu C # na literał ciągu znaków ucieczki

195

Czy w C # mogę przekonwertować wartość ciągu na literał ciągu, tak jak widziałbym to w kodzie? Chciałbym zastąpić tabulatory, znaki nowej linii itp. Sekwencjami ucieczki.

Jeśli ten kod:

Console.WriteLine(someString);

produkuje:

Hello
World!

Chcę ten kod:

Console.WriteLine(ToLiteral(someString));

produkować:

\tHello\r\n\tWorld!\r\n
Hallgrim
źródło

Odpowiedzi:

180

Znalazłem to:

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

Ten kod:

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

Produkuje:

    Hello
    World!
"\tHello\r\n\tWorld!"
Hallgrim
źródło
1
Właśnie znalazłem to w google temat. To musi być najlepsze, nie ma sensu wymyślać rzeczy, które .net może dla nas zrobić
Andy Morris,
16
Fajny, ale pamiętaj, że w przypadku dłuższych ciągów wstawi to operatory „+”, znaki nowej linii i wcięcia. Nie mogłem znaleźć sposobu, aby to wyłączyć.
Timwi
2
A co z odwrotnością? Jeśli masz plik z tekstem zawierającym sekwencje specjalne, w tym specjalny znak uciekł wraz ze swoim kodem ascii? Jak wyprodukować surową wersję?
Luciano,
1
Jeśli uruchomisz: void Main () {Console.WriteLine (ToLiteral ("test \" \ '\\\ 0 \ a \ b \ f \ n \ r \ t \ v \ uaaaa \\\ blah "));} zauważysz, że to nie rozwiązuje kilku ucieczek. Ronnie Overby wskazał \ f, inni są \ ai \ b
costa
4
Czy istnieje sposób, aby wyświetlał @"..."literały dosłowne ( )?
rookie1024
38

Co z Regex.Escape (String) ?

Regex.Escape unika minimalnego zestawu znaków (\, *, +,?, |, {, [, (,), ^, $,., # I białych znaków), zastępując je kodami ucieczki.

Shqdooow
źródło
6
+1 nie mam pojęcia, dlaczego tak jest poniżej. Inne odpowiedzi są zbyt szczegółowe i wyglądają jak wynalezienie kół
Adriano Carneiro,
39
O to nie prosi OP. Nie zwraca literału ciągu, zwraca ciąg znaków ze znakami specjalnymi Regex. Zmieniłoby się to Hello World?w Hello World\?, ale jest to niepoprawny literał łańcuchowy.
atheaos
1
Zgadzam się z @atheaos, to świetna odpowiedź na zupełnie inne pytanie.
hypehuman
5
+1, chociaż nie do końca odpowiada na pytanie PO, to było to, czego szukałem (i podejrzewam, że inni), kiedy natknąłem się na to pytanie. :)
GazB
To nie zadziała w razie potrzeby. Znaki specjalne wyrażeń regularnych nie są takie same. Będzie działał na przykład dla \ n, ale gdy masz spację, zostanie przekonwertowany na „\”, co nie jest tym, co zrobiłby C # ...
Ernesto
24

EDYCJA: Bardziej uporządkowane podejście, obejmujące wszystkie sekwencje specjalne dla strings i chars.
Nie zastępuje znaków Unicode ich dosłownym odpowiednikiem. Nie gotuje też jajek.

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
źródło
To nie wszystkie sekwencje specjalne;)
TcKs
1
Działa lepiej niż powyższe rozwiązanie - a inne sekwencje specjalne można łatwo dodać.
Arno Peters,
Verbatim w przyjętej odpowiedzi doprowadzał mnie do szału. Działa to w 100% dla moich celów. Zamieniono wyrażenie regularne na @"[\a\b\f\n\r\t\v\\""/]"i dodano m_replaceDict.Add("/", @"\/");dla JSON.
ciekawe-nazwa-tutaj
Ponadto, jeśli chcesz, musisz dodać załączające cytaty.
ciekawe-nazwa-tutaj
19

próbować:

var t = HttpUtility.JavaScriptStringEncode(s);
Arsen Zahray
źródło
Nie działa. Jeśli mam „abc \ n123” (bez cudzysłowów, 8 znaków), chcę „abc” + \ n + „123” (7 znaków). Zamiast tego produkuje „abc” + „\\” + „\ n123” (9 znaków). Zauważ, że ukośnik został podwojony i nadal zawiera literał „\ n” jako dwa znaki, a nie znak zmiany znaczenia.
Paul
2
@Paul To, czego chcesz, jest jednak przeciwieństwem pytania. To, według twojego opisu, odpowiada na pytanie, a zatem czyni pracę.
Pozew Fund Moniki
Uważam, że jest to przydatne, aby uciec przed nazwami active directory w interfejsie
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)];
    }
}
ICR
źródło
1
Dlaczego w pierwszej wartości słownika znajdują się 3 ukośniki odwrotne i dwa znaki wymowy?
James Yeoman
Fajna odpowiedź, @JamesYeoman, ponieważ wzorzec wyrażenia regularnego musi zostać zmieniony.
Ali Mousavi Kherad
18

W pełni działająca implementacja, w tym unikanie znaków niedrukowalnych Unicode i ASCII. Nie wstawia znaków „+”, takich jak odpowiedź Hallgrima .

    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
źródło
2
Powinieneś użyć, Char.GetUnicodeCategory(c) == UnicodeCategory.Controlaby zdecydować, czy uciec, bo ludzie, którzy nie mówią ASCII, nie będą bardzo szczęśliwi.
deerchao
Zależy to od sytuacji, czy wynikowy ciąg zostanie użyty w środowisku obsługującym Unicode, czy nie.
Smilediver
Dodałem input = input ?? string.Empty;jako pierwszy wiersz metody, więc mogłem przekazać nulli wrócić ""zamiast wyjątku odniesienia zerowego.
Andy
Miły. Zmień załączające cudzysłowy na 'i teraz masz to, co Python daje Ci od razu po wyjęciu z pudełka repr(a_string):).
z33k
17

Odpowiedź Hallgrima jest doskonała, ale dodatki „+”, nowa linia i wcięcia były dla mnie przełomowe. Prostym rozwiązaniem jest:

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
źródło
Działa świetnie. Dodałem również jedną linię przed, return literalaby była bardziej czytelna: literal = literal.Replace("\\r\\n", "\\r\\n\"+\r\n\"");
Bob
Dodano to literal = literal.Replace("/", @"\/");dla JSONfunkcjonalności.
ciekawe-nazwa-tutaj
Jest to 100% prosta i jedyna poprawna odpowiedź! Wszystkie pozostałe odpowiedzi albo nie rozumiały pytania, albo wymyśliły koło na nowo.
bytecode77
Niestety, nie można tego uruchomić w DOTNET CORE. Czy ktoś ma lepszą odpowiedź?
sk
8

Oto mała poprawka dla odpowiedzi Smilediver, nie uniknie ona wszystkich znaków bez ASCII, ale tylko one są naprawdę potrzebne.

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
źródło
8

Interesujące pytanie.

Jeśli nie możesz znaleźć lepszej metody, zawsze możesz ją wymienić.
Jeśli zdecydujesz się na to, możesz użyć tej listy sekwencji ucieczki C # :

  • \ '- pojedynczy cytat, potrzebny do literałów znakowych
  • \ "- podwójny cudzysłów, potrzebny do literałów łańcuchowych
  • \ - ukośnik wsteczny
  • \ 0 - znak Unicode 0
  • \ a - Alert (znak 7)
  • \ b - Backspace (znak 8)
  • \ f - Form feed (znak 12)
  • \ n - Nowa linia (znak 10)
  • \ r - Powrót karetki (znak 13)
  • \ t - Zakładka pozioma (znak 9)
  • \ v - cytat pionowy (znak 11)
  • \ uxxxx - Sekwencja ucieczki Unicode dla znaku o wartości szesnastkowej xxxx
  • \ xn [n] [n] [n] - Sekwencja ucieczki Unicode dla znaku o wartości szesnastkowej nnnn (wersja zmiennej \ uxxxx)
  • \ Uxxxxxxxx - Sekwencja ucieczki Unicode dla znaku o wartości szesnastkowej xxxxxxxx (do generowania surogatów)

Ta lista znajduje się w C # Często zadawane pytania Jakie sekwencje specjalne znaków są dostępne?

Nelson Reis
źródło
2
Ten link już nie działa, jest to podręcznikowy przykład, dlaczego odradzane są odpowiedzi zawierające tylko linki.
James
Bardzo prawda, @James, ale dzięki Jamie Twells informacje są ponownie dostępne: +1:
Nelson Reis
5

Jest na to metoda w pakiecie Microsoft.CodeAnalysis.CSharp Roslyn w nugecie:

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

Oczywiście nie istniało to w momencie pierwotnego pytania, ale może pomóc osobom, które trafią tutaj z Google.

Graham
źródło
3

Jeśli konwencje JSON są wystarczające dla nieskalowanych ciągów znaków, które chcesz uciec i już używasz Newtonsoft.Jsonw swoim projekcie (ma dość duży narzut), możesz użyć tego pakietu w następujący sposób:

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
źródło
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.
źródło
2

Moja próba dodania ToVerbatim do zaakceptowanej odpowiedzi Hallgrima powyżej:

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
źródło
1

Odpowiedź Hallgrima była doskonała. Oto drobna poprawka na wypadek, gdybyś musiał przeanalizować dodatkowe znaki białych znaków i łamanie linii za pomocą wyrażenia regularnego ac #. Potrzebowałem tego w przypadku zserializowanej wartości Jsona do wstawiania do arkuszy Google i miałem problemy, ponieważ kod wstawiał tabulatory, +, spacje itp.

  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
źródło
-1

Przesyłam własną implementację, która obsługuje nullwartości i powinna być bardziej wydajna ze względu na użycie tablic wyszukiwania tablic, ręczną konwersję szesnastkową i unikanie switchinstrukcji.

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
źródło
-7

Kod:

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

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

Wynik:

    Hello
    World!

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

Czy to jest to, czego chcesz?

rfgamaral
źródło
Mam someString1, ale jest on odczytywany z pliku. Chcę, aby pojawiał się jako someString2 po wywołaniu jakiejś metody.
Hallgrim