Введение.
При необходимости прочитать 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
Спасибо за внимание.