Как исправить ошибку модели XDTO: Дублирование имени свойства при чтении XML?

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

При интеграции с внешними сервисами разработчики 1С нередко сталкиваются с ситуацией, когда сторонний API возвращает XML-документ с архитектурно некорректной структурой. Одной из частых проблем является наличие нескольких одноименных тегов, находящихся на одном уровне иерархии, но разделенных другими элементами. При попытке описать такой XML стандартной схемой XSD и загрузить её в пакет XDTO платформа выдает ошибку проверки модели: xdto-objectType-3.1.2: Дублирование имени свойства. Рассмотрим подробнее, почему возникает данное исключение и какими способами можно обойти это ограничение платформы 1С.

Выясним причину ошибки: ограничения объектной модели XDTO

Проанализируем природу поведения платформы 1С:Предприятие. В стандарте W3C XML Schema формально допустимо объявлять элементы с одинаковыми именами в рамках одного контейнера <xs:sequence>, если они идут в разном порядке. Однако внутренняя модель XDTO в 1С проецирует типы данных XML на строгие объектные сущности — ТипОбъектаXDTO и ОбъектXDTO. В этой модели каждое свойство становится именованным полем объекта, к которому платформа обращается по имени через точку.

Поскольку у объекта не может существовать двух разных свойств с абсолютно одинаковым идентификатором (например, два разных поля с именем APMReferenceID), регистрация пакета XDTO завершается аварийно. Попытка переименовать свойство с указанием локального имени в XSD также не решает проблему, так как правила сопоставления XDTO по-прежнему требуют уникальности ключевых имен.

Решение 1. Объединение дублирующихся тегов в единое свойство со списочной кратностью

Разберем основной и наиболее быстрый способ обхода ограничения схемы, не требующий изменения исходного входящего 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 будут доступны оба значения из входящего файла.

Решение 2. Использование последовательного типа (Последовательность XDTO)

Если для бизнес-логики критически важно знать точный исходный порядок следования тегов и их контекст среди соседних полей, у составного типа в модели XDTO можно взвести признак Последовательный = Истина (в XSD это реализуется через упорядоченные наборы).

В этом случае дубликат свойства в схеме также не объявляется (остается одно свойство с maxOccurs="unbounded"), однако обход прочитанных данных выполняется не через стандартные поля объекта, а через специальный интерфейс последовательности:


// Получаем доступ к последовательности элементов транзакции
ПоследовательностьТранзакции = ОбъектТранзакция.Последовательность();

Для Счетчик = 0 По ПоследовательностьТранзакции.Количество() - 1 Цикл
    СвойствоXDTO = ПоследовательностьТранзакции.ПолучитьСвойство(Счетчик);
    Значение = ПоследовательностьТранзакции.Получить(Счетчик);
    
    Если СвойствоXDTO.Имя = "APMReferenceID" Тогда
        // Обрабатываем каждое вхождение тега в строгом порядке его следования в файле
        Сообщить("Найдено значение APMReferenceID: " + Строка(Значение));
    КонецЕсли;
КонецЦикла;

Проблема отсутствия пространств имен (xmlns) и ее решение

Часто в паре с кривой структурой тегов приходит XML, в котором у корневого узла отсутствует объявление целевого пространства имен (xmlns). Если фабрика XDTO настроена на работу с URI, десериализация не сможет сопоставить прочитанный XML с зарегистрированным типом.

Рассмотрим, почему попытка модификации документа через ПостроительDOM и вызов метода УстановитьСоответствиеПространстваИмен на корневом элементе не приводит к успеху: метод добавляет текстовый XML-атрибут в корень, но объектная модель DOM в оперативной памяти уже распределила внутренние узлы по пространствам имен (в данном случае они остались без URI). Поэтому ФабрикаXDTO при чтении через ЧтениеУзловDOM продолжает видеть дочерние узлы как пустые (no-namespace).

Разберем три надежных подхода к обработке отсутствующих пространств имен:

  1. XSD без пространства имен (наиболее правильный подход): Если сервер принципиально отдает XML без атрибута xmlns, удалим в своей схеме XSD директиву targetNamespace и соответствующие префиксы. Схема без пространства имен будет валидировать исходный файл без любых предварительных текстовых модификаций.
  2. Потоковое XSLT-преобразование (ПреобразованиеXSL): Если файл имеет значительный объем, а строковая замена СтрЗаменить() опасна случайной подменой данных внутри значений, применим потоковую трансформацию. Шаблон XSLT может на лету присвоить всем узлам целевой xmlns или переименовать второй тег-дубликат (например, во второй тег APMReferenceID_2), что сделает схему максимально прозрачной.
  3. Прямое чтение через объект ЧтениеXML: Когда XML-ответ сервиса изобилует ошибками валидации W3C, жесткая привязка к XDTO становится нецелесообразной. Быстрее и надежнее распарсить структуру линейным считывателем:
    
    Чтение = Новый ЧтениеXML;
    Чтение.УстановитьСтроку(ТекстОтвета);
    
    Пока Чтение.Прочитать() Цикл
        Если Чтение.ТипУзла = ТипУзлаXML.НачалоЭлемента И Чтение.Имя = "APMReferenceID" Тогда
            Чтение.Прочитать(); // Переходим к текстовому содержимому
            ЗначениеТега = Чтение.Значение;
            // Обработка полученного значения
        КонецЕсли;
    КонецЦикла;
    
    Чтение.Закрыть();
    

Таким образом, исправление ошибки сводится либо к адаптации кратности тега в схеме (сборка массива значений в рамках одного свойства XDTO), либо к переводу обработки нестандартного XML на потоковые механизмы платформы.

← На главную