Робимо візуальний web-редактор документів на основі LibreOffice, jodconverter і TinyMCE

Как же я люблю спецификацию офиса!З написання попередньої статті про генерацію 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 в потрібного формату файл і віддати користувачеві.
 
 Під катом невеликий клас-обгортка для jodconverter з коментарями: Libre.java
 
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()

 
Ось, власне, і все. Будемо раді, якщо ця стаття комусь допоможе і зменшить час, витрачений на танці з бубном!
 
Матеріал підготували: akaiser , boiler5 .

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.