Как исправить ошибки «Ошибочный порядок записи XML» и «Несоответствие типов» при выгрузке табличной части в 1С

Программист 1С v8.3 (Обычные формы) IT и автоматизация бизнеса
← На главную

Выгрузка данных в формат XML — одна из наиболее частых задач, с которыми сталкиваются разработчики при интеграции 1С с внешними информационными системами. На первый взгляд, использование встроенного объекта ЗаписьXML выглядит простым и интуитивно понятным. Однако при ручном формировании структуры файла легко допустить ошибки, которые приводят к аварийному завершению работы программы.

В этой статье мы подробно проанализируем ошибки, возникающие при построчном экспорте табличной части документа в XML, разберем причины их появления и найдем наиболее эффективные пути их устранения. Мы также обратим внимание на скрытые архитектурные проблемы исходного кода, которые могут критически повлиять на производительность и стабильность всей СУБД.

Анализ ошибки «Ошибочный порядок записи XML»

Рассмотрим первую проблему, с которой столкнулся автор темы. При попытке выполнить метод ЗаписатьНачалоЭлемента("Стр.Номенклатура") система выдает ошибку:

«Ошибочный порядок записи XML»

Эта ошибка возникает из-за нарушения спецификации и стандартов построения XML-документа. В XML-файле обязательно должен присутствовать один-единственный корневой (родительский) элемент, внутри которого располагаются все остальные узлы. Если мы посмотрим на исходный алгоритм автора, структура его файла выглядит следующим образом:


НовыйФайлXML.ЗаписатьНачалоЭлемента("Контрагент");
// ... запись атрибутов контрагента
НовыйФайлXML.ЗаписатьКонецЭлемента(); // Закрыли элемент "Контрагент"

Для каждого Стр Из Источник.Товары Цикл
    НовыйФайлXML.ЗаписатьНачалоЭлемента("Стр.Номенклатура"); // Попытка открыть новый элемент на верхнем уровне!
    // ...
КонецЦикла;

После того как был вызван метод ЗаписатьКонецЭлемента() для узла Контрагент, этот элемент закрылся. Так как он являлся корневым, XML-документ считается завершенным. Попытка записать следующий элемент Стр.Номенклатура на том же уровне иерархии воспринимается парсером 1С как грубое нарушение структуры, из-за чего и генерируется исключение.

Решение проблемы: необходимо спроектировать правильную иерархическую структуру дерева XML. Все данные документа должны быть обернуты в один общий корневой узел, например, Документ или Реализация. Внутри него будут последовательно открываться и закрываться подчиненные узлы.

Схематично правильная структура XML должна выглядеть так:


<Документ>
    <Контрагент ИдентификационныйКодЛичности="..." />
    <Товары>
        <СтрокаНоменклатуры Код="..." Количество="..." />
        <СтрокаНоменклатуры Код="..." Количество="..." />
    </Товары>
</Документ>

Использование специальных символов в именах тегов

В исходном коде автора фигурирует имя тега Стр.Номенклатура. Использование точки в именах XML-элементов технически допустимо спецификацией W3C, однако в практике разработки на платформе 1С это считается крайне плохим тоном по следующим причинам:

  1. Такие имена усложняют чтение XML-схемы сторонними парсерами и XPath-запросами.
  2. Точка может вызывать проблемы при автоматической десериализации данных во внешних системах.
  3. Это затрудняет сопоставление узлов с реальными объектами метаданных 1С.

Мы рекомендуем использовать стандартные беспробельные имена в стиле CamelCase, например: СтрокаНоменклатуры, Товар или СтрокаТабличнойЧасти.

Анализ ошибки «Несоответствие типов» при записи атрибутов

Вторая ошибка, возникшая в процессе отладки кода:

«Ошибка при вызове метода контекста (ЗаписатьАтрибут) ... по причине: Несоответствие типов (параметр номер '2')»

Эта ошибка происходит в строке:

НовыйФайлXML.ЗаписатьАтрибут("Количество", Стр.Количество);

Метод платформы ЗаписатьАтрибут() принимает в качестве второго параметра исключительно строковый тип данных (Строка). Переменная Стр.Количество содержит числовое значение. Платформа 1С не выполняет автоматическое неявное приведение типов в данном методе, поэтому трансляция числа напрямую вызывает ошибку несоответствия типов.

Решение проблемы: необходимо преобразовать число в строку перед записью. Однако здесь есть важный нюанс. Если мы воспользуемся стандартной функцией Строка(), платформа отформатирует число в соответствии с региональными настройками операционной системы. Например, число 1500.50 превратится в строку с неразрывным пробелом в качестве разделителя разрядов и запятой: "1 500,5". Внешняя система не сможет корректно распознать такое число.

