Как распарсить XML-файл ТТН ЕГАИС v3 с марками и коробами в 1С?

Программист 1С v7.7 Торговля и дистрибуция
← На главную

Переход на поштучный (помарочный) учет в ЕГАИС версии 3.0 и выше принес разработчикам немало сложных задач. Одной из главных проблем стало изменение формата обмена XML-документами. В накладных (ТТН) появилась иерархическая структура — так называемое «дерево упаковок», в котором акцизные марки упакованы в коробки (групповую тару), а коробки могут быть объединены в паллеты.

Особенно остро эта проблема встала перед разработчиками, поддерживающими исторические конфигурации на платформе 1С:Предприятие 7.7 («клюшки»), где стандартные средства парсинга XML часто дают сбой при работе со сложными пространствами имен (Namespaces). Ниже мы подробно разберем структуру реального файла выгрузки ТТН v3 и рассмотрим практические способы обхода этой структуры в 1С.

Структура XML-файла ТТН ЕГАИС v3 с помарочным учетом

Проанализируем структуру блока MarkInfo, который находится внутри товарной позиции Position в XML-файле накладной. Именно здесь описывается связь между групповой тарой и марками.


<wb:InformF2>
    <ce:F2RegId>TEST-FB-000000036674379</ce:F2RegId>
    <ce:MarkInfo>
        <ce:boxpos>
            <ce:boxnumber>128500</ce:boxnumber>
            <ce:amclist>
                <ce:amc>22N0000030JBSAJA55608I760419003000377ICTHRFMEX71GUEWYVHJGZEV9CNFEVOP</ce:amc>
            </ce:amclist>
        </ce:boxpos>
    </ce:MarkInfo>
</wb:InformF2>

Разберем назначение ключевых тегов этой структуры:

  1. ce:MarkInfo — корневой узел с информацией о маркировке для данной позиции справки 2 (регистрационного номера РФУ2).
  2. ce:boxpos — описание конкретной коробки (упаковки). Сколько коробок в партии — столько и будет таких блоков.
  3. ce:boxnumber — уникальный идентификатор групповой тары (штрихкод коробки или паллеты в формате GS1/SSCC).
  4. ce:amclist — список марок, вложенных в данную коробку.
  5. ce:amc — уникальный код акцизной или федеральной специальной марки (длиной 68 или 150 символов).

Полный рабочий пример структуры XML-документа WayBill_v3 выглядит следующим образом:


