C# и запись в .rtf-шаблон

127
08 августа 2019, 05:20

коллеги.

Есть БД MySQL с русским текстом. Есть приложение C# WinForms x86 .net 3.5. Есть файл .rtf с вставками вида #template# и русским текстом.

Задача: записать текст из БД в нужные места в файле. Читаю через File.ReadAllText, затем склеиваю строки и пишу обратно через File.WriteAllText в новый файл.

И получаю кракозябры вместо вставленного текста. При этом тот текст, что был, остаётся в порядке.

Пробовал читать в одной кодировке, писать в другой... Один пёс.

Может, кто-то сталкивался с этим .rtf - какую комбинацию кодировок посоветуете для базы данных, самого файла, File.ReadAllText и File.WriteAllText? Или может юзать File.Write/ReadAllBytes. Или перекодировать на лету вставляемые куски текста?

Заранее спасибо.

Я до сих пор не въехал, какая родная кодировка у .rtf - 1251 или 1252.

UPD: Код:

string agreementTemplate = File.ReadAllText("PaymentTemplate.rtf", Encoding.GetEncoding(1251));
agreementTemplate = agreementTemplate.Replace("#klientName#", CurrentCredit.Client);
agreementTemplate = agreementTemplate.Replace("#klientAdr#", CurrentCredit.ClientAdr);
File.WriteAllText("Сведения о платежах для договора №" + CurrentCredit.Number + ".doc", agreementTemplate, Encoding.GetEncoding(1251));

Результат: Андрей Семёнов óë. Ëåíèíà, 24

Добавил новый файл для примера. При загрузке: "{\rtf1\ansi\ansicpg1251\deff0{\fonttbl{\f0\fnil\fcharset204 Calibri;}{\f1\fnil\fcharset0 Calibri;}}\r\n\viewkind4\uc1\pard\sa200\sl276\slmult1\lang1049\f0\fs22\'d0\'f3\'f1\'f1\'ea\'e8\'e9 \'f2\'e5\'ea\'f1\'f2\lang1033\f1\par\r\n\lang1049\b\f0\'d0\'f3\'f1\'f1\'ea\'e8\'e9 \'f2\'e5\'ea\'f1\'f2\lang1033\f1\par\r\n\lang1049\ul\b0\f0\'d0\'f3\'f1\'f1\'ea\'e8\'e9 \'f2\'e5\'ea\'f1\'f2\lang1033\f1\par\r\n\lang1049\b\f0\'d0\'f3\'f1\'f1\'ea\'e8\'e9 \'f2\'e5\'ea\'f1\'f2\lang1033\f1\par\r\n\lang1049\ulnone\b0\f0\'d0\'f3\'f1\'f1\'ea\'e8\'e9 \'f2\'e5\'ea\'f1\'f2\lang1033\f1\par\r\n\lang1049\f0\'d0\'f3\'f1\'f1\'ea\'e8\'e9 \'f2\'e5\'ea\'f1\'f2: \lang1033\f1 #replace#\par\r\n\lang1049\f0\'d0\'f3\'f1\'f1\'ea\'e8\'e9 \'f2\'e5\'ea\'f1\'f2\lang1033\f1\par\r\n\lang1049\f0\'d0\'f3\'f1\'f1\'ea\'e8\'e9 \'f2\'e5\'ea\'f1\'f2\lang1033\f1\par\r\n\par\r\n\par\r\n}\r\n"

После замены replace на русский текст