Для корректного преобразования типов при выгрузке в XML в 1С существует специализированная встроенная функция — XMLСтрока(). Она приводит любое значение к каноническому XML-виду. Например, число будет записано без пробелов-разделителей и с точкой в качестве разделителя дробной части: "1500.5".

Правильный вариант записи атрибута:

НовыйФайлXML.ЗаписатьАтрибут("Количество", XMLСтрока(Стр.Количество));

Скрытые ошибки и опечатки в исходном коде автора

Проанализируем исходный текст процедуры и выявим критические ошибки логики, которые не были замечены участниками форума:

  1. Обращение к несуществующему реквизиту: В первом сообщении автора присутствовала строка: Стр.Номенклатура.Количество. Переменная Стр.Номенклатура возвращает ссылку на элемент справочника «Номенклатура». У самого справочника нет и не может быть реквизита «Количество», так как количество хранится непосредственно в строке табличной части документа. Этот код неизбежно вызвал бы ошибку «Поле объекта не обнаружено». Правильное обращение к числу должно выполняться напрямую через строку: Стр.Количество.
  2. Критическая опечатка в именах констант: В ветке создания нового файла используется константа Константы.jcpc_КаталогОбменаФайлами, а в ветке перезаписи — Константы.КаталогОбменаФайлами. Это классическая ошибка копирования кода. Если в конфигурации отсутствует одна из этих констант, программа аварийно завершится. Если существуют обе — файлы будут записываться в разные папки в зависимости от результатов проверки, что приведет к рассинхронизации обмена данными.
  3. Абсолютная избыточность проверки существования файла: Автор использует тяжелую конструкцию проверки НайтиФайлы() и полностью дублирует блоки кода для создания и перезаписи файла. Метод ЗаписьXML.ОткрытьФайл() по умолчанию работает в режиме полной перезаписи. Если файла на диске нет, он будет создан автоматически; если он есть — перезаписан. Проверка существования файла здесь абсолютно бессмысленна и только раздувает код.
  4. Бесполезные функции очистки пробелов: Функция СокрЛП("export_upp.xml") не имеет практического смысла, так как имя файла задано жестко в коде строковым литералом без пробелов по краям.

Архитектурная проблема: Выгрузка файлов внутри транзакции

Самая опасная ошибка автора заключается в том, что процедура выгрузки вызывается в подписке на событие «ПриЗаписи» (ВыгрузкаВХМЛПриЗаписи).

Событие «ПриЗаписи» выполняется внутри неделимой транзакции базы данных. Любые дисковые операции ввода-вывода (I/O) выполняются медленно. Если каталог обмена находится на сетевом диске или сетевом ресурсе, задержка записи файла может составить несколько секунд. Пока файл записывается на диск, транзакция СУБД остается открытой, блокируя таблицы документов для других пользователей. При высокой интенсивности работы в многопользовательском режиме это гарантированно приведет к ошибкам взаимоблокировок и ожидания блокировок («Database lock timeout»).

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

Как правильно реализовать архитектуру обмена:

Реализация надежного кода выгрузки

Посмотрим на пример правильного, оптимизированного и безопасного кода выгрузки данных через ручное конструирование XML-документа. Мы объединим дублирующиеся ветки и добавим обработку исключений.


