Пишем XML из 1С через ЗаписьXML: от пустого файла до выгрузки документов

· 10 мин

Содержание

XML-файл в 1С 8.3 создают объектом ЗаписьXML: открываете файл методом ОткрытьФайл(), пишете объявление, затем элементы парами ЗаписатьНачалоЭлемента() / ЗаписатьКонецЭлемента(), а в конце вызываете Закрыть(). Всё это делается кодом на встроенном языке, в Конфигураторе или во внешней обработке. Готовой кнопки «Сохранить как XML» в типовых конфигурациях для произвольных данных нет.

Частое заблуждение — собирать XML обычной строкой через + и сохранять в текстовый файл. Это работает до первого контрагента с амперсандом в названии: ООО "Рога & Копыта" ломает файл, и получатель его не откроет. ЗаписьXML экранирует спецсимволы сама и следит за кодировкой. Механизм общий для платформы и не зависит от конфигурации: код одинаково работает в БП 3.0, УТ 11 и ЗУП 3.1.

Подготовка: где писать код и как настроить ЗаписьXML

Для разовой выгрузки удобнее всего внешняя обработка. В Конфигураторе: Файл → Новый → Внешняя обработка, добавьте форму, на форму — команду. Обработчик команды на клиенте вызывает серверную процедуру, где и формируется файл.

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

Создание объекта и открытие файла

Минимальная инициализация выглядит так:

Запись = Новый ЗаписьXML;
Запись.ОткрытьФайл("C:\Обмен\export.xml", "UTF-8");

Второй параметр — кодировка. Если нужны отступы и переносы строк (файл читает человек), используйте объект ПараметрыЗаписиXML:

// Кодировка, версия XML, отступы
Параметры = Новый ПараметрыЗаписиXML("UTF-8", "1.0", Истина);
Запись = Новый ЗаписьXML;
Запись.ОткрытьФайл(ИмяФайла, Параметры);

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

Запись в строку вместо файла

Если XML нужен для HTTP-запроса, веб-сервиса или отладки, файл не нужен вовсе:

Запись = Новый ЗаписьXML;
Запись.УстановитьСтроку("UTF-8");
// ... запись элементов ...
ТекстXML = Запись.Закрыть();

При записи в строку метод Закрыть() возвращает готовый текст. Удобно посмотреть результат в отладчике до того, как писать на диск.

Какой способ выбрать

У платформы несколько механизмов работы с XML. ЗаписьXML — основной для потоковой выгрузки, но не единственный.

СпособКогда подходитОграничения
ЗаписьXMLПроизвольная структура, большие объёмы, построчная выгрузкаСтруктуру держите в голове сами: открыли элемент — закройте
ДокументDOM + ЗаписьDOMНужно собрать дерево целиком, потом что-то поправить в серединеВесь документ в памяти, на сотнях тысяч узлов тяжело
XDTO-пакет по XSD-схемеПолучатель дал схему (банк, ФНС-подобный формат, маркетплейс)Нужно загрузить схему в конфигурацию или фабрику XDTO
ЗаписатьXML(Запись, Значение)Сериализация объектов 1С для обмена между базами 1СФормат «внутренний», сторонние системы его обычно не ждут

Дальше речь про ЗаписьXML: для выгрузки в стороннюю систему по описанию формата его хватает почти всегда.

Как задать объявление и корневой элемент XML-документа

Первым делом пишется объявление — строка <?xml version="1.0" encoding="UTF-8"?>:

Запись.ЗаписатьОбъявлениеXML();

Кодировку в объявление платформа подставляет ту, с которой открыт файл. Вручную её дописывать не надо.

Затем — корневой элемент. В правильном XML он ровно один, всё остальное лежит внутри него:

Запись.ЗаписатьНачалоЭлемента("Выгрузка");
// ... всё содержимое ...
Запись.ЗаписатьКонецЭлемента(); // закрывает <Выгрузка>

Пространства имён

Если формат получателя требует namespace, его объявляют сразу после открытия корня:

Запись.ЗаписатьНачалоЭлемента("Выгрузка");
Запись.ЗаписатьСоответствиеПространстваИмен("", "http://example.ru/exchange/1.0");

Пустой первый параметр задаёт пространство имён по умолчанию. Если нужен префикс, передайте его первым параметром, например "ex".

