Почему не выгружаются данные табличной части документа в XML в 1С 8.3?

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

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

Анализ исходного кода и выявление ключевых ошибок

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


Для Каждого Док Из ТаблицаРеализаций Цикл 
    Если НЕ Док.Выгружать Тогда 
        Продолжить; 
    КонецЕсли; 
    Для каждого Строка из ТаблицаРеализаций Цикл 
        Выборка = Документы.ПеремещениеТоваров.ПолучитьСсылку.Товары; 
        Для каждого Строка из Выборка Цикл 
            ХМЛ.ЗаписатьНачалоЭлемента("Товар"); 
            // Запись атрибутов...
            ХМЛ.ЗаписатьКонецЭлемента(); 
        КонецЦикла; 
    КонецЦикла; 
КонецЦикла;

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

  1. Обращение к пустой ссылке менеджера документов. Выражение Документы.ПеремещениеТоваров.ПолучитьСсылку.Товары пытается прочитать табличную часть у пустой ссылки (ссылочного идентификатора, не привязанного к конкретному существующему документу в информационной базе). У объекта менеджера метод ПолучитьСсылку() возвращает пустую ссылку или генерирует новый УникальныйИдентификатор, не существующий в базе. Естественно, табличная часть такого пустого значения всегда пуста, и вложенный цикл просто не выполняет ни одной итерации.
  2. Дублирование циклов и некорректная вложенность. Во внешнем цикле происходит обход коллекции ТаблицаРеализаций с проверкой флага выгрузки, но сразу же внутри запускается повторный цикл по той же самой таблице: Для каждого Строка из ТаблицаРеализаций Цикл. Кроме того, переменная итератора Строка перезаписывается во внутреннем цикле, что приводит к полной потере контекста текущей строки.

Исправление алгоритма: прямое чтение табличной части

Если в таблице на форме (или в таблице значений) хранится колонка с ссылкой на конкретный документ (например, колонка Расходная, содержащая документ вида ДокументСсылка.ПеремещениеТоваров), мы должны обращаться к табличной части непосредственно через полученную ссылку. Уберем дублирующий цикл и исправим обращение к данным.

Посмотрим на исправленный вариант серверной процедуры:


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

Форматирование значений: почему нельзя использовать Строка()

Обратим внимание на форматирование чисел и строковых представлений. В исходном коде использовалась системная функция Строка(СтрТЧ.Количество). Использовать её при формировании XML крайне не рекомендуется.

Функция Строка() преобразует числовые данные в соответствии с региональными установками операционной системы. Например, число 1500 превратится в строку с неразрывным пробелом в качестве разделителя групп: "1 500", а дробная часть отделится запятой ("1 500,00"). Для XML-парсеров сторонних систем такое значение является синтаксической ошибкой (стандарт требует точку и отсутствие разделителей групп).

Для корректной сериализации значений в XML в платформе 1С предусмотрена функция XMLСтрока(). Она автоматически преобразует числа, даты и булевы значения в канонический формат стандарта XML Schema.

Оптимизация производительности: использование запроса вместо разыменования через точку

Конструкция СтрТаблицы.Расходная.Товары и последующее обращение СтрТЧ.Номенклатура.Артикул в цикле порождают проблему неявных запросов (проблема N+1). При каждой итерации цикла платформа отправляет в СУБД отдельные запросы за значениями реквизитов каждого ссылочного объекта. Если выгружается десяток документов по сотне строк в каждом, операция займет длительное время.

Рассмотрим профессиональное решение задачи с предварительной выборкой данных единым запросом:


