1С магія XDTO-пакетів на прикладі інтеграцій з ДВС ЖКГ

Є дуже багато статей про те, як працювати з XSL/XSD з 1С, але всі вони в стилі: візьмемо нашу XSD-схему (просту і удбоную) або наш веб-сервіс і дивіться, як все легко експортувати або імпортувати. А що робити, якщо нам дали пачку XSD-схеми зі складними взаємозв'язками і змінювати них ми не можемо, а працювати і підтримувати актуальність схем треба?

Відразу скажу, питання шифрування/підписи за ГОСТОМ при роботі з ГІС ЖКГ за рамками цієї статті та на хабре вже висвітлювалися. Хоча без підписів запити виконати не вдасться.

image
Почнемо з простого — завантажити пакет форматів інтеграційного взаємодії з ДВС ЖКГ, імпортуємо всі xsd схеми з пакету інтеграцій, наведемо порядок перейменуємо все як нам зручно. В результаті отримаємо як показано на картинці:

Ну а тепер приступимо до магії. Спробуємо запросити дані з довідника організацій по ОРГН. Це підсистема organizations-registry-common метод exportOrgRegist.

У hcs-organizations-registry-common-service.wsdl зазначено:

Специфікація з hcs-organizations-registry-common-service.wsdl
...
<wsdl:message name="exportOrgRegistryRequest">
<wsdl:part name="exportOrgRegistryRequest" element="ro:exportOrgRegistryRequest"/>
</wsdl:message>
<wsdl:message name="exportOrgRegistryResult">
<wsdl:part name="exportOrgRegistryResult" element="ro:exportOrgRegistryResult"/>
</wsdl:message>
...
<wsdl:message name="ISRequestHeader">
<wsdl:part name="Header" element="ISRequestHeader"/>
</wsdl:message>

...

<wsdl:operation name="exportOrgRegistry">
<wsdl:documentation>експорт інформації про організації</wsdl:documentation>
<wsdl:input message="tns:exportOrgRegistryRequest"/>
<wsdl:output message="tns:exportOrgRegistryResult"/>
<wsdl:fault name="InvalidRequest" message="tns:Fault"/>
</wsdl:operation>

...

<wsdl:operation name="exportOrgRegistry">
<soap:operation soapAction="urn:exportOrgRegistry"/>
<wsdl:input>
<soap:body use="literal"/>
<soap:message header="tns:ISRequestHeader" part="Header" use="literal"/>
</wsdl:input>
<wsdl:output>
<soap:body use="literal"/>
<soap:message header="tns:ResultHeader" part="Header" use="literal"/>
</wsdl:output>
<wsdl:fault name="InvalidRequest">
<soap:fault name="InvalidRequest" use="literal"/>
</wsdl:fault>
</wsdl:operation>


Треба зібрати SOAP пакет з заголовка ISRequestHeader, тіла exportOrgRegistryRequest. Подивимося їх у xsd схеми специфікацій по інтеграцій.
hcs-base.xsd
...
<xs:element name="ISRequestHeader" type="tns:HeaderType">
<xs:annotation>
<xs:documentation>Заголовок</xs:documentation>
</xs:annotation>
</xs:element> 
...
<xs:complexType name="HeaderType">
<xs:annotation>
<xs:documentation>Базовий тип заголовка</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:element name="Date" type="xs:dateTime">
<xs:annotation>
<xs:documentation>Дата відправлення пакета</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="MessageGUID" type="tns:GUIDType">
<xs:annotation>
<xs:documentation>Ідентифікатор повідомлення</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:complexType> 
...
<xs:simpleType name="GUIDType">
<xs:annotation>
<xs:documentation>GUID-тип.</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:string">
<xs:pattern value="([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}"/>
</xs:restriction>
</xs:simpleType>
...

hcs-organizations-base.xsd
...
<xs:element name="OGRN" type="tns:OGRNType">
<xs:annotation>
<xs:documentation>ОГРН</xs:documentation>
</xs:annotation>
</xs:element>
<xs:simpleType name="OGRNType">
<xs:restriction base="xs:string">
<xs:length value="13"/>
</xs:restriction>
</xs:simpleType>
...

