Генерация документов с помощью ONLYOFFICE DocumentBuilder

от автора

Привет, Хабр.

Я хочу рассказать об утилите под названием DocumentBuilder, которая позволяет генерировать документы, таблицы и презентации, а также показать, как можно его использовать для решения ваших задач, на примере создания резюме из шаблонов.

Работает билдер следующим образом: вы пишете код на js, используя специальные методы из документации, отдаете его утилите, а она собирает документ. Или не собирает, если есть ошибки.

Использовать билдер целесообразно, если вы хотите решить задачи вида:

  • Нужно сделать много документов с небольшими вариациями, или документы на основе большого количества данных.
  • Нужно встроить генерирование документов в какой-либо сервис.

Существует сервисы, позволяющие создавать резюме: пользователь заполняет необходимые поля, а система генерирует документ и отдает пользователю. Это и будет примером того, как можно использовать билдер.

В качестве инструментов буду использовать Node.js (express).

План:

  1. Пользователь вводит данные в форму на странице в браузере, форма отправляется на сервер.
  2. На сервере Node.js создает на основе пользовательских данных скрипт для билдера.
  3. Node.js отдает скрипт билдеру.
  4. Билдер делает с помощью скрипта документ.
  5. Node.js возвращает пользователю ссылку на документ.

Создание формы

Для начала создадим форму, в которую пользователь будет вводить свои данные. В форме будет 8 полей: «Full name», «Phone number», «email», «profile», «degree», «university», «location», «year», «skill». Поле skill можно клонировать.

Создаем файл index.html, и добавляем в него код шаблона.

<div class="fill-name">     <input type="text" id="fill-name" placeholder="full name"> </div>  <div class="phone-number">     <input type="number" id="phone-number" placeholder="phone number"> </div>  <div class="email">     <input type="text" id="email" placeholder="email"> </div>  <div class="profile">     <textarea id="profile" placeholder="Insert a brief description of yourself"></textarea> </div>  <div class="education">     <input type="text" id="degree" placeholder="degree">     <input type="text" id="university" placeholder="university">     <input type="text" id="location" placeholder="location">     <input type="date" id="year" placeholder="year"> </div>  <div class="skills">     <div class="skill">         <input type="text" id="new-skill" placeholder="skill" onkeyup="add_skill_by_enter(event)">         <button onclick="add_skill()">+</button>     </div> </div>

Здесь я использую две функции add_skill_by_enter(event) и add_skill(). Они нужны, чтобы добавлять несколько полей по нажатию кнопки + или Enter. Сами эти функции опишу чуть позже.

Добавляем кнопку для отправки формы на сервер:

<button onclick="sendForm()">Send</button>

Теперь напишем функции для работы с формой.

Первая функция — add_skill()

