При интеграции с внешними сервисами разработчики 1С нередко сталкиваются с ситуацией, когда сторонний API возвращает XML-документ с архитектурно некорректной структурой. Одной из частых проблем является наличие нескольких одноименных тегов, находящихся на одном уровне иерархии, но разделенных другими элементами. При попытке описать такой XML стандартной схемой XSD и загрузить её в пакет XDTO платформа выдает ошибку проверки модели: xdto-objectType-3.1.2: Дублирование имени свойства. Рассмотрим подробнее, почему возникает данное исключение и какими способами можно обойти это ограничение платформы 1С.
Проанализируем природу поведения платформы 1С:Предприятие. В стандарте W3C XML Schema формально допустимо объявлять элементы с одинаковыми именами в рамках одного контейнера <xs:sequence>, если они идут в разном порядке. Однако внутренняя модель XDTO в 1С проецирует типы данных XML на строгие объектные сущности — ТипОбъектаXDTO и ОбъектXDTO. В этой модели каждое свойство становится именованным полем объекта, к которому платформа обращается по имени через точку.
Поскольку у объекта не может существовать двух разных свойств с абсолютно одинаковым идентификатором (например, два разных поля с именем APMReferenceID), регистрация пакета XDTO завершается аварийно. Попытка переименовать свойство с указанием локального имени в XSD также не решает проблему, так как правила сопоставления XDTO по-прежнему требуют уникальности ключевых имен.
Разберем основной и наиболее быстрый способ обхода ограничения схемы, не требующий изменения исходного входящего XML-файла. Если внешний сервис присылает несколько одинаковых тегов внутри родительского блока, мы должны описать в схеме только одно свойство, но разрешить ему множественное появление через атрибут maxOccurs.
Посмотрим на пример: если тег APMReferenceID встречается в документе дважды, в схеме XSD удалим его повторное объявление, а у оставшегося укажем кратность. Чтобы схема позволяла элементам встречаться не в строго фиксированном порядке, контейнер <xs:sequence> заменяют на <xs:choice> с множественным числом повторений.
Важное замечание: если в XSD использовать блок <xs:choice> без параметров, по умолчанию его maxOccurs равен 1, то есть документ пропустит только один элемент из всего списка. Чтобы валидатор принял всю совокупность тегов объекта, блоку выбора необходимо задать maxOccurs="unbounded".
Приведем скорректированный фрагмент XSD-схемы для типа Transaction:
<xs:complexType name="Transaction">
<xs:choice maxOccurs="unbounded">
<xs:element name="TransactionID" type="xs:int"/>
<xs:element name="ClientName" type="xs:string"/>
<xs:element name="RelatedTransactionID" type="xs:int"/>
<xs:element name="ClientUniqueID" type="xs:string"/>
<xs:element name="Result" type="xs:string"/>
<xs:element name="Reason" type="xs:string"/>
<xs:element name="AuthCode" type="xs:string"/>
<xs:element name="Amount" type="xs:decimal"/>
<xs:element name="Currency" type="xs:string"/>
<xs:element name="UserID" type="xs:string"/>
<xs:element name="TransactionDate" type="xs:dateTime"/>
<xs:element name="TransactionType" type="xs:string"/>
<xs:element name="IsRebill" type="xs:boolean"/>
<xs:element name="MembershipID" type="xs:string"/>
<xs:element name="Bank" type="xs:string"/>
<xs:element name="CardCompany" type="xs:string"/>
<xs:element name="Last4Digits" type="xs:short"/>
<xs:element name="Email" type="xs:string"/>
<xs:element name="FirstName" type="xs:string"/>
<xs:element name="LastName" type="xs:string"/>
<xs:element name="WebSite" type="xs:string"/>
<xs:element name="ShopID" type="xs:byte"/>
<xs:element name="CreditTypeId" type="xs:byte"/>
<xs:element name="APMTransactionID" type="xs:string"/>
<xs:element name="APMReferenceID" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="Bin" type="xs:int"/>
<xs:element name="ExpDate" type="xs:short"/>
<xs:element name="Address" type="xs:string"/>
<xs:element name="City" type="xs:string"/>
<xs:element name="State" type="xs:string"/>
<xs:element name="ZipCode" type="xs:string"/>
<xs:element name="Country" type="xs:string"/>
<xs:element name="IPAddress" type="xs:string"/>
<xs:element name="Phone" type="xs:string"/>
<xs:element name="ReferenceID" type="xs:string"/>
<xs:element name="IsVoiceAuth" type="xs:boolean"/>
<xs:element name="RequestID" type="xs:string"/>
<xs:element name="UniqueCC" type="xs:string"/>
<xs:element name="ECI" type="xs:byte"/>
<xs:element name="ExternalAccountID" type="xs:string"/>
<xs:element name="ProductID" type="xs:string"/>
</xs:choice>
<xs:attribute name="TransactionID" type="xs:int"/>
</xs:complexType>
При чтении такого XML через ФабрикаXDTO.ПрочитатьXML() свойство APMReferenceID вернет объект типа СписокXDTO, в котором по индексам 0 и 1 будут доступны оба значения из входящего файла.
Если для бизнес-логики критически важно знать точный исходный порядок следования тегов и их контекст среди соседних полей, у составного типа в модели XDTO можно взвести признак Последовательный = Истина (в XSD это реализуется через упорядоченные наборы).
В этом случае дубликат свойства в схеме также не объявляется (остается одно свойство с maxOccurs="unbounded"), однако обход прочитанных данных выполняется не через стандартные поля объекта, а через специальный интерфейс последовательности:
// Получаем доступ к последовательности элементов транзакции
ПоследовательностьТранзакции = ОбъектТранзакция.Последовательность();
Для Счетчик = 0 По ПоследовательностьТранзакции.Количество() - 1 Цикл
СвойствоXDTO = ПоследовательностьТранзакции.ПолучитьСвойство(Счетчик);
Значение = ПоследовательностьТранзакции.Получить(Счетчик);
Если СвойствоXDTO.Имя = "APMReferenceID" Тогда
// Обрабатываем каждое вхождение тега в строгом порядке его следования в файле
Сообщить("Найдено значение APMReferenceID: " + Строка(Значение));
КонецЕсли;
КонецЦикла;
Часто в паре с кривой структурой тегов приходит XML, в котором у корневого узла отсутствует объявление целевого пространства имен (xmlns). Если фабрика XDTO настроена на работу с URI, десериализация не сможет сопоставить прочитанный XML с зарегистрированным типом.
Рассмотрим, почему попытка модификации документа через ПостроительDOM и вызов метода УстановитьСоответствиеПространстваИмен на корневом элементе не приводит к успеху: метод добавляет текстовый XML-атрибут в корень, но объектная модель DOM в оперативной памяти уже распределила внутренние узлы по пространствам имен (в данном случае они остались без URI). Поэтому ФабрикаXDTO при чтении через ЧтениеУзловDOM продолжает видеть дочерние узлы как пустые (no-namespace).
Разберем три надежных подхода к обработке отсутствующих пространств имен:
xmlns, удалим в своей схеме XSD директиву targetNamespace и соответствующие префиксы. Схема без пространства имен будет валидировать исходный файл без любых предварительных текстовых модификаций.
ПреобразованиеXSL): Если файл имеет значительный объем, а строковая замена СтрЗаменить() опасна случайной подменой данных внутри значений, применим потоковую трансформацию. Шаблон XSLT может на лету присвоить всем узлам целевой xmlns или переименовать второй тег-дубликат (например, во второй тег APMReferenceID_2), что сделает схему максимально прозрачной.
ЧтениеXML: Когда XML-ответ сервиса изобилует ошибками валидации W3C, жесткая привязка к XDTO становится нецелесообразной. Быстрее и надежнее распарсить структуру линейным считывателем:
Чтение = Новый ЧтениеXML;
Чтение.УстановитьСтроку(ТекстОтвета);
Пока Чтение.Прочитать() Цикл
Если Чтение.ТипУзла = ТипУзлаXML.НачалоЭлемента И Чтение.Имя = "APMReferenceID" Тогда
Чтение.Прочитать(); // Переходим к текстовому содержимому
ЗначениеТега = Чтение.Значение;
// Обработка полученного значения
КонецЕсли;
КонецЦикла;
Чтение.Закрыть();
Таким образом, исправление ошибки сводится либо к адаптации кратности тега в схеме (сборка массива значений в рамках одного свойства XDTO), либо к переводу обработки нестандартного XML на потоковые механизмы платформы.