hcs-organizations-registry-common-types.xsd
...
<!--Експорт з реєстру організацій-->
<xs:element name="exportOrgRegistryRequest">
<xs:annotation>
<xs:documentation>Експорт інформації з реєстру організацій</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:complexContent>
<xs:extension base="base:BaseType">
<xs:sequence>
<xs:element name="SearchCriteria" maxOccurs="100">
<xs:annotation>
<xs:documentation>Критерій пошуку організацій.</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:choice>
<xs:choice>
<xs:annotation>
<xs:documentation>Пошук за реквізитами.</xs:documentation>
</xs:annotation>
<xs:element ref="organizations-base:OGRNIP"/>
<xs:sequence>
<xs:element ref="organizations-base:OGRN"/>
<xs:element ref="organizations-base:KPP" minOccurs="0"/>
</xs:sequence>
<xs:element ref="organizations-base:NZA"/>
</xs:choice>
<xs:element ref="organizations-registry-base:orgVersionGUID"/>
<xs:element ref="organizations-registry-base:orgRootEntityGUID"/>
</xs:choice>
<xs:element name="isRegistered" type="xs:boolean" fixed="true" minOccurs="0">
<xs:annotation>
<xs:documentation>Пошук серед організацій, що мають особистих кабінет</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="lastEditingDateFrom" type="xs:date" minOccurs="0">
<xs:annotation>
<xs:documentation>Час останньої зміни (від)</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
<xs:attribute ref="base:version" use="required" fixed="10.0.2.1"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:element name="exportOrgRegistryResult">
<xs:complexType>
<xs:complexContent>
<xs:extension base="base:BaseType">
<xs:choice>
<xs:element ref="base:ErrorMessage"/>
<xs:element name="OrgData" type="tns:exportOrgRegistryResultType" minOccurs="0" maxOccurs="life">
<xs:annotation>
<xs:documentation>Знайдена організація.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:choice>
<xs:attribute ref="base:version" use="required" fixed="10.0.2.1"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:complexType name="exportOrgRegistryResultType">
<xs:sequence>
<xs:element ref="organizations-registry-base:orgRootEntityGUID"/>
<xs:element name="OrgVersion">
<xs:annotation>
<xs:documentation>Версія організації в реєстрі організацій</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element ref="organizations-registry-base:orgVersionGUID"/>
<xs:element name="lastEditingDate" type="xs:date">
<xs:annotation>
<xs:documentation>Час останньої зміни</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="IsActual" type="xs:boolean">
<xs:annotation>
<xs:documentation>Ознака актуальності запису</xs:documentation>
</xs:annotation>
</xs:element>
<xs:choice>
<xs:element name="Legal" type="organizations-registry-base:LegalType">
<xs:annotation>
<xs:documentation>Юридична особа</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="Subsidiary">
<xs:annotation>
<xs:documentation>Відокремлений підрозділ</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:complexContent>
<xs:extension base="organizations-registry-base:SubsidiaryType">
<xs:sequence>
<xs:element name="StatusVersion">
<xs:annotation>
<xs:documentation>Статус версії </xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:string"/>
</xs:simpleType>
</xs:element>
<xs:element name="ParentOrg">
<xs:annotation>
<xs:documentation>Інформація про головний організації</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element ref="organizations-registry-base:RegOrgVersion"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:element name="Entrp" type="organizations-registry-base:EntpsType">
<xs:annotation>
<xs:documentation>Індивідуальний підприємець</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="ForeignBranch" type="organizations-registry-base:ForeignBranchType">
<xs:annotation>
<xs:documentation>ФПИЮЛ (Філія або представництво іноземної юридичної особи)</xs:documentation>
</xs:annotation>
</xs:element>
</xs:choice>
<xs:element name="registryOrganizationStatus" minOccurs="0">
<xs:annotation>
<xs:documentation>Статус:
(P)UBLISHED - опублікована в одному з документів в рамках розкриття</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="P"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element ref="base:orgPPAGUID" minOccurs="0"/>
<xs:element name="organizationRoles" type="nsi-base:nsiRef" minOccurs="0" maxOccurs="life">
<xs:annotation>
<xs:documentation>Повноваження організації (НДІ №20)</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="isRegistered" type="xs:boolean" fixed="true" minOccurs="0">
<xs:annotation>
<xs:documentation>Зареєстрована в ГІС ЖКГ</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:complexType>
...

Ну приступимо, відкриємо потрібні нам пакети XDTO. Виявляється, потрібні суті є не типами, а властивостями, як з цим працювати в документації на XDTO в статтях, які я знаходив, не описано, тому скористаємося уроків магії:

  • Створимо об'єкти з властивостей
  • Створимо об'єкти з внутрішніх типів властивостей або тип об'єктів
  • Створимо об'єкти з Типів значень
image

Почнемо з тіла exportOrgRegistryRequest.

