Почему не создаются документы при импорте из XML в 1С и как это исправить?

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

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

В чем кроется причина «тихой» проблемы?

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

Рассмотрим по шагам основные критические ошибки, которые часто допускают при реализации подобных механизмов.

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

Для того чтобы понять, почему документы не создаются, проанализируем стандартные ошибки парсинга на конкретных примерах.

1. Взаимоисключающие условия (конфликт типов узлов)

Часто в обработчиках чтения XML можно встретить конструкцию следующего вида:


Если ЧтениеДанных.ТипУзла = ТипУзлаXML.Текст Тогда
    // Получение значений реквизитов
    Если ЧтениеДанных.ТипУзла = ТипУзлаXML.КонецЭлемента и Атрибут = "Накладная" Тогда
        СоздатьДокумент(СЗ);
    КонецЕсли;
КонецЕсли;

Давайте проанализируем эту конструкцию. Внутреннее условие проверки на тип узла ТипУзлаXML.КонецЭлемента вложено внутрь внешнего условия, которое проверяет, является ли текущий узел типом ТипУзлаXML.Текст. Однако один и тот же узел XML-документа физически не может одновременно являться и текстовым содержимым, и закрывающим тегом (концом элемента). Из-за этой логической ошибки управление никогда не передается процедуре создания документа, и алгоритм завершается без ошибок, но и без результата.

2. Неверное определение имени тега

Еще одна распространенная ошибка связана с попыткой прочитать имя текущего элемента через чтение его атрибутов:


Если ЧтениеДанных.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
    ЧтениеДанных.ПрочитатьАтрибут;
    Атрибут = ЧтениеДанных.Значение;
КонецЕсли;

Разберем, почему этот код не решает поставленную задачу:

  1. Метод ПрочитатьАтрибут() предназначен исключительно для чтения параметров внутри открывающего тега (например, id="123" в теге <Документ id="123">). Если у тега нет атрибутов, метод вернет значение Ложь.
  2. Свойство Значение для узла типа НачалоЭлемента возвращает пустую строку, так как само текстовое значение находится на следующем шаге чтения — в текстовом узле.
  3. Для получения имени самого тега (например, названия реквизита "Накладная") необходимо использовать свойство Имя объекта чтения XML.

3. Проблема доступа к файлу (Клиент-Серверная архитектура)

При работе в клиент-серверном варианте базы данных код, выполняемый под директивой компиляции &НаСервере, не имеет прямого доступа к файловой системе локального компьютера пользователя (клиента). Если на сервере вызвать метод ОткрытьФайл(Объект.КаталогИмпорта), где указан локальный путь типа "C:\Users\...", сервер выдаст ошибку отсутствия файла, либо (что хуже) не выполнит чтение, если перехват ошибок настроен некорректно.

4. Использование индексированного Списка Значений для хранения структуры

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

---

Пошаговое руководство по правильной реализации загрузки XML

Для того чтобы исправить вышеописанные проблемы, мы разделим весь процесс импорта на три четких этапа:

  1. Безопасная передача файла с клиента на сервер через временное хранилище.
  2. Корректный обход узлов XML-файла на сервере с использованием свойства Имя.
  3. Заполнение реквизитов документа с типизацией данных и записью в базу.

Этап 1. Передача файла на сервер

Для корректной работы в любом режиме (файловом или клиент-серверном) мы должны сначала поместить файл в специальный буфер — временное хранилище на сервере.

На клиенте мы получаем путь к файлу и отправляем его на сервер:


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

&НаКлиенте
Процедура ПослеПомещенияФайлаНаСервер(РезультатПередачи, ДополнительныеПараметры) Экспорт
    
    Если РезультатПередачи.Количество() > 0 И РезультатПередачи[0].Хранилище <> Неопределено Тогда
        // Файл успешно загружен во временное хранилище, вызываем серверный метод обработки
        ЗагрузкаДокументовНаСервере(РезультатПередачи[0].Хранилище);
    Иначе
        ПоказатьПредупреждение(, "Не удалось передать файл на сервер.");
    EndЕсли;
    
КонецПроцедуры

Этап 2. Надежный разбор XML на сервере

Теперь на сервере мы извлекаем файл из временного хранилища, записываем его во временный каталог сервера и производим чтение. Вместо списков значений мы применим структуру для хранения реквизитов. Это гарантирует защиту от смещения индексов.