"{\rtf1\ansi\ansicpg1251\deff0{\fonttbl{\f0\fnil\fcharset204 Calibri;}{\f1\fnil\fcharset0 Calibri;}}\r\n\viewkind4\uc1\pard\sa200\sl276\slmult1\lang1049\f0\fs22\'d0\'f3\'f1\'f1\'ea\'e8\'e9 \'f2\'e5\'ea\'f1\'f2\lang1033\f1\par\r\n\lang1049\b\f0\'d0\'f3\'f1\'f1\'ea\'e8\'e9 \'f2\'e5\'ea\'f1\'f2\lang1033\f1\par\r\n\lang1049\ul\b0\f0\'d0\'f3\'f1\'f1\'ea\'e8\'e9 \'f2\'e5\'ea\'f1\'f2\lang1033\f1\par\r\n\lang1049\b\f0\'d0\'f3\'f1\'f1\'ea\'e8\'e9 \'f2\'e5\'ea\'f1\'f2\lang1033\f1\par\r\n\lang1049\ulnone\b0\f0\'d0\'f3\'f1\'f1\'ea\'e8\'e9 \'f2\'e5\'ea\'f1\'f2\lang1033\f1\par\r\n\lang1049\f0\'d0\'f3\'f1\'f1\'ea\'e8\'e9 \'f2\'e5\'ea\'f1\'f2: \lang1033\f1\'c0\'ed\'e4\'f0\'e5\'e9 \'d1\'e5\'ec\'b8\'ed\'ee\'e2\par\r\n\lang1049\f0\'d0\'f3\'f1\'f1\'ea\'e8\'e9 \'f2\'e5\'ea\'f1\'f2\lang1033\f1\par\r\n\lang1049\f0\'d0\'f3\'f1\'f1\'ea\'e8\'e9 \'f2\'e5\'ea\'f1\'f2\lang1033\f1\par\r\n\par\r\n\par\r\n}\r\n"

Содержимое файла

Русский текст Русский текст (жирным) Русский текст (подчёркнутым) Русский текст (жирным подч) Русский текст Русский текст: #replace# Русский текст Русский текст

Обновление: решение проблемы, как обычно, с помощью костылей. Вместо шаблона вида "#replace#" делаем шаблон КИРИЛЛИЦЕЙ вида "ИмяКлиента".

То есть текст в шаблоне: "клиент, в лице ИмяКлиента, обязывается..."

Затем, с помощью функции, предложенной nick_n_a, делаем такую кракозябру:

templateStr = loadfile...
templateStr = templateStr.Replace(ToRtf("ИмяКлиента"), ToRtf(CurrentCredit.Name));

где CurrentCredit.Name - русский текст из БД На кодировку ложим болт ибо теперь неважно.

В общем, оно работает. Но... сами понимаете. Кто придумает что-то лучше - милости просим)

ЗЫ: правда, на основных файлах ещё не проверял, если что - вернусь)

Answer 1

Для вставки текста RTF в шаблон я использовал такое приведение

  public static string toRTF(string value)
        {  /*Конвертирует текст, в текст пригодный для RTF документа */
            //                                                                                                             FF
            string syms = @"ЎўЈЈ¤Ґ¦§Ё©Є«¬­®Ї°±Ііґµ¶•ё№є»јЅѕїАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюя";                      
            string rep_1 = @"0123456789abcdef";
            int j = 0;
            int k = 0;
            while (j < value.Length)
            {
                if (syms.IndexOf(value[j]) >= 0)
                {
                    k = syms.IndexOf(value[j]);
                    value = value.Replace(value[j].ToString(), "\\'" + rep_1[(k / 16) + 10].ToString() + rep_1[k & 15].ToString());
                };
                j++;
            };
            value = value.Replace("\\n", "\\par;");
            return value;
        }

Да.. пожалуй хранить и копировать таблицу сложно... вариант 2 - без таблицы

  public static string toRTF(string value)
        {  /*Конвертирует текст, в текст пригодный для RTF документа */
            byte[] t = new byte[1];
            int j = 0;                
            while (j < value.Length)
            {
                if (value[j] >= '\x7F')
                {
                    System.Text.Encoding.GetEncoding(1251).GetBytes(value,j,1,t,0);                      
                    value = value.Replace(value[j].ToString(), "\\'" + t[0].ToString('x'));
                };
                j++;
            };
            value = value.Replace("\\n", "\\par;");
            return value;
        }

