1С магия XDTO-пакетов на примере интеграций с ГИС ЖКХ

Есть очень много статей о том, как работать с XSL/XSD из 1С, но все они в стиле: возьмем нашу XSD схему (простую и удбоную) или наш web-сервис и смотрите, как все легко экспортировать или импортировать. А что делать, если нам дали пачку XSD-схем со сложным взаимосвязями и изменять мы них не можем, а работать и поддерживать актуальность схем надо?

Сразу скажу, вопросы шифрования/подписи по ГОСТУ при работе с ГИС ЖКХ за рамками этой статьи и на хабре уже освещались. Хотя без подписей запросы выполнить не удастся.

image
Начнем с простого — скачаем пакет форматов по интеграционному взаимодействию с ГИС ЖКХ, импортируем все xsd схемы из пакета интеграций, наведем порядок переименуем все как нам удобно. В итоге получим как показано на картинке:

Ну, а теперь приступим к магии. Попробуем запросить данные из справочника организаций по ОРГН. Это подсистема organizations-registry-common метод exportOrgRegist.

В hcs-organizations-registry-common-service.wsdl указано:

Спецификация из hcs-organizations-registry-common-service.wsdl
...

  


  

...

  


...


  экспорт сведений об организациях
  
  
  


...


  
    
      
      
    
    
      
      
    
    
      
    



Надо собрать SOAP пакет из заголовка ISRequestHeader, тела exportOrgRegistryRequest. Посмотрим их в xsd схемах спецификаций по интеграций.
hcs-base.xsd
...
  
    
      Заголовок запроса
    
     
...
   
      
         Базовый тип заголовка
      
   
      
         
            Дата отправки пакета
         
      
      
         
            Идентификатор сообщения
         
      
      
       
...
   
      
         GUID-тип.
      
      
         
      
   
...

hcs-organizations-base.xsd
...
   
      
         ОГРН
      
   
   
      
         
      
   
...

hcs-organizations-registry-common-types.xsd
...

  
    
      Экспорт сведений из реестра организаций
    
    
      
        
          
            
              
                Критерий поиска организаций.
              
              
                
                  
                    
                      
                        Поиск по реквизитам.
                      
                      
                      
                        
                        
                      
                      
                    
                    
                    
                  
                  
                    
                      Поиск среди организаций, имеющих личных кабинет
                    
                  
                
              
            
            
              
                Время последнего изменения (от)
              
            
          
          
        
      
    
  
  
    
      
        
          
            
            
              
                Найденная организация.
              
            
          
          
        
      
    
  
  
    
      
      
        
          Версия организации в реестре организаций
        
        
          
            
            
              
                Время последнего изменения
              
            
            
              
                Признак актуальности записи
              
            
            
              
                
                  Юридическое лицо
                
              
              
                
                  Обособленное подразделение
                
                
                  
                    
                      
                        
                          
                            Статус версии 
                          
                          
                            
                          
                        
                        
                          
                            Информация о головной организации
                          
                          
                            
                              
                            
                          
                        
                      
                    
                  
                
              
              
                
                  Индивидуальный предприниматель
                
              
              
                
                  ФПИЮЛ (Филиал или представительство иностранного юридического лица)
                
              
            
            
              
                Статус:
(P)UBLISHED - опубликована в одном из документов в рамках раскрытия
              
              
                
                  
                
              
            
          
        
      
      
      
        
          Полномочие организации (НСИ №20)
        
      
      
        
          Зарегистрирована в ГИС ЖКХ
        
      
    
  
...

Ну приступим, откроем нужные нам пакеты XDTO. Оказывается, нужные сущности являются не типами, а свойствами, как с этим работать в документации на XDTO в статьях, которые я находил, не описано, поэтому воспользуемся урокам магии:
  • Создадим объекты из свойств
  • Создадим объекты из внутренних типов свойств или тип объектов
  • Создадим объекты из Типов значений

image

Начнем с тела exportOrgRegistryRequest.

