При интеграции 1С с внешними системами (например, при выгрузке файлов для банков, в частности, формата Сбербанка) разработчики часто сталкиваются с жесткими требованиями к синтаксису XML. По спецификации W3C XML 1.0 формы записи <Индекс /> и <Индекс></Индекс> семантически абсолютно эквивалентны. Однако сторонние приемные модули могут использовать упрощенные или устаревшие парсеры, которые выдают ошибку при обработке самозакрывающегося тега и требуют обязательного наличия парного закрывающего тега или тега с пробельным символом.
Разберем причины такого поведения платформы «1С:Предприятие» и рассмотрим практические способы решения этой задачи.
Проанализируем стандартное поведение объекта платформы ЗаписьXML. При потоковой записи или сериализации DOM-модели, если между вызовами методов ЗаписатьНачалоЭлемента и ЗаписатьКонецЭлемента не было выведено никакого содержимого (или была передана пустая строка), встроенный сериализатор автоматически оптимизирует XML и выводит компактную форму <Индекс />.
Даже если попытаться явно передать пустую строку через ЗаписьXML.ЗаписатьТекст(""), результат останется прежним. Рассмотрим рабочие способы обхода этого ограничения.
В платформе 1С предусмотрен объект КаноническаяЗаписьXML, который формирует документ в строгом соответствии со стандартом Canonical XML (c14n). Согласно этому стандарту, пустые элементы обязательно выводятся с открывающим и закрывающим тегами.
Посмотрим на пример формирования такого документа:
ЗаписьXML = Новый КаноническаяЗаписьXML;
ЗаписьXML.УстановитьСтроку();
ЗаписьXML.ЗаписатьНачалоЭлемента("МестоРождения");
ЗаписьXML.ЗаписатьНачалоЭлемента("Индекс");
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.ЗаписатьКонецЭлемента();
ТекстXML = ЗаписьXML.Закрыть();
В результате работы данного кода мы получим XML следующей структуры без самозакрывающихся тегов:
<МестоРождения><Индекс></Индекс></МестоРождения>
Если в вашем коде уже используется стандартный объект ЗаписьXML и переход на каноническую запись нежелателен, можно воспользоваться методом ЗаписатьБезОбработки (WriteRaw). Этот метод выводит переданную строку байт в байт без участия механизма нормализации тегов.
Разберем по шагам реализацию данного подхода при проверке значения на заполненность:
ЗаписьXML.ЗаписатьНачалоЭлемента("МестоРождения");
Если ПустаяСтрока(ЗначениеИндекс) Тогда
// Выводим тег напрямую в требуемом виде
ЗаписьXML.ЗаписатьБезОбработки("<Индекс></Индекс>");
Иначе
ЗаписьXML.ЗаписатьНачалоЭлемента("Индекс");
ЗаписьXML.ЗаписатьТекст(Строка(ЗначениеИндекс));
ЗаписьXML.ЗаписатьКонецЭлемента();
КонецЕсли;
ЗаписьXML.ЗаписатьКонецЭлемента();
Еще один надежный способ — получить итоговый текст XML-файла и выполнить строковую замену перед сохранением документа на диск или отправкой по API.
Посмотрим на пример реализации:
ЗаписьXML.УстановитьСтроку().Закрыть().СтрЗаменить.ЗаписьТекста.
ТекстXML = ЗаписьXML.Закрыть();
// Если принимающая система ожидает тег без пробела:
ТекстXML = СтрЗаменить(ТекстXML, "<Индекс />", "<Индекс></Индекс>");
ТекстXML = СтрЗаменить(ТекстXML, "<Индекс/>", "<Индекс></Индекс>");
// Если формат банка строго требует тег с пробелом внутри:
// ТекстXML = СтрЗаменить(ТекстXML, "<Индекс />", "<Индекс> </Индекс>");
ЗаписьФайла = Новый ЗаписьТекста(ПутьКФайлу, КодировкаТекста.UTF8);
ЗаписьФайла.Записать(ТекстXML);
ЗаписьФайла.Закрыть();
Если принимающая сторона интерпретирует тег <Индекс> </Индекс> именно как содержащий символ пробела (или неразрывный пробел Символы.НПП), достаточно передать данный символ в значение текстового узла:
ЗаписьXML.ЗаписатьНачалоЭлемента("Индекс");
ЗаписьXML.ЗаписатьТекст(" "); // или ЗаписьXML.ЗаписатьТекст(Символы.НПП);
ЗаписьXML.ЗаписатьКонецЭлемента();
Так как узел содержит значащий символ, объект ЗаписьXML не станет сворачивать его в краткую форму и выведет открывающий и закрывающий теги.