Работа с произвольными XML-схемами в 1С:Предприятие 8


В противоположность предыдущим статьям, где были представлены примеры выгрузки в файл XML объектов конфигурации и, соответственно, эксплуатировались уже заложенные в 1С: Предприятие 8 схемы данных. В данной статье пойдет речь и будет представлен пример формирования методами 1С: Предприятие 8 XML-файла построенного, вообще говоря, по произвольной схеме данных. Предлагаемый в данной статье пример входит в состав прототипа обработки рассылки почтовых сообщений сотрудникам компании с расчетными листами заработной платы в качестве приложения. Поэтому, «www.spaming.org» в качестве URI пространства имен пусть вас не смущает. По мне, так любая массовая рассылка, пусть и полезная, с точки зрения отправителя, является спамом.

Настройка схемы данных XML в 1С: Предприятие 8

Конфигуратор 1С: Предприятие 8 снабжен редактором XML-схем файлов данных. Чтобы его открыть, нужно перейти в режим редактирования или создать новый пакет XDTO (XML Data Transfer Objects). Каждый пакет имеет уникальный в пределах конфигурации URI пространство имен, в данном случае «http://www.spaming.org», директивы импорта, если схема наследует объекты других схем и, собственно, описание самих типов объектов схемы.

Рис. 1 Схема в редакторе XDTO пакетов 1С: Предприятие 8.

В редакторе можно задавать свойства каждого из узлов схемы, в итоге, текстовое описание (XSD-файл) вышеуказанной схемы выглядит следующим образом:

<xs:schema xmlns:ns1="http://v8.1c.ru/8.1/data/core" xmlns:ns2="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:tns="http://www.spaming.org" xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.spaming.org" attributeFormDefault="unqualified" elementFormDefault="qualified">
    <xs:import namespace="http://v8.1c.ru/8.1/data/core"/>
    <xs:import namespace="http://v8.1c.ru/8.1/data/enterprise/current-config"/>
    <xs:complexType name="ИнтернетПочтовыйПрофиль">
        <xs:sequence>
            <xs:element name="АдресСервераSMTP" type="xs:string" minOccurs="0"/>
            <xs:element name="АдресСервераPOP3" type="xs:string" minOccurs="0"/>
            <xs:element name="АдресСервераIMAP" type="xs:string" minOccurs="0"/>
            <xs:element name="ПортSMTP" type="xs:integer" minOccurs="0"/>
            <xs:element name="ПортPOP3" type="xs:integer" minOccurs="0"/>
            <xs:element name="ПортIMAP" type="xs:integer" minOccurs="0"/>
            <xs:element name="Пользователь" type="xs:string" minOccurs="0"/>
            <xs:element name="ПользовательIMAP" type="xs:string" minOccurs="0"/>
            <xs:element name="ПользовательSMTP" type="xs:string" minOccurs="0"/>
            <xs:element name="Пароль" type="xs:string" minOccurs="0"/>
            <xs:element name="ПарольIMAP" type="xs:string" minOccurs="0"/>
            <xs:element name="ПарольSMTP" type="xs:string" minOccurs="0"/>
            <xs:element name="АутентификацияSMTP" type="ns2:EnumRef.СпособыSMTPАутентификации" minOccurs="0"/>
            <xs:element name="ВремяОжидания" type="xs:integer" minOccurs="0"/>
            <xs:element name="ИспользоватьSSLSMTP" type="xs:boolean" minOccurs="0"/>
            <xs:element name="ИспользоватьSSLPOP3" type="xs:boolean" minOccurs="0"/>
            <xs:element name="ИспользоватьSSLIMAP" type="xs:boolean" minOccurs="0"/>
        </xs:sequence>
    </xs:complexType>
    <xs:complexType name="Рассылка">
        <xs:sequence>
            <xs:element name="Идентификатор" type="xs:string"/>
            <xs:element name="Сообщения">
                <xs:complexType>
                    <xs:sequence>
                        <xs:element name="Сообщение" type="tns:Сообщение" minOccurs="0" maxOccurs="unbounded"/>
                    </xs:sequence>
                </xs:complexType>
            </xs:element>
        </xs:sequence>
    </xs:complexType>
    <xs:complexType name="Сообщение">
        <xs:sequence>
            <xs:element name="Отправитель" type="xs:string"/>
            <xs:element name="ИмяОтправителя" type="xs:string"/>
            <xs:element name="Тема" type="xs:string"/>
            <xs:element name="Текст" type="xs:string"/>
            <xs:element name="Получатели" type="ns1:Array"/>
            <xs:element name="Вложения" type="ns1:Array"/>
            <xs:element name="Идентификатор" type="xs:string"/>
            <xs:element name="Заголовок" type="xs:string"/>
        </xs:sequence>
    </xs:complexType>
    <xs:complexType name="ФайлДанных">
        <xs:sequence>
            <xs:element name="ИнтернетПочтовыйПрофиль" type="tns:ИнтернетПочтовыйПрофиль"minOccurs="0" maxOccurs="1"/>
            <xs:element name="Рассылки""minOccurs="0" maxOccurs="1">
                <xs:complexType>
                    <xs:sequence>
                        <xs:element name="Рассылка" type="tns:Рассылка" minOccurs="0" maxOccurs="unbounded"/>
                    </xs:sequence>
                </xs:complexType>
            </xs:element>
        </xs:sequence>
    </xs:complexType>
