使用Node.js實(shí)現(xiàn)Word文檔差異比較并高亮標(biāo)注工具
引言
當(dāng)「找不同」遇上程序員的智慧
你是否經(jīng)歷過(guò)這樣的場(chǎng)景?
法務(wù)同事發(fā)來(lái)合同第8版修改版,卻說(shuō)不清改了哪里
導(dǎo)師在論文修改稿里標(biāo)注了十幾處調(diào)整,需要逐一核對(duì)
團(tuán)隊(duì)協(xié)作文檔頻繁更新,版本差異讓人眼花繚亂
傳統(tǒng)的手動(dòng)比對(duì)不僅效率低下,還容易遺漏關(guān)鍵修改。今天,我將揭秘如何用30行Node.js代碼,打造一個(gè)智能Word文檔差異比對(duì)工具,實(shí)現(xiàn):
- 自動(dòng)識(shí)別文本差異
- 智能標(biāo)注修改痕跡
- 生成專業(yè)比對(duì)報(bào)告
一、安裝所需依賴
npm install docx mammoth diff fs-extra
二、代碼注釋與技術(shù)原理詳解
1、完整代碼以及注釋
/**
* Word文檔差異比較工具
* 本腳本使用Node.js比較兩個(gè)Word(.docx)文件的內(nèi)容差異,并生成帶有顏色標(biāo)注的比較結(jié)果文檔
*/
const fs = require('fs');
const path = require('path');
const { Document, Paragraph, TextRun, HeadingLevel, Packer } = require('docx');
const mammoth = require('mammoth');
const diff = require('diff');
/**
* 比較兩個(gè)Word文件并生成差異報(bào)告
* @param {string} file1Path - 第一個(gè)Word文件路徑(原始文件)
* @param {string} file2Path - 第二個(gè)Word文件路徑(修改后的文件)
* @param {string} outputPath - 差異報(bào)告輸出路徑
*
* 技術(shù)原理:
* 1. 使用mammoth庫(kù)提取兩個(gè)Word文檔的純文本內(nèi)容
* 2. 使用diff庫(kù)進(jìn)行文本差異比較,識(shí)別添加、刪除和未更改的部分
* 3. 使用docx庫(kù)構(gòu)建新的Word文檔,用不同顏色和樣式標(biāo)注差異部分
*/
async function compareWordFiles(file1Path, file2Path, outputPath) {
try {
// 提取兩個(gè)Word文件的文本內(nèi)容
// mammoth.extractRawText會(huì)解析docx文件的XML結(jié)構(gòu),提取所有文本內(nèi)容
const file1Content = await extractTextFromDocx(file1Path);
const file2Content = await extractTextFromDocx(file2Path);
// 使用diff庫(kù)比較文本差異
// diffWords函數(shù)會(huì)將文本分解為單詞級(jí)別的差異,返回一個(gè)差異對(duì)象數(shù)組
// 每個(gè)差異對(duì)象包含value(文本內(nèi)容)和added/removed標(biāo)志
const differences = diff.diffWords(file1Content, file2Content);
// 使用docx庫(kù)創(chuàng)建新的Word文檔
// Document對(duì)象表示整個(gè)Word文檔,包含一個(gè)或多個(gè)sections
const doc = new Document({
sections: [{
properties: {},
children: [
// 文檔標(biāo)題
new Paragraph({
text: "Word文檔差異比較結(jié)果",
heading: HeadingLevel.HEADING_1,
spacing: { after: 200 } // 設(shè)置段后間距(單位:twip,1/20磅)
}),
// 原始文件信息
new Paragraph({
text: `原文件: ${path.basename(file1Path)}`,
spacing: { after: 100 }
}),
// 修改后文件信息
new Paragraph({
text: `新文件: ${path.basename(file2Path)}`,
spacing: { after: 200 }
}),
// 插入差異內(nèi)容段落
...generateDiffParagraphs(differences)
]
}]
});
// 將Document對(duì)象轉(zhuǎn)換為Buffer并寫入文件
// Packer.toBuffer內(nèi)部使用JSZip庫(kù)打包docx文件(本質(zhì)上是ZIP格式的XML文件集合)
const buffer = await Packer.toBuffer(doc);
fs.writeFileSync(outputPath, buffer);
console.log(`差異比較結(jié)果已保存到: ${outputPath}`);
} catch (error) {
console.error('比較過(guò)程中出錯(cuò):', error);
}
}
/**
* 從Word文檔中提取純文本內(nèi)容
* @param {string} filePath - Word文件路徑
* @returns {Promise<string>} 提取的純文本內(nèi)容
*
* 技術(shù)原理:
* mammoth庫(kù)解析docx文件(實(shí)際是ZIP壓縮的XML文件),
* 遍歷document.xml中的段落和文本節(jié)點(diǎn),拼接成純文本字符串
*/
async function extractTextFromDocx(filePath) {
const result = await mammoth.extractRawText({ path: filePath });
return result.value;
}
/**
* 根據(jù)差異結(jié)果生成帶有樣式標(biāo)注的段落數(shù)組
* @param {Array} differences - diff庫(kù)生成的差異數(shù)組
* @returns {Array} 包含Paragraph對(duì)象的數(shù)組
*
* 技術(shù)原理:
* 1. 處理diff庫(kù)輸出的差異數(shù)組,每個(gè)部分可能是added/removed/unchanged
* 2. 按換行符分割文本,確保每個(gè)段落獨(dú)立
* 3. 為不同差異類型創(chuàng)建不同樣式的TextRun:
* - 新增內(nèi)容: 藍(lán)色(#1800a1)、加粗
* - 刪除內(nèi)容: 紅色(#FF0000)、刪除線
* - 未更改內(nèi)容: 黑色(#000000)
*/
function generateDiffParagraphs(differences) {
const paragraphs = [];
let currentParagraph = [];
// 遍歷每個(gè)差異部分
differences.forEach(part => {
// 按換行符分割文本,處理多行內(nèi)容
const lines = part.value.split('\n');
lines.forEach((line, lineIndex) => {
// 跳過(guò)空行
if (line.trim() === '') return;
let textRun;
if (part.added) {
// 新增內(nèi)容樣式
textRun = new TextRun({
text: line,
color: '1800a1', // 藍(lán)色表示新增
bold: true // 加粗突出顯示
});
} else if (part.removed) {
// 刪除內(nèi)容樣式
textRun = new TextRun({
text: line,
color: 'FF0000', // 紅色表示刪除
strike: true // 刪除線表示已移除
});
} else {
// 未更改內(nèi)容樣式
textRun = new TextRun({
text: line,
color: '000000' // 黑色表示未更改
});
}
// 將文本段添加到當(dāng)前段落
currentParagraph.push(textRun);
// 如果不是最后一行,創(chuàng)建新段落
if (lineIndex < lines.length - 1) {
paragraphs.push(new Paragraph({
children: currentParagraph
}));
currentParagraph = []; // 重置當(dāng)前段落
}
});
});
// 添加最后一個(gè)未完成的段落
if (currentParagraph.length > 0) {
paragraphs.push(new Paragraph({
children: currentParagraph
}));
}
return paragraphs;
}
// 使用示例
const file1 = path.join(__dirname, './word/dl_v1.docx');
const file2 = path.join(__dirname, './word/js_xg_v1.docx');
const output = path.join(__dirname, './word/comparison_result.docx');
// 執(zhí)行比較
compareWordFiles(file1, file2, output);
2、關(guān)鍵技術(shù)原理詳解
Word文檔解析:
mammoth庫(kù)解析.docx文件(實(shí)際是ZIP壓縮包),提取其中的document.xml文件內(nèi)容- 解析XML結(jié)構(gòu),提取所有文本節(jié)點(diǎn),忽略格式、圖片等非文本元素
差異比較算法:
diff庫(kù)使用Myers差分算法找出兩個(gè)文本序列的最長(zhǎng)公共子序列(LCS)diffWords方法進(jìn)行單詞級(jí)別的比較,比字符級(jí)比較更符合文檔比較的需求
Word文檔生成:
docx庫(kù)構(gòu)建符合Office Open XML標(biāo)準(zhǔn)的Word文檔結(jié)構(gòu)- 文檔結(jié)構(gòu)包含段落(Paragraph)、文本塊(TextRun)等元素
- 顏色使用16進(jìn)制RGB格式表示(如
1800a1表示藍(lán)色)
差異可視化:
- 新增內(nèi)容用藍(lán)色加粗顯示,便于識(shí)別添加的內(nèi)容
- 刪除內(nèi)容用紅色刪除線顯示,表示已移除的內(nèi)容
- 保留原始文本的段落結(jié)構(gòu),確保可讀性
文件處理:
- 最終生成的.docx文件實(shí)際上是包含多個(gè)XML文件的ZIP壓縮包
Packer.toBuffer方法將內(nèi)存中的文檔結(jié)構(gòu)打包成符合標(biāo)準(zhǔn)的.docx文件
結(jié)語(yǔ):讓機(jī)器做枯燥的事,讓人做有創(chuàng)意的事
通過(guò)這個(gè)項(xiàng)目,我們不僅實(shí)現(xiàn)了一個(gè)實(shí)用的文檔比對(duì)工具,更演示了如何:
- 組合現(xiàn)有工具解決實(shí)際問(wèn)題
- 用少量代碼創(chuàng)造高價(jià)值產(chǎn)出
- 將復(fù)雜技術(shù)轉(zhuǎn)化為簡(jiǎn)單接口
下次當(dāng)同事還在用"Ctrl+F"艱難找不同時(shí),你可以優(yōu)雅地說(shuō):“試試我的智能比對(duì)工具?”
到此這篇關(guān)于使用Node.js實(shí)現(xiàn)Word文檔差異比較并高亮標(biāo)注工具的文章就介紹到這了,更多相關(guān)Node.js Word差異比較并標(biāo)注內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
node.js中的path.dirname方法使用說(shuō)明
這篇文章主要介紹了node.js中的path.dirname方法使用說(shuō)明,本文介紹了path.dirname的方法說(shuō)明、語(yǔ)法、使用實(shí)例和實(shí)現(xiàn)源碼,需要的朋友可以參考下2014-12-12
vscode無(wú)法運(yùn)行npm命令的問(wèn)題解決(cmd可行)
本文主要介紹了vscode無(wú)法運(yùn)行npm命令的問(wèn)題解決(cmd可行),VSCode無(wú)法調(diào)用npm可能是因?yàn)榄h(huán)境路徑配置錯(cuò)誤,下面就來(lái)具體介紹一下原因及解決方法,感興趣的可以了解一下2024-04-04
node操作mysql數(shù)據(jù)庫(kù)實(shí)例詳解
這篇文章主要介紹了node操作mysql數(shù)據(jù)庫(kù),結(jié)合實(shí)例形式較為詳細(xì)的分析了node操作數(shù)據(jù)庫(kù)的連接、增刪改查、事務(wù)處理及錯(cuò)誤處理相關(guān)操作技巧,需要的朋友可以參考下2017-03-03
node schedule實(shí)現(xiàn)定時(shí)任務(wù)的示例代碼
實(shí)際工作中,可能會(huì)遇到定時(shí)清除某個(gè)文件夾內(nèi)容,本文主要介紹了node schedule實(shí)現(xiàn)定時(shí)任務(wù)的示例代碼,具有一定的參考價(jià)值,感興趣的可以了解一下2024-08-08
Node.js的MongoDB驅(qū)動(dòng)Mongoose基本使用教程
這篇文章主要介紹了Node.js的MongoDB驅(qū)動(dòng)Mongoose的基本使用教程,前端js+后端Node.js+數(shù)據(jù)庫(kù)MongoDB是當(dāng)下流行的JavaScript全棧開發(fā)方案,需要的朋友可以參考下2016-03-03