&НаСервереБезКонтекста
Функция ПолучитьДанныеОрганизацияПоОГРН(ОГРН)
  //Получим нужные пакеты
  ПакетOrgRegCom = ФабрикаXDTO.Пакеты.Получить("http://dom.gosuslugi.ru/schema/integration/organizations-registry-common/");
  ПакетOrgBase = ФабрикаXDTO.Пакеты.Получить("http://dom.gosuslugi.ru/schema/integration/organizations-base/");

  //Магия:выйдем на нужный тип через свойство, этот метод нам очень часто пригодится:
  СвойствоXDTO = ПакетOrgRegCom.КорневыеСвойства.Получить("exportOrgRegistryRequest");
  //Создадим сам объект для тела запроса
  ОбъектXDTO = ФабрикаXDTO.Создать(СвойствоXDTO.Тип);
  //признак что это элемент надо подписать сертификатом
  ОбъектXDTO.Id = "signed-data-container";
  //требования стандарта, импорт XDTO не обрабатывает аттрибут fixed 
  ОбъектXDTO.version = "10.0.2.1";

  //критерий поиска
  //Магия:опять выйдем на строенный тип, через свойства объекта
  SearchCriteriaЗапись = ФабрикаXDTO.Создать(ОбъектXDTO.SearchCriteria.ВладеющееСвойство.Тип);
	
  //огрн надо ставить через типизированное значение
  OGRN = ФабрикаXDTO.Создать(ПакетOrgBase.КорневыеСвойства.Получить("OGRN").Тип, Строка(ОГРН));
  SearchCriteriaЗапись.OGRN = OGRN;
  ОбъектXDTO.SearchCriteria.Добавить(SearchCriteriaЗапись);
	
  //сохраним тело
  Запрос = Новый Структура("ОбъектXDTO,СвойствоXDTO,Имя", ОбъектXDTO, СвойствоXDTO);
  //сохраним тип ответа, нам по надобиться для десериализаций ответа
  ОтветСвойствоXDTOResult = ПакетOrgRegCom.КорневыеСвойства.Получить("exportOrgRegistryResult");
	
  Возврат СформироватьXMLЗапрос(Новый УникальныйИдентификатор, Запрос, ОтветСвойствоXDTOResult );
КонецФункции

Напишем функцию для сбора XML-запроса:
&НаСервереБезКонтекста
Функция СформироватьXMLЗапрос(GuidЗаголовка, ОтправкаXDTO, ОтветXDTO)
  ПакетBase = ФабрикаXDTO.Пакеты.Получить("http://dom.gosuslugi.ru/schema/integration/base/");
  ПространствоИменSOAP = "http://schemas.xmlsoap.org/soap/envelope/";
  //XML файл	
  ЗаписьXML = Новый ЗаписьXML;
  ПараметрыЗаписиXML = Новый ПараметрыЗаписиXML("UTF-8");
  ЗаписьXML.УстановитьСтроку(ПараметрыЗаписиXML);
  ЗаписьXML.ЗаписатьОбъявлениеXML();
    ЗаписьXML.ЗаписатьНачалоЭлемента("Envelope", ПространствоИменSOAP);
    ЗаписьXML.ЗаписатьСоответствиеПространстваИмен("soap", ПространствоИменSOAP);
    ЗаписьXML.ЗаписатьСоответствиеПространстваИмен("xsi", "http://www.w3.org/2001/XMLSchema-instance");
    ЗаписьXML.ЗаписатьСоответствиеПространстваИмен("xs", "http://www.w3.org/2001/XMLSchema");
		
    ЗаписьXML.ЗаписатьСоответствиеПространстваИмен("base", "http://dom.gosuslugi.ru/schema/integration/base/");
    ЗаписьXML.ЗаписатьСоответствиеПространстваИмен("organizations-registry-base", "http://dom.gosuslugi.ru/schema/integration/organizations-registry-base/"); 
    ЗаписьXML.ЗаписатьСоответствиеПространстваИмен("ns", "http://www.w3.org/2000/09/xmldsig#");
    ЗаписьXML.ЗаписатьСоответствиеПространстваИмен("ro", "http://dom.gosuslugi.ru/schema/integration/organizations-registry-common/");
		
     //Заголовок ISRequestHeader
    ЗаписьXML.ЗаписатьНачалоЭлемента("Header", ПространствоИменSOAP);
      //снова метод через свойства
      ЗаголовокСвойствоXDTO = ПакетBase.КорневыеСвойства.Получить("ISRequestHeader");
      ЗаголовокЗапроса = ФабрикаXDTO.Создать(ЗаголовокСвойствоXDTO.Тип);
      ЗаголовокЗапроса.Date = ТекущаяДата();
      ЗаголовокЗапроса.MessageGUID = ФабрикаXDTO.Создать(ФабрикаXDTO.Тип("http://dom.gosuslugi.ru/schema/integration/base/", "GUIDType"), Строка(GuidЗаголовка));
      ФабрикаXDTO.ЗаписатьXML(ЗаписьXML, ЗаголовокЗапроса, ЗаголовокСвойствоXDTO.ЛокальноеИмя);
    ЗаписьXML.ЗаписатьКонецЭлемента();	
    //Тело у нас будет подготовленый exportOrgRegistryRequest
    ЗаписьXML.ЗаписатьНачалоЭлемента("Body", ПространствоИменSOAP);
      ФабрикаXDTO.ЗаписатьXML(ЗаписьXML, ОтправкаXDTO.ОбъектXDTO, ОтправкаXDTO.СвойствоXDTO.ЛокальноеИмя, ОтправкаXDTO.СвойствоXDTO.URIПространстваИмен);
      ЗаписьXML.ЗаписатьКонецЭлемента();
  ЗаписьXML.ЗаписатьКонецЭлемента();
  //отдаем результат для отправки
  XMLЗапрос = Новый Структура();
  XMLЗапрос.Вставить("XMLTекст", ЗаписьXML.Закрыть());
  XMLЗапрос.Вставить("ОтветXDTO", ОтветXDTO);
  Возврат XMLЗапрос;
