Как правильно прочитать блок CDATA при разборе XML в 1С:Предприятие

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При работе с внешними интеграциями программисты 1С часто сталкиваются с необходимостью разбора файлов XML. Одной из неочевидных проблем является чтение секций CDATA (Character Data). Разработчики часто путаются, почему стандартный код обходит эти секции стороной или возвращает пустые строки вместо ожидаемого содержимого. В этой статье мы подробно разберем, как устроен процесс чтения XML в 1С, почему возникает эта проблема и рассмотрим четыре эффективных способа ее решения.

Выясняем причину проблемы: как 1С видит XML-документ

Для начала проанализируем, как работает объект ЧтениеXML. Это последовательный (потоковый) парсер. Он не загружает весь документ в память, а двигается по нему «шаг за шагом» при каждом вызове метода Прочитать(). На каждом шаге меняется свойство ТипУзла.

Представим, что у нас есть следующий фрагмент XML-кода:

<description><![CDATA[Важная информация о контрагенте]]></description>

Когда мы последовательно читаем этот фрагмент в цикле, парсер делает три отдельных шага:

  1. Шаг 1: Парсер встает на открывающий тег <description>. Свойство ТипУзла принимает значение ТипУзлаXML.НачалоЭлемента. Свойство Имя равно "description", а свойство Значение в этот момент пустое!
  2. Шаг 2: Парсер переходит внутрь тега и видит блок CDATA. Свойство ТипУзла теперь равно ТипУзлаXML.СекцияCDATA (или ТипУзлаXML.Текст в зависимости от настроек). Имя узла здесь пустое, зато в свойстве Значение находится наш текст: "Важная информация о контрагенте".
  3. Шаг 3: Парсер переходит на закрывающий тег </description>. Свойство ТипУзла становится равным ТипУзлаXML.КонецЭлемента.

Типичная ошибка разработчика заключается в том, что он пытается получить значение сразу же, как только обнаружил узел НачалоЭлемента с именем "description". Но в этой точке парсер еще физически не дошел до текста! Именно поэтому код автора темы «пролетал» мимо данных.

Теперь, когда логика работы парсера понятна, разберем способы решения этой задачи.

Способ 1. Потоковое чтение с принудительным шагом вперед

Если обрабатываемый XML-файл имеет огромный размер (сотни мегабайт или гигабайты), нам подходит только потоковое чтение через ЧтениеXML, так как оно практически не потребляет оперативную память. Чтобы прочитать содержимое CDATA, при обнаружении нужного нам начала элемента мы должны принудительно вызвать метод Прочитать() еще раз.

Рассмотрим пример реализации этого подхода:


ЧтениеXMLСписокКонтрагентов = Новый ЧтениеXML;
ЧтениеXMLСписокКонтрагентов.ОткрытьФайл(ПутьКФайлу);

Пока ЧтениеXMLСписокКонтрагентов.Прочитать() Цикл
    
    // Проверяем, что мы находимся на начале нужного нам элемента
    Если ЧтениеXMLСписокКонтрагентов.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
        
        Если ЧтениеXMLСписокКонтрагентов.Имя = "description" Тогда
            
            // Делаем принудительный шаг вперед, чтобы спозиционироваться на содержимом тега
            Если ЧтениеXMLСписокКонтрагентов.Прочитать() Тогда
                
                // Проверяем, является ли следующий узел секцией CDATA или обычным текстом
                Если ЧтениеXMLСписокКонтрагентов.ТипУзла = ТипУзлаXML.СекцияCDATA 
                   Или ЧтениеXMLСписокКонтрагентов.ТипУзла = ТипУзлаXML.Текст Тогда
                    
                    ОписаниеКонтрагента = ЧтениеXMLСписокКонтрагентов.Значение;
                    Сообщить("Найдено описание: " + ОписаниеКонтрагента);
                    
                КонецЕсли;
                
            КонецЕсли;
            
        КонецЕсли;
        
    КонецЕсли;
    
КонецЦикла;

ЧтениеXMLСписокКонтрагентов.Закрыть();

Этот способ надежен и быстр, однако требует написания громоздких вложенных условий.

Способ 2. Использование параметров чтения (СекцииCDATAКакТекст)

Мы можем значительно упростить логику обхода узлов в цикле, если укажем парсеру интерпретировать секции CDATA как обычный текст. По умолчанию свойство СекцииCDATAКакТекст объекта ПараметрыЧтенияXML установлено в Ложь. Давайте включим его.

Проанализируем следующий пример:


// Настраиваем параметры чтения XML
ПараметрыЧтения = Новый ПараметрыЧтенияXML;
ПараметрыЧтения.СекцииCDATAКакТекст = Истина;

