引言:當「找不同」遇上程序員的智慧
你是否經歷過這樣的場景?
法務同事發來合同第8版修改版,卻說不清改了哪里
導師在論文修改稿里標注了十幾處調整,需要逐一核對
團隊協作文檔頻繁更新,版本差異讓人眼花繚亂
傳統的手動比對不僅效率低下,還容易遺漏關鍵修改。今天,我將揭秘如何用30行Node.js代碼,打造一個智能Word文檔差異比對工具,實現:
? 自動識別文本差異
? 智能標注修改痕跡
? 生成專業比對報告
一、安裝所需依賴
npm install docx mammoth diff fs-extra
二、代碼注釋與技術原理詳解
1、完整代碼以及注釋
/*** Word文檔差異比較工具* 本腳本使用Node.js比較兩個Word(.docx)文件的內容差異,并生成帶有顏色標注的比較結果文檔*/const fs = require('fs');
const path = require('path');
const { Document, Paragraph, TextRun, HeadingLevel, Packer } = require('docx');
const mammoth = require('mammoth');
const diff = require('diff');/*** 比較兩個Word文件并生成差異報告* @param {string} file1Path - 第一個Word文件路徑(原始文件)* @param {string} file2Path - 第二個Word文件路徑(修改后的文件)* @param {string} outputPath - 差異報告輸出路徑* * 技術原理:* 1. 使用mammoth庫提取兩個Word文檔的純文本內容* 2. 使用diff庫進行文本差異比較,識別添加、刪除和未更改的部分* 3. 使用docx庫構建新的Word文檔,用不同顏色和樣式標注差異部分*/
async function compareWordFiles(file1Path, file2Path, outputPath) {try {// 提取兩個Word文件的文本內容// mammoth.extractRawText會解析docx文件的XML結構,提取所有文本內容const file1Content = await extractTextFromDocx(file1Path);const file2Content = await extractTextFromDocx(file2Path);// 使用diff庫比較文本差異// diffWords函數會將文本分解為單詞級別的差異,返回一個差異對象數組// 每個差異對象包含value(文本內容)和added/removed標志const differences = diff.diffWords(file1Content, file2Content);// 使用docx庫創建新的Word文檔// Document對象表示整個Word文檔,包含一個或多個sectionsconst doc = new Document({sections: [{properties: {},children: [// 文檔標題new Paragraph({text: "Word文檔差異比較結果",heading: HeadingLevel.HEADING_1,spacing: { after: 200 } // 設置段后間距(單位:twip,1/20磅)}),// 原始文件信息new Paragraph({text: `原文件: ${path.basename(file1Path)}`,spacing: { after: 100 }}),// 修改后文件信息new Paragraph({text: `新文件: ${path.basename(file2Path)}`,spacing: { after: 200 }}),// 插入差異內容段落...generateDiffParagraphs(differences)]}]});// 將Document對象轉換為Buffer并寫入文件// Packer.toBuffer內部使用JSZip庫打包docx文件(本質上是ZIP格式的XML文件集合)const buffer = await Packer.toBuffer(doc);fs.writeFileSync(outputPath, buffer);console.log(`差異比較結果已保存到: ${outputPath}`);} catch (error) {console.error('比較過程中出錯:', error);}
}/*** 從Word文檔中提取純文本內容* @param {string} filePath - Word文件路徑* @returns {Promise<string>} 提取的純文本內容* * 技術原理:* mammoth庫解析docx文件(實際是ZIP壓縮的XML文件),* 遍歷document.xml中的段落和文本節點,拼接成純文本字符串*/
async function extractTextFromDocx(filePath) {const result = await mammoth.extractRawText({ path: filePath });return result.value;
}/*** 根據差異結果生成帶有樣式標注的段落數組* @param {Array} differences - diff庫生成的差異數組* @returns {Array} 包含Paragraph對象的數組* * 技術原理:* 1. 處理diff庫輸出的差異數組,每個部分可能是added/removed/unchanged* 2. 按換行符分割文本,確保每個段落獨立* 3. 為不同差異類型創建不同樣式的TextRun:* - 新增內容: 藍色(#1800a1)、加粗* - 刪除內容: 紅色(#FF0000)、刪除線* - 未更改內容: 黑色(#000000)*/
function generateDiffParagraphs(differences) {const paragraphs = [];let currentParagraph = [];// 遍歷每個差異部分differences.forEach(part => {// 按換行符分割文本,處理多行內容const lines = part.value.split('\n');lines.forEach((line, lineIndex) => {// 跳過空行if (line.trim() === '') return;let textRun;if (part.added) {// 新增內容樣式textRun = new TextRun({text: line,color: '1800a1', // 藍色表示新增bold: true // 加粗突出顯示});} else if (part.removed) {// 刪除內容樣式textRun = new TextRun({text: line,color: 'FF0000', // 紅色表示刪除strike: true // 刪除線表示已移除});} else {// 未更改內容樣式textRun = new TextRun({text: line,color: '000000' // 黑色表示未更改});}// 將文本段添加到當前段落currentParagraph.push(textRun);// 如果不是最后一行,創建新段落if (lineIndex < lines.length - 1) {paragraphs.push(new Paragraph({children: currentParagraph}));currentParagraph = []; // 重置當前段落}});});// 添加最后一個未完成的段落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');// 執行比較
compareWordFiles(file1, file2, output);
2、關鍵技術原理詳解
-
Word文檔解析:
mammoth
庫解析.docx文件(實際是ZIP壓縮包),提取其中的document.xml
文件內容- 解析XML結構,提取所有文本節點,忽略格式、圖片等非文本元素
-
差異比較算法:
diff
庫使用Myers差分算法找出兩個文本序列的最長公共子序列(LCS)diffWords
方法進行單詞級別的比較,比字符級比較更符合文檔比較的需求
-
Word文檔生成:
docx
庫構建符合Office Open XML標準的Word文檔結構- 文檔結構包含段落(Paragraph)、文本塊(TextRun)等元素
- 顏色使用16進制RGB格式表示(如
1800a1
表示藍色)
-
差異可視化:
- 新增內容用藍色加粗顯示,便于識別添加的內容
- 刪除內容用紅色刪除線顯示,表示已移除的內容
- 保留原始文本的段落結構,確保可讀性
-
文件處理:
- 最終生成的.docx文件實際上是包含多個XML文件的ZIP壓縮包
Packer.toBuffer
方法將內存中的文檔結構打包成符合標準的.docx文件
結語:讓機器做枯燥的事,讓人做有創意的事
通過這個項目,我們不僅實現了一個實用的文檔比對工具,更演示了如何:
🔧 組合現有工具解決實際問題
🚀 用少量代碼創造高價值產出
🌐 將復雜技術轉化為簡單接口
下次當同事還在用"Ctrl+F"艱難找不同時,你可以優雅地說:“試試我的智能比對工具?”
原創技術博客,轉載請注明出處。