Как вывести строку unicode в RTF (используя C#)

Я пытаюсь вывести строку unicode в формат RTF. (используя c# и winforms)

из Википедии:

Если требуется escape Unicode, используется управляющее слово u, за которым следует 16-разрядное десятичное целое число со знаком, задающее номер кодовой точки Unicode. В интересах программ без поддержки Unicode за этим должно следовать ближайшее представление этого символа на указанной кодовой странице. Например, u1576? дал бы Арабское письмо beh, указывающее, что старые программы, которые не имеют поддержки Unicode, должны отображать его как знак вопроса.

Я не знаю, как преобразовать символ Юникода в кодовую точку Юникода ("u1576"). Преобразование в UTF 8, UTF 16 и аналогичные легко, но я не знаю, как преобразовать в codepoint.

сценарий, в котором я использую этот:

  • я читаю существующий RTF-файл в строку (я читаю шаблон)
  • строку.замените # TOKEN# на MyUnicodeString (шаблон заполняется данными)
  • записать результат в другой файл RTF.

проблема, возникает, когда символы Unicode прибыли

4 ответов


при условии, что все персонажи, которых вы обслуживаете, существуют в Основной Многоязычный Самолет (маловероятно, что вам понадобится что-то еще), тогда достаточно простой кодировки UTF-16.

Википедия:

все возможные кодовые точки от U + 0000 до U+10FFFF, за исключением суррогатные кодовые точки от U+D800 до U б+до dfff (которые не являются символами), являются уникально сопоставленный UTF-16 независимо текущей или будущей кодовой точки назначение или использование символов.

следующий пример программы иллюстрирует делать что-то в соответствии с тем, что вы хотите:

static void Main(string[] args)
{
    // ë
    char[] ca = Encoding.Unicode.GetChars(new byte[] { 0xeb, 0x00 });
    var sw = new StreamWriter(@"c:/helloworld.rtf");
    sw.WriteLine(@"{\rtf
{\fonttbl {\f0 Times New Roman;}}
\f0\fs60 H" + GetRtfUnicodeEscapedString(new String(ca)) + @"llo, World!
}"); 
    sw.Close();
}

static string GetRtfUnicodeEscapedString(string s)
{
    var sb = new StringBuilder();
    foreach (var c in s)
    {
        if (c <= 0x7f)
            sb.Append(c);
        else
            sb.Append("\u" + Convert.ToUInt32(c) + "?");
    }
    return sb.ToString();
}

важный момент-это Convert.ToUInt32(c) который по существу возвращает значение кодовой точки для рассматриваемого символа. Rtf escape для unicode требует десятичного значения unicode. The System.Text.Encoding.Unicode кодировка соответствует UTF-16 согласно документации MSDN.


исправлен код из принятого ответа-добавлен специальный символ escaping, как описано в этом ссылке

static string GetRtfUnicodeEscapedString(string s)
{
    var sb = new StringBuilder();
    foreach (var c in s)
    {
        if(c == '\' || c == '{' || c == '}')
            sb.Append(@"\" + c);
        else if (c <= 0x7f)
            sb.Append(c);
        else
            sb.Append("\u" + Convert.ToUInt32(c) + "?");
    }
    return sb.ToString();
}

вам придется преобразовать строку в byte[] массив (используя Encoding.Unicode.GetBytes(string)), затем пройдите через этот массив и добавьте \ и u символ для всех символов Юникода, которые вы найдете. Когда вы затем преобразуете массив обратно в строку, вам придется оставить символы Юникода в виде чисел.

например, если Ваш массив выглядит так:

byte[] unicodeData = new byte[] { 0x15, 0x76 };

это стало бы:

// 5c = \, 75 = u
byte[] unicodeData = new byte[] { 0x5c, 0x75, 0x15, 0x76 };

основываясь на спецификации, вот некоторый код в java, который тестируется и работает:

  public static String escape(String s){
        if (s == null) return s;

        int len = s.length();
        StringBuilder sb = new StringBuilder(len);
        for (int i = 0; i < len; i++){
            char c = s.charAt(i);
            if (c >= 0x20 && c < 0x80){
                if (c == '\' || c == '{' || c == '}'){
                    sb.append('\');
                }
                sb.append(c);
            }
            else if (c < 0x20 || (c >= 0x80 && c <= 0xFF)){
                sb.append("\'");
                sb.append(Integer.toHexString(c));
            }else{
                sb.append("\u");
                sb.append((short)c);
                sb.append("??");//two bytes ignored
            }
        }
        return sb.toString();
 }

важно то, что вам нужно добавить 2 символа (близко к символу Юникода или просто использовать ? вместо этого) после беглого раскодирования. потому что Юникод занимает 2 байта.

также спецификация говорит, что вы должны использовать отрицательное значение, если кодовая точка больше 32767, но в моем тесте это нормально, если вы не используете отрицательное значение.

здесь spec:

\uN это ключевое слово представляет один символ Юникода, который не имеет эквивалентного представления ANSI на основе текущей кодовой страницы ANSI. N представляет символьное значение Юникода, выраженное в виде десятичного числа. За этим ключевым словом немедленно следует эквивалентный символ(ы) в представлении ANSI. Таким образом, старые читатели проигнорируют ключевое слово \uN и правильно подберут представление ANSI. Когда это ключевое слово встречается, читатель должен игнорировать следующее N символы, где N соответствует последнему обнаруженному значению \ucN.

Как и во всех ключевых словах RTF, может присутствовать пространство, заканчивающееся ключевым словом (перед символами ANSI), которое не учитывается в символах для пропуска. Хотя это вряд ли произойдет (или рекомендуется), ключевое слово \bin, его аргумент и следующие двоичные данные считаются одним символом для пропуска. Если символ разделителя области RTF (то есть открывающая или закрывающая скобка) встречается во время сканирование пропускаемых данных, пропускаемые данные считаются завершенными перед разделителем. Это позволяет читателю выполнить некоторое рудиментарное восстановление ошибок. Чтобы включить разделитель RTF в пропускаемые данные, он должен быть представлен с помощью соответствующего управляющего символа (то есть экранированного с обратной косой чертой), как в обычном тексте. Любое управляющее слово или символ RTF считается одним символом для подсчета пропускаемых символов.

писатель RTF, когда он встречает символ Юникода без соответствующего символа ANSI, должен выводить \uN с последующим лучшим представлением ANSI, которым он может управлять. Кроме того, если символ Юникода преобразуется в поток символов ANSI с количеством байтов, отличным от текущего количества байтов символов Юникода, он должен выдать ключевое слово \ucN до ключевого слова \uN, чтобы уведомить читателя об изменении.

управляющие слова RTF обычно принимают подписанные 16-разрядные числа в качестве аргументов. По этой причине Unicode значения больше 32767 должны быть выражены отрицательным числом