&НаСервере
Процедура ЗагрузкаДокументовНаСервере(АдресХранилища)
    
    // Получаем двоичные данные из временного хранилища
    ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресХранилища);
    
    // Создаем временный файл на диске сервера
    ИмяВременногоФайла = ПолучитьИмяВременногоФайла("xml");
    ДвоичныеДанные.Записать(ИмяВременногоФайла);
    
    ЧтениеДанных = Новый ЧтениеXML;
    Попытка
        ЧтениеДанных.ОткрытьФайл(ИмяВременногоФайла);
    Исключение
        ЗаписьЖурналаРегистрации("ИмпортXML", УровеньЖурналаРегистрации.Ошибка, , , 
            "Ошибка открытия файла на сервере: " + ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
        Возврат;
    КонецПопытки;
    
    // Инициализируем структуру для сбора данных текущего документа
    ДанныеДокумента = Новый Структура;
    ДанныеДокумента.Вставить("НомерАкт", "");
    ДанныеДокумента.Вставить("ДатаАкт", '00010101');
    ДанныеДокумента.Вставить("Накладная", "");
    
    ТекущееИмяТега = "";
    
    Пока ЧтениеДанных.Прочитать() Цикл
        
        ТипУзла = ЧтениеДанных.ТипУзла;
        
        // 1. Позиционируемся на начале элемента тега и запоминаем его имя
        Если ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
            ТекущееИмяТега = ЧтениеДанных.Имя;
            
        // 2. Считываем текстовые данные, находящиеся внутри элемента
        ИначеЕсли ТипУзла = ТипУзлаXML.Текст Тогда
            Если ДанныеДокумента.Свойство(ТекущееИмяТега) Тогда
                // Заполняем соответствующее свойство в нашей структуре
                ДанныеДокумента[ТекущееИмяТега] = ЧтениеДанных.Значение;
            КонецЕсли;
            
        // 3. Отслеживаем закрывающий тег группы данных
        ИначеЕсли ТипУзла = ТипУзлаXML.КонецЭлемента Тогда
            // Если закрывается корневой тег нашего логического блока, создаем документ
            Если ЧтениеДанных.Имя = "Накладная" Тогда
                СоздатьДокументНаСервере(ДанныеДокумента);
                
                // Очищаем данные структуры перед чтением следующего блока
                ДанныеДокумента.НомерАкт = "";
                ДанныеДокумента.ДатаАкт = '00010101';
                ДанныеДокумента.Накладная = "";
            КонецЕсли;
            
            // Сбрасываем текущий тег при его закрытии
            ТекущееИмяТега = "";
        КонецЕсли;
        
    КонецЦикла;
    
    // Обязательно закрываем файл для освобождения системных ресурсов
    ЧтениеДанных.Закрыть();
    
    // Удаляем временный файл с диска сервера
    Попытка
        УдалитьФайлы(ИмяВременногоФайла);
    Исключение
        // Исключение не прерывает работу пользователя, файл очистится позже операционной системой
    КонецПопытки;
    
КонецПроцедуры

Этап 3. Запись документа и преобразование типов данных

При записи полученных значений важно правильно приводить типы. Из XML все данные считываются как обычные строки. Дату необходимо преобразовывать с помощью системной функции XMLЗначение, а ссылочные типы (если поле "Накладная" — это ссылка на другой документ или справочник) необходимо предварительно искать в базе данных.


&НаСервере
Процедура СоздатьДокументНаСервере(ДанныеДокумента)
    
    // Проверим минимальную заполненность структуры для исключения пустых записей
    Если Не ЗначениеЗаполнено(ДанныеДокумента.НомерАкт) Тогда
        Возврат;
    КонецЕсли;
    
    НовыйДокумент = Документы.ПоступлениеТоваровУслуг.СоздатьДокумент();
    
    // Заполняем реквизиты
    НовыйДокумент._НомерТТН = ДанныеДокумента.НомерАкт;
    
    // Преобразование строкового представления даты в тип Дата 1С
    Попытка
        НовыйДокумент.Дата = XMLЗначение(Тип("Дата"), ДанныеДокумента.ДатаАкт);
    Исключение
        НовыйДокумент.Дата = ТекущаяДата(); // Резервное заполнение при ошибке формата даты
    КонецПопытки;
    
    // Если "Накладная" в конфигурации является строкой:
    НовыйДокумент.Накладная = ДанныеДокумента.Накладная;
    
    // Если "Накладная" — это ссылка (например, ДокументСсылка.РасходнаяНакладная), 
    // здесь должен быть реализован алгоритм поиска по номеру/коду:
    // НовыйДокумент.Накладная = Документы.РасходнаяНакладная.НайтиПоНомеру(ДанныеДокумента.Накладная, НовыйДокумент.Дата);
    
    // Безопасная запись объекта
    Попытка
        НовыйДокумент.Записать(РежимЗаписиДокумента.Запись);
    Исключение
        // Логируем ошибку записи конкретного документа, не прерывая чтение всего файла
        ЗаписьЖурналаРегистрации("ИмпортXML.ЗаписьДокумента", УровеньЖурналаРегистрации.Ошибка, , ,
            "Не удалось записать документ №" + ДанныеДокумента.НомерАкт + ". Описание: " + ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
    КонецПопытки;
    
КонецПроцедуры

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

← На главную