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