</xs:schema>

Импорт XML-схемы в конфигурацию 1С: Предприятие 8

Если мы создали XML-схему данных с помощью конфигуратора, тогда, при сохранении изменений конфигурации, схема данных уже будет импортирована и доступна в любом месте программы:

ИнтернетПочтовыйПрофильТип = ФабрикаXDTO.Тип("http://www.spaming.org", "ИнтернетПочтовыйПрофиль");

ФабрикаXDTO — предопределенный объект конфигурации, набор схем которого содержит все схемы заданные в пакетах XDTO, плюс некоторые стандартные. Вместо этого объекта, 1С: Предприятие позволяет нам создать собственную фабрику и импортировать туда только те схемы данных, которые будем использовать:

// Производим чтение XML-схемы данных, записанной в XSD-файле в объект "ДокументDOM"
ЧтениеХМЛ = Новый ЧтениеXML;
ЧтениеХМЛ.ОткрытьФайл(ИмяФайла);
        
ПостроительДОМ = Новый ПостроительDOM;
ДокументDOM = ПостроительДОМ.Прочитать(ЧтениеХМЛ);
 
// Преобразование описания XML-схемы, представленной в объекте "ДокументDOM" в объект "СхемаXML"
ПостроительСхемXML = Новый ПостроительСхемXML;
СхемаXML = ПостроительСхемXML.СоздатьСхемуXML(ДокументDOM);
 
// Формирование набора XML-схем, на базе которого будет построена новая фабрика XDTO    
НаборСхемXML = Новый НаборСхемXML;
НаборСхемXML.Добавить(СхемаXML);
 
// Импорт "зависимостей" из стандартной Фабрики XDTO для нашей новой фабрики в соответствии с директивами импорта    
НаборСхемЭкспорт = ФабрикаXDTO.ЭкспортСхемыXML("http://v8.1c.ru/8.1/data/core");
Для Каждого СхемаXML Из НаборСхемЭкспорт Цикл
    НаборСхемXML.Добавить(СхемаXML);
КонецЦикла;    
    
НаборСхемЭкспорт = ФабрикаXDTO.ЭкспортСхемыXML("http://v8.1c.ru/8.1/data/enterprise/current-config");
Для Каждого СхемаXML Из НаборСхемЭкспорт Цикл
    НаборСхемXML.Добавить(СхемаXML);
КонецЦикла;    
 
// Собственно, создание фабрики    
Фабрика = Новый ФабрикаXDTO(НаборСхемXML);
 
// Проверка работы механизма
ИнтернетПочтовыйПрофильТип = Фабрика.Тип("http://www.spaming.org""ИнтернетПочтовыйПрофиль"); 

В общем случае, если нет необходимости подгружать XML-схему данных динамически, более целесообразно в плане простоты доступа, хранить эту схему непосредственно в конфигурации. Чтобы избежать необходимости анализа зависимостей импортируемой XML-схемы, можно перегружать все схемы из предопределенной фабрики XDTO. Добавить каким то образом XML-схему непосредственно в предопределенную фабрику XDTO не получится. Только через конфигурирование нового пакета XDTO с сохранением конфигурации.

Создание XML документа подчиненного заданной XML-схеме данных

Для простоты примера, будем предполагать, что XML-схема задана в пакетах XDTO. Тогда нижеуказанный код сформирует пример файла:

// Объект "ЗаписьXML" для формирования конечного XML-файла в строку
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.УстановитьСтроку();
 
