Как записать секцию CDATA в одну строку в XML без нежелательных переносов при интеграции с Честным Знаком

Программист 1С v8.3 (Управляемые формы) Торговля и дистрибуция
← На главную

При выгрузке данных маркировки в государственные информационные системы (ГИС), такие как Честный Знак (ЦРПТ), разработчики часто сталкиваются с жесткими требованиями к структуре и форматированию XML-документа. Одна из наиболее распространенных проблем связана с передачей кодов маркировки в тегах, содержащих секции CDATA. Принимающая сторона требует, чтобы код маркировки вместе с обрамляющим тегом передавался строго в одну строку, например: <ki><![CDATA[КодМаркировки]]></ki>.

Однако стандартные механизмы платформы 1С:Предприятие при использовании объекта ЗаписьXML зачастую автоматически форматируют XML-код, добавляя переносы строк и отступы перед секцией CDATA или перед закрывающим тегом. В результате валидатор ГИС возвращает ошибку, так как лишние символы переноса строки (\r\n) и пробелы внутри CDATA воспринимаются парсером как часть уникального кода маркировки. В этой статье мы подробно разберем причины такого поведения платформы и рассмотрим четыре эффективных способа решения этой проблемы.

Выясняем причину: почему ЗаписьXML делает переносы строк

Для начала проанализируем поведение стандартного кода, который использовал автор темы:


ЗаписьXML.ЗаписатьНачалоЭлемента("product");        
ЗаписьXML.ЗаписатьНачалоЭлемента("ki");
ЗаписьXML.ЗаписатьСекциюCDATA(Марка.ШтрихКодМарки);   
ЗаписьXML.ЗаписатьКонецЭлемента(); // Закрываем "ki"
ЗаписьXML.ЗаписатьКонецЭлемента(); // Закрываем "product"

Даже если при инициализации объекта ЗаписьXML мы отключаем форматирование с помощью свойства ИспользоватьОтступы = Ложь (или Отступ = Ложь в параметрах записи), платформа 1С считает вызов метода ЗаписатьСекциюCDATA() добавлением отдельного дочернего узла. Для сохранения логической структуры XML-документа встроенный сериализатор платформы принудительно осуществляет перенос строки перед закрывающим тегом </ki>.

В итоге сформированный XML-файл выглядит следующим образом:


<ki>
<![CDATA[0104600000000000215YtR]]>
</ki>

С точки зрения классической спецификации XML данный файл валиден. Но для парсера «Честного Знака» переносы строк внутри блока <ki> являются значащими символами. Код считывается с символами переноса строки, из-за чего система сообщает, что марка не найдена в реестре. Попытки использовать метод ЗаписатьБезОбработки() внутри пары ЗаписатьНачалоЭлемента / ЗаписатьКонецЭлемента также терпят неудачу, так как метод закрытия элемента всё равно перенесет тег на новую строку.

Разберем детально способы решения этой задачи, сохранив валидность документа.

Способ 1. Использование метода ЗаписатьБезОбработки для всего тега целиком

Это самый элегантный и простой способ, позволяющий не отказываться от преимуществ объекта ЗаписьXML для построения остальной части документа. Вместо того чтобы создавать тег ki и секцию CDATA через стандартные методы платформы, мы запишем всю конструкцию как готовую текстовую строку в обход парсера форматирования.

Рассмотрим пример реализации:


// Инициализируем запись XML без отступов
ПараметрыЗаписи = Новый ПараметрыЗаписиXML("UTF-8", "1.0", Ложь);
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.ОткрытьФайл(ПутьКФайлу, ПараметрыЗаписи);

ЗаписьXML.ЗаписатьОбъявлениеXML();
ЗаписьXML.ЗаписатьНачалоЭлемента("product");

// Формируем строку тега с CDATA вручную, строго без пробелов и переносов
СтрокаCDATA = "<ki><![CDATA[" + Марка.ШтрихКодМарки + "]]></ki>";

// Записываем всю строку в обход дерева элементов 1С
ЗаписьXML.ЗаписатьБезОбработки(СтрокаCDATA);

ЗаписьXML.ЗаписатьКонецЭлемента(); // Закрываем "product"
ЗаписьXML.Закрыть();

При таком подходе платформа вставляет строку СтрокаCDATA «как есть». Поскольку методы открытия и закрытия элемента ki не вызывались, 1С не считает его дочерним структурным узлом и не производит перенос строки для закрывающего тега. Весь остальной XML-документ продолжает формироваться по строгим правилам платформы.

Способ 2. Использование Объектной Модели Документа (DOM)

Если ваш XML-документ собирается в оперативной памяти или вы предпочитаете использовать объектную модель вместо последовательной записи, оптимальным решением станет использование объектов ДокументDOM и ЗаписьDOM. DOM-сериализатор более строго следует правилам разметки и при отключенных отступах не форматирует текстовые узлы и секции CDATA.

Разберем этот процесс по шагам:


