При выгрузке данных маркировки в государственные информационные системы (ГИС), такие как Честный Знак (ЦРПТ), разработчики часто сталкиваются с жесткими требованиями к структуре и форматированию XML-документа. Одна из наиболее распространенных проблем связана с передачей кодов маркировки в тегах, содержащих секции CDATA. Принимающая сторона требует, чтобы код маркировки вместе с обрамляющим тегом передавался строго в одну строку, например: <ki><![CDATA[КодМаркировки]]></ki>.
Однако стандартные механизмы платформы 1С:Предприятие при использовании объекта ЗаписьXML зачастую автоматически форматируют XML-код, добавляя переносы строк и отступы перед секцией CDATA или перед закрывающим тегом. В результате валидатор ГИС возвращает ошибку, так как лишние символы переноса строки (\r\n) и пробелы внутри CDATA воспринимаются парсером как часть уникального кода маркировки. В этой статье мы подробно разберем причины такого поведения платформы и рассмотрим четыре эффективных способа решения этой проблемы.
Для начала проанализируем поведение стандартного кода, который использовал автор темы:
ЗаписьXML.ЗаписатьНачалоЭлемента("product");
ЗаписьXML.ЗаписатьНачалоЭлемента("ki");
ЗаписьXML.ЗаписатьСекциюCDATA(Марка.ШтрихКодМарки);
ЗаписьXML.ЗаписатьКонецЭлемента(); // Закрываем "ki"
ЗаписьXML.ЗаписатьКонецЭлемента(); // Закрываем "product"
Даже если при инициализации объекта ЗаписьXML мы отключаем форматирование с помощью свойства ИспользоватьОтступы = Ложь (или Отступ = Ложь в параметрах записи), платформа 1С считает вызов метода ЗаписатьСекциюCDATA() добавлением отдельного дочернего узла. Для сохранения логической структуры XML-документа встроенный сериализатор платформы принудительно осуществляет перенос строки перед закрывающим тегом </ki>.
В итоге сформированный XML-файл выглядит следующим образом:
<ki>
<![CDATA[0104600000000000215YtR]]>
</ki>
С точки зрения классической спецификации XML данный файл валиден. Но для парсера «Честного Знака» переносы строк внутри блока <ki> являются значащими символами. Код считывается с символами переноса строки, из-за чего система сообщает, что марка не найдена в реестре. Попытки использовать метод ЗаписатьБезОбработки() внутри пары ЗаписатьНачалоЭлемента / ЗаписатьКонецЭлемента также терпят неудачу, так как метод закрытия элемента всё равно перенесет тег на новую строку.
Разберем детально способы решения этой задачи, сохранив валидность документа.
Это самый элегантный и простой способ, позволяющий не отказываться от преимуществ объекта Запись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-документ продолжает формироваться по строгим правилам платформы.
Если ваш 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> без каких-либо побочных спецсимволов.
Этот способ незаменим, если выгрузка 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);
Этот способ надежно решает проблему «лишних символов» и легко встраивается в любой существующий алгоритм экспорта данных.
Если структура файла крайне проста и состоит из небольшого количества элементов, можно применить подход, к которому пришел автор темы на форуме, — ручную сборку файла через объект ЗаписьТекста.
Важное предупреждение: используйте этот метод с осторожностью. При ручном формировании XML-структуры вам придется самостоятельно следить за кодировкой, экранированием спецсимволов во всех остальных тегах (например, заменять & на &), а также за корректным закрытием всех узлов.
Проанализируем пример кода:
ЗаписьТекста = Новый ЗаписьТекста(ПутьКФайлу, КодировкаТекста.UTF8);
ЗаписьТекста.ЗаписатьСтроку("<?xml version=""1.0"" encoding=""UTF-8""?>");
ЗаписьТекста.ЗаписатьСтроку("<product>");
// Пишем тег ki строго в одну строку без вызова вспомогательных XML-объектов
СтрокаМарки = "<ki><![CDATA[" + Марка.ШтрихКодМарки + "]]></ki>";
ЗаписьТекста.ЗаписатьСтроку(СтрокаМарки);
ЗаписьТекста.ЗаписатьСтроку("</product>");
ЗаписьТекста.Закрыть();
Этот метод полностью решает проблему форматирования, так как вы полностью контролируете каждый символ, попадающий в итоговый файл. Однако для сложных, объемных XML-документов мы настоятельно рекомендуем использовать Способ 1 или Способ 2.