Запись текста в кодировке UTF-8 без BOM из 1с 8.2

Как-то хочется из 8.2 записать в кодировке "UTF-8 без BOM" любым из менее затратных способов.

Смысл такой: на WEB-сайт потребовалось выгрузить файл в кодировке UTF-8. Но проблема в том, что UTF-8 имеет 2 варианта: «с BOM» и «без BOM». Файлы, сохраненные в этих двух кодировках, получаются одинаковые за исключением того, что в первом случае в начало файла добавлены три байта EF BB BF (в 16-ричной системе счисления). Это и есть символы BOM (сигнатура Byte Order Mark). Так вот, на сайт нужно было отправить «без BOM», а 1с 8.2 сохраняет исключительно «с BOM».

Вот из-за того, что 1с 8.2 не может сохранять текстовые файлы в нужном мне формате и возникла идея «вырезать» эти символы из двоичного файла, но как это сделать в 1с 8.2, я не нашел. Там вообще работа с двоичными данными очень ограничена.

Поэтому пошел по простому пути: что не изобретено, на том катаемся.
Придумал через «очень много» так:

это приблизительный код вызова:

текст = Новый ТекстовыйДокумент;
...
здесь в "текст" что-то пихаем
...
папкаФайла = "какая-то папка";
папкаВОМ = папкаФайла+"temp_BOM";
имяФайла = "как-то.почему-то";
СоздатьКаталог(папкаВОМ);
текст.Записать(папкаВОМ+имяФайла, КодировкаТекста.UTF8, Символы.ВК);
УбитьВОМ(папкаВОМ+имяФайла, папкаФайла+имяФайла, папкаВОМ);


далее непосредственно главная процедура "УбитьВОМ":


Процедура УбитьВОМ(Знач ИсходныйФайл, РезультирующийФайл, ВременнаяПапка, МассивФайлов = Неопределено)
Если МассивФайлов = Неопределено Тогда
МассивФайлов = Новый Массив;
КонецЕсли;
бин = Новый ДвоичныеДанные(ИсходныйФайл);
размер = бин.Размер();
новыйРазмер = Макс(Окр(размер/2,0),3);
массив = РазделитьФайл(ИсходныйФайл,новыйРазмер);
Если массив.Количество() = 2 Тогда
МассивФайлов.Вставить(0,массив[1]);
КонецЕсли;
Если новыйРазмер = 3 Тогда
ОбъединитьФайлы(МассивФайлов,РезультирующийФайл);
УдалитьФайлы(ВременнаяПапка);
Иначе
УбитьВОМ(массив[0],РезультирующийФайл,ВременнаяПапка,МассивФайлов);
КонецЕсли;
КонецПроцедуры


В результате получается файл в кодировке "UTF-8 без BOM".

Примечание. понимаю, что через "ж", но если кто подскажет другой вариант это будет интересно со всех сторон.

А в остальном: кому нужно - пользуйтесь и экспериментируйте.

