С написания предыдущей статьи про генерацию Excel документов по шаблону прошло много времени и задача несколько изменилась. Новая задача была поставлена следующим образом: из готового документа excel или word сделать шаблон через веб-интерфейс. В процессе формирования подставлять в шаблон нужные значения, убирать и/или «клонировать» куски шаблона. После формирования, документ должен быть доступен пользователю для визуального редактирования в браузере. Готовый документ должен сохраниться на сервере, быть доступным для скачивания пользователем как в своём расширении (*.doc/*.xls), так и в pdf. При этом верстка скачиваемого файла должна быть идентична шаблону, который был загружен в самом начале (без всяких искажений полей и областей печати).Что же, задача есть — будем решать!
1. Испробованные инструменты
Сначала нужно решить, чем перегонять загружаемые файлы из doc, docx, xls, xlsx в html и обратно, при этом не испортив вёрстку.
Apache POI: Отличный инструмент, который мы успешно использовали, но он не умеет генерировать HTML разметку из существующего документа.
DocX4J: С этой либой была долгая история. Она умеет много всяких приятных вещей, о которых неоднократно писалось. И изначально мы именно этой библиотекой и хотели воспользоваться.
Недостатки DocX4J: работать можно только с docx и xlsx. Но это не так страшно. Проблемы начинаются, когда пытаешься HTML обратно сконвертировать в docx или xlsx. Едут все стили документа, шрифты прописываются вообще произвольные и т.д. Обратились к разработчику. Он сказал, что есть такая проблема и решена она частично в платной версии — docx4j-web-editor. Но и платная версия тоже со своими багами оказалась. В конце коцов от этой библиотеки тоже пришлось отказаться.
Решение — использовать LibreOffice. Пусть он сам на сервере конвертирует файлы в HTML и обратно. Осталось только связать его с нашим web-приложением.
Для работы с LibreOffice используется маленькая библиотека — jodconverter которая, к сожалению, давно не обновляется, но работает при этом отлично. Она подключается к LibreOffice через TCP сокет и отдает ему файл на конвертирование, в ответ приходит отконвертированный файл. Все это работает гораздо быстрее и правильнее чем все вышеперечисленные Java библиоткеи. Кроме того, LibreOffice работает в своем процессе, освобождая Java приложение от такой грамоздкой задачи, как разбор и хранение документа в куче web-приложения.
2. Загружаем файл на сервер и делаем из него шаблон
Но jodconverter умеет работать с файловой системой на сервере. Поэтому нужно передать в него из веб-приложения загружаемый файл и решить обратную задачу — сконвертировать HTML в нужного формата файл и отдать пользователю.
package ru.cpro.uchteno.util; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.logging.Level; import java.util.logging.Logger; import org.artofsolving.jodconverter.OfficeDocumentConverter; import org.artofsolving.jodconverter.office.ExternalOfficeManagerConfiguration; import org.artofsolving.jodconverter.office.OfficeConnectionProtocol; import org.artofsolving.jodconverter.office.OfficeManager; public class Libre {//Класс для конвертирования документов public static void doc2html(InputStream is, OutputStream os) {//конвертит doc в html try { File inf = File.createTempFile("doc", ".doc"); //создаем временный файл FileOutputStream infos = new FileOutputStream(inf); //делаем из него поток //переписываем все из входого потока в этот файл int n = 0; byte buff[] = new byte[1024]; while (n >= 0) { n = is.read(buff); if (n > 0) { infos.write(buff, 0, n); } } //закрываем все is.close(); infos.close(); //Создаем выходной файл File onf = File.createTempFile("doc", ".html"); //создаем конфигурацию jodconverter'а ExternalOfficeManagerConfiguration officeConfiguration = new ExternalOfficeManagerConfiguration(); //через tcp сокет officeConfiguration .setConnectionProtocol(OfficeConnectionProtocol.SOCKET); //порт officeConfiguration.setPortNumber(2002); //стрим по конфигу officeManager OfficeManager officeManager = officeConfiguration .buildOfficeManager(); //стартуем ьенеджера с имеющейся конфигурацией officeManager.start(); //Делаем конвертор OfficeDocumentConverter converter = new OfficeDocumentConverter( officeManager); //конвертируем документ через либреофис converter.convert(inf, onf); //останавливаем менеджера officeManager.stop(); //Теперь перегоняем из созданного офисом файла в выходной поток FileInputStream outfis = new FileInputStream(onf); n = 0; while (n >= 0) { n = outfis.read(buff); if (n > 0) { os.write(buff, 0, n); } } //закрываем все outfis.close(); os.close(); //Глушим временные файлы inf.delete(); onf.delete(); } catch (IOException ex) { Logger.getLogger(Libre.class.getName()).log(Level.SEVERE, null, ex); } } public static void doc2pdf(InputStream is, OutputStream os) { try { File inf = File.createTempFile("doc", ".doc"); FileOutputStream infos = new FileOutputStream(inf); int n = 0; byte buff[] = new byte[1024]; while (n >= 0) { n = is.read(buff); if (n > 0) { infos.write(buff, 0, n); } } is.close(); infos.close(); File onf = File.createTempFile("doc", ".pdf"); ExternalOfficeManagerConfiguration officeConfiguration = new ExternalOfficeManagerConfiguration(); officeConfiguration .setConnectionProtocol(OfficeConnectionProtocol.SOCKET); officeConfiguration.setPortNumber(2002); OfficeManager officeManager = officeConfiguration .buildOfficeManager(); officeManager.start(); OfficeDocumentConverter converter = new OfficeDocumentConverter( officeManager); converter.convert(inf, onf); officeManager.stop(); FileInputStream outfis = new FileInputStream(onf); n = 0; while (n >= 0) { n = outfis.read(buff); if (n > 0) { os.write(buff, 0, n); } } outfis.close(); os.close(); inf.delete(); onf.delete(); } catch (IOException ex) { Logger.getLogger(Libre.class.getName()).log(Level.SEVERE, null, ex); } } public static void html2doc(InputStream is, OutputStream os) { try { File inf = File.createTempFile("doc", ".html"); FileOutputStream infos = new FileOutputStream(inf); int n = 0; byte buff[] = new byte[1024]; while (n >= 0) { n = is.read(buff); if (n > 0) { infos.write(buff, 0, n); } } is.close(); infos.close(); File onf = File.createTempFile("doc", ".doc"); ExternalOfficeManagerConfiguration officeConfiguration = new ExternalOfficeManagerConfiguration(); officeConfiguration .setConnectionProtocol(OfficeConnectionProtocol.SOCKET); officeConfiguration.setPortNumber(2002); OfficeManager officeManager = officeConfiguration .buildOfficeManager(); officeManager.start(); OfficeDocumentConverter converter = new OfficeDocumentConverter( officeManager); converter.convert(inf, onf); officeManager.stop(); FileInputStream outfis = new FileInputStream(onf); n = 0; while (n >= 0) { n = outfis.read(buff); if (n > 0) { os.write(buff, 0, n); } } outfis.close(); os.close(); inf.delete(); onf.delete(); } catch (IOException ex) { Logger.getLogger(Libre.class.getName()).log(Level.SEVERE, null, ex); } } public static void html2docx(InputStream is, OutputStream os) { try { File inf = File.createTempFile("doc", ".html"); FileOutputStream infos = new FileOutputStream(inf); int n = 0; byte buff[] = new byte[1024]; while (n >= 0) { n = is.read(buff); if (n > 0) { infos.write(buff, 0, n); } } is.close(); infos.close(); File onf = File.createTempFile("doc", ".docx"); ExternalOfficeManagerConfiguration officeConfiguration = new ExternalOfficeManagerConfiguration(); officeConfiguration .setConnectionProtocol(OfficeConnectionProtocol.SOCKET); officeConfiguration.setPortNumber(2002); OfficeManager officeManager = officeConfiguration .buildOfficeManager(); officeManager.start(); OfficeDocumentConverter converter = new OfficeDocumentConverter( officeManager); converter.convert(inf, onf); officeManager.stop(); FileInputStream outfis = new FileInputStream(onf); n = 0; while (n >= 0) { n = outfis.read(buff); if (n > 0) { os.write(buff, 0, n); } } outfis.close(); os.close(); inf.delete(); onf.delete(); } catch (IOException ex) { Logger.getLogger(Libre.class.getName()).log(Level.SEVERE, null, ex); } } public static void html2pdf(InputStream is, OutputStream os) { try { File inf = File.createTempFile("doc", ".html"); FileOutputStream infos = new FileOutputStream(inf); int n = 0; byte buff[] = new byte[1024]; while (n >= 0) { n = is.read(buff); if (n > 0) { infos.write(buff, 0, n); } } is.close(); infos.close(); File onf = File.createTempFile("doc", ".pdf"); ExternalOfficeManagerConfiguration officeConfiguration = new ExternalOfficeManagerConfiguration(); officeConfiguration .setConnectionProtocol(OfficeConnectionProtocol.SOCKET); officeConfiguration.setPortNumber(2002); OfficeManager officeManager = officeConfiguration .buildOfficeManager(); officeManager.start(); OfficeDocumentConverter converter = new OfficeDocumentConverter( officeManager); converter.convert(inf, onf); officeManager.stop(); FileInputStream outfis = new FileInputStream(onf); n = 0; while (n >= 0) { n = outfis.read(buff); if (n > 0) { os.write(buff, 0, n); } } outfis.close(); os.close(); inf.delete(); onf.delete(); } catch (IOException ex) { Logger.getLogger(Libre.class.getName()).log(Level.SEVERE, null, ex); } } }
3. Работаем с шаблоном
Когда у нас есть HTML, нужные операции над ним достаточно легко производятся с помощью velocity. Всё легко делается по описанию.
4. Визуальное редактирование документа
С визуальными редакторами есть своя особенность – визуальные редакторы портят HTML-код и при обратном конвертировании вся вёрстка нашего документа будет исковеркана до неузнаваемости. В ходе экспериментов с разными редакторами пришли к тому, что TinyMCE меньше всего умничает коверкает разметку и на конечном результате при обратном конвертировании практически не сказывается.
В итоге методом тыка проб и ошибок подобрали оптимальную конфигурацию редактора:
tinymce.init({ selector: "textarea", theme: "modern", fullpage_default_doctype: "<!DOCTYPE xhtml>", plugins: [ "advlist autolink lists link image charmap print preview hr anchor pagebreak", "searchreplace wordcount visualblocks visualchars code fullscreen", "insertdatetime media nonbreaking save table contextmenu directionality", "emoticons template paste textcolor fullpage" ], toolbar1: "insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image", toolbar2: "print preview media | forecolor backcolor emoticons", image_advtab: true });
Каждый раз чтобы сбросить содержимое редактора в DOM не забываем делать tinyMCE.triggerSave();
5. Скачиваем готовый документ
Для этих целей опять воспользуемся библиотечкой Libre.java:
Конертит hmtl в doc — html2doc()
Конертит hmtl в docx — html2docx()
Конертит hmtl в pdf — html2pdf()
Вот, собственно, и всё. Будем рады, если эта статья кому-то поможет и уменьшит время, потраченное на пляски с бубном!
ссылка на оригинал статьи http://habrahabr.ru/post/224795/
Добавить комментарий