Проанализируем ситуацию: мы формируем выгрузку (обычно CSV) из 1С (удобно через универсальную выгрузку документов в CSV и XML), сохраняем файл в КодировкаТекста.UTF8, а при загрузке на сайт получаем отказ или некорректное чтение первой колонки/первого поля. Причина часто одна и та же — в начале файла появляются три байта BOM (Byte Order Mark) для UTF-8: EF BB BF. Многие веб-системы и «строгие» парсеры ожидают UTF-8 без BOM и воспринимают BOM как часть данных.
Разберем по шагам, как сделать так, чтобы файл формировался корректно сразу из 1С, без ручного открытия в редакторе и пересохранения.
В типичном сценарии мы собираем текст (CSV) в ТекстовыйДокумент или строку, а затем пишем на диск методом Записать с указанием КодировкаТекста.UTF8. Во многих версиях платформы при такой записи 1С добавляет BOM автоматически. То есть файл формально остается UTF-8, но уже «UTF-8 with BOM». Для обменов это часто критично.
Что важно понять: наличие BOM — это не «ошибка кодировки», а дополнительная сигнатура в начале файла. Поэтому «лечится» она либо записью без BOM (если платформа/метод позволяет), либо удалением первых трех байт после записи.
Рассмотрим подробнее наиболее удобный способ: не просим 1С «записать текст в UTF-8», а сами превращаем строку в ДвоичныеДанные в UTF-8 без BOM и затем сохраняем двоичные данные на диск. В ряде версий платформы для этого есть функция глобального контекста ПолучитьДвоичныеДанныеИзСтроки с параметром, управляющим записью BOM.
Разберем по шагам:
Собираем CSV как строку (или получаем текст из ТекстовыйДокумент).
Преобразуем строку в ДвоичныеДанные в КодировкаТекста.UTF8, но с флагом «не записывать BOM».
Пишем ДвоичныеДанные на диск методом Записать.
Посмотрим на пример (минимальный, но рабочий):
СтрCSV = "col1;col2" + Символы.ПС
+ "значение1;значение2";
ИмяФайла = "C:\Temp\export.csv";
// ВАЖНО: ЗаписатьBOM = Ложь — принципиальный момент
ЗаписатьBOM = Ложь;
// Получаем двоичные данные UTF-8 без BOM
ДвоичныеДанныеСтроки = ПолучитьДвоичныеДанныеИзСтроки(СтрCSV, КодировкаТекста.UTF8, ЗаписатьBOM);
// Записываем на диск как двоичные данные
ДвоичныеДанныеСтроки.Записать(ИмяФайла);
Ключевой объект/метод: ПолучитьДвоичныеДанныеИзСтроки + параметр «ЗаписатьBOM». Именно он позволяет контролировать появление BOM при формировании UTF-8.
Теперь усложним пример до «реального CSV»: добавим экранирование, кавычки, разделитель и контроль переносов строк. Разберем на практике типичные требования сайтов: разделитель ; или ,, перенос строк Символы.ПС, текстовые поля в кавычках, кавычки внутри — удваиваем.
Функция ЭкранироватьCSV(Знач Значение, Разделитель = ";") Экспорт
// Приводим к строке (чтобы числа/даты корректно ушли в CSV)
Стр = Строка(Значение);
// Нормализуем переводы строк внутри значения
Стр = СтрЗаменить(Стр, Символы.ВК, "");
Стр = СтрЗаменить(Стр, Символы.ПС, " ");
// Если внутри есть кавычки — удваиваем по правилам CSV
Стр = СтрЗаменить(Стр, """", """""");
// Если есть разделитель, кавычки или пробелы по краям — оборачиваем в кавычки
Если Найти(Стр, Разделитель) > 0 Или Найти(Стр, """") > 0 Тогда
Стр = """" + Стр + """";
КонецЕсли;
Возврат Стр;
КонецФункции
Процедура ВыгрузитьCSVБезBOM(МассивСтрок, ИмяФайла, Разделитель = ";") Экспорт
// Собираем весь CSV одной строкой (для простоты примера)
Буфер = "";
Для Каждого СтрокаДанных Из МассивСтрок Цикл
// СтрокаДанных — это, например, массив значений одной строки CSV
СтрЛиния = "";
Для Инд = 0 По СтрокаДанных.Количество() - 1 Цикл
ЗначениеЯчейки = СтрокаДанных[Инд];
Часть = ЭкранироватьCSV(ЗначениеЯчейки, Разделитель);
Если Инд = 0 Тогда
СтрЛиния = Часть;
Иначе
СтрЛиния = СтрЛиния + Разделитель + Часть;
КонецЕсли;
КонецЦикла;
Буфер = Буфер + СтрЛиния + Символы.ПС;
КонецЦикла;
// Пишем UTF-8 без BOM через двоичные данные
ЗаписатьBOM = Ложь;
Двоичные = ПолучитьДвоичныеДанныеИзСтроки(Буфер, КодировкаТекста.UTF8, ЗаписатьBOM);
Двоичные.Записать(ИмяФайла);
КонецПроцедуры
Посмотрим, что мы сделали: мы контролируем CSV-формат и гарантируем отсутствие BOM в итоговом файле, потому что итоговая запись идет как ДвоичныеДанные.
Практический совет: если выгрузка очень большая, собирать гигантскую строку целиком может быть тяжело по памяти. Тогда мы делаем запись порциями (стриминг), но при этом все равно важно держать под контролем BOM. Это приведет нас к следующему решению.
Проанализируем ситуацию: иногда в конкретной версии платформы нет удобного параметра «не писать BOM» или вы используете стандартную запись текстового файла (например, ТекстовыйДокумент.Записать) и не хотите менять архитектуру. Тогда применяем универсальный прием: записываем файл как обычно, затем читаем его как двоичный и удаляем первые три байта, если это BOM UTF-8.
Смысл: BOM — это строго первые 3 байта UTF-8. Если файл начинается с EF BB BF, мы отрезаем их и перезаписываем файл обратно.
Разберем по шагам:
Пишем файл любым привычным способом в UTF-8 (даже если добавится BOM).
Считываем файл в ДвоичныеДанные.
Проверяем первые три байта.
Если это BOM — отрезаем и сохраняем заново.
Посмотрим на пример. В зависимости от версии платформы доступны разные инструменты работы с двоичными данными и потоками, поэтому покажем подход «в лоб» через двоичные данные и преобразования. Мы будем действовать аккуратно: сначала проверим сигнатуру, и только затем изменим файл.
Процедура УдалитьBOM_UTF8(ИмяФайла) Экспорт
// Читаем файл целиком в двоичные данные
Двоичные = Новый ДвоичныеДанные(ИмяФайла);
// Преобразуем в Base64, чтобы удобно работать с байтами косвенно (универсальный прием)
// В некоторых версиях проще получить доступ к байтам именно через Base64 и массив чисел.
СтрBase64 = Base64Строка(Двоичные);
// Преобразуем Base64 в массив байт
МассивБайт = Новый Массив;
ЗаполнитьМассивБайтИзBase64(СтрBase64, МассивБайт);
Если МассивБайт.Количество() < 3 Тогда
Возврат;
КонецЕсли;
// Проверяем BOM UTF-8: EF BB BF (239 187 191)
Если МассивБайт[0] = 239 И МассивБайт[1] = 187 И МассивБайт[2] = 191 Тогда
// Удаляем первые 3 байта
МассивБайт.Удалить(0);
МассивБайт.Удалить(0);
МассивБайт.Удалить(0);
// Собираем обратно двоичные данные и перезаписываем файл
НовыеДвоичные = ПолучитьДвоичныеДанныеИзМассиваБайт(МассивБайт);
НовыеДвоичные.Записать(ИмяФайла);
КонецЕсли;
КонецПроцедуры
Процедура ЗаполнитьМассивБайтИзBase64(СтрBase64, МассивБайт) Экспорт
// Универсальная заготовка: раскодируем Base64 в двоичные данные,
// затем читаем по байтам через поток.
Двоичные = Base64Значение(СтрBase64);
Поток = Двоичные.ОткрытьПотокДляЧтения();
Попытка
Пока НЕ Поток.КонецПотока() Цикл
МассивБайт.Добавить(Поток.Прочитать(1)[0]);
КонецЦикла;
Исключение
// Если в вашей версии нет такого API потоков, используйте альтернативный способ чтения байт.
КонецПопытки;
КонецПроцедуры
Функция ПолучитьДвоичныеДанныеИзМассиваБайт(МассивБайт) Экспорт
// Собираем двоичные данные обратно через поток записи
Запись = Новый ЗаписьДвоичныхДанных;
ПотокЗаписи = Запись.ОткрытьПотокДляЗаписи();
Для Каждого Байт Из МассивБайт Цикл
ПотокЗаписи.Записать(Новый Массив(Байт));
КонецЦикла;
ПотокЗаписи.Закрыть();
Возврат Запись.ЗакрытьИПолучитьДвоичныеДанные();
КонецФункции
Важно: в конкретной версии платформы набор классов потоков может отличаться. Логика при этом не меняется: нам нужно либо (а) получить доступ к первым 3 байтам, либо (б) преобразовать двоичные данные в форму, где мы умеем «отрезать» первые байты, и потом записать обратно.
Если у вас современная платформа, обычно проще не усложнять и воспользоваться Решением 1, потому что оно короче, надежнее и быстрее.
Теперь рассмотрим подробнее ситуацию с большими выгрузками: десятки или сотни тысяч строк. Собрать весь CSV в одну строку может быть накладно, а писать через ТекстовыйДокумент удобно, но BOM снова появляется. Нам нужен гибридный подход: пишем в файл порциями, но не добавляем BOM.
Здесь стратегия такая:
Открываем поток записи в файл.
Каждую строку CSV преобразуем в ДвоичныеДанные UTF-8 без BOM.
Записываем байты в поток последовательно.
Посмотрим на пример концепции. В разных версиях платформы объекты потоков и записи могут называться/вести себя немного по-разному, но сама идея одинакова: каждая порция текста превращается в двоичные данные без BOM и дописывается в файл.
Процедура ВыгрузитьБольшойCSVБезBOM(Выборка, ИмяФайла, Разделитель = ";") Экспорт
// Открываем запись двоичных данных (поток)
Запись = Новый ЗаписьДвоичныхДанных;
Поток = Запись.ОткрытьПотокДляЗаписи();
// Пишем заголовок
Заголовок = "col1" + Разделитель + "col2" + Разделитель + "col3" + Символы.ПС;
ДвоичныеЗаголовка = ПолучитьДвоичныеДанныеИзСтроки(Заголовок, КодировкаТекста.UTF8, Ложь);
Поток.Записать(ДвоичныеЗаголовка);
// Пишем строки по одной (или блоками)
Пока Выборка.Следующий() Цикл
СтрЛиния =
ЭкранироватьCSV(Выборка.Поле1, Разделитель) + Разделитель +
ЭкранироватьCSV(Выборка.Поле2, Разделитель) + Разделитель +
ЭкранироватьCSV(Выборка.Поле3, Разделитель) + Символы.ПС;
ДвоичныеСтроки = ПолучитьДвоичныеДанныеИзСтроки(СтрЛиния, КодировкаТекста.UTF8, Ложь);
Поток.Записать(ДвоичныеСтроки);
КонецЦикла;
Поток.Закрыть();
// Получаем итоговые двоичные данные и записываем в файл
ИтоговыеДвоичные = Запись.ЗакрытьИПолучитьДвоичныеДанные();
ИтоговыеДвоичные.Записать(ИмяФайла);
КонецПроцедуры
Идея, которую важно запомнить: BOM появляется только если мы его добавляем при преобразовании текста. Если мы каждый раз вызываем преобразование с параметром «не писать BOM», тогда даже при порционной записи BOM не возникнет вообще.
Если в вашей версии платформы нет удобной потоковой записи/чтения, мы все равно можем идти тем же путем: писать во временный файл, а затем применить Решение 2 (отрезать BOM) — но уже к одному итоговому файлу.
Чтобы не гадать, давайте действовать системно. Проверка делается просто: читаем первые три байта файла и сравниваем с UTF-8 BOM. Мы можем сделать это прямо в 1С и вывести диагностическое сообщение в лог/сообщение пользователю.
Посмотрим на пример проверки (концептуально):
Функция ФайлНачинаетсяСBOM_UTF8(ИмяФайла) Экспорт
Двоичные = Новый ДвоичныеДанные(ИмяФайла);
Поток = Двоичные.ОткрытьПотокДляЧтения();
Байты = Поток.Прочитать(3);
Поток.Закрыть();
Если Байты.Количество() < 3 Тогда
Возврат Ложь;
КонецЕсли;
// EF BB BF = 239 187 191
Возврат (Байты[0] = 239 И Байты[1] = 187 И Байты[2] = 191);
КонецФункции
Практический подход: добавим эту проверку на этапе тестирования выгрузки и один раз убедимся, что итоговый файл начинается сразу с данных (например, с буквы заголовка), а не с BOM.
Разберем типовые «подводные камни», которые часто всплывают рядом с BOM, и сразу заложим правильное поведение выгрузки.
Неправильный разделитель. Сайт может ожидать строго ; или строго ,. Договоримся заранее: задаем разделитель параметром и используем его везде (в заголовке и строках).
Переводы строк. Кто-то ожидает LF, кто-то CRLF. В 1С мы обычно используем Символы.ПС. Если сайт «капризный», будем формировать перенос вручную: например Символы.ВК + Символы.ПС. Главное — делать это единообразно.
Кавычки и разделитель внутри данных. Если поле содержит разделитель или кавычки, CSV должен корректно экранировать значение. Мы выше уже заложили это в функцию ЭкранироватьCSV.
Лишние пробелы и невидимые символы. Иногда проблема похожа на BOM, но на самом деле в начале первой ячейки стоят пробелы, табы или неразрывные пробелы. Давайте чистить значения: СокрЛП и замены служебных символов — хорошая практика.
Смешение кодировок. Если часть данных приходит из внешних источников, убедимся, что все строки в 1С в одном внутреннем Unicode-представлении, а на выходе мы всегда делаем единое преобразование в КодировкаТекста.UTF8 без BOM.
Проанализируем ситуацию без привязки к конкретной версии: если у нас есть возможность управлять BOM при преобразовании строки в двоичные данные — используем Решение 1/3. Если нет — используем Решение 2 (удаляем BOM после записи).
Чтобы принять решение быстро, действуем так:
Пробуем реализовать Решение 1 на тестовом фрагменте: сформировать строку, сделать ПолучитьДвоичныеДанныеИзСтроки с флагом, записать файл, проверить первые 3 байта.
Если функция/параметр недоступны или поведение не то — не тратим время, переходим к Решению 2 и гарантированно вырезаем EF BB BF.
Если файл большой — комбинируем: пишем как удобно (хоть стандартно), но в конце один раз применяем вырезание BOM.
Главное: мы не пытаемся «подобрать кодировку строкой типа "UTF-8 without BOM"» в методе записи, потому что в реальном мире поддержка таких имен кодировок непредсказуема. Надежнее работать на уровне байт: либо не добавлять BOM, либо удалять его явно.
Давайте соберем практический план, чтобы быстро внести изменения в рабочую обработку/отчет, где CSV уже формируется — для этого подойдёт универсальная выгрузка данных из 1С в CSV.
Находим место, где файл записывается на диск. Обычно это ТекстовыйДокумент.Записать или запись строки через ЗаписьТекста.
Оставляем формирование CSV как есть (чтобы не ломать логику), но меняем способ записи: вместо текстовой записи делаем преобразование в ДвоичныеДанные без BOM и записываем двоичные данные.
Если менять запись нельзя — после текущей записи файла вызываем процедуру «Удалить BOM», которая проверит первые 3 байта и при необходимости вырежет их.
Добавляем тест: одна проверка первых байт и контрольная загрузка на сайт.
Фиксируем формат CSV: разделитель, перенос строк, правила кавычек — чтобы проблема больше не «маскировалась» другими ошибками.
Подытожим и закрепим выбор:
Хотим «правильно сразу» и минимум магии — используем ПолучитьДвоичныеДанныеИзСтроки с флагом «не писать BOM» и сохраняем через ДвоичныеДанные.Записать.
Нужна 100% совместимость со старыми сценариями — пишем как раньше, затем удаляем BOM (EF BB BF) из начала файла.
Выгрузка очень большая — пишем порциями в поток, каждую порцию преобразуем в UTF-8 без BOM и дописываем в файл.
Если действовать по этим шагам, мы гарантированно получим CSV в UTF-8 без BOM, который будет корректно приниматься сайтами и внешними системами без ручных пересохранений. Для этой задачи есть обработка автоматической выгрузки из 1С в CSV и JSON.
Информация носит справочный характер.