&НаСервере
Процедура ВыгрузитьДокументыОптимально(ИмяФайла)
    
    // Собираем массив документов, отмеченных для выгрузки
    МассивДокументов = Новый Массив;
    Для Каждого Стр Из ТаблицаРеализаций Цикл
        Если Стр.Выгружать И ЗначениеЗаполнено(Стр.Расходная) Тогда
            МассивДокументов.Добавить(Стр.Расходная);
        КонецЕсли;
    КонецЦикла;
    
    Если МассивДокументов.Количество() = 0 Тогда
        ВызватьИсключение "Не выбрано ни одного документа для выгрузки!";
    КонецЕсли;
    
    // Получаем все данные одним запросом к базе
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   ТЧ.Ссылка КАК ДокументСсылка,
        |   ТЧ.Номенклатура.Наименование КАК НоменклатураНаименование,
        |   ТЧ.Номенклатура.Артикул КАК Артикул,
        |   ТЧ.Номенклатура.ЕдиницаИзмерения.Наименование КАК ЕдиницаИзмерения,
        |   ТЧ.Количество КАК Количество,
        |   ТЧ.Цена КАК Цена
        |ИЗ
        |   Документ.ПеремещениеТоваров.Товары КАК ТЧ
        |ГДЕ
        |   ТЧ.Ссылка В(&МассивДокументов)
        |ИТОГИ ПО
        |   ДокументСсылка";
        
    Запрос.УстановитьПараметр("МассивДокументов", МассивДокументов);
    РезультатЗапроса = Запрос.Выполнить();
    
    ХМЛ = Новый ЗаписьXML;
    ХМЛ.ОткрытьФайл(ИмяФайла, "UTF-8");
    ХМЛ.ЗаписатьОбъявлениеXML();
    ХМЛ.ЗаписатьНачалоЭлемента("Товары");
    ХМЛ.ЗаписатьНачалоЭлемента("ТабличнаяЧасть");
    
    ВыборкаДокументов = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
    Пока ВыборкаДокументов.Следующий() Цикл
        
        ВыборкаСтрок = ВыборкаДокументов.Выбрать();
        Пока ВыборкаСтрок.Следующий() Цикл
            
            ХМЛ.ЗаписатьНачалоЭлемента("Товар");
            ХМЛ.ЗаписатьАтрибут("Номенклатура", XMLСтрока(ВыборкаСтрок.НоменклатураНаименование));
            ХМЛ.ЗаписатьАтрибут("Количество", XMLСтрока(ВыборкаСтрок.Количество));
            ХМЛ.ЗаписатьАтрибут("Цена", XMLСтрока(ВыборкаСтрок.Цена));
            ХМЛ.ЗаписатьАтрибут("Артикул", XMLСтрока(ВыборкаСтрок.Артикул));
            ХМЛ.ЗаписатьАтрибут("ЕдиницаИзмерения", XMLСтрока(ВыборкаСтрок.ЕдиницаИзмерения));
            ХМЛ.ЗаписатьКонецЭлемента();
            
        КонецЦикла;
        
    КонецЦикла;
    
    ХМЛ.ЗаписатьКонецЭлемента();
    ХМЛ.ЗаписатьКонецЭлемента();
    ХМЛ.Закрыть();
    
КонецПроцедуры

Работа с файлами в клиент-серверной архитектуре

Разберем еще один важный нюанс клиент-серверного взаимодействия. В коде автора выбор пути к файлу происходит на клиенте через ДиалогВыбораФайла, после чего этот путь передается параметром в процедуру &НаСервере, где вызывается ХМЛ.ОткрытьФайл(ИмяФайла).

В файловой информационной базе такой код отработает корректно. Однако в клиент-серверном режиме (SQL) метод ЗаписьXML.ОткрытьФайл() пытается создать файл в файловой системе сервера 1С, а не на компьютере пользователя. Если на сервере нет пути, аналогичного клиентскому (например, C:\Users\Иван\Desktop\...), запись завершится системной ошибкой доступа либо файл запишется на диске сервера, а пользователь его не найдет.

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

  1. На сервере формировать XML во временный файл через ПолучитьИмяВременногоФайла("xml") либо в оперативную память через метод ХМЛ.УстановитьСтроку().
  2. Помещать полученный файл во временное хранилище через ПоместитьВоВременноеХранилище().
  3. На клиенте скачивать сформированный файл на компьютер пользователя с помощью асинхронных методов ПолучитьФайлИзВременногоХранилища() или НачатьПолучениеФайлаССервера().
← На главную