Т.е. буква я маленькая имеет код \'FF. Плюс... шаблон должен содержать метку 1251, т.е. в файле с поддержкой 1251 заголовок должен быть такой

 {\rtf1\adeflang1025\ansi\ansicpg1251\

Еще я заменяю возврат каретки на \par; потому что текст так переносится в rtf.

Текст можно вообще не преобразовывать, оставить как есть и лишь перенести в кодировку 1251, если есть уверенность, что ваш файл будут открывать исключительно в среде windows. Некоторые "неродные" редакторы не понимают файлы без указанного перевода.

P.S. RTF не самый простой формат, не могу рассказать подробно о его деталях. Приведенный код использую в одном из проэктов. Преобразование в heх можно реализовать лучше.. но так уже получилось - не само оптимально. В вики внизу есть ссылка на спецификацию http://ru.wikipedia.org/wiki/Rich_Text_Format, если есть сложности - думаю спецификацикация даст ответ на ваши вопросы.

Answer 2

Если вас не интересует поддержка старого ПО, можно закодировать в Unicode, тогда проблемы кодировки не будет. Для этого пропустите строку через такой метод:

static string RtfEncode(string input)
{
    StringBuilder sb = new StringBuilder(input.Length * 4);
    foreach (var c in input)
    {
        if (c > 127) //заменяем не-ASCII символы на их Unicode представление
        {
            string escape = "\\u" + ((Int16)c).ToString() + "?";
            sb.Append(escape);
        }
        else //ASCII добавляем без изменений
        {
            sb.Append(c);
        }
    }
    return sb.ToString();
}

Тогда замену можно произвести как-то так:

string template = "#template#";
string word = "Иванов И.А.";
string contents = File.ReadAllText(input_file);
word = RtfEncode(word);
contents = contents.Replace(template, word);
File.WriteAllText(output_file, contents);

О кодировке не нужно заботиться, так как после RtfEncode задействованы только ASCII символы. При таком способе ничего не должно нарушиться, при условии что маркер замены в шаблоне никак не пересекает границы управляющих конструкций RTF. (Отредактируйте шаблон в блокноте, и убедитесь, что строка замены представлена именно так, как вы ожидаете.)

Answer 3

Как всё заработало в итоге.

//виртуальный буфер (глобальная переменная)
private RichTextBox _richTextBox = new RichTextBox();
//название файла для сохранения
string target = "Договор №" + CurrentCredit.Number + ".doc";
//грузим шаблон в RichTextBox - контрол сам порешает за все кодировки
_richTextBox.LoadFile(Storage.AgreementTemplateFile, RichTextBoxStreamType.RichText);
//магия
Replace("#nomer#", CurrentCredit.Number.ToString());
//при сохранении о кодировке опять-таки позаботится RichTextBox
_richTextBox.SaveFile(target);
//главное - функция Replace:
private void Replace(string from, string to)
{
    int start = _richTextBox.Find(from, 0, RichTextBoxFinds.None);
    _richTextBox.Select(start, from.Length);
    _richTextBox.SelectedText = to;
}
READ ALSO
Visual Studio Code C# Ввод с клавиатуры

Visual Studio Code C# Ввод с клавиатуры

Написал простенький код, который при вводе числа должен выдать на 10 большекомпилирую, нажимая F5

137
Затемнение окон Unity по нажатию кнопки

Затемнение окон Unity по нажатию кнопки

Доброго времени суток

124
Удаление выделенной строчки в DataGridView из базы данных

Удаление выделенной строчки в DataGridView из базы данных

Помогите сделать так чтобы при нажатии на кнопку удалялась выделенная строчка в DataGridView из базы данных

108
Вывести на печать 2 листа

Вывести на печать 2 листа

как можно перенести на 2 лист, где: Нижняя челюсть - Левая сторона от 44, хочу перенести на 2 листНо как можно сделать?

139