add_skill = () => {     const newSkill = document.getElementById("new-skill");     if (newSkill.value === '') {return; } // ничего не делаем, если в поле не введены данные     const div = document.createElement("div"); .//внешний div     const span = document.createElement("span");  // название навыка     const button = document.createElement("button"); // кнопка удаления навыка     span.innerText += newSkill.value; // добавляем в span введенный текст     newSkill.value = ''; // обнуляет поле с названием способности     newSkill.focus(); // возвращаем фокус в поле с названием навыка     button.innerText += "-";     button.onclick = () => {  // добавляем действие на кнопку удаления     div.remove(); }; div.appendChild(span); // добавляем span в div div.appendChild(button); //добавляем кнопку удаления document.getElementsByClassName('skills')[0].appendChild(div); // добавляем объект на страницу }; add_skill_by_enter()   add_skill_by_enter = (event) => {         if (event.code === "Enter") { // добавляем элемент, только если нажали на кнопку enter             add_skill();         }     };

Добавляем простую функцию для сбора данных из полей и отправки их на сервер.

    get_skill_values = () => {         const skills = [];         if (document.getElementById('new-skill').value !== '') {             skills.push(document.getElementById('new-skill').value);         }         Array.from(document.getElementsByClassName('skillfield')).forEach(current_element => {             skills.push(current_element.innerHTML);         });         return skills;     }; sendForm()     sendForm = () => {         fetch('/', {             method: 'POST',             headers: {                 'Content-Type': 'application/json'             },             body: JSON.stringify({                 userData: {                     fillName: document.getElementById('fill-name').value,                     phoneNumber: document.getElementById('phone-number').value,                     email: document.getElementById('email').value,                     profile: document.getElementById('profile').value,                     education: {                         degree: document.getElementById('degree').value,                         university: document.getElementById('university').value,                         location: document.getElementById('location').value,                         year: document.getElementById('year').value,                     },                     skills: get_skill_values()                 }             })         }).then(res => res.json())             .then(response => {                 location.replace('/' + response.filename); // скачиваем файл, который будет доступен по этой ссылке             })             .catch(error => console.error('Error:', error));     };

Серверная часть для работы с формой

Серверную часть пишу на express. Подключение всех библиотек, конфигурация сервера и описание get и post методов выглядит так:

const path = require('path'); const express = require('express'); const bodyParser = require('body-parser'); const app = express();  app.use(bodyParser.json());  app.get('/', (req, res) => {     res.sendFile(path.join(__dirname + '/index.html')); });  app.post('/', (req, res) => {     // сюда допишем потом функцию для сборки файла });  app.listen(3000, () => console.log(`Example app listening on port ${3000}!`));

Запускаем express:
node main.js

Открываем в браузере адреc:
http:localhost:3000

Видим созданную форму. Заполняем ее произвольными данными:

Получаем следующий json:

{"userData":{"fillName":"Rotatyy Dmitryi","phoneNumber":"89879879898","email":"flamine@list.ru","profile":"Hi, my name is Joe\nAnd I work in a button factory\nI got a wife and two kids\nOne day, my boss says, “Joe, are you busy?”\nI said, “no”\n“Then push this button with your right hand”","country":"Russia","city":"Nizhniy Novgorod","education":{"degree":"Master of Pupets","university":"Nizhny novgorod state technical university","location":"Nizhniy Novgorod","year":"2015-05-02"},"skills":["apple.js","vintage.js","zerg.js","css","html","linux"]}};

Теперь нужно написать скрипт для билдера. Я взял за основу шаблон, который предлагает Google Docs (шаблон резюме).

Выглядит этот шаблон так:
image

Используя эти данные и шаблон, нужно сделать скрипт, с помощью которого билдер создаст документ.

Есть несколько вариантов как это сделать, и самый простой — это скачать desktop-версию редакторов ONLYOFFICE и написать макрос, который создаст документ, используя данные. А потом дописать к макросу создание и сохранение файла — получится скрипт для билдера. Это сработает, потому что макросы и билдер используют один и тот же API.

Создание скрипта для билдера

Начинаем с инициализации объекта страницы и добавления данных, которые пришли от пользователя:

const Document = Api.GetDocument(); const data =   {"userData":{"fillName":"Rotatyy Dmitryi","phoneNumber":"89879879898","email":"flamine@list.ru","profile":"Hi, my name is Joe\nAnd I work in a button factory\nI got a wife and two kids\nOne day, my boss says, “Joe, are you busy?”\nI said, “no”\n“Then push this button with your right hand”","country":"Russia","city":"Nizhniy Novgorod","education":{"degree":"Master of Pupets","university":"Nizhny novgorod state technical university","location":"Nizhniy Novgorod","year":"2015-05-02"},"skills":["apple.js","vintage.js","zerg.js","css","html","linux"]}};

Теперь нужно добавить параграф с полным именем пользователя. Он написан 14 шрифтом, жирным, и у этого параграфа line spacing равен 1.15.

let paragraph = document.GetElement(0); // в документе всегда есть первый параграф FullName_style = Document.CreateStyle("FullName"); // создаем новый стиль FullName_style.GetTextPr().SetFontSize(28); // меняем размер шрифта FullName_style.GetTextPr().SetBold(true); // добавляем bold paragraph.SetStyle(FullName_style); // применяем созданный стиль к параграфу paragraph.SetSpacingLine(1.15 * 240, "auto"); // меняем межстрочный интервал paragraph.AddText(data.userData.fillName);  // добавляем текст в параграф

Также добавляем и остальные параграфы:

// Country and city const CountryCity_style = Document.CreateStyle("CountryCity"); CountryCity_style.GetTextPr().SetFontSize(20); CountryCity_style.GetTextPr().SetCaps(true); CountryCity_style.GetTextPr().SetBold(true); paragraph = Api.CreateParagraph(); paragraph.AddText(data.userData.country + ', ' + data.userData.city); paragraph.SetStyle(CountryCity_style); paragraph.SetSpacingAfter(0); Document.Push(paragraph);  // phone number const PhoneNumber_style = Document.CreateStyle("PhoneNumber"); PhoneNumber_style.GetTextPr().SetFontSize(20); PhoneNumber_style.GetParaPr().SetSpacingAfter(0); PhoneNumber_style.GetTextPr().SetBold(true); paragraph = Api.CreateParagraph(); paragraph.AddText(data.userData.phoneNumber); paragraph.SetStyle(PhoneNumber_style); Document.Push(paragraph);  // email const Email_style = Document.CreateStyle("Email"); Email_style.GetTextPr().SetFontSize(18); Email_style.GetParaPr().SetSpacingAfter(0); Email_style.GetTextPr().SetBold(true); paragraph = Api.CreateParagraph(); paragraph.AddText(data.userData.email); paragraph.SetStyle(Email_style); Document.Push(paragraph);  // SectionHeader style const SectionHeader = Document.CreateStyle("SectionHeader"); SectionHeader.GetTextPr().SetBold(true); SectionHeader.GetTextPr().SetColor(247, 93, 93, false); SectionHeader.GetTextPr().SetFontSize(28); SectionHeader.GetParaPr().SetSpacingBefore(1.33 * 240); SectionHeader.GetParaPr().SetSpacingLine(1 * 240, "auto");  // add header Profile: paragraph = Api.CreateParagraph(); paragraph.AddText("Profile:") paragraph.SetStyle(SectionHeader); Document.Push(paragraph);  // add profile text: paragraph = Api.CreateParagraph(); paragraph.AddText(data.userData.profile) Document.Push(paragraph);  // add header Education: paragraph = Api.CreateParagraph(); paragraph.AddText("Education:") paragraph.SetStyle(SectionHeader); Document.Push(paragraph);  // add education year: const EducationYear_style = Document.CreateStyle("EducationYear"); EducationYear_style.GetTextPr().SetColor(102, 102, 102); EducationYear_style.GetTextPr().SetFontSize(18); EducationYear_style.GetParaPr().SetSpacingAfter(0); paragraph = Api.CreateParagraph(); paragraph.SetStyle(EducationYear_style); paragraph.AddText(data.userData.education.year) Document.Push(paragraph);  // add education university: paragraph = Api.CreateParagraph();  run = Api.CreateRun(); run.AddText(data.userData.education.university) run.AddText(', ') run.AddText(data.userData.education.location) run.SetBold(true); paragraph.AddElement(run); run = Api.CreateRun(); run.AddText(' – ' + data.userData.education.degree) paragraph.AddElement(run); Document.Push(paragraph);  // add header Skills: paragraph = Api.CreateParagraph(); paragraph.AddText("Skills:") paragraph.SetStyle(SectionHeader); Document.Push(paragraph);  // add skills text: paragraph = Api.CreateParagraph(); const skills = data.userData.skills.map(x => ' ' + x).toString(); paragraph.AddText(skills) Document.Push(paragraph);

Исполнив этот скрипт, мы получим такой документ:

Теперь пришло время добавить функции для записи кода скрипта в файл и генерации документа.

Генерируем скрипт -> записываем в файл -> отдаем файл билдеру -> возвращаем пользователю ссылку на файл.

Добавляем подключение дополнений для работы с файлами и запуска команд с помощью Node.js, а также создаем папку «public» и делаем ее публичной:

const {exec} = require('child_process'); const fs = require('fs'); app.use(express.static('public'));

Функция для генерации текста со скриптом будет очень простой — она просто будет возвращать строку со всем кодом для билдера, добавляя при этом пользовательские данные. Важно добавить символ переноса строки в конце каждой строки, иначе ничего не заработает.

generate_script = (data) => {     let first_template = 'builder.CreateFile("docx");\n' +         'const Document = Api.GetDocument();\n';     first_template += 'const data = ' + JSON.stringify(data) + ';\n';     first_template += 'let paragraph = Document.GetElement(0);\n' +         'FullName_style = Document.CreateStyle("FullName");\n' + .... остальной код ~~~~~~~~~~~     return first_template; };

Теперь нужно записать скрипт в файл и отдать его билдеру. По сути, вся работа с билдером будет сводиться к тому, что нам нужно исполнить команду documentbuilder path/script.js с помощью Node.js

Напишем функцию build, которая будет это делать:

build = (data, res) => {     const filename = Math.random().toString(36).substring(7) + '.docx'; // случайное имя файла      let script = generate_script(data);     script += 'builder.SaveFile("docx", "' + __dirname + '/public/' + filename + '");\n' + 'builder.CloseFile();';     fs.writeFile('public/' + filename + 'js', script, () => {         exec('documentbuilder ' + 'public/' + filename + 'js', () => { res.send({'filename': filename }); });     }); };

Добавляем вызов метода build(req.body, res); при post запросе

app.post('/', (req, res) => {     build(req.body, res); });

И все готово. На всякий случай полный код примера я выложил сюда.

Так можно встроить ONLYOFFICE DocumentBuilder в web-приложение.

Надеюсь, что несмотря на большое количество упрощений, все понятно. Хотел написать только необходимый минимум кода, чтобы показать, как все работает.

В данный момент есть мысли допилить утилиту и расширить спектр решаемых проблем. Буду признателен, если вы поделитесь реальными кейсами генерации документов (ну и таблиц с презентациями, конечно, тоже) в комментариях или в личке.


ссылка на оригинал статьи https://habr.com/ru/post/466217/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *