При работе с выгрузкой данных из 1С в формат XML мы часто сталкиваемся с необходимостью корректно обрабатывать многострочный текст, например, содержимое полей, таких как "ДомашнееЗадание". Обычная выгрузка часто приводит к тому, что все строки записываются в одну, что делает данные нечитаемыми и сложными для дальнейшей обработки. Давайте разберем, как правильно сохранить и передать символы переноса строки при выгрузке в XML.
Предположим, у нас есть документ в 1С, содержащий поле "ДомашнееЗадание" с несколькими строками текста. Наша задача — выгрузить это поле в XML таким образом, чтобы информация о переносах строк была сохранена и могла быть распознана внешней системой (например, сайтом).
Прежде всего, давайте рассмотрим типичный код выгрузки в XML, который приводит к проблеме с переносами строк. Мы используем объект ЗаписьXML для создания XML-файла:
Путь = Константы.ПутьВыгрузкиДЗ.Получить();
Файл = Новый ЗаписьXML;
Файл.ОткрытьФайл(Путь + "my_xml.xml");
Файл.ЗаписатьОбъявлениеXML();
Файл.ЗаписатьНачалоЭлемента("Корневой");
Файл.ЗаписатьАтрибут("ВыгружаемыйДокумент","Занятие");
Файл.ЗаписатьКомментарий("Выгрузка домашнего задания");
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| Занятие.Дата КАК Дата,
| Занятие.Дисциплина КАК Дисциплина,
| Занятие.Преподаватель КАК Преподаватель,
| Занятие.ДомашнееЗадание КАК ДомашнееЗадание,
| Занятие.Тема КАК Тема,
| Занятие.Группа КАК Группа,
| Занятие.ВидНагрузки КАК ВидНагрузки
|ИЗ
| Документ.Занятие КАК Занятие";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий Цикл
Файл.ЗаписатьНачалоЭлемента("Элемент");
Файл.ЗаписатьАтрибут("Дата",Строка(Выборка.Дата));
Файл.ЗаписатьАтрибут("Дисциплина",Строка(Выборка.Дисциплина));
Файл.ЗаписатьНачалоЭлемента("Преподаватель");
Файл.ЗаписатьТекст(Строка(Выборка.Преподаватель));
Файл.ЗаписатьКонецЭлемента();
Файл.ЗаписатьНачалоЭлемента("Тема");
Файл.ЗаписатьТекст(Строка(Выборка.Тема));
Файл.ЗаписатьКонецЭлемента();
Файл.ЗаписатьНачалоЭлемента("ДомашнееЗадание");
Файл.ЗаписатьТекст(Строка(Выборка.ДомашнееЗадание)); // Здесь проблема с переносами
Файл.ЗаписатьКонецЭлемента();
Файл.ЗаписатьНачалоЭлемента("Группа");
Файл.ЗаписатьТекст(Строка(Выборка.Группа));
Файл.ЗаписатьКонецЭлемента();
Файл.ЗаписатьНачалоЭлемента("ВидНагрузки");
Файл.ЗаписатьТекст(Строка(Выборка.ВидНагрузки));
Файл.ЗаписатьКонецЭлемента();
Файл.ЗаписатьКонецЭлемента();
КонецЦикла;
Файл.ЗаписатьКонецЭлемента(); // Закрываем "Корневой"
Файл.Закрыть();
В этом коде строка Файл.ЗаписатьТекст(Строка(Выборка.ДомашнееЗадание)); записывает содержимое поля "ДомашнееЗадание" в XML. Однако, при открытии XML-файла в браузере или блокноте, мы можем обнаружить, что весь многострочный текст слился в одну строку.
Первое, что часто приходит на ум при форматировании XML, это использование ПараметрыЗаписиXML. Посмотрим на пример его применения:
Файл = Новый ЗаписьXML;
Файл.ОткрытьФайл(Путь + "my_xml.xml", Новый ПараметрыЗаписиXML(,,,,Символы.ПС));
Здесь мы добавляем Символы.ПС в качестве параметра для ПараметрыЗаписиXML. Что это дает? На самом деле, этот параметр влияет на форматирование самого XML-документа, то есть на то, как элементы и атрибуты будут располагаться в файле — будут ли они разделены переносами строк и отступами для лучшей читаемости. Это очень полезно для отладки и просмотра структуры XML, но не влияет на содержимое текстовых полей внутри элементов.
Если мы применим этот код, а затем посмотрим на выгруженный XML, то увидим, что элементы вроде <Преподаватель> или <Тема> будут выведены на отдельных строках с отступами, но текст внутри элемента <ДомашнееЗадание> по-прежнему останется однострочным, если в самом тексте не было явно указано иное.
Мы выяснили, что ПараметрыЗаписиXML форматирует XML-документ, но не текст внутри элементов. Следовательно, если мы хотим, чтобы переносы строк появились внутри текста элемента, нам необходимо явно указать их в строке перед записью. 1С не "знает", где в вашем многострочном поле должен быть перенос, если вы его не обозначили.
Для обозначения переноса строки в 1С используется системный символ Символы.ПС (Line Feed). Давайте посмотрим, как его можно вставить:
СтрТекст = "Тут первая строка" + Символы.ПС + "тут вторая строка" + Символы.ПС + "тут третья строка";
Файл.ЗаписатьТекст(СтрТекст);
При использовании ЗаписатьТекст(), 1С автоматически экранирует специальные символы XML, такие как <, >, &, и, что важно для нас, Символы.ПС. Это означает, что в итоговом XML-файле Символы.ПС (который соответствует символу LF или байту 0x0A) будет представлен как 
 или просто байтом 0x0A, что является стандартным и корректным способом представления переноса строки в XML.
