При разработке интерфейсов на управляемых формах часто возникает задача не просто загрузить файл на сервер, но и предварительно его обработать. Например, пользователь выбирает счет в формате Excel (XLS или XLSX), а система должна автоматически преобразовать его в PDF для единообразного хранения или печати. В этой статье мы подробно разберем, почему нельзя изменить файл «на лету» в момент выбора, и как правильно реализовать цепочку событий для достижения нужного результата.
Рассмотрим стандартный механизм асинхронного помещения файла. Метод НачатьПомещениеФайлаНаСервер() принимает несколько описаний оповещений, включая событие ПередНачаломОбратныйВызов. На первый взгляд кажется, что именно здесь можно перехватить выбранный файл и подменить его. Однако проанализируем технические ограничения платформы:
СсылкаНаФайл, который передается в это событие, предназначен только для чтения метаданных (имя, расширение, размер).Отказ = Истина, но не подменить содержимое.Следовательно, правильная стратегия заключается в том, чтобы позволить файлу переместиться во временное хранилище, а затем обработать его на стороне сервера в обработчике завершения.
Разберем процесс трансформации файла по шагам. Нам необходимо выполнить следующие действия:
ТабличныйДокумент и сохранить их в новом формате.Сначала подготовим процедуру запуска выбора файла. Мы будем использовать ПараметрыДиалогаПомещенияФайлов, чтобы ограничить выбор только нужными нам форматами.
Процедура ЗагрузитьСчетНаФорме()
ПараметрыДиалога = Новый ПараметрыДиалогаПомещенияФайлов;
ПараметрыДиалога.МножественныйВыбор = Ложь;
ПараметрыДиалога.Заголовок = "Выберите файл со сканом счета";
ПараметрыДиалога.Фильтр = "Файлы Excel (*.xls;*.xlsx)|*.xls;*.xlsx|Файл PDF (*.pdf)|*.pdf";
ЗавершениеОбратныйВызов = Новый ОписаниеОповещения("ПослеЗагрузкиФайла", ЭтотОбъект);
НачатьПомещениеФайлаНаСервер(ЗавершениеОбратныйВызов, , , , ПараметрыДиалога, ЭтаФорма.УникальныйИдентификатор);
КонецПроцедуры
Обратите внимание, что мы передаем УникальныйИдентификатор формы. Это критически важно: данные во временном хранилище будут «жить» до тех пор, пока открыта форма, что позволит нам манипулировать ими без риска преждевременного удаления.
Теперь проанализируем процедуру оповещения ПослеЗагрузкиФайла. Если пользователь выбрал XLS, мы должны запустить процесс конвертации.
&НаКлиенте
Процедура ПослеЗагрузкиФайла(Результат, ДополнительныеПараметры) Экспорт
Если Результат = Неопределено Тогда
Возврат;
КонецЕсли;
АдресВХ = Результат.Адрес;
Расширение = Результат.СсылкаНаФайл.Расширение;
Если НРег(Расширение) = ".xls" Или НРег(Расширение) = ".xlsx" Тогда
// Запускаем серверную конвертацию
НовыйАдресPDF = КонвертироватьXLSвPDFНаСервере(АдресВХ, Расширение);
АдресВХ = НовыйАдресPDF;
КонецЕсли;
// Дальнейшая логика записи адреса в справочник или документ
ЗаписатьФайлВБазу(АдресВХ);
КонецПроцедуры
Рассмотрим подробнее серверную функцию. Основным инструментом здесь выступает ТабличныйДокумент. Он обладает мощным методом Прочитать(), который понимает форматы Excel, и методом Записать(), умеющим сохранять результат в PDF.
&НаСервере
Функция КонвертироватьXLSвPDFНаСервере(Знач ИсходныйАдрес, Знач Расширение)
// 1. Получаем двоичные данные из хранилища
ДанныеExcel = ПолучитьИзВременногоХранилища(ИсходныйАдрес);
// 2. Создаем временный файл на диске сервера для чтения
ИмяВременногоФайла = ПолучитьИмяВременногоФайла(Расширение);
ДанныеExcel.Записать(ИмяВременногоФайла);
// 3. Читаем файл в табличный документ
ТабДок = Новый ТабличныйДокумент;
Попытка
ТабДок.Прочитать(ИмяВременногоФайла);
Исключение
ОбщегоНазначенияКлиентСервер.СообщитьПользователю("Ошибка чтения файла Excel: " + ОписаниеОшибки());
Возврат ИсходныйАдрес;
КонецПопытки;
// 4. Настраиваем параметры печати для корректного PDF
ТабДок.АвтоМасштаб = Истина;
ТабДок.ОриентацияСтраницы = ОриентацияСтраницы.Ландшафт; // Или Портрет
// 5. Сохраняем в PDF во временный файл
ИмяФайлаPDF = ПолучитьИмяВременногоФайла("pdf");
ТабДок.Записать(ИмяФайлаPDF, ТипФайлаТабличногоДокумента.PDF);
// 6. Помещаем PDF обратно в хранилище
ДанныеPDF = Новый ДвоичныеДанные(ИмяФайлаPDF);
НовыйАдрес = ПоместитьВоВременноеХранилище(ДанныеPDF, ЭтаФорма.УникальныйИдентификатор);
// 7. Очистка: удаляем старый адрес и временные файлы
УдалитьИзВременногоХранилища(ИсходныйАдрес);
УдалитьФайлы(ИмяВременногоФайла);
УдалитьФайлы(ИмяФайлаPDF);
Возврат НовыйАдрес;
КонецФункции
Разберем несколько важных моментов, которые помогут избежать ошибок при эксплуатации данного решения.
Работа с памятью: В коде выше мы использовали УдалитьИзВременногоХранилища(ИсходныйАдрес). Проанализируем ситуацию: когда файл загружается на сервер, он занимает место в оперативной памяти (или в сеансовых данных на диске). Если пользователи загружают сотни тяжелых файлов Excel, сервер может начать испытывать дефицит ресурсов. Принудительное удаление старого адреса после конвертации — признак хорошего тона в разработке.
Шрифты на сервере Linux: Если ваш сервер 1С работает под управлением Linux, вы можете столкнуться с тем, что в PDF вместо текста отображаются «квадратики». Это происходит из-за отсутствия стандартных шрифтов Windows (Arial, Times New Roman). Выясним причину: ТабличныйДокумент при рендеринге PDF ищет шрифты, указанные в исходном файле. Чтобы решить эту проблему, необходимо установить пакет msttcorefonts на сервер.
Параметры отображения: При конвертации Excel часто бывает слишком широким. Если не установить свойство АвтоМасштаб = Истина, PDF получится «разрезанным» на несколько листов по вертикали, что крайне неудобно для чтения счета. Также полезно анализировать количество колонок и программно менять ОриентацияСтраницы.
Начиная с версии платформы 8.3.15, мы можем сделать код более линейным и читаемым, используя конструкцию Ждать. Посмотрим на пример, как это упрощает логику:
&НаКлиенте
Асинхр Процедура ЗагрузитьИКонвертироватьАсинхронно()
ПараметрыДиалога = Новый ПараметрыДиалогаПомещенияФайлов;
// ... настройка параметров ...
Результат = Ждать ПоместитьФайлНаСерверАсинхрон( , , , ПараметрыДиалога, ЭтаФорма.УникальныйИдентификатор);
Если Результат <> Неопределено Тогда
Адрес = Результат.Адрес;
Если СтрЗаканчивается(Результат.СсылкаНаФайл.Имя, ".xls") Тогда
Адрес = КонвертироватьXLSвPDFНаСервере(Адрес, ".xls");
КонецЕсли;
// Продолжаем работу с переменной Адрес
КонецЕсли;
КонецПроцедуры
Этот подход избавляет нас от необходимости создавать отдельные процедуры-оповещения и делает структуру программы прозрачной.
Мы выяснили, что прямая подмена файла внутри метода НачатьПомещениеФайлаНаСервер невозможна. Оптимальный путь — это постобработка во временном хранилище. Использование объекта ТабличныйДокумент позволяет быстро и без сторонних библиотек конвертировать офисные документы в PDF. Помните о важности очистки временных данных и настройки параметров печати, чтобы ваши пользователи получали качественные и красивые документы.