Формы, формы, формы! Независимо от того, где я нахожусь в своей карьере — будь то моя нынешняя работа или предыдущая — формы всегда следуют за мной. Точнее, шаблонные формы.
Постановка задачи
Представьте себе, что вам нужно добавить стандартный заголовок, содержащий определенные сведения, такие как имя формы, заголовок и номер, к сотням файлов .docx
. Делать это вручную было бы невероятно утомительной задачей. К счастью, я нашел программный способ сделать это.
Знакомство с библиотекой DOCX
Поиски решения привели меня к библиотеке DOCX на JavaScript. Эта библиотека позволяет вам поместить тег в файл .docx
, а затем автоматически генерировать данные для этого тега. Звучит многообещающе!
Понимание структуры файла DOCX
Файл .docx
по сути представляет собой ZIP-архив, содержащий файлы XML и другие ресурсы. Текст и элементы, такие как абзацы и таблицы, хранятся в формате XML в этом ZIP-архиве.
Роль PizZip
Чтобы манипулировать этими файлами, я нашел PizZip, библиотеку, которая умеет работать с ZIP-архивами. Это позволяет нам извлечь содержимое файла .docx
, отредактировать XML, а затем упаковать его обратно в файл .docx
.
Начало работы: настройка вашего проекта
- Инициализируйте новый проект Node.js:
npm init -y
2. Установите необходимые пакеты:
npm install node pizzip xml2js docx
3. Включите модули ES6, добавив "type": "module"
в ваш package.json
.
{ "name": "your-package-name", "version": "1.0.0", // ... "type": "module", // ... }
Добавление тега в файл DOCX
Сначала давайте добавим тег в наш файл шаблона DOCX, который я назвал MyDocx.docx
.
Создайте новый файл tagDocx.js
и добавьте следующий код:
import PizZip from 'pizzip'; import xml2js from 'xml2js'; import fs from 'fs'; // Reading the contents of the docx file as a binary content. Add the relative path of your docx file here const content = fs.readFileSync('MyDocx.docx', 'binary'); // Initializing a new PizZip instance with the content of the .docx file const zip = new PizZip(content); // Extracting the main content of the document (XML format) from the .docx ZIP archive. const docXml = zip.files['word/document.xml'].asText(); // Use xml2js to parse the extract XML content to convert it to a Javascript object xml2js.parseString(docXml, (err, result) => { // If there's an error during XML parsing, throw it. if (err) throw err; // Creating a new text element with a specific structure for .docx files. const newText = { 'w:r': { 'w:t': '{{ my_tag_here }}', }, }; // Adding the new text element to the beginning of the document body. result['w:document']['w:body'][0]['w:p'].unshift(newText); // Initializing a new XML builder to convert the modified JavaScript object back to XML format. const builder = new xml2js.Builder(); // Building the updated XML content from the modified JavaScript object. const updatedDocXml = builder.buildObject(result); // Replacing the original document content in the .docx ZIP archive with the updated XML content. zip.file('word/document.xml', updatedDocXml); // Generating the updated .docx content from the modified ZIP archive. const updatedDocxContent = zip.generate({ type: 'nodebuffer' }); // Writing the updated .docx content back to the original file location. fs.writeFileSync('MyDocx.docx', updatedDocxContent); });
запустите этот файл:
node tagDocx.js
Теперь, если вы откроете MyDocx.docx
, вы должны увидеть свой тег вверху!
Замена тега динамическими данными
Теперь, когда у нас есть тег, мы можем использовать функцию patchDocument
библиотеки DOCX, чтобы заменить его динамическими данными. В моем случае мне нужно было заменить тег таблицей.
создайте еще один файл:
touch createTable.js
Вставьте в файл следующий код:
import * as fs from 'fs'; import { Paragraph, patchDocument, PatchType, Table, TableCell, TableRow, TextDirection, WidthType, } from 'docx'; const editDocx = async ({ date, title, formNumber, }) => { try { const doc = await patchDocument(fs.readFileSync('MyDocx.docx'), { patches: { my_tag_here: { type: PatchType.DOCUMENT, children: [ new Table({ columnWidths: [3505, 5505], rows: [ new TableRow({ children: [ new TableCell({ children: [new Paragraph({ text: 'Date' }), new Paragraph({})], textDirection: TextDirection.LEFT_TO_RIGHT_TOP_TO_BOTTOM, width: { size: 50, type: WidthType.PERCENTAGE, }, }), new TableCell({ children: [new Paragraph({ text: date }), new Paragraph({})], textDirection: TextDirection.LEFT_TO_RIGHT_TOP_TO_BOTTOM, width: { size: 50, type: WidthType.PERCENTAGE, }, }), ], }), new TableRow({ children: [ new TableCell({ children: [new Paragraph({ text: 'Title' }), new Paragraph({})], textDirection: TextDirection.LEFT_TO_RIGHT_TOP_TO_BOTTOM, width: { size: 50, type: WidthType.PERCENTAGE, }, }), new TableCell({ children: [new Paragraph({ text: title}), new Paragraph({})], textDirection: TextDirection.LEFT_TO_RIGHT_TOP_TO_BOTTOM, width: { size: 50, type: WidthType.PERCENTAGE, }, }), ], }), new TableRow({ children: [ new TableCell({ children: [new Paragraph({ text: 'Form Number' }), new Paragraph({})], textDirection: TextDirection.LEFT_TO_RIGHT_TOP_TO_BOTTOM, width: { size: 50, type: WidthType.PERCENTAGE, }, }), new TableCell({ children: [new Paragraph({ text: formNumber}), new Paragraph({})], textDirection: TextDirection.LEFT_TO_RIGHT_TOP_TO_BOTTOM, width: { size: 50, type: WidthType.PERCENTAGE, }, }), ], }), ], }), ], }, }, }); fs.writeFileSync('MyDocx.docx', doc); } catch (error) { console.error(`Error: ${error}`); } }; editDocx({ date: '8/23/23', title: 'Form Title', formNumber: 'Form Number' }) .then(() => { console.log('Document edited successfully.'); }) .catch((error) => { console.error(`Failed to edit document: ${error}`); });
Более подробно о том, как работает функция patchDocument
, вы можете узнать в этой документации.
Обратите внимание, что я поместил основную функциональность в функцию более высокого порядка. Это позволяет мне динамически передавать такие параметры, как имя, заголовок и номер.
Запустите этот скрипт:
node createTable.js
Теперь просмотрите MyDocx.docx, и вы увидите таблицу с двумя столбцами и тремя строками!
От ручного присвоения тегов документам на моей предыдущей работе до автоматического выполнения этого с помощью кода — для меня это был момент полного цикла. Автоматизация задач, которые когда-то были обыденными и трудоемкими, кажется победой. Счастливые слезы! 🥲