<ns:Documents xmlns:c="http://fsrar.ru/WEGAIS/Common" xmlns:ce="http://fsrar.ru/WEGAIS/CommonV3" xmlns:ns="http://fsrar.ru/WEGAIS/WB_DOC_SINGLE_01" xmlns:oref="http://fsrar.ru/WEGAIS/ClientRef_v2" xmlns:pref="http://fsrar.ru/WEGAIS/ProductRef_v2" xmlns:wb="http://fsrar.ru/WEGAIS/TTNSingle_v3" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <ns:Owner>
        <ns:FSRAR_ID>010060692222</ns:FSRAR_ID>
    </ns:Owner>
    <ns:Document>
        <ns:WayBill_v3>
            <wb:Identity>881732a5-1109-11e8-8639-0013ce8a85e4</wb:Identity>
            <wb:Header>
                <wb:Type>WBInvoiceFromMe</wb:Type>
                <wb:NUMBER>A0000000001</wb:NUMBER>
                <wb:Date>2018-02-15</wb:Date>
                <wb:ShippingDate>2018-02-14</wb:ShippingDate>
                <wb:Transport>
                    <wb:TRAN_TYPE>413</wb:TRAN_TYPE>
                    <wb:TRAN_COMPANY/>
                </wb:Transport>
                <wb:Shipper>
                    <oref:UL>
                        <oref:ClientRegId>010060692222</oref:ClientRegId>
                        <oref:FullName>ООО Поставщик</oref:FullName>
                        <oref:ShortName>Поставщик</oref:ShortName>
                        <oref:INN>222222222</oref:INN>
                        <oref:KPP>22222222</oref:KPP>
                        <oref:address>
                            <oref:Country>643</oref:Country>
                            <oref:RegionCode>50</oref:RegionCode>
                            <oref:description>Адрес поставщика</oref:description>
                        </oref:address>
                    </oref:UL>
                </wb:Shipper>
                <wb:Consignee>
                    <oref:UL>
                        <oref:ClientRegId>010060691111</oref:ClientRegId>
                        <oref:FullName>ООО Получатель</oref:FullName>
                        <oref:ShortName>Получатель</oref:ShortName>
                        <oref:INN>111111111</oref:INN>
                        <oref:KPP>11111111</oref:KPP>
                        <oref:address>
                            <oref:Country>643</oref:Country>
                            <oref:RegionCode>50</oref:RegionCode>
                            <oref:description>Адрес получателя</oref:description>
                        </oref:address>
                    </oref:UL>
                </wb:Consignee>
            </wb:Header>
            <wb:Content>
                <wb:Position>
                    <wb:Quantity>1</wb:Quantity>
                    <wb:Price>100</wb:Price>
                    <wb:Party>1</wb:Party>
                    <wb:Identity>1</wb:Identity>
                    <wb:Product>
                        <pref:UnitType>Packed</pref:UnitType>
                        <pref:Type>АП</pref:Type>
                        <pref:FullName>Бальзам Керамика</pref:FullName>
                        <pref:AlcCode>0011023000001188746</pref:AlcCode>
                        <pref:ShortName>Бальзам Керамика</pref:ShortName>
                        <pref:Capacity>0.5</pref:Capacity>
                        <pref:AlcVolume>43</pref:AlcVolume>
                        <pref:Producer>
                            <oref:UL>
                                <oref:ClientRegId>010000000210</oref:ClientRegId>
                                <oref:FullName>ООО Производитель</oref:FullName>
                                <oref:ShortName>Производитель</oref:ShortName>
                                <oref:INN>1012003576</oref:INN>
                                <oref:KPP>101201001</oref:KPP>
                                <oref:address>
                                    <oref:Country>643</oref:Country>
                                    <oref:RegionCode>10</oref:RegionCode>
                                    <oref:description>Адрес завода</oref:description>
                                </oref:address>
                            </oref:UL>
                        </pref:Producer>
                        <pref:ProductVCode>212</pref:ProductVCode>
                    </wb:Product>
                    <wb:FARegId>TEST-FA-000000001704789</wb:FARegId>
                    <wb:InformF2>
                        <ce:F2RegId>TEST-FB-000000036674379</ce:F2RegId>
                        <ce:MarkInfo>
                            <ce:boxpos>
                                <ce:boxnumber>128500</ce:boxnumber>
                                <ce:amclist>
                                    <ce:amc>22N0000030JBSAJA55608I760419003000377ICTHRFMEX71GUEWYVHJGZEV9CNFEVOP</ce:amc>
                                </ce:amclist>
                            </ce:boxpos>
                        </ce:MarkInfo>
                    </wb:InformF2>
                </wb:Position>
            </wb:Content>
        </ns:WayBill_v3>
    </ns:Document>
</ns:Documents>

Проблема разбора XML с пространствами имен в 1С 7.7

При использовании стандартной библиотеки v7plus.dll или парсера XMLParser в среде 1С 7.7 разработчики часто сталкиваются со следующей проблемой. Метод КоличествоПодчиненных() на узле ce:boxpos возвращает значение «1», видя только узел ce:boxnumber, и наотрез «отказывается» видеть соседний узел списка марок ce:amclist.

Выясним причину этой ошибки. Она кроется в некорректной работе старых парсеров с пространствами имен XML (Namespaces), обозначенными префиксами вроде ce: или wb:. Парсер путает префиксы с именами тегов или спотыкается на невидимых символах переноса строк внутри структуры.

Разберем два проверенных решения этой технической проблемы.

Решение №1: Предварительная очистка XML от пространств имен

Самый простой и надежный «костыль», который с успехом применяли разработчики на платформе 1С 7.7. Перед тем как передать XML-файл в объект парсера, мы считываем его как текстовый файл и вырезаем все префиксы пространств имен с помощью функции СтрЗаменить.

Реализуем этот метод по шагам:


// Создаем объект для работы с текстом
ТекстФайла = СоздатьОбъект("Текст");
ТекстФайла.Открыть(ПутьКФайлуТТН);

// Собираем весь файл в одну текстовую строку
СодержимоеXML = "";
Для НомСтроки = 1 По ТекстФайла.КоличествоСтрок() Цикл
    СодержимоеXML = СодержимоеXML + ТекстФайла.ПолучитьСтроку(НомСтроки) + РазделительСтрок;
КонецЦикла;

// Удаляем префиксы пространств имен
СодержимоеXML = СтрЗаменить(СодержимоеXML, "ce:", "");
СодержимоеXML = СтрЗаменить(СодержимоеXML, "wb:", "");
СодержимоеXML = СтрЗаменить(СодержимоеXML, "ns:", "");
СодержимоеXML = СтрЗаменить(СодержимоеXML, "oref:", "");
СодержимоеXML = СтрЗаменить(СодержимоеXML, "pref:", "");

// Записываем очищенный текст во временный файл
ИмяВременногоФайла = КаталогВременныхФайлов() + "clean_ttn.xml";
ТекстОчищенный = СоздатьОбъект("Текст");
ТекстОчищенный.ДобавитьСтроку(СодержимоеXML);
ТекстОчищенный.Записать(ИмяВременногоФайла);