Процедура ВыгрузкаВХМЛПриЗаписи(Источник, Отказ) Экспорт
    
    // Проверяем условия выгрузки
    Если Источник.Склад.Код <> "00000008" Тогда
        Возврат;
    КонецЕсли;
    
    // Получаем путь к каталогу из константы (используем одну сквозную константу)
    КаталогОбмена = Константы.КаталогОбменаФайлами.Получить();
    ИмяФайла = КаталогОбмена + "\export_upp.xml";
    
    НовыйФайлXML = Новый ЗаписьXML();
    
    Попытка
        // Открытие файла (автоматически создаст или перезапишет существующий)
        НовыйФайлXML.ОткрытьФайл(ИмяФайла, "UTF-8");
        НовыйФайлXML.ЗаписатьОбъявлениеXML();
        
        // Открываем единый корневой элемент документа
        НовыйФайлXML.ЗаписатьНачалоЭлемента("ДанныеВыгрузки");
        
        // Записываем сведения о контрагенте
        НовыйФайлXML.ЗаписатьНачалоЭлемента("Контрагент");
        НовыйФайлXML.ЗаписатьАтрибут("ИдентификационныйКодЛичности", XMLСтрока(Источник.Контрагент.ИдентификационныйКодЛичности));
        НовыйФайлXML.ЗаписатьКонецЭлемента(); // Закрываем Контрагент
        
        // Открываем узел-коллекцию для товаров
        НовыйФайлXML.ЗаписатьНачалоЭлемента("Товары");
        
        Для каждого Стр Из Источник.Товары Цикл
            НовыйФайлXML.ЗаписатьНачалоЭлемента("Товар");
            // Корректно получаем код номенклатуры и количество из строки табличной части
            НовыйФайлXML.ЗаписатьАтрибут("Код", XMLСтрока(Стр.Номенклатура.Код));
            НовыйФайлXML.ЗаписатьАтрибут("Количество", XMLСтрока(Стр.Количество));
            НовыйФайлXML.ЗаписатьКонецЭлемента(); // Закрываем Товар
        КонецЦикла;
        
        НовыйФайлXML.ЗаписатьКонецЭлемента(); // Закрываем Товары
        
        // Закрываем корневой элемент
        НовыйФайлXML.ЗаписатьКонецЭлемента(); // Закрываем ДанныеВыгрузки
        
        // Обязательно закрываем файл для освобождения ресурсов ОС
        НовыйФайлXML.Закрыть();
        
    Исключение
        // В случае ошибки обязательно закрываем файл, чтобы он не остался заблокированным в системе
        НовыйФайлXML.Закрыть();
        
        // Записываем ошибку в журнал регистрации для администратора
        ЗаписьЖурналаРегистрации("Выгрузка в XML",
            УровеньЖурналаРегистрации.Ошибка,
            ,,,
            ОписаниеОшибки());
            
        // Если это происходит в транзакции, лучше не прерывать транзакцию проведения,
        // а просто зафиксировать ошибку в логах.
    КонецПопытки;
    
КонецПроцедуры

Альтернативные и более простые способы выгрузки данных в XML

Платформа 1С предоставляет разработчикам гораздо более продвинутые и защищенные от ошибок механизмы выгрузки данных, которые избавляют от необходимости построчно писать открывающие и закрывающие теги.

Способ 1: Встроенная сериализация (ЗаписатьXML)

Если формат принимающей стороны не имеет жестких и специфических требований к оформлению XML-схемы, мы можем выгрузить всю табличную часть одной строчкой кода. Платформа 1С автоматически сформирует правильную иерархическую структуру XML, преобразует все типы данных (числа, даты, ссылки) и исключит синтаксические ошибки:


НовыйФайлXML = Новый ЗаписьXML();
НовыйФайлXML.ОткрытьФайл(ИмяФайла, "UTF-8");
НовыйФайлXML.ЗаписатьОбъявлениеXML();

НовыйФайлXML.ЗаписатьНачалоЭлемента("ДокументРеализация");

// Выгружаем табличную часть целиком одной командой!
ЗаписатьXML(НовыйФайлXML, Источник.Товары, "Товары");

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

Этот метод самостоятельно разложит табличную часть по строкам, преобразует типы реквизитов и запишет их в стандартном формате 1С, что существенно экономит время разработчика и повышает надежность кода.

Способ 2: Использование фабрики XDTO

Если принимающая сторона требует строгого соответствия определенной XSD-схеме, лучшим решением будет использование технологии XDTO (XML Data Transfer Objects). Вы создаете пакет XDTO в конфигураторе, описываете структуру данных, а затем заполняете объектную модель в коде.

Пример выгрузки с помощью XDTO:


// Создаем объект на основании схемы пакета XDTO
ТипОбъекта = ФабрикаXDTO.Тип("http://www.yourdomain.ru/exchange", "ДокументВыгрузки");
ДанныеВыгрузки = ФабрикаXDTO.Создать(ТипОбъекта);

ДанныеВыгрузки.КонтрагентКод = Источник.Контрагент.ИдентификационныйКодЛичности;

Для каждого Стр Из Источник.Товары Цикл
    ТипСтроки = ФабрикаXDTO.Тип("http://www.yourdomain.ru/exchange", "СтрокаТовара");
    НоваяСтрока = ФабрикаXDTO.Создать(ТипСтроки);
    НоваяСтрока.Код = Стр.Номенклатура.Код;
    НоваяСтрока.Количество = Стр.Количество;
    
    // Добавляем строку в список товаров
    ДанныеВыгрузки.Товары.Добавить(НоваяСтрока);
КонецЦикла;

// Записываем готовый объект XDTO в файл XML
НовыйФайлXML = Новый ЗаписьXML();
НовыйФайлXML.ОткрытьФайл(ИмяФайла, "UTF-8");
ФабрикаXDTO.ЗаписатьXML(НовыйФайлXML, ДанныеВыгрузки);
НовыйФайлXML.Закрыть();

Использование фабрики XDTO полностью изолирует разработчика от ручной сборки XML-строк, автоматически проверяет типы данных на соответствие схеме при формировании файла и гарантирует валидность полученного документа.

← На главную