Могу ли я преобразовать строковое значение C # в экранированный строковый литерал

196

Могу ли я преобразовать строковое значение в строковый литерал в C #, как я вижу это в коде? Я хотел бы заменить вкладки, новые строки и т. Д. На их escape-последовательности.

Если этот код:

Console.WriteLine(someString);

производит:

Hello
World!

Я хочу этот код:

Console.WriteLine(ToLiteral(someString));

производить:

\tHello\r\n\tWorld!\r\n
Hallgrim
источник

Ответы:

181

Я нашел это:

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

Этот код:

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

Производит:

    Hello
    World!
"\tHello\r\n\tWorld!"
Hallgrim
источник
1
Только что нашел это от Google тему. Это должно быть лучше, нет смысла заново изобретать то, что .net может сделать для нас
Энди Моррис
16
Хороший, но имейте в виду, что для более длинных строк будут вставлены операторы «+», новые строки и отступы. Я не мог найти способ выключить это.
Тимви
2
А как насчет обратного? Если у вас есть файл с текстом, который содержит escape-последовательности, включая специальный символ, экранированный с помощью кода ascii? Как произвести сырую версию?
Лучано
1
Если вы запустите: void Main () {Console.WriteLine (ToLiteral ("test \" \ '\\\ 0 \ a \ b \ f \ n \ r \ t \ v \ uaaaa \\\ blah "));} Вы заметите, что это не заботится о нескольких побегах. Ронни Оверби указал \ f, остальные - \ a и \ b
costa
4
Есть ли способ заставить его выводить @"..."литералы verbatim ( )?
rookie1024
39

Что насчет Regex.Escape (String) ?

Regex.Escape экранирует минимальный набор символов (\, *, +,?, |, {, [, (,), ^, $,., # И пробел), заменяя их на управляющие коды.

Shqdooow
источник
6
+1 понятия не имею, почему это ниже. Другие ответы слишком многословны и выглядят как переизобретение колес
Адриано Карнейру
40
Это не то, что требует OP. Он не возвращает строковый литерал, он возвращает строку с экранированными специальными символами Regex. Это превратится Hello World?в Hello World\?, но это неверный строковый литерал.
atheaos
2
Я согласен с @atheaos, это отличный ответ на совершенно другой вопрос.
Гипхуман человек
5
+1, хотя это не совсем отвечает на вопрос ОП, это было то, что я (и я подозреваю, может быть, другие) искали, когда натолкнулся на этот вопрос. :)
ГазБ
Это не будет работать по мере необходимости. Специальные символы регулярного выражения не совпадают. Он будет работать, например, для \ n, но когда у вас есть пробел, он будет преобразован в "\", что не делает C # ...
Эрнесто
25

РЕДАКТИРОВАТЬ: более структурированный подход, включая все escape-последовательности для strings и chars.
Не заменяет символы Юникода их буквальным эквивалентом. Яйца тоже не готовит.

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

    }
}
Кристиан Дьяконеску
источник
Это не все escape-последовательности;)
TcKs
1
Работает лучше, чем решение выше - и другие escape-последовательности могут быть легко добавлены.
Арно Питерс
Дословно принятый ответ сводил меня с ума. Это работает на 100% для моей цели. Заменено регулярное выражение на @"[\a\b\f\n\r\t\v\\""/]"и добавлено m_replaceDict.Add("/", @"\/");для JSON.
интересно-имя-здесь
Кроме того, вы должны добавить в него кавычки, если хотите.
интересно имя здесь
19

пытаться:

var t = HttpUtility.JavaScriptStringEncode(s);
Арсен Захрай
источник
Не работает. Если у меня есть «abc \ n123» (без кавычек, 8 символов), я хочу «abc» + \ n + «123» (7 символов). Вместо этого он производит "abc" + "\\" + "\ n123" (9 символов). Обратите внимание, что косая черта была удвоена, и она по-прежнему содержит строковый литерал «\ n» в виде двух символов, а не экранированный символ.
Пол
2
@Paul То, что вы хотите, это противоположность того, что задает вопрос. Это, согласно вашему описанию, отвечает на вопрос, и, следовательно , работает.
Фонд Моника иск
Я нашел это полезным, чтобы избежать активных имен каталогов во
внешнем
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
источник
1
Почему в первом значении словаря есть 3 обратные косые черты и две речевые метки?
Джеймс
Хороший ответ, @JamesYeoman, это потому что шаблон регулярных выражений должен быть экранирован.
Али Мусави Херад
18