// Теперь загружаем файл в XML-анализатор
Анализатор = СоздатьОбъект("AddIn.XMLParser");
ДокXML = Анализатор.СоздатьДокумент();
ДокXML.Загрузить(ИмяВременногоФайла);

// Теперь методы КоличествоПодчиненных() и ПолучитьПодчиненныйПоИмени() отработают идеально!

После удаления префиксов структура превращается в стандартный XML без двоеточий, и 1С 7.7 с легкостью находит узлы boxnumber и amclist как обычные подчиненные элементы.

Решение №2: Использование MSXML2.DOMDocument через ActiveX

Если мы не хотим модифицировать исходный файл, откажемся от нестабильной библиотеки v7plus.dll. Вместо этого обратимся напрямую к возможностям операционной системы Windows и задействуем COM-объект MSXML2.DOMDocument. Этот объект поддерживает мощный язык запросов XPath, который позволяет выбирать нужные узлы напрямую, игнорируя иерархические проблемы обхода дерева.

Проанализируем пример кода с использованием XPath:


// Создаем COM-объект парсера MSXML
XMLДок = СоздатьОбъект("MSXML2.DOMDocument.6.0");
XMLДок.async = 0;
XMLДок.resolveExternals = 0;
XMLДок.load(ПутьКФайлуТТН);

// Важно: настраиваем соответствие пространств имен для выполнения XPath-запросов
XMLДок.setProperty("SelectionNamespaces", "xmlns:ce='http://fsrar.ru/WEGAIS/CommonV3' xmlns:wb='http://fsrar.ru/WEGAIS/TTNSingle_v3'");

// Напрямую выбираем все узлы коробок в документе
Коробки = XMLДок.selectNodes("//ce:boxpos");
Для Ном = 0 По Коробки.length - 1 Цикл
    УзелКоробки = Коробки.item(Ном);
    
    // Получаем номер коробки
    НомерКоробки = УзелКоробки.selectSingleNode("ce:boxnumber").text;
    Сообщить("Найдена коробка №: " + НомерКоробки);
    
    // Получаем список всех марок в этой коробке
    СписокМарок = УзелКоробки.selectNodes("ce:amclist/ce:amc");
    Для НомМарки = 0 По СписокМарок.length - 1 Цикл
        КодМарки = СписокМарок.item(НомМарки).text;
        Сообщить("--- Акцизная марка: " + КодМарки);
    КонецЦикла;
КонецЦикла;

Использование XPath решает проблему на корню: нам больше не требуется рекурсивно перебирать уровни вложенности и беспокоиться о количестве подчиненных узлов. Мы получаем коллекции нужных элементов напрямую по их путям.

Современное решение для платформы 1С 8.3

Если вы работаете на современной платформе 1С:Предприятие 8.3, то ручной разбор XML-строк уходит в прошлое. Для чтения документов ЕГАИС используется механизм XDTO-пакетов (XML Data Transfer Objects) или потоковое чтение с помощью встроенного объекта ЧтениеXML, который отлично справляется с любыми пространствами имен.

Посмотрим на пример быстрого чтения марок и коробок на платформе 1С 8.3:


ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.ОткрытьФайл(ПутьКФайлуТТН);

ТекущийНомерКоробки = "";

Пока ЧтениеXML.Прочитать() Цикл
    Если ЧтениеXML.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
        ЛокальноеИмя = ЧтениеXML.ЛокальноеИмя;
        
        Если ЛокальноеИмя = "boxnumber" Тогда
            // Читаем значение номера коробки
            ЧтениеXML.Прочитать();
            ТекущийНомерКоробки = ЧтениеXML.Значение;
            СтрокаСообщения = СтрШаблон("Упаковка: %1", ТекущийНомерКоробки);
            Сообщить(СтрокаСообщения);
            
        ИначеЕсли ЛокальноеИмя = "amc" Тогда
            // Читаем значение марки внутри текущей коробки
            ЧтениеXML.Прочитать();
            КодМарки = ЧтениеXML.Значение;
            СтрокаСообщения = СтрШаблон("   -> Марка: %1", КодМарки);
            Сообщить(СтрокаСообщения);
        КонецЕсли;
    КонецЕсли;
КонецЦикла;

ЧтениеXML.Закрыть();

Использование метода ЛокальноеИмя позволяет игнорировать любые префиксы пространств имен (будь то ce:, wb: или любые другие), получая чистое имя тега для анализа. Это делает код кроссплатформенным и устойчивым к изменениям версий схем ЕГАИС.

Резюме и рекомендации

Интеграция поштучного учета ЕГАИС требует от разработчика особого внимания к структуре XML. При проектировании архитектуры обмена рекомендуем придерживаться следующих правил:

← На главную