При интеграции систем и обмене данными разработчики часто сталкиваются с необходимостью обработки больших XML-файлов. Платформа 1С:Предприятие предлагает мощный инструмент для работы с объектными моделями данных — Фабрику XDTO. Однако попытка прочитать весь файл целиком в один объект XDTO (например, большой список контрагентов или пациентов) неизбежно приводит к избыточному потреблению оперативной памяти и падению производительности сервера.
В таких случаях фирма 1С рекомендует использовать гибридный подход: последовательно перемещаться по файлу с помощью объекта ЧтениеXML, а преобразовывать в объекты XDTO только целевые узлы. Но при реализации этого алгоритма начинающие программисты часто сталкиваются с неприятной проблемой — парсер начинает считывать элементы через один. В этой статье мы подробно разберем, почему возникает эта ошибка, как устроен механизм позиционирования указателя, и рассмотрим несколько эффективных способов решения задачи.
Рассмотрим классический пример ошибочной реализации, который часто встречается на практике:
Пока ЧтениеХМЛ.Прочитать() Цикл
Если ЧтениеХМЛ.ЛокальноеИмя = "patient" И ЧтениеХМЛ.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
// Читаем элемент в объект XDTO
Данные = ФабрикаXDTO.ПрочитатьXML(ЧтениеХМЛ, лТип);
Сообщить(Данные.surname);
КонецЕсли;
КонецЦикла;
На первый взгляд код выглядит логичным. Мы перебираем узлы методом Прочитать(), находим начало нужного элемента, десериализуем его и идем дальше. Однако при запуске этого кода мы обнаружим, что программа вывела данные 1-го, 3-го, 5-го пациентов, проигнорировав чётные элементы. Почему так происходит?
Разберем поведение указателя XML-потока по шагам:
ЧтениеХМЛ.Прочитать() находит первый открывающий тег <patient>. Указатель позиционируется на типе узла НачалоЭлемента.Если, и вызывается метод ФабрикаXDTO.ПрочитатьXML().ФабрикаXDTO начинает самостоятельно считывать данные из потока ЧтениеXML. Он обрабатывает все вложенные реквизиты пациента и останавливается только тогда, когда полностью закроет тег </patient>. Указатель в объекте ЧтениеXML теперь позиционируется сразу за закрывающим тегом прочитанного элемента (на границе следующего узла).Пока, где первым делом снова вызывается метод ЧтениеХМЛ.Прочитать().<patient> следующего элемента, переходя к его внутреннему содержимому или вовсе к третьему элементу.В результате такого "двойного" сдвига мы теряем ровно половину данных. Вызов метода Пропустить(), как пытался сделать автор темы, только усугубляет ситуацию, так как он предназначен для пропуска всей ветки текущего узла и также сдвигает указатель.
Давайте разберем правильные архитектурные решения этой проблемы.
Чтобы избежать нежелательного вызова Прочитать() на итерации внешнего цикла, мы можем использовать вложенный цикл Пока. Этот подход позволяет обрабатывать идущие друг за другом однотипные элементы непрерывно, пока указатель позиционирован на них.
Проанализируем следующий код:
ВсегоПациентов = 0;
Пока ЧтениеХМЛ.Прочитать() Цикл
// Внутренний цикл обрабатывает пачку элементов подряд
Пока ЧтениеХМЛ.ЛокальноеИмя = "patient" И ЧтениеХМЛ.ТипУзла = ТипУзлаXML.НачалоЭлемента Цикл
ВсегоПациентов = ВсегоПациентов + 1;
// Метод считывает элемент целиком и сдвигает указатель на начало следующего узла
Данные = ФабрикаXDTO.ПрочитатьXML(ЧтениеХМЛ, лТип);
Сообщить(Данные.surname);
КонецЦикла;
КонецЦикла;
Как это работает по шагам:
patient.ФабрикаXDTO.ПрочитатьXML(). Объект успешно считывается, а указатель автоматически встает на начало следующего узла.patient, нам не нужно вызывать метод Прочитать(). Программа сразу переходит к десериализации второго объекта.</patients>), условие внутреннего цикла вернет Ложь. Управление вернется во внешний цикл, который безопасно выполнит метод Прочитать() для продвижения по структуре документа дальше.Если вложенные циклы кажутся вам сложными для отладки и понимания, можно применить подход с явным ручным управлением итератором. В этом случае мы используем бесконечный цикл, а сдвиг указателя выполняем только тогда, когда это действительно необходимо (когда текущий узел не является целевым элементом для XDTO).
Рассмотрим пример реализации:
Пока Истина Цикл
// Проверяем, стоим ли мы на нужном элементе
Если ЧтениеХМЛ.ЛокальноеИмя = "patient" И ЧтениеХМЛ.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
// Чтение элемента. Метод ПрочитатьXML() сам сдвинет указатель на следующий узел
Данные = ФабрикаXDTO.ПрочитатьXML(ЧтениеХМЛ, лТип);
Сообщить(Данные.surname);
Иначе
// Сдвигаем указатель вперед, только если мы не читали данные через Фабрику
Если Не ЧтениеХМЛ.Прочитать() Тогда
Прервать; // Достигнут конец XML-документа
КонецЕсли;
КонецЕсли;
КонецЦикла;
Данный алгоритм максимально прозрачен: мы либо считываем объект XDTO (автоматически продвигая указатель потока), либо принудительно делаем шаг вперед с помощью ЧтениеХМЛ.Прочитать(), если текущий узел — это технический тег, пробел или перенос строки.
Для конфигураций, в которых активно используется стандартный механизм сериализации 1С, существует еще более элегантное и лаконичное решение. Мы можем задействовать встроенную системную функцию ВозможностьЧтенияXML().
Посмотрим на пример применения этого метода:
Пока ЧтениеХМЛ.Прочитать() Цикл
// Метод проверяет, соответствуют ли текущие данные типу XDTO
Пока ВозможностьЧтенияXML(ЧтениеХМЛ) Цикл
// Автоматическое чтение объекта без ручного указания типа
Данные = ПрочитатьXML(ЧтениеХМЛ);
Сообщить(Данные.surname);
КонецЦикла;
КонецЦикла;
Этот способ избавляет разработчика от необходимости вручную прописывать текстовые проверки имени узла (ЛокальноеИмя = "patient") и типа узла, перекладывая эту задачу на плечи платформы 1С. Однако помните, что для работы этого метода схема XML должна быть известна конфигурации (например, добавлена в XDTO-пакеты).
При работе с файлами XML, предназначенными для чтения человеком, часто используются отступы, знаки табуляции и переводы строк. Для платформы 1С каждый такой символ является отдельным узлом с типом ТипУзлаXML.Текст или ТипУзлаXML.ЗначимыеПробелы.
При обходе больших файлов парсер тратит колоссальное количество времени на бесполезный перебор этих пустых элементов. Чтобы ускорить чтение файлов в разы и сделать код обхода более стабильным, обязательно настраивайте свойства объекта ПараметрыЧтенияXML перед открытием файла.
Давайте разберем, как правильно инициализировать чтение с отключением обработки пробелов:
// Создаем параметры чтения. 9-й параметр отвечает за игнорирование пробельных символов
ПараметрыЧтения = Новый ПараметрыЧтенияXML(,,,,,,,, Истина);
ЧтениеХМЛ = Новый ЧтениеXML;
ЧтениеХМЛ.ОткрытьФайл(ПутьКФайлу, ПараметрыЧтения);
// Теперь парсер будет перемещаться исключительно по значащим тегам структуры XML!
Использование свойства игнорирования пробелов не только повышает скорость работы алгоритма (иногда до 2-3 раз на больших объемах данных), но и гарантирует, что указатель после вызова ПрочитатьXML() будет позиционироваться непосредственно на начале следующего тега, сводя к минимуму вероятность логических ошибок при чтении.
Применяйте рассмотренные шаблоны проектирования в своих интеграционных решениях, и ваши обработки импорта XML-данных будут работать быстро, стабильно и без лишней нагрузки на серверную память!