&НаСервереБезКонтекста
Функція ПолучитьДанныеОрганизацияПоОГРН(ОГРН)
//Отримаємо потрібні пакети
ПакетOrgRegCom = ФабрикаXDTO.Пакеты.Получить("http://dom.gosuslugi.ru/schema/integration/organizations-registry-common/");
ПакетОгдВаѕе = Фабрика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.Створити(ПакетОгдВаѕе.КорневыеСвойства.Отримати("OGRN").Тип, Рядок(ОГРН));
SearchCriteriaЗапись.OGRN = OGRN;
ОбъектXDTO.SearchCriteria.Додати(SearchCriteriaЗапись);

//збережемо тіло
Запит = Новий Структура("ОбъектXDTO,СвойствоXDTO,Ім'я", ОбъектXDTO, СвойствоXDTO);
//збережемо тип відповіді, нам по надобиться для десериализаций відповіді
ОтветСвойствоXDTOResult = ПакетOrgRegCom.КорневыеСвойства.Отримати("exportOrgRegistryResult");

Повернення СформироватьXMLЗапрос(Новий УникальныйИдентификатор, Запит, ОтветСвойствоXDTOResult );
КонецФункции

Напишемо функцію для збору XML запиту:

&НаСервереБезКонтекста
Функція СформироватьXMLЗапрос(GuidЗаголовка, ОтправкаXDTO, ОтветXDTO)
ПакетВаѕе = ФабрикаXDTO.Пакети.Отримати("http://dom.gosuslugi.ru/schema/integration/base/");
ПространствоИменЅОАР = "http://schemas.xmlsoap.org/soap/envelope/";
//XML файл 
ЗаписьXML = Новий ЗаписьXML;
ПараметрыЗаписиXML = Новий ПараметрыЗаписиXML("UTF-8");
ЗаписьXML.УстановитьСтроку(ПараметрыЗаписиXML);
ЗаписьXML.ЗаписатьОбъявлениеXML();
ЗаписьXML.ЗаписатьНачалоЭлемента("Envelope", ПространствоИменЅОАР);
ЗаписьXML.ЗаписатьСоответствиеПространстваимен("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", ПространствоИменЅОАР);
//знову метод через властивості
ЗаголовокСвойствоXDTO = ПакетВаѕе.КорневыеСвойства.Отримати("ISRequestHeader");
ЗаголовокЗапроса = ФабрикаXDTO.Створити(ЗаголовокСвойствоXDTO.Тип);
ЗаголовокЗапроса.Date = ТекущаяДата();
ЗаголовокЗапроса.MessageGUID = ФабрикаXDTO.Створити(ФабрикаXDTO.Тип("http://dom.gosuslugi.ru/schema/integration/base/", "GUIDType"), Рядок(GuidЗаголовка));
ФабрикаXDTO.ЗаписатьXML(ЗаписьXML, ЗаголовокЗапроса, ЗаголовокСвойствоXDTO.ЛокальноеИмя);
ЗаписьXML.ЗаписатьКонецЭлемента(); 
//Тіло в нас буде підготовлений exportOrgRegistryRequest
ЗаписьXML.ЗаписатьНачалоЭлемента("Body", ПространствоИменЅОАР);
ФабрикаXDTO.ЗаписатьXML(ЗаписьXML, ОтправкаXDTO.ОбъектXDTO, ОтправкаXDTO.СвойствоXDTO.ЛокальноеИмя, ОтправкаXDTO.СвойствоXDTO.URIПространстваИмен);
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.ЗаписатьКонецЭлемента();
//віддаємо результат для відправки
XMLЗапрос = Новий Структура();
XMLЗапрос.Вставити("XMLTекст", ЗаписьXML.Закрити());
XMLЗапрос.Вставити("ОтветXDTO", ОтветXDTO);
Повернення XMLЗапрос;
КонецФункции

В результаті отримаємо запит:

XML запит exportOrgRegistryRequest
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:base="http://dom.gosuslugi.ru/schema/integration/base/" xmlns:hm="http://dom.gosuslugi.ru/schema/integration/house-management/" xmlns:ns="http://www.w3.org/2000/09/xmldsig#" xmlns:nsi-base="http://dom.gosuslugi.ru/schema/integration/nsi-base/" xmlns:nsi-common="http://dom.gosuslugi.ru/schema/integration/nsi-common/" xmlns:organizations-registry-base="http://dom.gosuslugi.ru/schema/integration/organizations-registry-base/" xmlns:ro="http://dom.gosuslugi.ru/schema/integration/organizations-registry-common/" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="http://dom.gosuslugi.ru/schema/integration/house-management-service/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soap:Header>
<base:ISRequestHeader>
<base:Date>2016-10-29T20:06:35</base:Date>
<base:MessageGUID>8120c453-d4ee-4918-84f8-276257bb2c84</base:MessageGUID>
</base:ISRequestHeader>
</soap:Header>
<soap:Body>
<ro:exportOrgRegistryRequest Id="signed-data-container" base:version="10.0.2.1">
<!--у бойових запитах тут великий шматок підпису запиту, шматки сертифікатів та інші-->
<ro:SearchCriteria>
<OGRN xmlns="http://dom.gosuslugi.ru/schema/integration/organizations-base/">1027700132195</OGRN>
</ro:SearchCriteria>
</ro:exportOrgRegistryRequest>
</soap:Body>
</soap:Envelope>


