gifts2017

Элегантное чтение больших XML файлов

Опубликовал rtnm rtnm (rtnm) в раздел Программирование - Практика программирования

В статье показано как элегантно производить чтение больших XML файлов

Введение.

При необходимости прочитать XML файл можно применять один из подходов: полная загрузка в DOM и последовательное чтение. Полная загрузка XML файла в DOM реализована в 1C c помощью объекта ДокументDOM. DOM - это классно, но говорить о нем сегодня не буду, так как речь идет об относительно больших файлах XML, и тут DOM не подходит. Последовательное чтение XML файлов реализовано в 1С с помощью такого объекта как ЧтениеXML. Есть и другие объекты в 1С для работы с XML, но сегодня, опять же, не о них.

Объект ЧтениеXML - яркий представитель подхода последовательного чтения XML файла. Я уверен, что вы представляете как выглядит код с использованием объекта ЧтениеXML. В нем есть циклы и много условий на сравнение типа и имени узла. Мне не нравится такой код и я не люблю его писать. Если вы не представляете как выглядит такой код, то решите описанную ниже демонстрационную задачу с использованием объекта ЧтениеXML и вам станет понятнее о чем я говорю.

Я же хочу показать как можно читать XML файл элегантно. А пока сформулирую демонстрационную задачу.

Демонстрационная задача

На входе файл Контрагенты.xml следующей структуры и содержания:

<?xml version="1.0"?>
<Контрагенты>
    <Контрагент Наименование="Контрагент 1" Код="000000001">
        <Телефон>333-33-33</Телефон>
    </Контрагент>
    <Контрагент Наименование="Контрагент 2" Код="000000002"/>
    <Контрагент Наименование="Контрагент 3" Код="000000003">
        <Телефон>111-11-11</Телефон>
        <Телефон>222-22-22</Телефон>
    </Контрагент>
</Контрагенты>

Требуется загрузить данные файла в справочник "Контрагенты", который имеет реквизит "Телефоны". Подразумевается, что в реквизите "Телефоны" указано несколько телефонов. При загрузке необходимо производить поиск контрагента по коду и только если контрагент не найден, то создавать нового.

Реализация

Реализуем загрузку контрагентов в обработке "Загрузка контрагентов". При нажатии на кнопку "Загрузить" выполняется следующий код:

ЭлегантноеЧтениеXML = Обработки.ЭлегантноеЧтениеXML.Создать();
ЗагруженныеКонтрагенты = ЭлегантноеЧтениеXML.Прочитать(Файл, ЭтотОбъект);
	
Для Каждого ТекущийКонтрагент Из ЗагруженныеКонтрагенты Цикл
	Сообщить(ТекущийКонтрагент);		
КонецЦикла;

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

Объект-делегат должен реализовывать следующие методы:

1. Процедура Начало(ЧтениеXML) Экспорт - данная процедура запускается при начале обработки XML файла, в ней можно инициировать какие-нибудь переменные и настроить объект ЧтениеXML

2. Функция Завершение() Экспорт - данная функция запускается самой последней, и должна вернуть результат обработки файла, может вернуть и неопределено - это нормально

3. Функция УзелНачало(ЧтениеXML, Узел) Экспорт - данная функция запускается каждый раз, когда начинает обрабатываться новый узел. В параметре Узел находится уже считанная информация об узле: локальное имя, URI пространства имен, таблица атрибутов и так далее. Параметр ЧтениеXML передается для того, чтобы вы могли самостоятельно дополнить Узел нужными незаполненными свойствами. Ключевой момент - функция должна вернуть Истина, если узел был обработан в этой функции, и ложь если не обрабатывался. В случае если узел не обрабатывался, то будет вызван метод Узел{Тег}Начало(ЧтениеXML, Узел), иначе не будет

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

5. Процедура Узел{Тег}Начало(ЧтениеXML, Узел) Экспорт - данная процедура запускается каждый раз, когда начинает обрабатываться узел с локальным именем Тег, если конечно до этого узел не был обработан при вызове УзелНачало(ЧтениеXML, Узел)

6. Процедура Узел{Тег}Завершение(Узел) Экспорт - данная процедура запускается каждый раз, когда заканчивает обрабатываться узел с локальным именем Тег, если конечно до этого узел не был обработан при вызове УзелЗавершение(Узел)

Параметр Узел помимо того, что содержит информацию о текущем узле, он еще ссылается на родительский узел. Родительские узлы существуют пока обрабатываются все его дочерние узлы. Иными словами получить доступ к родительским узлам вы можете всегда.

Дедушка = Узел.Родитель.Родитель;

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

Перем ЗагруженныеКонтрагенты;

Процедура Начало(ЧтениеXML) Экспорт
	ЗагруженныеКонтрагенты = Новый Массив;
КонецПроцедуры

Функция Завершение() Экспорт
	Возврат ЗагруженныеКонтрагенты;
КонецФункции

Функция УзелНачало(ЧтениеXML, Узел) Экспорт
	Обработано = Ложь;
	Возврат Обработано;
