При работе с XML-файлами в среде 1С:Предприятие (особенно в версии 7.7 с использованием внешней компоненты V7PLUS.DLL или встроенного объекта XMLParser) разработчики часто сталкиваются с терминами «Узел» (Node) и «Элемент» (Element). На первый взгляд может показаться, что это синонимы, поскольку в литературе и коде эти слова постоянно сменяют друг друга. Однако смешивание этих понятий приводит к трудночитаемому коду и регулярным ошибкам времени выполнения (runtime errors).
Выясним причину этой путаницы, разберем по шагам теоретическую базу объектной модели DOM (Document Object Model), проанализируем разницу в свойствах и методах компоненты V7PLUS.DLL и научимся правильно обрабатывать данные без досадных сбоев программы.
Главная причина непонимания разницы между Узлом и Элементом кроется в объектно-ориентированной архитектуре XML-парсеров. В соответствии со спецификацией W3C XML DOM, весь XML-документ представляется в виде древовидной структуры, состоящей из объектов. Отношения между Узлом и Элементом строятся на базовом принципе ООП — наследовании.
Узел (Node / IXMLDOMNode) — это самый базовый, абстрактный класс (интерфейс) для абсолютно любого объекта внутри XML-документа. Всё, что парсер считывает из файла и помещает в оперативную память, является Узлом. Узлом является сам документ, строка комментария, служебное объявление в начале файла, имя тега, текст внутри этого тега и его атрибуты.
Элемент (Element / IXMLDOMElement) — это специализированный класс-наследник, который расширяет возможности базового Узла. Элементом в XML называют исключительно полноценную структурную единицу — тег (открывающий, закрывающий или самозакрывающийся).
Сформулируем главное правило, которое необходимо навсегда уяснить для правильного построения архитектуры в своей голове: любой Элемент гарантированно является Узлом, но далеко не каждый Узел является Элементом.
Рассмотрим подробнее небольшой фрагмент XML-кода, чтобы наглядно увидеть, где здесь скрываются узлы, а где элементы. Проанализируем следующую структуру:
<!-- Список бытовой техники -->
<Товар Код="00015">Холодильник</Товар>
Для человеческого глаза здесь видна всего одна строка с товаром и один комментарий. Однако XML-парсер, строящий DOM-дерево в памяти компьютера, разобьет этот фрагмент сразу на пять различных объектов-узлов, и лишь один из них получит статус Элемента:
<!-- Список бытовой техники --> преобразуется в узел с типом Comment. Это узел, но он не является элементом. У него нет имени тега или атрибутов.<Товар ...></Товар> преобразуется в объект с типом Element. Это единственный полноправный Элемент в данном фрагменте. У него есть имя тега (Товар), и только у него мы можем запрашивать атрибуты или вложенную структуру.Код="00015" преобразуется в узел типа Attribute. Он прочно связан с родительским элементом Товар, но в иерархии дерева DOM он позиционируется как отдельный узел.Холодильник преобразуется в узел типа Text. Крайне важно понять: сам элемент Товар непосредственно в себе этот текст не хранит! Текст является вложенным («дочерним») объектом по отношению к элементу. Элемент хранит ссылку на подчиненный текстовый узел.<Товар> парсер также может прочитать и представить в виде служебного текстового узла разметки.Если вам все еще трудно разложить эти понятия «по полочкам», воспользуемся аналогией из мира техники:
Попытка вызвать специфический метод элемента (например, получение атрибута) у текстового узла — это то же самое, что попытка установить мобильное приложение на обычный медный провод. И провод, и смартфон — это устройства (узлы), но приложение может работать только на смартфоне (элементе).
Давайте посмотрим на проблему глазами разработчика 1С 7.7. Когда мы загружаем внешнюю компоненту V7PLUS.DLL и создаем объект XMLParser, мы оперируем интерфейсами Microsoft MSXML. Посмотрим, какими методами обладают эти сущности.
Базовый объект УЗЕЛ (Node) предоставляет методы для навигации по иерархии:
КоличествоПодчиненных() — возвращает общее число вложенных узлов любого типа.ПолучитьПодчиненныйПоНомеру(Индекс) — извлекает дочерний узел по его порядковому номеру.ДобавитьПодчиненный(НовыйУзел) — добавляет узел в дерево.ВыбратьУзлы(ПутьXPath) — возвращает коллекцию узлов, соответствующих запросу.ВыбратьУзел(ПутьXPath) — находит первый узел по запросу.Тип (числовой код типа), ТипСтрока (например, "element", "text", "comment"), Наименование, Значение.Объект ЭЛЕМЕНТ (Element) добавляет к этому списку уникальные функции для работы с атрибутами:
ПолучитьАтрибут(ИмяАтрибута) — считывает значение атрибута тега.УстановитьАтрибут(ИмяАтрибута, Значение) — записывает или изменяет атрибут.УдалитьАтрибут(ИмяАтрибута) — удаляет атрибут из тега.КоличествоАтрибутов() — возвращает количество атрибутов у данного тега.Рассмотрим ситуацию, в которой программисты чаще всего совершают ошибку, путая узел и элемент. Представим, что нам необходимо прочитать значения атрибутов у всех подчиненных элементов внутри некоторого контейнера. Напишем код, который кажется логичным на первый взгляд:
// ОШИБОЧНЫЙ ВАРИАНТ КОДА!
РодительскийУзел = СозданныйДокументXML.ВыбратьУзел("Данные");
Количество = РодительскийУзел.КоличествоПодчиненных();
Для Индекс = 1 По Количество Цикл
ТекущийУзел = РодительскийУзел.ПолучитьПодчиненныйПоНомеру(Индекс);
// Попытка прочитать атрибут
ЗначениеАтрибута = ТекущийУзел.ПолучитьАтрибут("Код");
Сообщить(ЗначениеАтрибута);
КонецЦикла;
При выполнении этого кода программа на определенной итерации цикла с высокой долей вероятности аварийно завершится с ошибкой: «Поле агрегатного объекта не обнаружено (ПолучитьАтрибут)». Почему это происходит?
Дело в том, что метод КоличествоПодчиненных() считает абсолютно все узлы: и теги-элементы, и скрытые текстовые узлы (переводы строк, пробелы оформления), и комментарии. Метод ПолучитьПодчиненныйПоНомеру() послушно возвращает очередной узел. Если нам попался текстовый узел или комментарий, у него физически отсутствует метод ПолучитьАтрибут(), ведь текстовый фрагмент не может содержать атрибутов!
Чтобы избежать подобных ошибок, мы должны применить проверку типа узла перед вызовом специфических методов элемента. Напишем корректный и безопасный вариант кода:
// ПРАВИЛЬНЫЙ И БЕЗОПАСНЫЙ ВАРИАНТ КОДА
РодительскийУзел = СозданныйДокументXML.ВыбратьУзел("Данные");
Количество = РодительскийУзел.КоличествоПодчиненных();
Для Индекс = 1 По Количество Цикл
ТекущийУзел = РодительскийУзел.ПолучитьПодчиненныйПоНомеру(Индекс);
// Проверяем тип узла. Нам нужны только узлы-элементы (тип 1)
Если (ТекущийУзел.Тип = 1) Или (ВРег(ТекущийУзел.ТипСтрока) = "ELEMENT") Тогда
// Теперь мы на 100% уверены, что ТекущийУзел является Элементом
ЗначениеАтрибута = ТекущийУзел.ПолучитьАтрибут("Код");
Сообщить("Успешно прочитан код элемента: " + ЗначениеАтрибута);
Иначе
// Игнорируем текстовые узлы, комментарии и пробелы разметки
Сообщить("Пропущен служебный узел типа: " + ТекущийУзел.ТипСтрока);
КонецЕсли;
КонецЦикла;
Таким образом, понимание разницы между абстрактным «Узлом» и конкретным «Элементом» гарантирует стабильную работу ваших обработок импорта и экспорта данных. Четкое разграничение этих понятий защитит код от случайных сбоев при изменении форматирования XML-файлов (например, при появлении лишних пробелов или комментариев в структуре).