КонецФункции

В итоге получим запрос:
XML запрос exportOrgRegistryRequest


  
    
      2016-10-29T20:06:35
      8120c453-d4ee-4918-84f8-276257bb2c84
      
  
  
    
      
      
        1027700132195
      
    
  



Отправим запрос:
&НаСервере
Процедура ТестоваяОтправкаНаСервере()
  //Узнаем данные по Сбербанку
  XMLЗапрос = ПолучитьДанныеОрганизацияПоОГРН("1027700132195");
  //Это тема для другой статья
  //тут мы подписываем пакет данных
  //и отправляем Post запрос на сервис 
  XMLОтвет = ОтправкаXMLЗапроса(XMLЗапрос);
  Если XMLОтвет.КодОтвета < 299 Тогда
    //запрос успешен
    ОбъектXDTO = ДесериализацияОтвета(XMLОтвет.ИмяФайлРезультата, XMLЗапрос.ОтветXDTO)
  КонецЕсли; 
КонецПроцедуры

Ответ от серверов ГИС ЖКХ (СИТ-1):
XML ответ exportOrgRegistryResult


  
    
    2016-10-29T20:06:37.185+03:00
      8120c453-d4ee-4918-84f8-276257bb2c84
    
    
    
        
            
            
                50a8619b-2d27-4f20-8233-eab1ccf9dffe
                
                    50a8619b-2d27-4f20-8233-eab1ccf9dffe
                    2015-08-10+03:00
                    true
                    
                        ОАО «Сбербанк России»
                        ОАО «Сбербанк России»
                        1027700132195
                        2015-08-10+03:00
                        7707083893
                        775001001
                        12247
                        г. Москва, ул. Вавилова, д. 19
                        93409d8c-d8d4-4491-838f-f9aa1678b5e6
                    
                    P
                
                2c57ed5e-583a-4471-839e-776250bdde50
                
                    1
                    9875cc2e-73f9-41d6-bceb-47b48ed23395
                    Управляющая организация
                
                true
            
        
    



Как мы видим, ответ напрямую десериализовать не получится, потому что нет такого типа в предложенных xsd схемах. Нам надо как-то пропустить часть тэгов и обработать только область ответа. На эту тему я тоже не нашел информации, но методом проб и ошибок приходим к кусочку магий:
&НаСервереБезКонтекста
Функция ДесериализацияОтвета(ПутьКФайлу, ОтветXDTO)
  //Откроем файл
  Чтение = новый ЧтениеXML;
  Чтение.ОткрытьФайл(ПутьКФайлу, , , "UTF-8"); 
  //Начинаем пропускать все служебное заголовки, тело ищем наш тэг ответа
  Пока Чтение.Прочитать() Цикл
    Если Чтение.ЛокальноеИмя = ОтветXDTO.СвойствоXDTO.Имя Тогда
      //т.к. вы спозиционировались на нужном место можно десериализовать данные. Магия
      ОбъектXDTO = ФабрикаXDTO.ПрочитатьXML(Чтение, ОтветXDTO.СвойствоXDTO.Тип); 
      КонецЕсли; 
  КонецЦикла;
  Возврат ОбъектXDTO;