// 1. Создаем структуру документа DOM
ДокументDOM = Новый ДокументDOM;
КорневойЭлемент = ДокументDOM.СоздатьЭлемент("root");
ДокументDOM.ЗаписатьВетвь(КорневойЭлемент);

ЭлементProduct = ДокументDOM.СоздатьЭлемент("product");
КорневойЭлемент.ДобавитьДочерний(ЭлементProduct);

// 2. Создаем элемент под марку
ЭлементKI = ДокументDOM.СоздатьЭлемент("ki");
ЭлементProduct.ДобавитьДочерний(ЭлементKI);

// 3. Создаем узел CDATA и добавляем его внутрь элемента "ki"
УзелCDATA = ДокументDOM.СоздатьСекциюCDATA(Марка.ШтрихКодМарки);
ЭлементKI.ДобавитьДочерний(УзелCDATA);

// 4. Записываем DOM-дерево в файл без использования отступов
ЗаписьXML = Новый ЗаписьXML;
ПараметрыЗаписи = Новый ПараметрыЗаписиXML("UTF-8", "1.0", Ложь); // Ложь - отключает форматирование
ЗаписьXML.ОткрытьФайл(ПутьКФайлу, ПараметрыЗаписи);

ЗаписьDOM = Новый ЗаписьDOM;
ЗаписьDOM.Записать(ДокументDOM, ЗаписьXML);

ЗаписьXML.Закрыть();

Использование DOM гарантирует, что секция CDATA будет записана в одну строку с родительским тегом <ki> без каких-либо побочных спецсимволов.

Способ 3. Пост-обработка готового XML-файла

Этот способ незаменим, если выгрузка XML-документа происходит через типовые механизмы конфигураций (например, через Библиотеку Стандартных Подсистем — БСП), логику которых крайне нежелательно или сложно переписывать. В таком случае мы можем позволить платформе выгрузить файл стандартным образом, а затем программно скорректировать его содержимое перед отправкой.

Посмотрим на пример кода, реализующего очистку лишних символов переноса строки:


// Шаг 1. Читаем сформированный XML-файл в текстовую переменную
ТекстДокумента = Новый ТекстовыйДокумент;
ТекстДокумента.Прочитать(ПутьКФайлу, КодировкаТекста.UTF8);
ПолныйТекст = ТекстДокумента.ПолучитьТекст();

// Шаг 2. Заменяем переносы строк вокруг тегов "ki" на пустые строки.
// Учитываем возможные варианты переноса строк (Windows \r\n и Unix \n)

// Удаляем перенос перед закрывающим тегом </ki>
ПолныйТекст = СтрЗаменить(ПолныйТекст, "]]>" + Символы.ПС + "</ki>", "]]></ki>");
ПолныйТекст = СтрЗаменить(ПолныйТекст, "]]>" + Символы.ВК + Символы.ПС + "</ki>", "]]></ki>");

// Удаляем перенос после открывающего тега <ki>
ПолныйТекст = СтрЗаменить(ПолныйТекст, "<ki>" + Символы.ПС + "<![CDATA[", "<ki><![CDATA[");
ПолныйТекст = СтрЗаменить(ПолныйТекст, "<ki>" + Символы.ВК + Символы.ПС + "<![CDATA[", "<ki><![CDATA[");

// Шаг 3. Записываем исправленный текст обратно в файл
ТекстДокумента.УстановитьТекст(ПолныйТекст);
ТекстДокумента.Записать(ПутьКФайлу, КодировкаТекста.UTF8);

Этот способ надежно решает проблему «лишних символов» и легко встраивается в любой существующий алгоритм экспорта данных.

Способ 4. Полный отказ от ЗаписьXML в пользу ЗаписьТекста (Решение автора темы)

Если структура файла крайне проста и состоит из небольшого количества элементов, можно применить подход, к которому пришел автор темы на форуме, — ручную сборку файла через объект ЗаписьТекста.

Важное предупреждение: используйте этот метод с осторожностью. При ручном формировании XML-структуры вам придется самостоятельно следить за кодировкой, экранированием спецсимволов во всех остальных тегах (например, заменять & на &amp;), а также за корректным закрытием всех узлов.

Проанализируем пример кода:


ЗаписьТекста = Новый ЗаписьТекста(ПутьКФайлу, КодировкаТекста.UTF8);
ЗаписьТекста.ЗаписатьСтроку("<?xml version=""1.0"" encoding=""UTF-8""?>");
ЗаписьТекста.ЗаписатьСтроку("<product>");

// Пишем тег ki строго в одну строку без вызова вспомогательных XML-объектов
СтрокаМарки = "<ki><![CDATA[" + Марка.ШтрихКодМарки + "]]></ki>";
ЗаписьТекста.ЗаписатьСтроку(СтрокаМарки);

ЗаписьТекста.ЗаписатьСтроку("</product>");
ЗаписьТекста.Закрыть();

Этот метод полностью решает проблему форматирования, так как вы полностью контролируете каждый символ, попадающий в итоговый файл. Однако для сложных, объемных XML-документов мы настоятельно рекомендуем использовать Способ 1 или Способ 2.

← На главную