КонецФункции

Функция УзелЗавершение(Узел) Экспорт
	Обработано = Ложь;
	Возврат Обработано;
КонецФункции

Процедура УзелКонтрагентыНачало(ЧтениеXML, Узел) Экспорт
КонецПроцедуры

Процедура УзелКонтрагентыЗавершение(Узел) Экспорт
КонецПроцедуры

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

Процедура УзелКонтрагентЗавершение(Узел) Экспорт
	Узел.ДопСвойства.КонтрагентОбъект.Записать();
	ЗагруженныеКонтрагенты.Добавить(Узел.ДопСвойства.КонтрагентОбъект.Ссылка);
КонецПроцедуры

Процедура УзелТелефонНачало(ЧтениеXML, Узел) Экспорт
КонецПроцедуры

Процедура УзелТелефонЗавершение(Узел) Экспорт
	КонтрагентОбъект = Узел.Родитель.ДопСвойства.КонтрагентОбъект;
	КонтрагентОбъект.Телефоны = КонтрагентОбъект.Телефоны + Узел.Значение + ";";	
КонецПроцедуры

Обратите внимание, что в приведенном коде нет ни циклов, ни условий на сравнение типа и имени узла. На мой взгляд, он читается очень легко.

Замечания

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

Замечание 1:  Разметка XML регистрозависима, язык программирования 1С регистронезависим, поэтому если в XML файле встречаются теги [Телефон] и [телефон], то обрабатывать их будет один и тот же метод УзелТелефонНачало(ЧтениеXML, Узел).  Для того чтобы их различать нужно использовать Узел.ЛокальноеИмя.

Замечание 2: В файле XML может быть два тега [Телефон] из разных URI пространства имен. Обрабатывать оба тега будет один и тот же метод УзелТелефонНачало(ЧтениеXML, Узел). Для того чтобы их различать нужно использовать Узел.URIПространстваИмен

Замечание 3: Согласно спецификации XML имя тега может содержать не только буквы, цифры и знак подчеркивания, а еще и другие символы, например "-" или ".". В XML допустим, например, такой тег [a-b.c]. Поэтому все недопустимые для идентификатора 1С символы игнорируются, так, для примера выше, тег будет обработан методом УзелABCНачало(ЧтениеXML, Узел). 

Замечание 4: Один и тот же тег может быть дочерним для разных родительских тегов. Так, например, родительским тегом для [Телефон] может быть и [Контрагент] и [КонтактноеЛицо]. Обрабатывать тег всегда будет УзелТелефонНачало(ЧтениеXML, Узел). Если обработка тега зависит от родительского тега, то вы можете ориентироваться на Узел.Родитель.

Замечание 5: Значение узла, т.е. текст который находится между открывающим и закрывающим тегами, можно получить как Узел.Значение, но сделать это можно только при завершении обработки узла, т.е. в методе УзелЗавершение(Узел) или Узел{Тег}Завершение(Узел).

Помимо описанных особенностей наверняка есть и другие.

Заключение

Уверен, что текущая реализация не идеальна, но я и не ставил перед собой такую задачу. Я лишь хотел продемонстрировать подход, который мне кажется элегантным.

Если остались вопросы, то скачивайте ЭлегантноеЧтениеXML.cf

Спасибо за внимание.


Скачать файлы

Наименование Файл Версия Размер Кол. Скачив.
ЭлегантноеЧтениеXML.cf
.cf 25,33Kb
01.10.14
21
.cf 25,33Kb 21 Скачать

См. также

Подписаться Добавить вознаграждение

Комментарии

1. Яков Коган (Yashazz) 02.10.14 11:06
Может, я торможу, но я правильно понял, что это просто насадка на то же самое последовательное чтение?
2. Александр Гладких (yku) 02.10.14 11:09
А что с процедурой "Прочитать"? Я как понимаю, там и циклы и условия?
3. Александр Капустин (kapustinag) 02.10.14 15:39
(0) Мне нравится. Да, это насадка, но красивая.
4. Андрей Овсянкин (Evil Beaver) 02.10.14 18:22
Поздравляю, вы изобрели XDTO.
veretennikoff; Tangram; Redokov; delete; Den_D; shalimski; +6 Ответить 1
5. rtnm rtnm (rtnm) 07.10.14 12:05
(4) Evil Beaver, А минус то за что? Расскажешь?
6. Александр *** (a1ex4ndr) 08.10.14 13:11
А на каком размере файла тестировалось? А то в тесте файл из трех строчек.....
7. rtnm rtnm (rtnm) 08.10.14 13:13
(6) a1ex4ndr, Дело не в размере файла, а в подходе - последовательное чтение. Никаких чудес не будет, все сопоставимо с ЧтениеXML.
8. DUH Technolover (DJDUH) 08.10.14 13:46
Интересно, а как же он элегантно читает телефоны?

,,,,,,,,,,,,,,,,,,,,,,,,,,,
        <Телефон>111-11-11</Телефон>
        <Телефон>222-22-22</Телефон>