Правила имён элементов

  • ✅ Кириллица допустима: <Контрагент> — корректное имя.
  • ❌ Пробелы нельзя: "Дата документа" даст ошибку или невалидный файл.
  • ❌ Имя не может начинаться с цифры: "1Строка" — нельзя.
  • ⚠️ Регистр важен: <Товар> и <товар> — разные элементы. Сверяйте с описанием формата получателя буквально.

Атрибуты и текст внутри узлов

Узел XML может нести данные двумя способами: атрибутом (<Товар Код="00012">) и текстом (<Цена>150.00</Цена>). Какой выбрать — решает формат получателя. Если формат свой, атрибуты удобны для коротких идентификаторов, текст — для всего остального.

Запись атрибута

Запись.ЗаписатьНачалоЭлемента("Товар");
Запись.ЗаписатьАтрибут("Код", "00012");
Запись.ЗаписатьАтрибут("Артикул", "АБ-15");

Атрибуты пишутся сразу после ЗаписатьНачалоЭлемента(), до текста и вложенных элементов. Попытка записать атрибут после вложенного узла вызовет ошибку выполнения.

Запись текстового значения

Запись.ЗаписатьНачалоЭлемента("Наименование");
Запись.ЗаписатьТекст("Бумага офисная А4");
Запись.ЗаписатьКонецЭлемента();

Символы <, >, & в тексте и кавычки в атрибутах платформа экранирует сама. Делать СтрЗаменить(..., "&", "&amp;") вручную не нужно — получится двойное экранирование &amp;amp;.

Числа, даты и булевы значения: функция XMLСтрока

Оба метода — ЗаписатьАтрибут() и ЗаписатьТекст() — принимают только строку. Передать число напрямую нельзя, а Строка(1500.5) вернёт представление по региональным настройкам: с пробелом-разделителем разрядов и запятой. Внешняя система такое число не разберёт.

Используйте XMLСтрока():

Значение в 1ССтрока()XMLСтрока()
Число 1500,51 500,51500.5
Дата 15.03.202515.03.2025 0:00:002025-03-15T00:00:00
Булево ИстинаДаtrue
Ссылка на элемент справочникаНаименованиеУникальный идентификатор

Это главный приём, о котором забывают при первой выгрузке. Числа и даты — всегда через XMLСтрока(). Если получатель ждёт дату в другом виде, например 15.03.2025, используйте Формат(Дата, "ДФ=dd.MM.yyyy").

CDATA и комментарии

Для текста с HTML-разметкой или большим количеством спецсимволов есть ЗаписатьСекциюCDATA(). Содержимое попадёт в файл как есть, без экранирования. Комментарий в файл пишет ЗаписатьКомментарий("Выгружено из БП 3.0") — полезно для отладки, получателю он не мешает.

Вложенные элементы и выгрузка данных из базы в цикле

Реальная задача — не один товар, а сотни документов со строками. Структура получается многоуровневой: корень → документ → табличная часть → строка. Каждому открытому элементу нужен свой ЗаписатьКонецЭлемента().

Вспомогательная процедура для простых полей

Чтобы не писать по три строки на каждое поле, вынесите их в процедуру:

&НаСервереБезКонтекста
Процедура ЗаписатьПоле(Запись, Имя, Значение)
	Запись.ЗаписатьНачалоЭлемента(Имя);
	Если ТипЗнч(Значение) = Тип("Строка") Тогда
		Запись.ЗаписатьТекст(Значение);
	ИначеЕсли Значение <> NULL И Значение <> Неопределено Тогда
		Запись.ЗаписатьТекст(XMLСтрока(Значение));
	КонецЕсли;
	Запись.ЗаписатьКонецЭлемента();
КонецПроцедуры

Процедура сама выбирает: строку пишет как есть, число и дату — через XMLСтрока(). Пустое значение из запроса (NULL) даёт пустой элемент, а не ошибку.

Пример: выгрузка реализаций за период

Запрос берёт строки табличной части «Товары» проведённых документов и упорядочивает их по документу. В цикле отслеживаем смену документа: пришла строка нового — закрываем предыдущий и открываем следующий.