Полностью рабочая реализация, включая экранирование Unicode и ASCII непечатаемых символов. Не вставляет знаки «+» как ответ Халлгрима .

    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
источник
2
Вы должны использовать, Char.GetUnicodeCategory(c) == UnicodeCategory.Controlчтобы решить, избежать ли этого, или люди, которые не говорят на ASCII, не будут очень счастливы.
Дирчао
Это зависит от ситуации, если полученная строка будет использоваться в среде, поддерживающей юникод, или нет.
Smilediver
Я добавил input = input ?? string.Empty;в качестве первой строки метода, чтобы я мог пройти nullи вернуться ""вместо исключения с нулевой ссылкой.
Энди
Ницца. Измените кавычки, 'и теперь у вас есть то, что Python дает вам из коробки repr(a_string):).
z33k
17

Ответ Халлгрима превосходен, но дополнения "+", новая строка и отступы нарушали мою функциональность. Простой способ обойти это:

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
источник
Прекрасно работает. Я также добавил одну строку перед тем, return literalчтобы сделать ее более читабельной: literal = literal.Replace("\\r\\n", "\\r\\n\"+\r\n\"");
Боб,
Добавлено это literal = literal.Replace("/", @"\/");для JSONфункциональности.
интересно имя здесь
Это 100% прямой и единственный правильный ответ! Все остальные ответы либо не поняли вопрос, либо заново изобрели колесо.
bytecode77
Печально, не могу заставить это работать под DOTNET CORE. У кого-нибудь есть лучший ответ?
ск
8

Вот небольшое улучшение для ответа Smilediver, оно не ускользнет от всех символов без ASCII, но только они действительно необходимы.

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
источник
8

Интересный вопрос.

Если вы не можете найти лучший метод, вы всегда можете заменить.
В случае, если вы выбираете это, вы можете использовать этот C # Escape Sequence List :

  • \ '- одинарная кавычка, необходимая для литералов символов
  • \ "- двойная кавычка, необходимая для строковых литералов
  • \ - обратная косая черта
  • \ 0 - Юникод символ 0
  • \ a - Оповещение (персонаж 7)
  • \ b - Backspace (символ 8)
  • \ f - подача формы (символ 12)
  • \ n - Новая строка (символ 10)
  • \ r - возврат каретки (символ 13)
  • \ t - Горизонтальная вкладка (символ 9)
  • \ v - Вертикальная кавычка (символ 11)
  • \ uxxxx - escape-последовательность Unicode для символа с шестнадцатеричным значением xxxx
  • \ xn [n] [n] [n] - escape-последовательность Unicode для символа с шестнадцатеричным значением nnnn (версия переменной длины \ uxxxx)
  • \ Uxxxxxxxx - escape-последовательность Unicode для символа с шестнадцатеричным значением xxxxxxxx (для генерации суррогатов)

Этот список можно найти в C # Часто задаваемые вопросы. Какие имеются escape-последовательности символов?

Нельсон Рейс
источник
2
Эта ссылка больше не работает, пример из учебника, почему ответы только на ссылки не рекомендуется.
Джеймс
Очень верно, @James, но благодаря Джейми Твелсу информация снова доступна: +1:
Нельсон Рейс
5

Есть способ для этого в пакете Рослина Microsoft.CodeAnalysis.CSharp на nuget:

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

Очевидно, что это не существовало во время первоначального вопроса, но могло бы помочь людям, которые попали сюда из Google.

Грэхем
источник
3

Если соглашений JSON достаточно для неэкранированных строк, которые вы хотите экранировать, и которые вы уже используете Newtonsoft.Jsonв своем проекте (он имеет довольно большие накладные расходы), вы можете использовать этот пакет, как показано ниже:

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
источник
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

Моя попытка добавить ToVerbatim к принятому ответу Халлгрима выше:

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

Халлгрим ответил превосходно. Вот небольшая настройка, если вам нужно разобрать дополнительные пробельные символы и разрывы строк с помощью регулярного выражения ac #. Мне это понадобилось в случае сериализованного значения Json для вставки в листы Google, и я столкнулся с проблемой, когда код вставлял символы табуляции, +, пробелы и т. Д.

  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

Я представляю свою собственную реализацию, которая обрабатывает nullзначения и должна быть более производительной за счет использования таблиц поиска в массивах, ручного преобразования в шестнадцатеричные числа и избегания switchоператоров.

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

Код:

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

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

Вывод:

    Hello
    World!

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

Это то, что вы хотите?

rfgamaral
источник
У меня есть someString1, но он читается из файла. Я хочу, чтобы он появился как someString2 после вызова некоторого метода.
Халлгрим