,,,,,,,,,,,,,,,,,,,,,,,,,,,



9. rtnm rtnm (rtnm) 08.10.14 14:01
(8) DJDUH, Не уверен что я правильно понял вопрос, но вот так он читает телефоны:

Процедура УзелТелефонЗавершение(Узел) Экспорт
    КонтрагентОбъект = Узел.Родитель.ДопСвойства.КонтрагентОбъект;
    КонтрагентОбъект.Телефоны = КонтрагентОбъект.Телефоны + Узел.Значение + ";";    
КонецПроцедуры
...Показать Скрыть
10. DAnry (DAnry) 08.10.14 15:38
Мне тоже нравится. Плюсанул...
11. Андрей Овсянкин (Evil Beaver) 08.10.14 18:47
(5) rtnm, минус за велосипед, выданный за некое элегантное чтение. Минус за то, что этот подход пропагандируется среди сообщества, вместо того, чтобы подталкивать новичков к изучению менее велосипедных решений.

Короче, сделан костыль и костыль отрекламирован. Теперь 1С-ник Вася вместо изучения XDTO будет лепить вот такие-вот обходные горы кода, вместо существующего, более автоматизированного и типобезопасного способа чтения XML.
Технически решение красивое, пожалуй, но на прикладную ценность не тянет. Разумеется, это чисто мое скромное мнение, вы можете с ним не соглашаться.
budunovmv; alexscamp; Ivon; YPermitin; нормальный такой; IvanBoychuk123; baton_pk; zqzq; DrAku1a; 1cWin; rtnm; +11 Ответить
12. Владимир Казначеев (Mogidin) 09.10.14 13:27
а можно воспользоваться Msxml2.DOMDocument
ФайлXML = Новый COMОбъект("MSXML2.DOMDocument.4.0");
и вперед
http://msdn.microsoft.com/en-us/library/aa923288.aspx

то же ЧтениеXML, но гораздо быстрее
kostyaomsk; +1 Ответить 2
13. Дмитрий Никс (aximo) 09.10.14 19:17
Кстати, Вы реально работали с большими XML-файлами? Дело в том, что при сохранение текста разметки на УФ больше 10 мб (по опыту) платформа 8.2 может выдавать ошибку сохранения файла.
14. rtnm rtnm (rtnm) 09.10.14 22:48
(13) aximo, с большими XML-файлами вместе с УФ не работал
15. Константин Юрин (kostyaomsk) 23.10.14 10:23
(12) Mogidin, там уже не к 1С относится, а общее описание стандарта DOM-технологии. Еще и на английском. Придется искать уже переработанное и на русском для 1С 8, но за идейку большой плюс.
Для маленьких и средних технология "проглатывания" разом большого документа в какой-то объект в ОЗУ очень хорошо: и прочитали все сразу и разобрали, и, возможно, запрещенные символы самим объектом удалось обработать. Еще может и надежнее, раз прочитали весь объект то видим все взаимосвязанные ссылки по всему объекту, а вот при последовательном чтении только какую-то часть большого файла. Свыше 2 Гб XML вообще не эффективен. Тут уже и DOMDocument не поможет. Любой сервер загнется.
Технологию описанную автором статьи, наверное, стоит применять если структура XML-файла простая (типа таблица ФИО, год рождения, адрес...), однотипная, без глубинных вложений, взаимосвязанных ссылок. Или если это остаток древней технологии старых решений.
Если можно, то приведите примеры ссылок на уже готовые простые решения DOM чтения и обработки.
16. Андрей Овсянкин (Evil Beaver) 28.10.14 17:01
(12) Mogidin, че, прям гораздо? А цифры есть?
17. DrZombi DrZombi (DrZombi) 05.11.14 15:52
Автор, без обид, но отстой :)
Сколько не писал обмены на XML... но самый лучший прирост мне дал обычный DBF файл :)
...а XML - это файл для организации мелких обменов, которые и тем же DBF файлами будут работать куда приятней :)

...
п.с. на DBF тоже можно организовывать древа, но накой оно надо ;)
18. rtnm rtnm (rtnm) 05.11.14 17:08
(17) DrZombi, в следующий раз так и скажи заказчику: "Ваш XML отстой. Дайте мне DBF, мне с ним куда приятней работать" :)
19. Константин Юрин (kostyaomsk) 14.11.14 20:47
Самое неприятное в формате DBF это длинна текстового поля (боюсь ошибиться) 254 символа и потом уже средствами 1С приходится склеивать строки или при выгрузке в DBF дробить на части. Причем сам формат DBF последних редакций может и поддерживать поле МЕМО (строка неограниченной длины), а вот в 1С объект XBase уже нет.
Смысл использовать dbf если только сторонняя АИС типа paradox (foxPro, 1С 7.7 в файловом режиме) его генерирует для обмена (или загружают в нее) или это и есть внешний источник данных.
Для написания сообщения необходимо авторизоваться
Прикрепить файл
Дополнительные параметры ответа