Відправимо запит:

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

Відповідь від серверів ДВС ЖКГ (ЗВТ-1):

XML відповідь exportOrgRegistryResult
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:ns10="http://dom.gosuslugi.ru/schema/integration/organizations-base/" xmlns:ns11="http://dom.gosuslugi.ru/schema/integration/payments-base/" xmlns:ns12="http://dom.gosuslugi.ru/schema/integration/bills-base/" xmlns:ns13="http://dom.gosuslugi.ru/schema/integration/organizations-registry-common/" xmlns:ns3="http://www.w3.org/2000/09/xmldsig#" xmlns:ns4="http://dom.gosuslugi.ru/schema/integration/base/" xmlns:ns5="http://dom.gosuslugi.ru/schema/integration/account-base/" xmlns:ns6="http://dom.gosuslugi.ru/schema/integration/nsi-base/" xmlns:ns7="http://dom.gosuslugi.ru/schema/integration/individual-registry-base/" xmlns:ns8="http://dom.gosuslugi.ru/schema/integration/metering-device-base/" xmlns:ns9="http://dom.gosuslugi.ru/schema/integration/organizations-registry-base/" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<ns4:ResultHeader>
<ns4:Date>2016-10-29T20:06:37.185+03:00</ns4:Date>
<ns4:MessageGUID>8120c453-d4ee-4918-84f8-276257bb2c84</ns4:MessageGUID>
</ns4:ResultHeader>
</soap:Header>
<soap:Body>
<ns13:exportOrgRegistryResult Id="signed-data-container" ns4:version="10.0.2.1">
<!--опистул шматок підпису пакета він не особливо цікавий-->
<ns13:OrgData>
<ns9:orgRootEntityGUID>50a8619b-2d27-4f20-8233-eab1ccf9dffe</ns9:orgRootEntityGUID>
<ns13:OrgVersion>
<ns9:orgVersionGUID>50a8619b-2d27-4f20-8233-eab1ccf9dffe</ns9:orgVersionGUID>
<ns13:lastEditingDate>2015-08-10+03:00</ns13:lastEditingDate>
<ns13:IsActual>true</ns13:IsActual>
<ns13:Legal>
<ns9:ShortName>ВАТ «Ощадбанк Росії»</ns9:ShortName>
<ns9:FullName>ВАТ «Ощадбанк Росії»</ns9:FullName>
<ns10:OGRN>1027700132195</ns10:OGRN>
<ns9:StateRegistrationDate>2015-08-10+03:00</ns9:StateRegistrationDate>
<ns10:INN>7707083893</ns10:INN>
<ns10:KPP>775001001</ns10:KPP>
<ns10:OKOPF>12247</ns10:OKOPF>
<ns9:Address>р. Москва, вул. Вавілова, д. 19</ns9:Address>
<ns9:FIASHouseGuid>93409d8c-d8d4-4491-838f-f9aa1678b5e6</ns9:FIASHouseGuid>
</ns13:Legal>
<ns13:registryOrganizationStatus>P</ns13:registryOrganizationStatus>
</ns13:OrgVersion>
<ns4:orgPPAGUID>2c57ed5e-583a-4471-839e-776250bdde50</ns4:orgPPAGUID>
<ns13:organizationRoles>
<ns6:Code>1</ns6:Code>
<ns6:GUID>9875cc2e-73f9-41d6-bceb-47b48ed23395</ns6:GUID>
<ns6:Name>Керуюча організація</ns6:Name>
</ns13:organizationRoles>
<ns13:isRegistered>true</ns13:isRegistered>
</ns13:OrgData>
</ns13:exportOrgRegistryResult>
</soap:Body>
</soap:Envelope>


Як ми бачимо, відповідь безпосередньо десериализовать не вийде, тому що немає такого типу в запропонованих xsd-схеми. Нам треба якось пропустити частина тегів і обробити тільки область відповіді. На цю тему я теж не знайшов інформації, але методом проб і помилок приходимо до шматочку магій:

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

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

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.