&НаСервереБезКонтекста
Процедура ВыгрузитьРеализации(ИмяФайла, Начало, Конец)

	Запрос = Новый Запрос;
	Запрос.Текст =
	"ВЫБРАТЬ
	|	Т.Ссылка КАК Документ,
	|	Т.Ссылка.Номер КАК Номер,
	|	Т.Ссылка.Дата КАК Дата,
	|	Т.Номенклатура.Наименование КАК Товар,
	|	Т.Количество КАК Количество,
	|	Т.Сумма КАК Сумма
	|ИЗ
	|	Документ.РеализацияТоваровУслуг.Товары КАК Т
	|ГДЕ
	|	Т.Ссылка.Дата МЕЖДУ &Начало И &Конец
	|	И Т.Ссылка.Проведен
	|УПОРЯДОЧИТЬ ПО
	|	Т.Ссылка.Дата,
	|	Т.Ссылка,
	|	Т.НомерСтроки";
	Запрос.УстановитьПараметр("Начало", НачалоДня(Начало));
	Запрос.УстановитьПараметр("Конец", КонецДня(Конец));
	Выборка = Запрос.Выполнить().Выбрать();

	Параметры = Новый ПараметрыЗаписиXML("UTF-8", "1.0", Истина);
	Запись = Новый ЗаписьXML;
	Запись.ОткрытьФайл(ИмяФайла, Параметры);
	Запись.ЗаписатьОбъявлениеXML();

	Запись.ЗаписатьНачалоЭлемента("Выгрузка");
	Запись.ЗаписатьАтрибут("ДатаФормирования", XMLСтрока(ТекущаяДатаСеанса()));

	ТекущийДокумент = Неопределено;
	Пока Выборка.Следующий() Цикл

		Если Выборка.Документ <> ТекущийДокумент Тогда
			Если ТекущийДокумент <> Неопределено Тогда
				Запись.ЗаписатьКонецЭлемента(); // </Товары>
				Запись.ЗаписатьКонецЭлемента(); // </Документ>
			КонецЕсли;
			Запись.ЗаписатьНачалоЭлемента("Документ");
			Запись.ЗаписатьАтрибут("Номер", Выборка.Номер);
			Запись.ЗаписатьАтрибут("Дата", XMLСтрока(Выборка.Дата));
			Запись.ЗаписатьНачалоЭлемента("Товары");
			ТекущийДокумент = Выборка.Документ;
		КонецЕсли;

		Запись.ЗаписатьНачалоЭлемента("Строка");
		ЗаписатьПоле(Запись, "Наименование", Выборка.Товар);
		ЗаписатьПоле(Запись, "Количество", Выборка.Количество);
		ЗаписатьПоле(Запись, "Сумма", Выборка.Сумма);
		Запись.ЗаписатьКонецЭлемента(); // </Строка>

	КонецЦикла;

	// Закрываем последний документ, если он был
	Если ТекущийДокумент <> Неопределено Тогда
		Запись.ЗаписатьКонецЭлемента(); // </Товары>
		Запись.ЗаписатьКонецЭлемента(); // </Документ>
	КонецЕсли;

	Запись.ЗаписатьКонецЭлемента(); // </Выгрузка>
	Запись.Закрыть();

КонецПроцедуры

Пример рассчитан на БП 3.0 и УТ 11, где у реализации есть табличная часть «Товары» с реквизитами Номенклатура, Количество, Сумма. В другой конфигурации откройте документ в дереве метаданных и подставьте свои имена.

На выходе получится такой файл:

<?xml version="1.0" encoding="UTF-8"?>
<Выгрузка ДатаФормирования="2025-03-20T10:41:07">
	<Документ Номер="0000-000124" Дата="2025-03-15T12:00:00">
		<Товары>
			<Строка>
				<Наименование>Бумага офисная А4</Наименование>
				<Количество>10</Количество>
				<Сумма>4500</Сумма>
			</Строка>
		</Товары>
	</Документ>
</Выгрузка>

Комментарий у каждого ЗаписатьКонецЭлемента() — не украшение. Метод не принимает имя элемента, он закрывает последний открытый. Через месяц, при доработке, комментарии спасут от перепутанной вложенности.

Почему именно запрос, а не обход справочника

Можно перебирать Справочники.Номенклатура.Выбрать() и обращаться к реквизитам через точку. Но каждое обращение к реквизиту ссылки — отдельное чтение из базы. На 50 000 позициях выгрузка затянется на минуты. Запрос получает все нужные поля одним обращением.

Закрытие файла, исключения и передача пользователю

Пока не вызван Закрыть(), файл может оставаться открытым и недописанным. Если код упал посередине, на диске останется обрывок, который получатель примет за целую выгрузку. Отсюда два правила.

Правило 1: оборачивайте запись в Попытку

Запись = Новый ЗаписьXML;
Попытка
	Запись.ОткрытьФайл(ИмяВременного, "UTF-8");
	// ... запись ...
	Запись.Закрыть();
