При обработке файлов обмена в формате XML разработчики часто сталкиваются с ситуацией, когда атрибуты тега считываются без проблем, а значение внутри самого тега получить не удается — свойство Значение возвращает пустую строку.
Рассмотрим структуру XML-документа, разберем причину такого поведения потокового парсера ЧтениеXML и выясним, какими способами можно быстро и надежно прочитать как атрибуты, так и текстовое содержимое узлов в платформе 1С:Предприятие.
Проанализируем исходный фрагмент XML:
<prices>
<price type="RRP" currencyId="KZT">109990</price>
<price type="Дилерская цена" currencyId="KZT">85117</price>
</prices>
Объект ЧтениеXML представляет собой последовательный потоковый считыватель (pull parser). Он двигается по документу шаг за шагом с помощью метода Прочитать(). Когда считыватель встречает открывающий тег <price ...>, свойство ТипУзла принимает значение ТипУзлаXML.НачалоЭлемента. На этом шаге доступны только имя тега (Чт.Имя) и его атрибуты.
Текстовые данные (в нашем примере число 85117) не являются частью открывающего тега — они представляют собой отдельный дочерний узел документа с типом ТипУзлаXML.Текст. Чтобы прочитать это число, необходимо продвинуть считыватель на следующий шаг.
Потоковое чтение является самым производительным методом и потребляет минимальное количество оперативной памяти, что критически важно при обработке объемных файлов (каталогов товаров, прайс-листов на сотни мегабайт).
Разберем по шагам, как правильно организовать чтение атрибутов и значений тегов в одном цикле:
ЧтениеXML.ТипУзлаXML.НачалоЭлемента для интересующего нас тега.ПолучитьАтрибут(), так как обращение по числовому индексу ненадежно при изменении порядка атрибутов в файле.ПустойЭлемент), переходим к чтению содержимого до закрывающего тега ТипУзлаXML.КонецЭлемента.ТипУзлаXML.Текст и ТипУзлаXML.СекцияCDATA.Посмотрим на практический пример реализации:
Чт = Новый ЧтениеXML;
Чт.ОткрытьФайл(ИмяФайла);
Пока Чт.Прочитать() Цикл
// Проверяем начало нужного нам тега
Если Чт.ТипУзла = ТипУзлаXML.НачалоЭлемента И Чт.Имя = "price" Тогда
ТипЦены = Чт.ПолучитьАтрибут("type");
Валюта = Чт.ПолучитьАтрибут("currencyId");
Цена = "";
// Если элемент самозакрывающийся (<price />), внутрь него заходить не нужно
Если Не Чт.ПустойЭлемент Тогда
// Читаем внутренние узлы до закрывающего тега </price>
Пока Чт.Прочитать() Цикл
Если Чт.ТипУзла = ТипУзлаXML.Текст Или Чт.ТипУзла = ТипУзлаXML.СекцияCDATA Тогда
Цена = Чт.Значение;
ИначеЕсли Чт.ТипУзла = ТипУзлаXML.КонецЭлемента И Чт.Имя = "price" Тогда
// Выходим из внутреннего цикла при достижении закрывающего тега
Прервать;
КонецЕсли;
КонецЦикла;
КонецЕсли;
Сообщить(СтрШаблон("Тип: %1, Валюта: %2, Цена: %3", ТипЦены, Валюта, Цена));
КонецЕсли;
КонецЦикла;
Чт.Закрыть();
Если размер XML-файла невелик (до нескольких десятков мегабайт) и он целиком помещается в оперативную память, гораздо удобнее использовать объектную модель документа (DOM). Объект ПостроительDOM считывает XML-файл и строит иерархическое дерево объектов, что избавляет от необходимости вручную контролировать указатель чтения.
Рассмотрим, как получить элементы по имени тега с помощью свойства ТекстовоеСодержимое:
Чтение = Новый ЧтениеXML;
Чтение.ОткрытьФайл(ИмяФайла);
Построитель = Новый ПостроительDOM;
ДокументDOM = Построитель.Прочитать(Чтение);
Чтение.Закрыть();
// Получаем коллекцию всех узлов с именем price
СписокЦен = ДокументDOM.ПолучитьЭлементыПоИмени("price");
Для Каждого ЭлементЦены Из СписокЦен Цикл
ТипЦены = ЭлементЦены.ПолучитьАтрибут("type");
Валюта = ЭлементЦены.ПолучитьАтрибут("currencyId");
Цена = ЭлементЦены.ТекстовоеСодержимое;
Сообщить(СтрШаблон("Тип: %1, Валюта: %2, Цена: %3", ТипЦены, Валюта, Цена));
КонецЦикла;
В случаях, когда из XML-документа требуется извлечь конкретное значение без перебора всех элементов (например, только значение для Дилерской цены), целесообразно использовать язык запросов XPath поверх загруженного ДокументDOM.
Посмотрим на пример выборки конкретного узла:
Чтение = Новый ЧтениеXML;
Чтение.ОткрытьФайл(ИмяФайла);
Построитель = Новый ПостроительDOM;
ДокументDOM = Построитель.Прочитать(Чтение);
Чтение.Закрыть();
// Вычисляем XPath-выражение с фильтром по атрибуту
РезультатXPath = ДокументDOM.ВычислитьВыражениеXPath("/prices/price[@type='Дилерская цена']");
УзелЦены = РезультатXPath.ПолучитьСледующий();
Если УзелЦены <> Неопределено Тогда
ДилерскаяЦена = УзелЦены.ТекстовоеСодержимое;
Сообщить("Дилерская цена: " + ДилерскаяЦена);
КонецЕсли;
Подведем итоги и определим, какой подход лучше выбрать под конкретную задачу: