При интеграции с внешними системами разработчики нередко сталкиваются с ситуацией, когда принимающий сервис требует строго определенный формат заголовка XML. Например, принимающий парсер ожидает ровно <?xml version="1.0"?> и выдает ошибку при наличии атрибута encoding="UTF-8". Однако при стандартном экспорте в файл платформа 1С автоматически дописывает кодировку в заголовок. Разберем, почему так происходит и какими способами можно получить требуемый результат.
Проанализируем сначала поведение платформы. Когда мы создаем объект ЗаписьXML и связываем его напрямую с диском через метод ОткрытьФайл(), платформа обязана ассоциировать поток с физической кодировкой. По умолчанию используется UTF-8. Поэтому последующий вызов метода ЗаписатьОбъявлениеXML() всегда выводит атрибут кодировки. Передача пустой кодировки в ПараметрыЗаписиXML не помогает — платформа все равно применит значение по умолчанию. Также нельзя обойти ограничение вызовом метода ЗаписатьИнструкциюОбработки("xml", "version=""1.0"""): имя «xml» зарезервировано стандартом, и платформа выбросит исключение.
Тем не менее, решить задачу можно несколькими надежными путями в зависимости от объема выгружаемых данных.
Рассмотрим основной способ, который подходит для небольших и средних документов. Если объект ЗаписьXML инициализируется в оперативной памяти через метод УстановитьСтроку(), кодировка выходного потока не фиксируется заранее. В этом случае метод ЗаписатьОбъявлениеXML() генерирует чистый заголовок <?xml version="1.0"?> без указания кодировки.
После завершения формирования структуры мы получаем готовую строку и сохраняем ее на диск при помощи объекта ЗаписьТекста. Посмотрим на пример реализации:
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.УстановитьСтроку();
// Формируем заголовок без атрибута encoding
ЗаписьXML.ЗаписатьОбъявлениеXML();
// Заполняем тело документа
ЗаписьXML.ЗаписатьНачалоЭлемента("Данные");
ЗаписьXML.ЗаписатьАтрибут("ВерсияФормата", "1.0");
ЗаписьXML.ЗаписатьНачалоЭлемента("Элемент");
ЗаписьXML.ЗаписатьТекст("Тестовое значение");
ЗаписьXML.ЗаписатьКонецЭлемента(); // Элемент
ЗаписьXML.ЗаписатьКонецЭлемента(); // Данные
ТекстXML = ЗаписьXML.Закрыть();
// Сохраняем полученную строку в файл
ЗаписьТекста = Новый ЗаписьТекста(ПутьКФайлу, КодировкаТекста.UTF8);
ЗаписьТекста.ЗаписатьСтроку(ТекстXML);
ЗаписьТекста.Закрыть();
Разберем по шагам, что здесь происходит:
УстановитьСтроку() переводит объект ЗаписьXML в режим записи в оперативную память.ЗаписатьОбъявлениеXML() добавляет строку <?xml version="1.0"?>, не добавляя кодировку, так как строковый буфер платформы не привязан к конкретному файловому формату.Закрыть() возвращает сформированный XML в виде обычной строки 1С.ЗаписьТекста записывает готовый текст в файл на диске с необходимой кодировкой без изменения содержимого самого XML.Выясним еще одну особенность стандарта XML. Согласно официальной спецификации XML 1.0, объявление <?xml ...?> в самом начале документа не является строго обязательным. Если принимающий парсер не настаивает на наличии самого тега объявления, а спотыкается именно о неизвестный или нестандартный атрибут encoding, самым простым и производительным решением будет отказ от вызова ЗаписатьОбъявлениеXML().
Посмотрим на пример формирования напрямую в файл:
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.ОткрытьФайл(ПутьКФайлу);
// Метод ЗаписатьОбъявлениеXML() намеренно не вызывается
ЗаписьXML.ЗаписатьНачалоЭлемента("Данные");
ЗаписьXML.ЗаписатьТекст("Пример без пролога");
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.Закрыть();
В этом случае файл сразу начинается с корневого элемента <Данные>. Подавляющее большинство корректных XML-парсеров по умолчанию интерпретируют документ в кодировке UTF-8.
Если файл занимает десятки или сотни мегабайт, формирование через УстановитьСтроку() приведет к ошибке нехватки оперативной памяти. Если при этом заголовок <?xml version="1.0"?> строго обязателен, применим потоковую замену первой строки через связку ЧтениеТекста и ЗаписьТекста.
Разберем этот алгоритм:
ЗаписьXML.ОткрытьФайл(), вызвав ЗаписатьОбъявлениеXML(). Файл запишется быстро и без расхода оперативной памяти, но в первой строке будет присутствовать encoding="UTF-8".ЧтениеТекста, а новый временный файл на запись через ЗаписьТекста.ПереместитьФайл().Посмотрим, как выглядит потоковая замена на практике:
// Предположим, что файл ИсходныйФайл.xml уже сформирован через ЗаписьXML
ФайлИсточника = "C:\Temp\Export.xml";
ФайлРезультата = "C:\Temp\Export_Ready.xml";
Чтение = Новый ЧтениеТекста(ФайлИсточника, КодировкаТекста.UTF8);
Запись = Новый ЗаписьТекста(ФайлРезультата, КодировкаТекста.UTF8);
ПерваяСтрока = Истина;
Строка = Чтение.ПрочитатьСтроку();
Пока Строка <> Неопределено Цикл
Если ПерваяСтрока Тогда
// Заменяем заголовок с кодировкой на чистый вариант
Если СтрНачинаетсяС(Строка, "<?xml") Тогда
Строка = "<?xml version=""1.0""?>";
КонецЕсли;
ПерваяСтрока = Ложь;
КонецЕсли;
Запись.ЗаписатьСтроку(Строка);
Строка = Чтение.ПрочитатьСтроку();
КонецЦикла;
Чтение.Закрыть();
Запись.Закрыть();
УдалитьФайлы(ФайлИсточника);
ПереместитьФайл(ФайлРезультата, ФайлИсточника);
Важный момент: такой подход практически не расходует оперативную память сервера, позволяя безопасно обрабатывать выгрузки любых объемов и передавать внешним системам файл именно в том формате, который они ожидают.