Как программно исправить XML-файл с несколькими корневыми элементами для корректного чтения в 1С

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

При обмене данными с внешними системами разработчики часто сталкиваются с ситуацией, когда полученный XML-файл не соответствует стандартам W3C. Одной из самых распространенных ошибок является наличие в одном файле нескольких XML-прологов (заголовков <?xml ... ?>) или нескольких корневых элементов. Согласно спецификации, корректный XML-документ должен иметь строго один корневой элемент, в который вложены все остальные данные.

Стандартный объект 1С ЧтениеXML при попытке обработать такой файл выдает ошибку, так как он ожидает увидеть завершение документа после закрытия первого корневого тега. В этой статье мы подробно разберем, как программно преобразовать такой «составной» файл в правильный XML-документ, который платформа 1С сможет прочитать без ошибок.

Анализ проблемы: почему файл считается некорректным

Рассмотрим ситуацию, когда внешняя система (например, некорректно настроенный веб-сервис или скрипт выгрузки) формирует отчеты один за другим в один и тот же файл, просто дописывая их в конец. В итоге получается структура следующего вида:


<?xml version="1.0" encoding="windows-1251"?>
<Report>
    <Record>Данные 1</Record>
</Report>
<?xml version="1.0" encoding="windows-1251"?>
<Report>
    <Record>Данные 2</Record>
</Report>

Для 1С это не один документ, а два разных документа, склеенных в один файл. Объект ЧтениеXML прочитает первый блок Report и, встретив заголовок второго блока, прервет работу с ошибкой «строка содержит недопустимый символ» или «обнаружено несколько корневых элементов». Наша задача — превратить это в единую структуру с одним общим корнем.

Способ 1: Использование объекта ТекстовыйДокумент для небольших файлов

Если размер файла не превышает нескольких десятков мегабайт, мы можем воспользоваться объектом ТекстовыйДокумент. Этот метод прост в реализации, так как позволяет считать весь текст файла в оперативную память, выполнить замены и записать его обратно.

Рассмотрим алгоритм действий по шагам:

  1. Прочитаем файл как обычный текст, указав правильную кодировку (чаще всего это windows-1251 или UTF-8).
  2. Подсчитаем количество вхождений заголовков и корневых тегов, чтобы убедиться, что файл действительно требует исправления.
  3. Удалим все лишние XML-прологи.
  4. Удалим лишние закрывающие и открывающие теги корневого элемента, чтобы «слить» внутреннее содержимое в один блок.
  5. Обернем итоговый результат в один общий корневой тег и добавим корректный заголовок.

Проанализируем пример кода, который реализует эту логику:


Процедура ПроверитьИИсправитьФайлXML(ИмяФайлаДанных)
    // Создаем объект для работы с текстом
    Текст = Новый ТекстовыйДокумент;
    
    // Читаем файл. Важно указать кодировку, в которой пришел файл.
    Текст.Прочитать(ИмяФайлаДанных, "windows-1251");
    
    // Получаем все содержимое в одну строковую переменную
    СтрокаXML = Текст.ПолучитьТекст();
    
    // Определяем маркеры заголовков и корневых элементов (например, ReportXML)
    МаркерЗаголовка = "<?xml version=""1.0"" encoding=""windows-1251""?>";
    МаркерНачалаКорня = "<ReportXML>";
    МаркерКонцаКорня = "</ReportXML>";
    
    // Проверяем, есть ли дубликаты
    ЧислоВхожденийКодировки = СтрЧислоВхождений(СтрокаXML, МаркерЗаголовка);
    
    Если ЧислоВхожденийКодировки > 1 Тогда
        // Удаляем все упоминания заголовков, чтобы потом добавить один в начало
        СтрокаXML = СтрЗаменить(СтрокаXML, МаркерЗаголовка, "");
        
        // Удаляем все открывающие и закрывающие корневые теги
        // Таким образом, мы очищаем текст от промежуточных разделителей
        СтрокаXML = СтрЗаменить(СтрокаXML, МаркерНачалаКорня, "");
        СтрокаXML = СтрЗаменить(СтрокаXML, МаркерКонцаКорня, "");
        
        // Теперь собираем правильную структуру вручную:
        // Один заголовок + один открывающий корень + все данные + один закрывающий корень
        ИтоговаяСтрокаXML = МаркерЗаголовка + Символы.ПС 
                          + МаркерНачалаКорня + Символы.ПС 
                          + СтрокаXML + Символы.ПС 
                          + МаркерКонцаКорня;
        
        // Записываем исправленный текст обратно в файл
        Текст.УстановитьТекст(ИтоговаяСтрокаXML);
        Текст.Записать(ИмяФайлаДанных, "windows-1251");
        
        Сообщить("Файл успешно исправлен. Обнаружено и устранено блоков: " + ЧислоВхожденийКодировки);
    КонецЕсли;