// Обращение по имени типа, при создании типа объекта "ФайлДанных", и URI пространства имен
ФайлДанныхТип = ФабрикаXDTO.Тип("http://www.spaming.org", "ФайлДанных"); 
ФайлДанных = ФабрикаXDTO.Создать(ФайлДанныхТип);
 
// Создание значения свойства по типу свойства, при этой, свойство само является объектом
ФайлДанных.Рассылки = ФабрикаXDTO.Создать(ФайлДанныхТип.Свойства.Получить("Рассылки").Тип);
    
РассылкаТип = ФабрикаXDTO.Тип("http://www.spaming.org", "Рассылка"); 
Рассылка = ФабрикаXDTO.Создать(РассылкаТип);
 
// Создание значения свойства по типу свойства, при этой, свойство само является примитивным типом - значением
Рассылка.Идентификатор = ФабрикаXDTO.Создать(РассылкаТип.Свойства.Получить("Идентификатор").Тип, "082017");
 
// Добавление элемента в список     
ФайлДанных.Рассылки.Рассылка.Добавить(Рассылка);
 
// Запись результата в XML-файл
ФабрикаXDTO.ЗаписатьXML(ЗаписьXML, ФайлДанных);
 
// Вывод XML-файла в строку
Сообщить(ЗаписьXML.Закрыть());

Результатом которого будет следующее:

<ФайлДанных xmlns="http://www.spaming.org" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <Рассылки>
        <Рассылка>
            <Идентификатор>082017</Идентификатор>
        </Рассылка>
    </Рассылки>
</ФайлДанных>

Проверка XML-файла на соответствие схеме

Проверить XML-файл на соответствие XML-схеме очень просто:

ЧтениеХМЛ = Новый ЧтениеXML;
ЧтениеХМЛ.ОткрытьФайл(БазаРассылки);
        
Попытка
 
    // Попытка чтения XDTO-объекта из XML-файла
    ФайлДанных = ФабрикаXDTO.ПрочитатьXML(ЧтениеХМЛ, ФабрикаXDTO.Тип("http://www.spaming.org", "ФайлДанных"));
Исключение
 
    // Текст ошибки несоответствия формата XML-файла описанию XML-схемы
    Сообщить(ОписаниеОшибки());
КонецПопытки;

В момент, когда фабрика производит чтение XML в объект XDTO, производится проверка файла на соответствие XML-схеме и конвертация значений и объектов, содержащихся в файле, в объекты XDTO. Если в описании некоторых типов объектов присутствуют типы, определенные для платформы или конфигурации 1С, тогда в момент чтения файла фабрикой XDTO происходит конвертация представленного в XML описания объекта в объект 1С.

Редактирование XML-файла в соответствии с заданной схемой данных

Редактирование XML-файла осуществляется тем же путем и так же просто, как и создание нового, с той лишь разницей, что процедуры изменения данных XML-файла должны придерживаться принципов целостности и безопасности данных. Попробуем добавить новый узел «Рассылка» с идентификатором «092017», предварительно проверив, что этим мы не нарушим уникальность элементов списка «Рассылки».

// Чтение текущих данных XML-файла
ЧтениеХМЛ = Новый ЧтениеXML;
ЧтениеХМЛ.ОткрытьФайл(БазаРассылки);
    
Попытка
 
    // Создание объекта XDTO, чтение/конвертация данных всех его узлов
    ФайлДанных = ФабрикаXDTO.ПрочитатьXML(ЧтениеХМЛ, ФабрикаXDTO.Тип("http://www.spaming.org", "ФайлДанных"));            
Исключение
 
    // Обработка ошибок
    Сообщить(ОписаниеОшибки());    
КонецПопытки;
 
// Флаг-индикатор того, что запись искомая существует                                                     
Найдена = Ложь;
 
ИдентификаторРассылки = "092017";
    
Для Каждого Рассылка Из ФайлДанных.Рассылки.Рассылка Цикл        
    Если Рассылка.Идентификатор = ИдентификаторРассылки Тогда
 
        // Обработка найденной записи
        Найдена = Истина;
    КонецЕсли;         
КонецЦикла;    
 
Если НЕ Найдена Тогда
 
    // Создание новой рассылки...   
    РассылкаТип = ФабрикаXDTO.Тип("http://www.spaming.org", "Рассылка"); 
    Рассылка = ФабрикаXDTO.Создать(РассылкаТип);
    Рассылка.Идентификатор = ФабрикаXDTO.Создать(РассылкаТип.Свойства.Получить("Идентификатор").Тип, ИдентификаторРассылки);     
 
    // ... добавление ее в список
    ФайлДанных.Рассылки.Рассылка.Добавить(Рассылка);