ЧтениеXMLСписокКонтрагентов = Новый ЧтениеXML;
// Открываем файл с передачей созданных параметров
ЧтениеXMLСписокКонтрагентов.ОткрытьФайл(ПутьКФайлу, ПараметрыЧтения);

ТекущееИмяУзла = "";
ОписаниеКонтрагента = "";

Пока ЧтениеXMLСписокКонтрагентов.Прочитать() Цикл
    
    ТипУзла = ЧтениеXMLСписокКонтрагентов.ТипУзла;
    
    Если ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
        
        // Запоминаем имя открывающегося тега
        ТекущееИмяУзла = ЧтениеXMLСписокКонтрагентов.Имя;
        
    ИначеЕсли ТипУзла = ТипУзлаXML.Текст Тогда
        
        // Благодаря параметрам чтения, блоки CDATA теперь тоже имеют тип Текст
        Если ТекущееИмяУзла = "description" Тогда
            ОписаниеКонтрагента = ЧтениеXMLСписокКонтрагентов.Value; // Или .Значение
            Сообщить("Получено значение: " + ОписаниеКонтрагента);
        КонецЕсли;
        
    ИначеЕсли ТипУзла = ТипУзлаXML.КонецЭлемента Тогда
        
        // Сбрасываем имя узла при закрытии тега
        ТекущееИмяУзла = "";
        
    КонецЕсли;
    
КонецЦикла;

ЧтениеXMLСписокКонтрагентов.Закрыть();

Использование свойства СекцииCDATAКакТекст избавляет нас от необходимости отдельно обрабатывать тип узла ТипУзлаXML.СекцияCDATA, сводя весь анализ содержимого к стандартному типу Текст.

Способ 3. Использование объектной модели DOM

Если ваш XML-файл имеет небольшой или средний размер (до 50-100 МБ), то самым удобным и современным решением будет использование объектной модели документов (DOM). Объект ПостроительDOM автоматически считывает весь файл в дерево объектов в памяти. При этом блоки CDATA и текстовые узлы внутри элементов прозрачно объединяются.

Разберем по шагам, как прочитать значение тега через DOM:


ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.ОткрытьФайл(ПутьКФайлу);

// Создаем построитель DOM и считываем весь документ в память
ПостроительDOM = Новый ПостроительDOM;
ДокументDOM = ПостроительDOM.Прочитать(ЧтениеXML);

// Ищем все элементы с именем "description"
СписокЭлементов = ДокументDOM.ПолучитьЭлементыПоИмени("description");

Для Каждого Элемент Из СписокЭлементов Цикл
    // Свойство ТекстовоеСодержимое автоматически соберет весь текст, включая CDATA
    ОписаниеТекст = Элемент.ТекстовоеСодержимое;
    Сообщить("Описание из DOM: " + ОписаниеТекст);
КонецЦикла;

ЧтениеXML.Закрыть();

Этот метод чрезвычайно лаконичен. Нам вообще не нужно заботиться о циклах чтения, типах узлов и флагах конфигурации парсера — платформа 1С делает всю грязную работу за нас.

Способ 4. Чтение через XDTO

Если структура вашего XML-файла строго определена и известна заранее, наилучшим архитектурным решением будет использование механизма XDTO. Фабрика XDTO позволяет преобразовать XML-документ в объектную модель 1С одной строчкой кода. Блоки CDATA при этом автоматически конвертируются в обычные строковые свойства объектов 1С.

Посмотрим на пример использования XDTO:


ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.ОткрытьФайл(ПутьКФайлу);

Попытка
    // Читаем XML в универсальный объект XDTO (без привязки к жесткой схеме пакета)
    ДанныеXML = ФабрикаXDTO.ПрочитатьXML(ЧтениеXML);
    
    // Если структура документа плоская или простая, текст из CDATA доступен как обычное свойство
    Если ДанныеXML.Свойства().Получить("description") <> Неопределено Тогда
        Описание = ДанныеXML.description;
        Сообщить("Описание через XDTO: " + Описание);
    КонецЕсли;
Исключение
    Сообщить("Ошибка чтения XDTO: " + ОписаниеОшибки());
КонецПопытки;

ЧтениеXML.Закрыть();

Использование фабрики XDTO — это наиболее профессиональный подход к интеграции, так как он абстрагирует разработчика от низкоуровневой работы с тегами и парсерами, переводя задачу в плоскость работы с объектами данных.

Резюме

Мы подробно разобрали внутреннее устройство парсера XML в 1С и выяснили, почему возникают сложности с чтением блоков CDATA. Выбор конкретного метода решения зависит от ваших задач:

← На главную