При работе с внешними системами, такими как ЕГАИС (УТМ) или веб-сервисы государственных органов, программисты 1С часто сталкиваются с проблемой: namespaceURI (адрес пространства имен) в XML-файле остается неизменным, а префиксы (например, ns:, h:, a:) меняются от документа к документу в зависимости от настроек ПО отправителя. Это делает невозможным использование прямых XPath-запросов типа /ns:Documents/ns:Document, так как префикс ns может внезапно превратиться в pre: или вовсе отсутствовать.
В данной статье мы разберем несколько эффективных стратегий решения этой задачи в среде 1С 7.7 и 8.x с использованием объектной модели MSXML. Мы научимся как определять текущие префиксы, так и работать полностью независимо от них.
Рассмотрим самый надежный и профессиональный метод. Вместо того чтобы пытаться угадать, какой префикс назначил отправитель, мы можем «приказать» объекту DOMDocument использовать наши собственные префиксы для конкретных URI. Это позволяет писать стабильный код, который не сломается при изменении входного файла.
Проанализируем ситуацию: нам пришел документ, где данные лежат в пространстве http://www.w3.org/TR/html4/. Мы хотим всегда обращаться к ним по префиксу myh. Для этого воспользуемся методом setProperty с параметром SelectionNamespaces.
// Создаем объект DOMDocument
xmlДок = СоздатьОбъект("Msxml2.DOMDocument.6.0");
xmlДок.async = 0;
xmlДок.load(ИмяФайла);
// Устанавливаем соответствие нашего префикса и URI документа
// Можно указать несколько пространств через пробел
ПространстваИмен = "xmlns:myh='http://www.w3.org/TR/html4/' xmlns:ns='http://fsrar.ru/WEGAIS/WB_DOC_SINGLE_01'";
xmlДок.setProperty("SelectionNamespaces", ПространстваИмен);
// Теперь мы можем делать запросы, используя НАШИ префиксы
Узел = xmlДок.selectSingleNode("//myh:table");
Если ПустоеЗначение(Узел) = 0 Тогда
Сообщить("Узел найден!");
КонецЕсли;
Важный нюанс: Перед выполнением выборок обязательно убедитесь, что в качестве языка запросов установлен XPath. В старых версиях MSXML (3.0) по умолчанию мог использоваться XSLPattern, который работает иначе. Выполним настройку:
xmlДок.setProperty("SelectionLanguage", "XPath");
Если структура документа проста и пространства имен не пересекаются (т.е. нет двух разных тегов с одинаковым именем в разных пространствах), мы можем полностью игнорировать префиксы. Свойство BaseName возвращает «чистое» имя тега без двоеточия и префикса.
Разберем пример обхода дерева узлов:
xmlДок = СоздатьОбъект("Msxml2.DOMDocument.6.0");
xmlДок.load(ИмяФайла);
// Получаем корневой элемент
Root = xmlДок.documentElement;
// Перебираем подчиненные узлы
Для н = 0 По Root.childNodes.length - 1 Цикл
ТекУзел = Root.childNodes.item(н);
// Вместо ТекУзел.nodeName используем BaseName
ИмяТега = ТекУзел.baseName;
Если ИмяТега = "Documents" Тогда
// Обрабатываем нужный узел
Сообщить("Нашли документ без учета префикса!");
КонецЕсли;
КонецЦикла;
Этот метод хорош для простого чтения, но он неудобен, если вам нужно использовать selectNodes для глубокого поиска в больших структурах.
Иногда нам все же нужно знать, какой префикс соответствует конкретному URI в текущем файле (например, для формирования ответного сообщения или логирования). Мы можем реализовать функцию, которая обходит документ и собирает карту соответствий в ТаблицуЗначений или СписокЗначений.
Посмотрим на пример рекурсивной процедуры:
Процедура ЗаполнитьТаблицуПрефиксов(Узел, ТаблицаПрефиксов)
// Извлекаем данные из текущего узла
ТекПрефикс = Узел.prefix;
ТекURI = Узел.namespaceURI;
Если СокрЛП(ТекПрефикс) <> "" Тогда
// Проверяем, нет ли уже такого префикса в таблице
// (логика поиска в ТЗ для 1С 7.7)
ТаблицаПрефиксов.НоваяСтрока();
ТаблицаПрефиксов.Префикс = ТекПрефикс;
ТаблицаПрефиксов.URI = ТекURI;
КонецЕсли;
// Рекурсивно идем по детям
Для и = 0 По Узел.childNodes.length - 1 Цикл
ЗаполнитьТаблицуПрефиксов(Узел.childNodes.item(и), ТаблицаПрефиксов);
КонецЦикла;
КонецПроцедуры
После обхода документа мы получим таблицу всех реально используемых префиксов. Однако помните, что в XML один и тот же URI может иметь разные префиксы в разных частях документа (хотя это и редкость для бизнес-данных), поэтому метод 1 (назначение своего префикса) остается более предпочтительным.
Существует способ написать XPath-запрос, который будет искать узлы по их локальному имени и URI, вообще не используя префиксы в строке запроса. Это делает код максимально гибким, хотя и несколько перегруженным синтаксически.
Проанализируем синтаксис такого запроса:
//*[local-name()='table' and namespace-uri()='http://www.w3.org/TR/html4/']
В 1С это будет выглядеть так:
Запрос = "//*[local-name()='Document' and namespace-uri()='http://fsrar.ru/WEGAIS/WB_DOC_SINGLE_01']";
СписокУзлов = xmlДок.selectNodes(Запрос);
Преимущества: Не нужно ничего настраивать в setProperty.
Недостатки: Снижение производительности на очень больших файлах (так как MSXML приходится проверять функции для каждого узла) и громоздкость кода.
При работе в 1С 7.7 крайне важно правильно инициализировать COM-объект. Рекомендуется использовать версию 6.0, так как она наиболее стабильна, безопасна и поддерживает современные стандарты парсинга.
Msxml2.DOMDocument.6.0 вместо устаревших Microsoft.XMLDOM или Msxml2.DOMDocument.3.0.async = 0 (выключайте асинхронную загрузку), чтобы программа дожидалась полного прочтения файла перед началом парсинга.xmlДок.setProperty("ResolveExternals", 1).Подводя итог, можно сказать, что наиболее сбалансированным решением является Метод №1. Он сочетает в себе высокую производительность XPath-запросов и независимость от стиля кодирования XML-файла вашим контрагентом. Вы просто определяете правила игры в начале работы с документом, назначая привычные вам префиксы нужным адресам пространств имен.