Исключение
	ТекстОшибки = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());
	Попытка
		Запись.Закрыть();
	Исключение
		// Файл не успел открыться — закрывать нечего
	КонецПопытки;
	УдалитьФайлы(ИмяВременного);
	ЗаписьЖурналаРегистрации("Выгрузка XML",
		УровеньЖурналаРегистрации.Ошибка, , , ТекстОшибки);
	ВызватьИсключение;
КонецПопытки;

Внутренняя Попытка нужна потому, что ошибка могла произойти ещё в ОткрытьФайл(). Тогда Закрыть() сам вызовет исключение и скроет исходную причину.

Правило 2: пишите во временный файл, потом переносите

Формируйте XML во временный файл, полученный через ПолучитьИмяВременногоФайла("xml"). Только после успешного Закрыть() переносите его в папку обмена методом ПереместитьФайл(). Внешняя система, которая забирает файлы по расписанию, никогда не увидит недописанный документ.

Как отдать файл пользователю

Файл, записанный на сервере, лежит на диске сервера. Пользователь на своём компьютере его не увидит. Порядок такой:

  1. На сервере после Закрыть() прочитайте файл: Данные = Новый ДвоичныеДанные(ИмяВременного).
  2. Поместите их во временное хранилище: Адрес = ПоместитьВоВременноеХранилище(Данные, УникальныйИдентификатор).
  3. Удалите временный файл на сервере через УдалитьФайлы().
  4. На клиенте передайте адрес в процедуру сохранения. В конфигурациях на БСП (БП 3.0, УТ 11, ЗУП 3.1) это ФайловаяСистемаКлиент.СохранитьФайл(). Пользователь увидит обычный диалог «Сохранить как».

Если код пишется во внешней обработке, УникальныйИдентификатор берите у формы. Без привязки к форме данные хранилища удаляются после завершения вызова сервера.

Ошибки при создании XML и проблемы с кодировкой

Большая часть проблем всплывает не в 1С, а у получателя: файл сформирован, а загрузить его не удаётся.

Диагностика: выберите симптом

Кодировка windows-1251

Некоторые банки и старые учётные системы до сих пор ждут XML в windows-1251. Файл открывается так же, меняется только строка кодировки:

Запись.ОткрытьФайл(ИмяФайла, "windows-1251");
Запись.ЗаписатьОбъявлениеXML(); // encoding="windows-1251"

Учтите: в windows-1251 нет части символов — например, € или иероглифов. Если они встретятся в наименованиях, вместо них получится мусор. Для таких данных договаривайтесь с получателем на UTF-8.

Невидимые управляющие символы

Пользователи иногда копируют наименования из PDF или почты, и вместе с текстом в базу попадают невидимые управляющие символы. В XML 1.0 большинство символов с кодами от 0 до 31 запрещены, кроме табуляции и переводов строки. Строка с таким символом либо оборвёт запись ошибкой, либо файл не примет получатель. Решение — чистить строковые поля перед записью: перебрать коды 0–31 кроме 9, 10 и 13 и вырезать их через СтрЗаменить(Значение, Символ(Код), "").

Частые вопросы

Можно ли создать XML-файл в 1С без программирования?
Для произвольной структуры — нет, нужен код. Для переноса данных между базами 1С есть универсальная обработка «Выгрузка и загрузка данных XML», но её формат рассчитан на загрузку в другую базу 1С, а не во внешние системы.
Как проверить XML по схеме XSD, которую прислал получатель?
Загрузите схему в фабрику XDTO и попробуйте прочитать готовый файл через объект XDTO этой схемы. Несоответствие типов или обязательных элементов даст ошибку с указанием узла. Либо проверьте файл любым внешним валидатором XSD.
Что делать, если выгрузка большая и 1С зависает?
ЗаписьXML пишет потоково и память почти не расходует. Тормозит обычно запрос или обращения к реквизитам через точку в цикле. Перенесите все поля в запрос, а для миллионов строк разбейте выгрузку по периодам.
Можно ли дописать данные в уже существующий XML-файл?
Через ЗаписьXML — нет, он создаёт файл заново. Чтобы добавить узлы в готовый документ, прочитайте его в ДокументDOM, вставьте элементы и запишите через ЗаписьDOM, либо сформируйте файл целиком повторно.
Как выгрузить пустой элемент, если поле не заполнено?
Вызовите ЗаписатьНачалоЭлемента() и сразу ЗаписатьКонецЭлемента() без текста. Если формат требует вообще не выводить пустые поля, проверьте значение условием перед записью и пропустите элемент.