33 Comments

  1. andrewks

    если в виндах, то можно заюзать ADODB.Stream

    например, так:

    Попытка
    Файл=СоздатьОбъект(«ADODB.Stream»);
    Файл.Mode=3; // r/w
    Файл.Type=1; //1-Binary, 2-Text
    Файл.Open();
    Файл.LoadFromFile(ИмяФайлаДанных);
    Файл.Position=3;
    ТекстБезБОМ=СоздатьОбъект(«ADODB.Stream»);
    ТекстБезБОМ.Mode=3; // r/w
    ТекстБезБОМ.Type=1; //1-Binary, 2-Text
    ТекстБезБОМ.Open();
    Файл.CopyTo(ТекстБезБОМ);
    Файл.Close();
    ТекстБезБОМ.SaveToFile(ИмяФайлаДанных,2);
    ТекстБезБОМ.Close();
    Исключение
    ТекстОшибки=ОписаниеОшибки();
    Сообщить(ТекстОшибки,»!»);
    Возврат;
    КонецПопытки;
    
    

    Показать

    синтаксис в примере 7-шный, но код применим для любой версии — 7.7, 8.х, главное, чтобы доступ к объекту ADODB.Stream был

    Reply
  2. Serj1C

    Еще можно провернуть такой танец с бубном (без ВК, работа в памяти, без файлов):

    1) получить двоичные данные из файла (как у вас)

    2) преобразуем двоичные данные в строку с помощью ЗначениеИзСтрокиВнутр

    3) анализируем формат, там не сложно, удаляем лишние переносы кареток (символ 10 вроде) // при желании могу описать подробнее

    4) упаковываем поток обратно, получая двоичные данные

    5) записываем в файл. Профит

    Reply
  3. andrewbc

    (1) andrewks, отлично отработало в следующем виде:

    Процедура УбитьВОМ(ИсходныйФайл,РезультирующийФайл)

    Попытка

    файл = Новый ComObject(«ADODB.Stream»);

    файл.Mode = 3; // r/w

    файл.Type = 1; //1-Binary, 2-Text

    файл.Open();

    файл.LoadFromFile(ИсходныйФайл);

    файл.Position = 3;

    текстБезБОМ = Новый ComObject(«ADODB.Stream»);

    текстБезБОМ.Mode = 3; // r/w

    текстБезБОМ.Type = 1; //1-Binary, 2-Text

    текстБезБОМ.Open();

    файл.CopyTo(текстБезБОМ);

    файл.Close();

    текстБезБОМ.SaveToFile(РезультирующийФайл,2);

    текстБезБОМ.Close();

    УдалитьФайлы(ИсходныйФайл);

    Исключение

    Сообщить(ОписаниеОшибки(),СтатусСообщения.Важное);

    КонецПопытки;

    КонецПроцедуры

    Reply
  4. andrewbc

    (2) Serj1C, не получилось сотворить такой вариант обработки:

    1. при преобразовании «ЗначениеВСтрокуВнутр» 1с преобразует двоичные данные в формат base64, при этом длина строки становится больше размера иходного файла почти в полтора раза, это значит, что у нас уже текст не в кодировке UTF-8.

    2. не совсем понятно: символы перевода строки (10) (возврат каретки — 13) обязательно удалять? а если это UTF-ный символ с кодом 10?

    3. каким образом «упаковать поток обратно»? у объекта «ДвоичныеДанные» только 2 метода — «Размер()» и «Записать(ИмяФайла»).

    4. как записать в файл результат?

    пробовал еще такой вариант:

    бин = Новый ДвоичныеДанные(ИсходныйФайл);

    бинСтрока = Base64Строка(бин);

    новаяБинСтрока = Сред(бинСтрока,4);

    бин = Base64Значение(новаяБинСтрока);

    бин.Записать(РезультирующийФайл);

    но там вообще ерунда получается.

    может, примерчик рабочий есть?

    Reply
  5. Serj1C
    Reply
  6. mc2

    Тоже долго мучился пока нашел как это сделать для записи текстовых файлов средствами 1С:

    УдалитьФайлы(ИмяФайла); // надо убедиться, что файл не существует, т.к. если он существует, то данные добавятся в конец файла

    ЗТ = Новый ЗаписьТекста(ИмяФайла,,, Истина, Символы.ПС);

    ЗТ.Записать(МояСтрока);

    ЗТ.Закрыть();

    И никаких извращений…

    Reply
  7. andrewbc

    (6) mc2, шикарный вариант, именно то, что нужно. спасибо!

    Reply
  8. andrewbc

    (5) Serj1C, наворочено как-то. для записи простого файлика в формате csv слишком много телодвижений.

    самый простой и подходящий вариант — (6).

    хотя для общего развития полезно. спасибо.

    Reply
  9. Serj1C

    (9) тут http://infostart.ru/public/137969/ тоже неплохо описан процесс записи произвольных данных в файл

    Reply
  10. andrewbc

    (9) Serj1C, http://infostart.ru/public/137969/ — это то, что написал andrewks (1)

    Reply
  11. mc2

    (7) Ну вот, в 8.3.1 без режима совместимости с 8.2.16, этот вариант работать больше не будет! Разработчики вместо того, чтобы делать то,что надо, делают то что не надо! Теперь извращения принимаются.

    Reply
  12. Gmix

    (4) Собственно так и делаю.

    Только у вас ошибка.

    Вот рабочий код:

    файлЛог=ПолучитьИмяВременногоФайла(«.txt»);
    Текст.Записать(файлЛог);
    
    // теперь нужно получить файл без BOM
    ОБ_ДД=Новый ДвоичныеДанные(файлЛог);
    Стр_Base64=Base64Строка(ОБ_ДД);
    ОБ_ДД=Base64Значение(Сред(Стр_Base64,5));
    ОБ_ДД.Записать(файлЛог);

    Использовал в Визуализация журнала регистрации Gource

    В 8.3 не проверял еще, но думаю должно сработать.

    Reply
  13. mc2

    (7) нашел новый способ, который годится и для 8.3:

    ЗТ = Новый ЗаписьТекста(ИмяФайла, КодировкаТекста.ANSI);

    ЗТ.Закрыть();

    ЗТ = Новый ЗаписьТекста(ИмяФайла,,, Истина, Символы.ПС);

    ЗТ.Записать(Данные);

    ЗТ.Закрыть();

    Reply
  14. andrewbc

    (13) mc2, долго смеялся. Действительно, извращения принимаются. Как так удается находить таких блох? За терпение и находчивость — однозначный +.

    Reply
  15. andrewbc

    (12) Gmix, этот вариант попробовал — работает. Спасибо за пример. Я просто в цифирьке ошибся. Бывает.

    Reply
  16. 1st RUS

    (6) mc2,

    Попробовал твой метод, и выяснил, что за вредные символы отвечает параметр «Дописывать» в конструкторе «ЗаписьТекста»

    в общем получилось так:

    УдалитьФайлы(имяФайлаОтправки); это да, это обязательно

    ЗаписьТекста = Новый ЗаписьТекста(имяФайлаОтправки,КодировкаТекста.UTF8,,Истина,Символы.ПС);

    ЗаписьТекста.ЗаписатьСтроку(«—«+boundary);

    ЗаписьТекста.ЗаписатьСтроку(«Content-Disposition: form-data; name=»»file»»; filename=»»goods.xml»»»);

    ЗаписьТекста.ЗаписатьСтроку(«Content-Type: text/xml»);

    ЗаписьТекста.ЗаписатьСтроку(«»);

    и ниже пишем содержимое XML файла

    СПАСИБО

    Reply
  17. kilokilo
  18. mc2

    (16) 1st RUS, Посмотрите пожалуйста (13) пост этой темы, т.к. то, что было написано в (6) не работает в 8.3.

    Reply
  19. Alex1c

    Писал обработку для хеширования по ГОСТ с помощью стороннего EXE. Программка работала через командную строку. Необходимо было указать ей путь к файлу с данными на вход. Она генерила выходной файл с хешем.

    Поимел проблему. У меня был тестовый файл и файл сгенеренный 1С, с виду абсолютно одинаковые. Разница в размере в 2 байта. Хеш программа выдавала разный. Оказалось что по умолчанию 1С указывается символ перевода строки и при создании файла и при добавлении строки. Указал в обоих местах в качестве символа «» и все пошло. Использовал много идей из этого поста. Возможно кому то будет полезен полный код.

    Функция ПолучитьХЭШГОСТ(СтрокаНаВход)

    // очистили входящий файл

    УдалитьФайлы(КаталогВременныхФайлов()+»in.txt»);

    ТекстIn = Новый ЗаписьТекста(КаталогВременныхФайлов()+»in.txt», КодировкаТекста.UTF8,»», Ложь,);

    ТекстIn.ЗаписатьСтроку(Строка(СокрЛП(ВРЕГ(ВходящиеДанные))));

    ТекстIn.Закрыть();

    // обрезаем ВОМ

    ОБ_ДД=Новый ДвоичныеДанные(КаталогВременныхФайлов()+»in.txt»);

    Стр_Base64=Base64Строка(ОБ_ДД);

    ОБ_ДД=Base64Значение(Сред(Стр_Base64,5));

    ОБ_ДД.Записать(КаталогВременныхФайлов()+»in.txt»);

    // очистили выходной файл

    УдалитьФайлы(КаталогВременныхФайлов()+»out.txt»);

    ТекстOut = Новый ЗаписьТекста(КаталогВременныхФайлов()+»out.txt», КодировкаТекста.UTF8,,Ложь,);

    ТекстOut.Закрыть();

    //запустили приложение хеширования

    ЗапуститьПриложение(Строка(ИмяФайла)+» —gost-cryptopro «+КаталогВременныхФайлов()+»in.txt»+» «+»—output=»+КаталогВременныхФайлов()+»out.txt»,,Истина,);

    //прочитали из выходного файла результат

    Текст = Новый ЧтениеТекста(КаталогВременныхФайлов()+»out.txt», КодировкаТекста.UTF8);

    Стр = Текст.ПрочитатьСтроку(«»); //при создании файла перевод строки убрали,при чтении тоже убрали, то есть читаем весь файл как строку

    Стр = СтрЗаменить(Стр,Строка(КаталогВременныхФайлов()+»in.txt»),»»);

    РезультатХеширования = Врег(Стр);

    Возврат РезультатХеширования;

    КонецФункции

    Reply
  20. ErrorEd88

    Кодировка UTF-8 без BOM называется «CESU-8». Подсказали.

    Reply
  21. andrewks

    (20) ErrorEd88, да ну! и где же об этом написано в описании стандарта? http://www.unicode.org/reports/tr26/

    Reply
  22. xzorkiix

    (16) 1st RUS, спасибо за

    ЗаписьТекста = Новый ЗаписьТекста(имяФайлаОтправки,КодировкаТекста.UTF8,,Истина,Символы.ПС); 

    то что надо!

    Reply
  23. Xephone

    (20) Спасибо! Помогло!

    Reply
  24. DrBlack

    (20) ErrorEd88, Спасибо!

    Информация верная!!! 🙂

    Reply
  25. dmitryafanasyev91

    (20) ErrorEd88, выручил.

    Reply
  26. loky12

    (20) ErrorEd88,

    Спасибо помог, очень выручило при склейке csv созданных в фоновых в многопоточной обработке.

    Reply
  27. andogskiy

    (6), Спасибо за совет.

    Reply
  28. aaa_pol

    Писал выгрузку на сайт через файл .csv столкнулся с данной проблемой. Перепробовал несколько способов из указанных выше. Нашел для себя наиболее оптимальный следующий метод, который меняет кодировку в уже готовом, ранее сформированном файле .csv

    ////создаем сам файл
    СписокЭкспорт = Новый ТекстовыйДокумент;
    СписокЭкспорт.ДобавитьСтроку(ПерваяСтрокаФайла);  ////в цикле добавляем все что надо
    СписокЭкспорт.Записать(ИмяФайлаЭкспортаCSV,КодировкаТекста.UTF8);  //// Где «ИмяФайлаЭкспортаCSV» полный путь к файлу
    
    ////меняем в готовом файле кодировку «с BOM» на «без BOM»
    КонвертируемыйФайл = Новый ДвоичныеДанные(ИмяФайлаЭкспортаCSV);
    Строка64=Base64Строка(КонвертируемыйФайл);
    Строка64=Прав(Строка64,СтрДлина(Строка64)-4);
    ДляЗаписиВФайл=Base64Значение(Строка64);
    ДляЗаписиВФайл.Записать(ИмяФайлаЭкспортаCSV);

    Показать

    Reply
  29. userGJ

    Варианты с сохранением в промежуточный файл изначально кривое решение. В 20 разумное решение с указанием другой кодировки.

    Reply
  30. Stas84

    20 -> то что нужно, пробуйте указать кодировку в наглую. Вместо КодировкаТекста.UTF8 ->»CESU-8″

    Например:

    Запрос.УстановитьТелоИзСтроки(ТекстPOSTЗапроса, «CESU-8»);

    Reply
  31. sultbec

    (30) в типовой нашел такой код:

    HTTPЗапрос.УстановитьТелоИзСтроки(ТелоЗапроса, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);
    Reply
  32. user852614

    Так решил проблему:

    ФП = Новый ФайловыйПоток(ИмяФайлаИсходного,РежимОткрытияФайла.СоздатьНовый,ДоступКФайлу.ЧтениеИЗапись);
    Текст = Новый ЗаписьТекста(ФП,,, Истина, Ложь);//Последний параметр самый важный
    Текст.Записать(СтрокаДанных);
    Текст.Закрыть();
    ФП.Закрыть();
    
    Reply
  33. user852614

    (32)

    Текст = Новый ЗаписьТекста(ФП,КодировкаТекста.UTF8,, , Ложь);//Предпоследний параметр не нужен, надо так 😉
    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *