При разработке механизмов интеграции (например, с ЕГАИС через УТМ) программисты часто сталкиваются со специфическим поведением парсера MSXML 6.0. Суть проблемы заключается в том, что при создании дочернего узла без явного указания пространства имен внутри родительского узла, имеющего пространство имен по умолчанию, система автоматически добавляет атрибут xmlns="". Это делается для соблюдения стандартов XML, но принимающая сторона (УТМ) часто выдает ошибку, так как не ожидает этого атрибута. В данной статье мы разберем несколько эффективных способов решения этой задачи в среде 1С:Предприятие 7.7.
Прежде всего, проанализируем ситуацию. Когда мы используем MSXML2.DOMDocument.6.0, парсер строго следит за соответствием узлов заявленным пространствам имен (Namespaces). Если у корневого элемента задан xmlns="http://some-uri", а дочерний элемент создается простым методом createElement("name"), парсер считает, что новый элемент относится к "пустому" пространству имен. Чтобы это зафиксировать в итоговом файле, он добавляет xmlns="".
Самый "чистый" технический способ — это создание узла с явным указанием того же URI пространства имен, которое используется в родительском элементе. Рассмотрим подробнее, как это реализовать. Вместо createElement нам необходимо использовать createNode, где вторым параметром передается имя узла, а третьим — URI пространства имен.
Посмотрим на пример кода:
// Создаем объект DOM
XML_DOM = СоздатьОбъект("MSXML2.DOMDocument.6.0");
// ... формирование заголовка и корня ...
// Допустим, URI нашего пространства имен задан в переменной
НашURI = "http://fsrar.ru/WEGAIS/QueryFilter";
// Вместо XML_DOM.createElement("bc") используем:
XML_BC = XML_DOM.createNode(1, "bc", НашURI);
XML_QF.appendChild(XML_BC);
XML_BC.text = "101200026723...";
В этом случае парсер поймет, что элемент bc принадлежит к тому же пространству имен, что и родительский QueryFilter, и лишний атрибут xmlns="" не появится.
Если структура XML сложная или парсер MSXML продолжает вести себя непредсказуемо, мы можем применить метод прямой текстовой записи. Этот способ гарантирует, что в файл попадет ровно то, что мы написали, без самодеятельности со стороны системных библиотек. Для работы с файлами удобно использовать объект Scripting.FileSystemObject.
Рассмотрим пошагово реализацию этого метода:
Пример реализации функции выгрузки:
Функция ВыгрузитьВТекст(ИмяФайла, ТЗ)
ФСО = СоздатьОбъект("Scripting.FileSystemObject");
ТекстСтрим = ФСО.OpenTextFile(ИмяФайла, 2, 1, 0); // 2 - запись, 1 - создать если нет, 0 - ANSI
ТекстСтрим.WriteLine("<?xml version=""1.0"" encoding=""UTF-8""?>");
ТекстСтрим.WriteLine("<ns:Documents xmlns:ns=""http://fsrar.ru/WEGAIS/WB_DOC_SINGLE_01"" xmlns=""http://fsrar.ru/WEGAIS/QueryFilter"" Version=""1.0"">");
// ... запись Owner и других узлов ...
ТекстСтрим.WriteLine(" <ns:Document>");
ТекстСтрим.WriteLine(" <ns:QueryFilter>");
ТЗ.ВыбратьСтроки();
Пока ТЗ.ПолучитьСтроку() = 1 Цикл
ТекстСтрим.WriteLine(" <bc>" + СокрЛП(ТЗ.ШК) + "</bc>");
КонецЦикла;
ТекстСтрим.WriteLine(" </ns:QueryFilter>");
ТекстСтрим.WriteLine(" </ns:Document>");
ТекстСтрим.WriteLine("</ns:Documents>");
ТекстСтрим.Close();
КонецФункции
Для упрощения поддержки сложных XML-файлов удобно использовать текстовые шаблоны. Мы создаем эталонный файл, где на месте динамических данных вставляем маркеры (например, %DOCNUM%), а затем программно заменяем их на реальные значения.
Проанализируем ситуацию, когда у нас есть повторяющиеся блоки (строки товаров или марок). В этом случае мы используем два шаблона: основной и шаблон строки.
// Шаблон строки
ШаблонBC = "<bc>%MARK%</bc>";
ВсеМарки = "";
ТЗ.ВыбратьСтроки();
Пока ТЗ.ПолучитьСтроку() = 1 Цикл
ВсеМарки = ВсеМарки + СтрЗаменить(ШаблонBC, "%MARK%", СокрЛП(ТЗ.ШК)) + РазделительСтрок;
КонецЦикла;
// Вставляем накопленный блок в основной шаблон
ИтоговыйXML = СтрЗаменить(ОсновнойШаблон, "%MARK_LIST%", ВсеМарки);
Важный момент: при использовании текстовых методов нужно самостоятельно следить за экранированием спецсимволов XML (амперсанд, кавычки, угловые скобки). Для этого напишем вспомогательную функцию:
Функция Экранировать(Стр)
Рез = СтрЗаменить(Стр, "&", "&");
Рез = СтрЗаменить(Рез, "<", "<");
Рез = СтрЗаменить(Рез, ">", ">");
Рез = СтрЗаменить(Рез, """", """);
Возврат Рез;
КонецФункции
Выясним причину проблем с кодировкой. 1С 7.7 "из коробки" плохо работает с UTF-8, предпочитая Windows-1251. Если мы просто запишем файл через объект Текст, кодировка будет неверной. Чтобы корректно сохранить XML в UTF-8, воспользуемся объектом ADODB.Stream.
Разберем алгоритм перекодировки файла:
Процедура СохранитьВUTF8(ИмяФайла, ТекстДанных)
Стрим = СоздатьОбъект("ADODB.Stream");
Стрим.Type = 2; // adTypeText
Стрим.Charset = "utf-8";
Стрим.Open();
Стрим.WriteText(ТекстДанных);
Стрим.SaveToFile(ИмяФайла, 2); // adSaveCreateOverWrite
Стрим.Close();
КонецПроцедуры
Если вы уже написали большой объем кода на базе DOMDocument и не хотите его переделывать, можно применить "хирургическое вмешательство": сформировать XML, сохранить его во временный файл, а затем программно удалить подстроку xmlns="".
// Сохраняем результат работы DOM
XML_DOM.save(ПутьКФайлу);
// Читаем файл как текст и правим
ТекстФайл = СоздатьОбъект("Текст");
ТекстФайл.Открыть(ПутьКФайлу);
ВесьТекст = "";
Для Сч = 1 По ТекстФайл.КоличествоСтрок() Цикл
Стр = ТекстФайл.ПолучитьСтроку(Сч);
Стр = СтрЗаменить(Стр, " xmlns=""""", "");
ВесьТекст = ВесьТекст + Стр + РазделительСтрок;
КонецЦикла;
// Записываем обратно через ADODB.Stream для сохранения UTF-8
СохранитьВUTF8(ПутьКФайлу, ВесьТекст);
Мы рассмотрели различные подходы к формированию "чистого" XML в 1С 7.7. Для новых задач мы рекомендуем использовать либо правильное указание Namespace URI в методе createNode, либо текстовые шаблоны с последующим сохранением через ADODB.Stream. Текстовый метод является наиболее гибким, так как он полностью исключает влияние настроек парсера MSXML на структуру итогового документа.