КонецФункции

В итоге работать можно с очень сложными xsd схемами через стандартные инструменты платформы. В целом 1С контролируют типизацию и заполнения, бывает чересчур излишне, особенно когда внутри свойства пакета используется базовый тип другого пакета, но в любом случае тип нужно привести к локальному из-за другого пространства URI. Удобно работать с десериализоваными данными, так как там всю работу на себя берет платформа. Но проверки происходят на этапе выполнения, а при написания кода платформа 1С не предоставляет никаких подсказок и проходится пользоваться сторонними утилитами, и даже при выполнении большая часть элементов находится в состоянии «Неопределено» и даже тип или его свойство можно увидеть только в спецификации.

Комментарии (6)

  • 29 октября 2016 в 23:15

    0

    А Битрикс тут каким боком?
    • 30 октября 2016 в 08:48

      0

      Я понимаю что тут описаны особенности платформы 1C, а не конкретно 1С: Битрикс, но какое еще хаб будет близок по тематики?
  • 30 октября 2016 в 09:58

    0

    Ага, битрикс тут ни к селу…, лучше добавьте тэг WSDL.
    Сразу скажу, вопросы шифрования/подписи по ГОСТУ при работе с ГИС ЖКХ за рамками этой статьи и на хабре уже освещались. Хотя без подписей запросы выполнить не удастся.

    А вот мне интересно, можно ли без использования ВК, штатными средствами подписывать запросы?

    • 30 октября 2016 в 10:11

      0

      Вообще в платформе есть механизмы работы с ГОСТ шифрованием, но в ГИС ЖКХ надо подписывать не весь файл, а только кусок XML технология XAdES. У нас отдельный web сервис на C#, который подписывать пакеты с помощью NET Framework. КриптоПро .NET перед отправкой в ГИС ЖКХ
  • 30 октября 2016 в 11:03

    0

    Вот у меня лично по коду есть два очень тревожащих меня вопроса знатокам:
    1. Не вызывает ли дополнительного утомления кодера исходный текст в смешанной раскладке, причём при их смешении прямо в имени переменных (в константах и комментариях они мешаются часто и в других языках)?

    2. Не слышно, 1С не планирует внести в платформу более человеческий метод работы с SOAP, т.к. это судя по всему основной протокол ГосAPI? Вот честно, у меня кровь из глаз хлынула на куске по формированию запроса через банальное конструирование XML на низком уровне…

    • 30 октября 2016 в 11:26

      0

      Про смесь кодировок, я не говорил, что это пойдет на продакшен, именно в таком виде, там фактически будут использованы обертки и обычный программист использует конструкции тип Структура = ЗапросоЮрЛицаПоОргн (»1234567890123»). Потом ответ будет типа Структура.Наименование, Структура.КПП и т.п.

      В стандартных объектах платформы ФабрикаXDTO, СвойствоXDTO, ЧтениеXML, ЧтениеJSON, ЕстьNULL тоже идет смешение раскладок. И вообще многие символы которые используются в код или запросах 1С нельзя поставить без смены кодировки (>, <, % и т.п. особенно раздражает символ | ), и если в коде спасает автозамена через шаблоны, то в запросах увы это не работает.

      Вообще как я уже писал в конце, работать с xsd очень не удобно. 1С фактически кроме контроля типов на этапе выполнения ничего не предоставляет почти все сложные типы инициализируются как Неопределено даже без типизаций, приходиться сидеть в xsd схемах и документаций. Хотя десериализация происходит нормально, там уже все сделает платформа из знания типов и текущих данных, и тогда уже можно работать через точку в удобном режиме. В том же C#, как других языках, через кодогенерацию можно получить готовые классы и не заниматься ручным сбором. Тут к сожалению такой метод мне не известен все руками.

      Вообще задача была показать как работать со сложными XSD схемами: разбитыми на куски с иерархией типов и т.п., а не ограничится примерами из документаций.

      Про ГосAPI я ничего не слышал. Но есть же в документообороте работа с ЭЦП, и обмен электронными счетами факторами тоже есть. Может что то и будет.

© Habrahabr.ru