КонецПроцедуры

Важный момент: При использовании СтрЗаменить нужно быть уверенным, что искомые строки (например, название тега <ReportXML>) не встречаются внутри самих данных (в значениях полей). Если такие совпадения возможны, следует использовать более сложную логику замены или регулярные выражения.

Способ 2: Потоковая обработка больших файлов

Метод с ТекстовыйДокумент.ПолучитьТекст() не подходит для файлов объемом в сотни мегабайт или гигабайты. В этом случае 1С может выдать ошибку «Недостаточно памяти». Для таких задач мы должны использовать объекты ЧтениеТекста и ЗаписьТекста, которые позволяют обрабатывать файл построчно без загрузки всего содержимого в RAM.

Разберем, как реализовать потоковую очистку:


Процедура ИсправитьБольшойXML(ИмяФайлаИсточник, ИмяФайлаПриемник)
    Чтение = Новый ЧтениеТекста(ИмяФайлаИсточник, "windows-1251");
    Запись = Новый ЗаписьТекста(ИмяФайлаПриемник, "windows-1251");
    
    // Записываем заголовок и открывающий тег один раз в начале
    Запись.ЗаписатьСтроку("<?xml version=""1.0"" encoding=""windows-1251""?>");
    Запись.ЗаписатьСтроку("<Root>");
    
    Строка = Чтение.ПрочитатьСтроку();
    Пока Строка <> Неопределено Цикл
        
        // Пропускаем строки, содержащие пролог XML
        Если Найти(Строка, "<?xml") > 0 Тогда
            Строка = Чтение.ПрочитатьСтроку();
            Продолжить;
        КонецЕсли;
        
        // Пропускаем повторные открывающие и закрывающие теги корневого элемента
        // Здесь мы предполагаем, что корень называется "Report"
        Если Найти(Строка, "<Report>") > 0 Или Найти(Строка, "</Report>") > 0 Тогда
            Строка = Чтение.ПрочитатьСтроку();
            Продолжить;
        КонецЕсли;
        
        // Все остальные строки (данные) записываем как есть
        Запись.ЗаписатьСтроку(Строка);
        
        Строка = Чтение.ПрочитатьСтроку();
    КонецЦикла;
    
    // Закрываем общий корневой тег
    Запись.ЗаписатьСтроку("</Root>");
    
    Чтение.Закрыть();
    Запись.Закрыть();
КонецПроцедуры

Такой подход позволяет обрабатывать файлы практически любого размера, так как в памяти одновременно находится только одна строка текста.

Особенности работы с кодировками и BOM

Проанализируем ситуацию с кодировками. Если ваш XML-файл сохранен в кодировке UTF-8, он может содержать так называемую метку BOM (Byte Order Mark) — невидимые символы в самом начале файла. При «склеивании» нескольких файлов эти метки могут оказаться в середине текста.

Стандартное СтрЗаменить может не увидеть эти символы, но ЧтениеXML обязательно споткнется на них. Чтобы избежать проблем, при записи исправленного файла в 1С рекомендуется явно указывать кодировку КодировкаТекста.UTF8 и следить за тем, чтобы промежуточные заголовки удалялись полностью, включая невидимые спецсимволы. Использование СтрНайти в сочетании с Сред позволяет более гибко вырезать куски текста, если вы не знаете точного содержания заголовка (например, если там меняется атрибут standalone).

Рекомендации по автоматизации

Выясним причину появления таких файлов. Часто это происходит из-за того, что внешняя программа не очищает файл перед выгрузкой новой порции данных, а использует режим дозаписи. Если у вас нет возможности повлиять на разработчиков внешней системы, лучшим решением будет создание «препроцессора» в 1С.

Создайте вспомогательную функцию, которая будет вызываться перед основной загрузкой данных. Эта функция должна проверять файл на «валидность». Самый быстрый способ проверки без полного чтения — попробовать открыть файл объектом ЧтениеXML и вызвать метод Следующий(). Если возникнет исключение в блоке Попытка...Исключение, значит, файл поврежден или содержит несколько корней, и его нужно пропустить через нашу процедуру исправления.

Резюме: Исправление XML как текстового файла — это надежный «костыль» для работы с некорректными внешними системами. Главное — правильно определить границы корневых элементов и не забыть про кодировку, чтобы не исказить данные при перезаписи.

← На главную