При работе с XML-данными в 1С часто возникает задача выборочного копирования элементов из одного XML-документа в другой, исключая при этом определенные узлы или изменяя структуру. Важно выполнять эту задачу эффективно, особенно когда речь идет о больших объемах данных, и, по возможности, избегать использования строковых функций для парсинга, так как это менее надежно и производительно.
Рассмотрим различные подходы к решению этой задачи в 1С, их преимущества, недостатки и особенности применения.
Этот подход является наиболее низкоуровневым, но при этом самым эффективным с точки зрения использования памяти, что делает его идеальным для обработки очень больших XML-файлов. Мы будем последовательно обходить исходный XML-документ, и при обнаружении необходимых элементов, записывать их в новый XML.
ЧтениеXML для пошагового чтения узлов исходного XML-документа.ЗаписьXML для формирования нового XML-документа.ТипУзла и ИмяУзла. Если узел соответствует нашим условиям выборочного копирования, мы записываем его в новый документ, включая его атрибуты и вложенные элементы.Пропустить(), чтобы не обрабатывать его дочерние элементы.Предположим, у нас есть исходный XML:
XML1 ="
<Documents>
<Document>
<Supplier ID="1"/>
<Buyer ID="2"/>
<Transporter ID="3"/>
</Document>
</Documents>";
И мы хотим получить:
<Documents>
<Document>
<Supplier ID="1"/>
<Buyer ID="2"/>
</Document>
</Documents>
Вот как мы можем реализовать это с помощью ЧтениеXML и ЗаписьXML:
ИсходныйXMLСтрока = XML1;
ЦелевойXMLСтрока = "";
// Создаем объекты для чтения и записи XML
ЧтениеИсходногоXML = Новый ЧтениеXML();
ЧтениеИсходногоXML.УстановитьСтроку(ИсходныйXMLСтрока);
ЗаписьЦелевогоXML = Новый ЗаписьXML();
ЗаписьЦелевогоXML.УстановитьСтроку(ЦелевойXMLСтрока);
ЗаписьЦелевогоXML.ЗаписатьОбъявлениеXML(); // Записываем объявление XML
// Обходим исходный XML
Пока ЧтениеИсходногоXML.Прочитать() Цикл
// Обрабатываем корневой элемент "Documents"
Если ЧтениеИсходногоXML.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
Если ЧтениеИсходногоXML.ИмяУзла = "Documents" Тогда
ЗаписьЦелевогоXML.ЗаписатьНачалоЭлемента(ЧтениеИсходногоXML.ИмяУзла);
// Копируем все атрибуты корневого элемента, если они есть
Для Каждого Атрибут Из ЧтениеИсходногоXML.Атрибуты Цикл
ЗаписьЦелевогоXML.ЗаписатьАтрибут(Атрибут.Имя, Атрибут.Значение);
КонецЦикла;
// Обходим дочерние элементы "Documents" (в данном случае - "Document")
Пока ЧтениеИсходногоXML.Прочитать() И ЧтениеИсходногоXML.ТипУзла <> ТипУзлаXML.КонецЭлемента И ЧтениеИсходногоXML.ИмяУзла <> "Documents" Цикл
Если ЧтениеИсходногоXML.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
Если ЧтениеИсходногоXML.ИмяУзла = "Document" Тогда
ЗаписьЦелевогоXML.ЗаписатьНачалоЭлемента(ЧтениеИсходногоXML.ИмяУзла);
// Копируем атрибуты элемента "Document", если есть
Для Каждого Атрибут Из ЧтениеИсходногоXML.Атрибуты Цикл
ЗаписьЦелевогоXML.ЗаписатьАтрибут(Атрибут.Имя, Атрибут.Значение);
КонецЦикла;
// Обходим дочерние элементы "Document"
Пока ЧтениеИсходногоXML.Прочитать() И ЧтениеИсходногоXML.ТипУзла <> ТипУзлаXML.КонецЭлемента И ЧтениеИсходногоXML.ИмяУзла <> "Document" Цикл
Если ЧтениеИсходногоXML.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
// Выборочное копирование: копируем "Supplier" и "Buyer", пропускаем "Transporter"
Если ЧтениеИсходногоXML.ИмяУзла = "Supplier" ИЛИ ЧтениеИсходногоXML.ИмяУзла = "Buyer" Тогда
ЗаписьЦелевогоXML.ЗаписатьНачалоЭлемента(ЧтениеИсходногоXML.ИмяУзла);
// Копируем атрибуты элемента (например, ID)
Для Каждого Атрибут Из ЧтениеИсходногоXML.Атрибуты Цикл
ЗаписьЦелевогоXML.ЗаписатьАтрибут(Атрибут.Имя, Атрибут.Значение);
КонецЦикла;
ЗаписьЦелевогоXML.ЗаписатьКонецЭлемента(); // Закрываем скопированный элемент
Иначе
// Пропускаем элемент "Transporter" и другие, если они не нужны
ЧтениеИсходногоXML.Пропустить();
КонецЕсли;
КонецЕсли;
КонецЦикла;
ЗаписьЦелевогоXML.ЗаписатьКонецЭлемента(); // Закрываем "Document"
Иначе
// Пропускаем другие элементы под "Documents", если они не "Document"
ЧтениеИсходногоXML.Пропустить();
КонецЕсли;
КонецЕсли;
КонецЦикла;
ЗаписьЦелевогоXML.ЗаписатьКонецЭлемента(); // Закрываем "Documents"
Иначе
// Пропускаем любые другие корневые элементы, если они не "Documents"
ЧтениеИсходногоXML.Пропустить();
КонецЕсли;
КонецЕсли;
КонецЦикла;
ЧтениеИсходногоXML.Закрыть();
РезультатXMLСтрока = ЗаписьЦелевогоXML.Закрыть();
Сообщить(РезультатXMLСтрока);
DOM представляет XML-документ в виде древовидной структуры объектов в памяти. Это удобный способ работы с XML, когда размер файла не является критическим.
ПостроительDOM для считывания всего XML-файла и построения его объектной модели в оперативной памяти.ПолучитьЭлементыПоИмени), чтения, изменения и удаления элементов.Для нашего примера с выборочным копированием Supplier и Buyer без Transporter, мы можем использовать DOM следующим образом:
ИсходныйXMLСтрока = XML1;
ЦелевойXMLСтрока = "";
// 1. Создаем построитель DOM и читаем исходный XML
Построитель = Новый ПостроительDOM;
ДокументИсходныйXML = Построитель.Прочитать(Новый ЧтениеXML().УстановитьСтроку(ИсходныйXMLСтрока));
// 2. Создаем новый DOM-документ для результата
ДокументЦелевойXML = Построитель.СоздатьДокумент();
// 3. Создаем корневой элемент "Documents" для целевого документа
КорневойЭлементЦелевой = ДокументЦелевойXML.СоздатьЭлемент("Documents");
ДокументЦелевойXML.ДобавитьДочерний(КорневойЭлементЦелевой);
// 4. Находим элемент "Document" в исходном XML
СписокДокументов = ДокументИсходныйXML.ПолучитьЭлементыПоИмени("Document");
Если СписокДокументов.Количество() > 0 Тогда
ЭлементИсходныйДокумент = СписокДокументов.Получить(0); // Берем первый найденный "Document"
// 5. Создаем элемент "Document" для целевого документа
ЭлементЦелевойДокумент = ДокументЦелевойXML.СоздатьЭлемент("Document");
КорневойЭлементЦелевой.ДобавитьДочерний(ЭлементЦелевойДокумент);
// 6. Обходим дочерние узлы исходного элемента "Document"
Для Каждого ДочернийУзел Из ЭлементИсходныйДокумент.ДочерниеУзлы Цикл
// Проверяем, является ли узел элементом и имеет ли нужное имя
Если ДочернийУзел.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
Если ДочернийУзел.ИмяУзла = "Supplier" ИЛИ ДочернийУзел.ИмяУзла = "Buyer" Тогда
// 7. Создаем новый элемент в целевом документе
НовыйДочернийЭлемент = ДокументЦелевойXML.СоздатьЭлемент(ДочернийУзел.ИмяУзла);
// 8. Копируем все атрибуты из исходного элемента в новый
Для Каждого Атрибут Из ДочернийУзел.Атрибуты Цикл
НовыйДочернийЭлемент.УстановитьАтрибут(Атрибут.Имя, Атрибут.Значение);
КонецЦикла;
// 9. Добавляем новый элемент в целевой "Document"
ЭлементЦелевойДокумент.ДобавитьДочерний(НовыйДочернийЭлемент);
КонецЕсли;
// Элемент "Transporter" будет пропущен, так как не соответствует условию
КонецЕсли;
КонецЦикла;
КонецЕсли;
// 10. Записываем получившийся DOM-документ в строку
ЗаписьЦелевогоXML = Новый ЗаписьXML();
ЗаписьЦелевогоXML.УстановитьСтроку(ЦелевойXMLСтрока);
ДокументЦелевойXML.Записать(ЗаписьЦелевогоXML);
РезультатXMLСтрока = ЗаписьЦелевойXML.Закрыть();
Сообщить(РезультатXMLСтрока);
XSLT - это мощный язык для преобразования XML-документов, который позволяет трансформировать один XML-формат в другой. Это очень эффективный способ для выборочного копирования и реструктуризации XML, особенно когда логику преобразования желательно отделить от основного кода 1С.
ПреобразованиеXSL для применения этой таблицы стилей к исходному XML.Сначала определим нашу XSLT-таблицу стилей. Она будет выглядеть так:
// XSLT-таблица стилей (можно сохранить в файл и загружать)
// В данном примере она встроена в строку
ТекстXSLT = "<?xml version=""1.0"" encoding=""UTF-8""?>
|<xsl:stylesheet version=""1.0"" xmlns:xsl=""http://www.w3.org/1999/XSL/Transform"">
| <xsl:output method=""xml"" indent=""yes""/>
|
| <!-- Главный шаблон: соответствует корневому элементу Documents -->
| <xsl:template match=""/Documents"">
| <Documents>
| <!-- Применяем шаблоны к дочерним элементам Document -->
| <xsl:apply-templates select=""Document""/>
| </Documents>
| </xsl:template>
|
| <!-- Шаблон для элемента Document -->
| <xsl:template match=""Document"">
| <Document>
| <!-- Копируем элемент Supplier со всеми его атрибутами и содержимым -->
| <xsl:copy-of select=""Supplier""/>
| <!-- Копируем элемент Buyer со всеми его атрибутами и содержимым -->
| <xsl:copy-of select=""Buyer""/>
| <!-- Элемент Transporter здесь не упоминается и, следовательно, будет пропущен -->
| </Document>
| </xsl:template>
|
| <!-- Шаблон по умолчанию для всех остальных узлов, чтобы они не копировались -->
| <xsl:template match=""text()|@*|node()""/>
|
|</xsl:stylesheet>";
Теперь применим эту таблицу стилей в 1С:
ИсходныйXMLСтрока = XML1;
ЦелевойXMLСтрока = "";
// Создаем объект ПреобразованиеXSL
Преобразователь = Новый ПреобразованиеXSL();
// Устанавливаем исходный XML-документ
ЧтениеИсходногоXML = Новый ЧтениеXML();
ЧтениеИсходногоXML.УстановитьСтроку(ИсходныйXMLСтрока);
Преобразователь.УстановитьИсходныйДокумент(ЧтениеИсходногоXML);
// Устанавливаем таблицу стилей XSLT
ЧтениеXSLT = Новый ЧтениеXML();
ЧтениеXSLT.УстановитьСтроку(ТекстXSLT);
Преобразователь.УстановитьТаблицуСтилей(ЧтениеXSLT);
// Выполняем преобразование
РезультатXMLСтрока = Преобразователь.Преобразовать();
// Закрываем потоки чтения
ЧтениеИсходногоXML.Закрыть();
ЧтениеXSLT.Закрыть();
Сообщить(РезультатXMLСтрока);
XDTO - это механизм 1С для обмена данными посредством XML, который позволяет оперировать не непосредственно узлами XML, а прикладными понятиями (объектами), определенными в XDTO-пакетах (схемах). Если у вас есть четко определенные XDTO-пакеты для исходной и целевой структуры XML, этот подход может быть очень удобным.
ФабрикаXDTO для создания объектов на основе описанных в пакетах типов.ФабрикаXDTO.ПрочитатьXML() считывает XML-данные и преобразует их в набор XDTO-объектов, соответствующих определенной схеме.ФабрикаXDTO.ЗаписатьXML().ЧтениеXMLВажный момент, на который мы обратили внимание в сообщениях форума (Сообщение 8 и 9): объект ЧтениеXML является потоковым. Это означает, что после того, как вы прочитали из него данные (например, с помощью ПостроительDOM.Прочитать(ЧтениеXML) или ФабрикаXDTO.ПрочитатьXML(ЧтениеXML)), указатель чтения перемещается в конец документа. Если вы попытаетесь прочитать из того же объекта ЧтениеXML повторно, он вернет пустые данные или вызовет ошибку, так как читать больше нечего.
Чтобы избежать этой ошибки, каждый раз, когда вы хотите прочитать XML-данные из строки или файла, необходимо создавать новый объект ЧтениеXML и устанавливать ему исходный XML-источник.
ИсходныйXMLСтрока = XML1;
// *** Пример ошибки (как в Сообщении 8) ***
// ЧтениеXML_Поток = Новый ЧтениеXML();
// ЧтениеXML_Поток.УстановитьСтроку(ИсходныйXMLСтрока);
//
// Построитель_DOM = Новый ПостроительDOM;
// ДокументXML_DOM = Построитель_DOM.Прочитать(ЧтениеXML_Поток); // Указатель ЧтениеXML_Поток теперь в конце
//
// // Следующая строка вызовет ошибку или прочитает ничего,
// // потому что ЧтениеXML_Поток уже прочитан до конца
// ДанныеXDTO = ФабрикаXDTO.ПрочитатьXML(ЧтениеXML_Поток);
// *** Правильный подход: каждый раз создаем новый объект ЧтениеXML ***
// 1. Использование DOM (например)
ЧтениеXML_ДляDOM = Новый ЧтениеXML();
ЧтениеXML_ДляDOM.УстановитьСтроку(ИсходныйXMLСтрока);
Построитель_DOM = Новый ПостроительDOM;
ДокументXML_DOM = Построитель_DOM.Прочитать(ЧтениеXML_ДляDOM);
ЧтениеXML_ДляDOM.Закрыть(); // Важно закрыть поток после использования
// 2. Использование XDTO после DOM (или в любой другой раз)
// Для XDTO нам потребуется определить XDTO-пакет, описывающий структуру XML.
// Предположим, у нас есть пакет "ПримерXML" и тип "DocumentsType".
// Пример определения XDTO-пакета (только для иллюстрации)
// В реальной ситуации вы бы загружали пакет из файла или метаданных
// ФабрикаXDTO.ЗагрузитьПакет("ПримерXML");
// ТипДокументовXDTO = ФабрикаXDTO.ПолучитьТип("http://www.example.com", "DocumentsType");
// Создаем новый объект ЧтениеXML для XDTO
ЧтениеXML_ДляXDTO = Новый ЧтениеXML();
ЧтениеXML_ДляXDTO.УстановитьСтроку(ИсходныйXMLСтрока);
// Если бы у нас был XDTO-пакет, мы бы прочитали так:
// ИсходныеДокументыXDTO = ФабрикаXDTO.ПрочитатьXML(ЧтениеXML_ДляXDTO, ТипДокументовXDTO);
// ЧтениеXML_ДляXDTO.Закрыть();
// **Концептуальный пример выборочного копирования с XDTO:**
// Предположим, что ИсходныеДокументыXDTO содержит структуру:
// ИсходныеДокументыXDTO.Document[0].Supplier.ID
// ИсходныеДокументыXDTO.Document[0].Buyer.ID
// ИсходныеДокументыXDTO.Document[0].Transporter.ID
// Создадим объект для целевых документов
// ЦелевыеДокументыXDTO = ФабрикаXDTO.Создать(ТипДокументовXDTO);
// Для Каждого ТекущийДокументXDTO Из ИсходныеДокументыXDTO.Document Цикл
// НовыйДокументXDTO = ФабрикаXDTO.Создать(ТипДокументовXDTO.Свойства.Получить("Document").Тип); // Тип для элемента Document
// НовыйДокументXDTO.Supplier = ТекущийДокументXDTO.Supplier; // Копируем Supplier
// НовыйДокументXDTO.Buyer = ТекущийДокументXDTO.Buyer; // Копируем Buyer
// // Элемент Transporter просто не копируем
// ЦелевыеДокументыXDTO.Document.Добавить(НовыйДокументXDTO);
// КонецЦикла;
// Запись полученных XDTO-объектов в XML
// ЗаписьЦелевогоXML = Новый ЗаписьXML();
// ЗаписьЦелевогоXML.УстановитьСтроку("");
// ФабрикаXDTO.ЗаписатьXML(ЗаписьЦелевогоXML, ЦелевыеДокументыXDTO);
// РезультатXMLСтрокаXDTO = ЗаписьЦелевогоXML.Закрыть();
// Сообщить("Результат XDTO: " + РезультатXMLСтрокаXDTO);
Сообщить("Пример XDTO показан концептуально, т.к. требует предварительного определения XDTO-пакетов.");
В этом примере мы видим, что для работы с XDTO требуется не только код 1С, но и заранее определенные XDTO-пакеты, описывающие структуру XML. Это делает XDTO мощным, но более сложным инструментом для ad-hoc задач.
При выборе метода и оптимизации работы с XML-файлами, особенно большими, мы рекомендуем учитывать следующие моменты:
ЧтениеXML / ЗаписьXML). XSLT может быть эффективен для любых размеров, так как его реализация часто оптимизирована для работы с потоками, но в 1С это зависит от конкретной реализации объекта ПреобразованиеXSL.В заключение, мы выяснили, что в 1С существует несколько эффективных способов выборочного копирования XML-элементов. Выбор конкретного метода должен основываться на размере обрабатываемых файлов, сложности требуемых преобразований и наличии предопределенных схем данных. Каждый из подходов – будь то потоковая обработка, DOM, XSLT или XDTO – имеет свои сильные стороны и оптимальные сценарии применения.