При записи данных в XML, особенно если это не простые строки (например, даты, булевы значения или числа, которые могут иметь специфический формат), рекомендуется использовать функцию XMLСтрока(). Эта функция выполняет стандартизированное преобразование значений к строковому представлению, соответствующему требованиям XML-схем. Например, даты будут преобразованы в формат ISO 8601, а текст будет корректно экранирован.
Поэтому, вместо Файл.ЗаписатьТекст(Строка(Выборка.ДомашнееЗадание)); правильнее использовать:
Файл.ЗаписатьТекст(XMLСтрока(Выборка.ДомашнееЗадание));
Это гарантирует, что все специальные символы в тексте будут правильно экранированы, а данные будут представлены в стандартном для XML виде.
Даже если мы корректно вставим Символы.ПС в наш многострочный текст, и 1С правильно экранирует его в XML как 
, при просмотре XML-файла в браузере (особенно в Internet Explorer) мы можем не увидеть ожидаемых переносов строк. Браузеры по умолчанию интерпретируют текст в XML-элементах как обычный текст и игнорируют символы переноса строк, заменяя их пробелами (аналогично тому, как они обрабатывают текст в HTML, если он не находится внутри тега <pre>).
Это не означает, что переносов строк нет в самом XML-файле. Они там есть, но браузер их просто не отображает визуально. Для проверки наличия переносов, мы всегда рекомендуем открыть XML-файл в текстовом редакторе (например, Блокноте, Notepad++, VS Code), который явно отображает все символы, включая невидимые.
Если цель состоит в том, чтобы разработчик сайта получил информацию о местах переносов строк для дальнейшей обработки на своей стороне, и стандартное XML-представление переносов (
) по какой-то причине не подходит или неудобно, мы можем договориться о передаче пользовательского маркера.
Один из эффективных подходов, который был применен в обсуждаемой теме, это замена стандартных символов переноса строки Символы.ПС на некий условный маркер, например, "/n". Этот маркер затем может быть легко распознан и обработан внешней системой.
Давайте рассмотрим, как это реализовать в нашем коде:
// ... (начало кода)
Файл.ЗаписатьНачалоЭлемента("ДомашнееЗадание");
// Используем СтрЗаменить для замены Символы.ПС на наш условный маркер "/n"
Файл.ЗаписатьТекст(СтрЗаменить(Выборка.ДомашнееЗадание, Символы.ПС, "/n"));
Файл.ЗаписатьКонецЭлемента();
// ... (продолжение кода)
В этом случае, в XML-файле вместо невидимых символов переноса строки появятся видимые текстовые последовательности /n. Разработчик сайта, получивший такой XML, будет знать, что каждый /n означает перенос строки и при отображении данных на сайте должен будет заменить его на соответствующий HTML-тег <br/> или любой другой механизм форматирования (например, путем установки CSS-свойства white-space: pre-wrap; для элемента, содержащего текст).
Важный момент: Такое решение требует согласования с разработчиком принимающей системы. Обе стороны должны четко понимать, какой символ или последовательность символов будет использоваться как маркер переноса строки, и как он должен быть обработан.
Давайте соберем все рекомендации в единый, улучшенный код для выгрузки в XML:
Путь = Константы.ПутьВыгрузкиДЗ.Получить();
Файл = Новый ЗаписьXML;
// Открываем файл с ПараметрамиЗаписиXML для форматирования самого XML-документа
Файл.ОткрытьФайл(Путь + "my_xml.xml", Новый ПараметрыЗаписиXML(,,,,Символы.ПС));
Файл.ЗаписатьОбъявлениеXML();
Файл.ЗаписатьНачалоЭлемента("Корневой");
Файл.ЗаписатьАтрибут("ВыгружаемыйДокумент","Занятие");
Файл.ЗаписатьКомментарий("Выгрузка домашнего задания");
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| Занятие.Дата КАК Дата,
| Занятие.Дисциплина КАК Дисциплина,
| Занятие.Преподаватель КАК Преподаватель,
| Занятие.ДомашнееЗадание КАК ДомашнееЗадание,
| Занятие.Тема КАК Тема,
| Занятие.Группа КАК Группа,
| Занятие.ВидНагрузки КАК ВидНагрузки
|ИЗ
| Документ.Занятие КАК Занятие";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий Цикл
Файл.ЗаписатьНачалоЭлемента("Элемент");
// Используем XMLСтрока для атрибутов
Файл.ЗаписатьАтрибут("Дата", XMLСтрока(Выборка.Дата));
Файл.ЗаписатьАтрибут("Дисциплина", XMLСтрока(Выборка.Дисциплина));
Файл.ЗаписатьНачалоЭлемента("Преподаватель");
Файл.ЗаписатьТекст(XMLСтрока(Выборка.Преподаватель));
Файл.ЗаписатьКонецЭлемента();
Файл.ЗаписатьНачалоЭлемента("Тема");
Файл.ЗаписатьТекст(XMLСтрока(Выборка.Тема));
Файл.ЗаписатьКонецЭлемента();
Файл.ЗаписатьНачалоЭлемента("ДомашнееЗадание");
// Заменяем Символы.ПС на '/n' для передачи во внешнюю систему
Файл.ЗаписатьТекст(СтрЗаменить(XMLСтрока(Выборка.ДомашнееЗадание), Символы.ПС, "/n"));
Файл.ЗаписатьКонецЭлемента();
Файл.ЗаписатьНачалоЭлемента("Группа");
Файл.ЗаписатьТекст(XMLСтрока(Выборка.Группа));
Файл.ЗаписатьКонецЭлемента();
Файл.ЗаписатьНачалоЭлемента("ВидНагрузки");
Файл.ЗаписатьТекст(XMLСтрока(Выборка.ВидНагрузки));
Файл.ЗаписатьКонецЭлемента();
Файл.ЗаписатьКонецЭлемента();
КонецЦикла;
Файл.ЗаписатьКонецЭлемента(); // Закрываем "Корневой"
Файл.Закрыть();
(или просто байтом 0x0A), а возврат каретки (Carriage Return, CR) — как (байт 0x0D). 1С при использовании XMLСтрока() и ЗаписатьТекст() будет автоматически экранировать Символы.ПС (который является LF) в этот стандартный вид. Это наиболее корректный и стандартный подход с точки зрения XML. Разработчик сайта должен быть готов парсить эти XML-сущности и преобразовывать их в HTML-переносы.<![CDATA[...]]>, интерпретируется парсером как чистый текстовый блок, без какой-либо XML-разметки. Переносы строк внутри CDATA будут сохранены "как есть".
Файл.ЗаписатьНачалоЭлемента("ДомашнееЗадание");
Файл.ЗаписатьНачалоСекцииCDATA();
Файл.ЗаписатьТекст(XMLСтрока(Выборка.ДомашнееЗадание)); // Здесь Символы.ПС будут сохранены напрямую
Файл.ЗаписатьКонецСекцииCDATA();
Файл.ЗаписатьКонецЭлемента();
Использование CDATA может быть полезно, если содержимое элемента очень сложное, содержит много специальных символов XML, или если есть необходимость передать HTML-разметку внутри XML-элемента. Однако, для простых переносов строк стандартное экранирование (
) обычно является достаточным.
white-space: pre-wrap; к элементу, отображающему содержимое "ДомашнегоЗадания". Это свойство заставит браузер сохранять пробелы и переносы строк, а также переносить текст на новую строку, если он достигает конца контейнера. Это работает при условии, что в XML-элементе присутствуют стандартные символы переноса строки (
).В конечном итоге, выбор метода передачи переносов строк зависит от требований принимающей системы и договоренностей между разработчиками. Наиболее стандартный и надежный способ — это позволить 1С автоматически экранировать Символы.ПС в 
. Если же требуется явный, видимый маркер для обработки внешней системой, тогда подход с СтрЗаменить(..., Символы.ПС, "/n") является прагматичным решением, требующим предварительного согласования. Всегда помните о функции XMLСтрока() для корректной подготовки данных к выгрузке в XML.