КонецЕсли;
 
// Запись результирующего XML-файла   
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.УстановитьСтроку();
    
ФабрикаXDTO.ЗаписатьXML(ЗаписьXML, ФайлДанных);
Сообщить(ЗаписьXML.Закрыть());

Это был вариант с перебором элементов списка и, в данном примере, его применение вполне оправданно. В общем же случае, думаю, без запросов XPath нам не обойтись и ниже представлен пример, как действовать в данном случае.

// Чтение текущих данных XML-файла, на этот раз в  DOM
 
ЧтениеХМЛ = Новый ЧтениеXML;
ЧтениеХМЛ.ОткрытьФайл(БазаРассылки);
    
ПостроительДОМ = Новый ПостроительDOM;
ДокументDOM = ПостроительДОМ.Прочитать(ЧтениеХМЛ);
 
// Временный объект для построения узлов-элементов DOM    
ЗаписьУзловDOM = Новый ЗаписьУзловDOM;
ЗаписьУзловDOM.Открыть(ДокументDOM.ДокументВладелец);
 
// Родительский узел списка рассылок    
УзелРассылки = ДокументDOM.ЭлементДокумента.ПолучитьЭлементыПоИмени("Рассылки")[0];
 
// Организация поиска узла методом XPath-запроса    
ИдентификаторРассылки = "092017";
    
Разыменователь = Новый РазыменовательПространствИменDOM("ns", "http://www.spaming.org");
        
РезультатЗапроса = ДокументDOM.ВычислитьВыражениеXPath("//ns:Идентификатор[.='" + ИдентификаторРассылки + "']", 
    УзелРассылки, Разыменователь, ТипРезультатаDOMXPath.ЛюбойНеупорядоченныйУзел);
        
Узел = РезультатЗапроса.ПолучитьСледующий();    
 
// Анализ результатов поиска    
Если Узел = Неопределено Тогда
 
    // Создание новой рассылки
    РассылкаТип = ФабрикаXDTO.Тип("http://www.spaming.org", "Рассылка"); 
                
    Рассылка = ФабрикаXDTO.Создать(РассылкаТип);
                
    Рассылка.Идентификатор = ФабрикаXDTO.Создать(РассылкаТип
         .Свойства.Получить("Идентификатор").Тип, ИдентификаторРассылки);
                
    ФабрикаXDTO.ЗаписатьXML(ЗаписьУзловDOM, Рассылка);
                
    УзелРассылки.ДобавитьДочерний(ЗаписьУзловDOM.УзелDOM);    
КонецЕсли;    
 
// Запись результирующего XML-файла      
ЗаписьXMLСтрока = Новый ЗаписьXML;
ЗаписьXMLСтрока.УстановитьСтроку("UTF-8");
    
ЗаписьDOM = Новый ЗаписьDOM;
ЗаписьDOM.Записать(ДокументDOM, ЗаписьXMLСтрока);
    
Сообщить(ЗаписьXMLСтрока.Закрыть());

И в том и в другом случае, результатом будет следующий документ:

<?xml version="1.0" encoding="UTF-8"?>
<ФайлДанных xmlns="http://www.spaming.org" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <Рассылки>
        <Рассылка>
            <Идентификатор>082017</Идентификатор>
        </Рассылка>
        <Рассылка>
            <Идентификатор>092017</Идентификатор>
        </Рассылка>
    </Рассылки>
</ФайлДанных>

В итоге, как мы уже имели возможность при помощи представленных в статье примеров убедиться, в «лице» XDTO мы имеем достаточно мощный механизм, позволяющий организовать не только двусторонний обмен между базами, вообще говоря, различной конфигурации, но и, скажем, работу с небольшой базой данных в формате XML для нужд каких либо обработок.

Представленный набор примеров покрывает, быть может, 10% всех возможностей XDTO и, конечно, не является описанием единственно правильного взаимодействия с этими объектами. Тем не менее, это вполне рабочий набор примеров. При работе с XML, я сознательно избегаю таких методов, как: «ЗаписатьНачалоАтрибута» или «ЗаписатьНачалоЭлемента». Я не считаю это хорошей практикой использования XML. На сайте есть статья на эту тему. Вместо этого, я стараюсь «смотреть» на XML со стороны DOM (Data Object Model) и XDTO (XML Data Transfer Objects). Потому, как, с учетом наличия в 1С: Предприятие 8 поддержки XSL и XPath, это, безусловно, более перспективное занятие.