Как обойти строки в XML-файле и загрузить их в табличную часть документа 1С?

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

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

Выясним причины ошибок при чтении XML

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

  1. Лишний вызов метода Прочитать() внутри цикла. Если строки оформлены как самозакрывающиеся теги (например, <СтрокаТабличнойЧасти Ключ="1" />), дополнительный вызов ФайлXML.Прочитать() сдвигает позицию чтения на следующий узел. В результате платформа перескакивает через строку или выходит из родительского тега.
  2. Смешивание ЧтениеXML и ПостроительDOM. Передача объекта ЧтениеXML в метод ПостроительDOM.Прочитать(ФайлXML) считывает поток до самого конца. Курсор чтения перемещается в финал файла, из-за чего последующие вызовы Прочитать() в основном цикле возвращают Ложь, и вторая табличная часть не загружается.
  3. Ошибки преобразования числовых значений. В выгрузках числа часто содержат пробелы (или неразрывные пробелы) и запятую в качестве десятичного разделителя (например, "22 033,9"). Стандартная функция Число() при обработке такой строки вызывает исключение.
  4. Некорректные имена тегов. Стандарт XML запрещает использование пробелов в названиях тегов (например, тег вида <табличная часть> является невалидным и приводит к синтаксической ошибке парсера).

Вариант 1. Потоковое чтение через ЧтениеXML (рекомендуемый для больших файлов)

Потоковый разбор с помощью объекта ЧтениеXML не создает дерево элементов в оперативной памяти, поэтому работает быстро и стабильно с файлами любого размера. Разберем по шагам, как правильно организовать такой цикл:

  1. Открываем файл и организуем единый цикл обхода Пока ФайлXML.Прочитать() Цикл.
  2. Используем строковую переменную-состояние для фиксации текущей открытой секции (табличной части).
  3. При обнаружении строки считываем атрибуты напрямую с помощью метода ПолучитьАтрибут() без сдвига курсора.
  4. При достижении конца родительского элемента сбрасываем состояние текущей секции.

Посмотрим на пример практической реализации данного подхода:


// Создаем объект чтения и открываем файл
ФайлXML = Новый ЧтениеXML;
ФайлXML.ОткрытьФайл(ПутьКФайлу);

// Переменная для отслеживания текущей секции документа
ТекущаяСекция = "";

Пока ФайлXML.Прочитать() Цикл
    
    // Обрабатываем вход в узел
    Если ФайлXML.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
        
        ИмяУзла = ФайлXML.Имя;
        
        // Фиксируем контекст табличной части
        Если ИмяУзла = "ТабличнаяЧастьПомещений" Или ИмяУзла = "ТабличнаяЧастьПлощадей" Тогда
            ТекущаяСекция = ИмяУзла;
            
        // Обработка строк первой табличной части
        ИначеЕсли ИмяУзла = "СтрокаПомещений" И ТекущаяСекция = "ТабличнаяЧастьПомещений" Тогда
            
            НоваяСтрока = Объект.Помещения.Добавить();
            
            СтрокаКлюча = ФайлXML.ПолучитьАтрибут("КлючСтроки");
            Если ЗначениеЗаполнено(СтрокаКлюча) Тогда
                НоваяСтрока.КлючСтроки = Число(СтрокаКлюча);
            КонецЕсли;
            
            ИмяПомещения = ФайлXML.ПолучитьАтрибут("Помещение");
            НоваяСтрока.Помещение = Справочники.бит_Помещения.НайтиПоНаименованию(ИмяПомещения);
            
            ИмяОбъекта = ФайлXML.ПолучитьАтрибут("Объект");
            НоваяСтрока.Объект = Справочники.бит_ОбъектыНедвижимости.НайтиПоНаименованию(ИмяОбъекта);
            
        // Обработка строк второй табличной части
        ИначеЕсли ИмяУзла = "СтрокаПлощадей" И ТекущаяСекция = "ТабличнаяЧастьПлощадей" Тогда
            
            НоваяСтрока = Объект.Площади.Добавить();
            
            СтрокаКлюча = ФайлXML.ПолучитьАтрибут("КлючСтроки");
            Если ЗначениеЗаполнено(СтрокаКлюча) Тогда
                НоваяСтрока.КлючСтроки = Число(СтрокаКлюча);
            КонецЕсли;
            
            ВидПлощади = ФайлXML.ПолучитьАтрибут("ВидПлощади");
            НоваяСтрока.ВидПлощади = Справочники.бит_ВидыПлощадей.НайтиПоНаименованию(ВидПлощади);
            
            // Преобразуем числовые поля с очисткой форматирования
            НоваяСтрока.Площадь = ПреобразоватьЧислоXML(ФайлXML.ПолучитьАтрибут("Площадь"));
            НоваяСтрока.Ставка = ПреобразоватьЧислоXML(ФайлXML.ПолучитьАтрибут("Ставка"));
            НоваяСтрока.СтавкаСНДС = ПреобразоватьЧислоXML(ФайлXML.ПолучитьАтрибут("СтавкаСНДС"));
            НоваяСтрока.РасчетПоСтавкеСНДС = Истина;
            
        КонецЕсли;
        
    // Обрабатываем выход из узла
    ИначеЕсли ФайлXML.ТипУзла = ТипУзлаXML.КонецЭлемента Тогда
        
        Если ФайлXML.Имя = ТекущаяСекция Тогда
            ТекущаяСекция = "";
        КонецЕсли;
        
    КонецЕсли;
    
КонецЦикла;

ФайлXML.Закрыть();

Вариант 2. Загрузка через объектную модель DOM

Рассмотрим альтернативный способ на основе объекта ПостроительDOM. Этот вариант удобен для небольших файлов, так как позволяет обращаться к узлам через коллекции и методы выборки. Важно: документ DOM должен быть прочитан из потока единожды целиком до начала обхода коллекций.

Разберем по шагам реализацию через DOM:


ФайлXML = Новый ЧтениеXML;
ФайлXML.ОткрытьФайл(ПутьКФайлу);

// Выполняем полное чтение документа в память один раз
ПостроительDOM = Новый ПостроительDOM;
ДокументDOM = ПостроительDOM.Прочитать(ФайлXML);
ФайлXML.Закрыть();

// Обходим строки первой табличной части
УзлыПомещений = ДокументDOM.ПолучитьЭлементыПоИмени("СтрокаПомещений");
Для Каждого Узел Из УзлыПомещений Цикл
    
    НоваяСтрока = Объект.Помещения.Добавить();
    
    УзелКлюча = Узел.Атрибуты.ПолучитьИменованныйЭлемент("КлючСтроки");
    Если УзелКлюча <> Неопределено Тогда
        НоваяСтрока.КлючСтроки = Число(УзелКлюча.ЗначениеУзла);
    КонецЕсли;
    
    УзелОбъекта = Узел.Атрибуты.ПолучитьИменованныйЭлемент("Объект");
    Если УзелОбъекта <> Неопределено Тогда
        НоваяСтрока.Объект = Справочники.бит_ОбъектыНедвижимости.НайтиПоНаименованию(УзелОбъекта.ЗначениеУзла);
    КонецЕсли;
    
    УзелПомещения = Узел.Атрибуты.ПолучитьИменованныйЭлемент("Помещение");
    Если УзелПомещения <> Неопределено Тогда
        НоваяСтрока.Помещение = Справочники.бит_Помещения.НайтиПоНаименованию(УзелПомещения.ЗначениеУзла);
    КонецЕсли;
    
КонецЦикла;

// Обходим строки второй табличной части
УзлыПлощадей = ДокументDOM.ПолучитьЭлементыПоИмени("СтрокаПлощадей");
Для Каждого Узел Из УзлыПлощадей Цикл
    
    НоваяСтрока = Объект.Площади.Добавить();
    
    УзелКлюча = Узел.Атрибуты.ПолучитьИменованныйЭлемент("КлючСтроки");
    Если УзелКлюча <> Неопределено Тогда
        НоваяСтрока.КлючСтроки = Число(УзелКлюча.ЗначениеУзла);
    КонецЕсли;
    
    УзелВида = Узел.Атрибуты.ПолучитьИменованныйЭлемент("ВидПлощади");
    Если УзелВида <> Неопределено Тогда
        НоваяСтрока.ВидПлощади = Справочники.бит_ВидыПлощадей.НайтиПоНаименованию(УзелВида.ЗначениеУзла);
    КонецЕсли;
    
    УзелПлощади = Узел.Атрибуты.ПолучитьИменованныйЭлемент("Площадь");
    Если УзелПлощади <> Неопределено Тогда
        НоваяСтрока.Площадь = ПреобразоватьЧислоXML(УзелПлощади.ЗначениеУзла);
    КонецЕсли;
    
    УзелСтавки = Узел.Атрибуты.ПолучитьИменованныйЭлемент("Ставка");
    Если УзелСтавки <> Неопределено Тогда
        НоваяСтрока.Ставка = ПреобразоватьЧислоXML(УзелСтавки.ЗначениеУзла);
    КонецЕсли;
    
    УзелСтавкиНДС = Узел.Атрибуты.ПолучитьИменованныйЭлемент("СтавкаСНДС");
    Если УзелСтавкиНДС <> Неопределено Тогда
        НоваяСтрока.СтавкаСНДС = ПреобразоватьЧислоXML(УзелСтавкиНДС.ЗначениеУзла);
    КонецЕсли;
    
    НоваяСтрока.РасчетПоСтавкеСНДС = Истина;
    
КонецЦикла;

Корректное преобразование чисел с разделителями

Рассмотрим функцию очистки строковых числовых литералов от неразрывных пробелов и запятых перед преобразованием в тип Число:


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

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

← На главную