import fs from 'fs';
import path from 'path';
import {
Document,
Paragraph,
TextRun,
HeadingLevel,
AlignmentType,
Packer,
Table,
TableRow,
TableCell,
WidthType,
BorderStyle,
UnderlineType
} from 'docx';
import mammoth from 'mammoth';
// 确保输出目录存在
function ensureOutputDir() {
const outputDir = path.join(process.cwd(), 'documents', 'output');
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
}
// 创建Word文档
export async function createDocument(fileName, title, content) {
ensureOutputDir();
// 创建文档段落
const paragraphs = [
new Paragraph({
children: [
new TextRun({
text: title,
bold: true,
size: 32,
}),
],
heading: HeadingLevel.TITLE,
alignment: AlignmentType.CENTER,
}),
new Paragraph({
children: [new TextRun("")],
}),
];
// 处理内容(支持换行和段落)
// 先处理转义的换行符(处理双反斜杠和单反斜杠的情况)
let processedContent = content.replace(/\\\\n/g, '\n'); // 处理双反斜杠
processedContent = processedContent.replace(/\\n/g, '\n'); // 处理单反斜杠
const contentLines = processedContent.split('\n');
contentLines.forEach(line => {
// 如果是空行,创建空段落用于分隔
if (line.trim() === '') {
paragraphs.push(new Paragraph({
children: [new TextRun("")],
}));
} else {
// 非空行创建正常段落
paragraphs.push(new Paragraph({
children: [new TextRun({
text: line.trim(),
size: 24,
})],
}));
}
});
const doc = new Document({
sections: [{
properties: {},
children: paragraphs,
}],
});
// 保存文档
const outputPath = path.join(process.cwd(), 'documents', 'output', `${fileName}.docx`);
const buffer = await Packer.toBuffer(doc);
fs.writeFileSync(outputPath, buffer);
return `✅ Word文档创建成功!\n📁 文件路径: documents/output/${fileName}.docx\n📝 标题: ${title}\n📄 内容长度: ${content.length} 字符`;
}
// 读取Word文档
export async function readDocument(filePath, format = 'text') {
const fullPath = path.join(process.cwd(), 'documents', filePath);
if (!fs.existsSync(fullPath)) {
throw new Error(`文件不存在: ${filePath}`);
}
const result = await mammoth.convertToHtml(fs.readFileSync(fullPath));
let output;
if (format === 'html') {
output = result.value;
} else if (format === 'markdown') {
// 简单的HTML到Markdown转换
output = result.value
.replace(/<h([1-6])>(.*?)<\/h[1-6]>/g, (match, level, text) => '#'.repeat(parseInt(level)) + ' ' + text + '\n')
.replace(/<p>(.*?)<\/p>/g, '$1\n\n')
.replace(/<strong>(.*?)<\/strong>/g, '**$1**')
.replace(/<em>(.*?)<\/em>/g, '*$1*')
.replace(/<br\s*\/?>/g, '\n')
.replace(/<[^>]*>/g, '');
} else {
// 处理文本格式,保留段落结构
output = result.value
.replace(/<p[^>]*>(.*?)<\/p>/g, '$1\n\n') // 段落转换为换行
.replace(/<br\s*\/?>/g, '\n') // br标签转换为换行
.replace(/<[^>]*>/g, '') // 移除所有HTML标签
.replace(/\n\s*\n\s*\n/g, '\n\n') // 合并多个连续空行为两个换行
.trim();
}
return `📄 文档读取成功 (${format}格式):\n\n${output}`;
}
// 替换文档中的文本
export async function replaceTextInDocument(filePath, searchText, replaceText, outputFileName) {
ensureOutputDir();
const fullPath = path.join(process.cwd(), 'documents', filePath);
if (!fs.existsSync(fullPath)) {
throw new Error(`文件不存在: ${filePath}`);
}
// 读取原文档内容
const result = await mammoth.convertToHtml(fs.readFileSync(fullPath));
let htmlContent = result.value;
// 执行文本替换
const originalCount = (htmlContent.match(new RegExp(searchText, 'g')) || []).length;
htmlContent = htmlContent.replace(new RegExp(searchText, 'g'), replaceText);
// 简单转换回文档格式(这里简化处理)
const textContent = htmlContent.replace(/<[^>]*>/g, '').replace(/\s+/g, ' ').trim();
const lines = textContent.split(/[.!?]+/).filter(line => line.trim());
const paragraphs = [
new Paragraph({
children: [
new TextRun({
text: "已处理的文档",
bold: true,
size: 28,
}),
],
heading: HeadingLevel.HEADING_1,
}),
];
lines.forEach(line => {
if (line.trim()) {
paragraphs.push(new Paragraph({
children: [new TextRun({
text: line.trim() + '.',
size: 24,
})],
}));
}
});
const doc = new Document({
sections: [{
properties: {},
children: paragraphs,
}],
});
// 保存文档
const outputPath = path.join(process.cwd(), 'documents', 'output', `${outputFileName}.docx`);
const buffer = await Packer.toBuffer(doc);
fs.writeFileSync(outputPath, buffer);
return `✅ 文本替换完成!\n🔍 查找: "${searchText}"\n✏️ 替换为: "${replaceText}"\n📊 替换次数: ${originalCount}\n📁 输出文件: documents/output/${outputFileName}.docx`;
}
// 创建格式化文档
export async function createFormattedDocument(fileName, title, contentArray) {
ensureOutputDir();
// 创建文档段落
const paragraphs = [
new Paragraph({
children: [
new TextRun({
text: title,
bold: true,
size: 32,
}),
],
heading: HeadingLevel.TITLE,
alignment: AlignmentType.CENTER,
}),
new Paragraph({
children: [new TextRun("")],
}),
];
// 处理格式化内容
contentArray.forEach(item => {
const textRun = new TextRun({
text: item.text,
fontFamily: item.fontFamily,
size: item.fontSize ? item.fontSize * 2 : 24,
bold: item.bold || false,
italics: item.italic || false,
underline: item.underline ? { color: item.color || '000000', type: UnderlineType.SINGLE } : undefined,
color: item.color || '000000',
});
const paragraph = new Paragraph({
children: [textRun],
alignment: item.alignment === 'center' ? AlignmentType.CENTER :
item.alignment === 'right' ? AlignmentType.RIGHT :
item.alignment === 'justify' ? AlignmentType.JUSTIFIED :
AlignmentType.LEFT,
heading: item.heading ?
(item.heading === 1 ? HeadingLevel.HEADING_1 :
item.heading === 2 ? HeadingLevel.HEADING_2 :
item.heading === 3 ? HeadingLevel.HEADING_3 :
item.heading === 4 ? HeadingLevel.HEADING_4 :
item.heading === 5 ? HeadingLevel.HEADING_5 :
item.heading === 6 ? HeadingLevel.HEADING_6 : undefined) : undefined,
});
paragraphs.push(paragraph);
});
const doc = new Document({
sections: [{
properties: {},
children: paragraphs,
}],
});
// 保存文档
const outputPath = path.join(process.cwd(), 'documents', 'output', `${fileName}.docx`);
const buffer = await Packer.toBuffer(doc);
fs.writeFileSync(outputPath, buffer);
return `✅ 格式化Word文档创建成功!\n📁 文件路径: documents/output/${fileName}.docx\n📝 标题: ${title}\n📄 内容段落数: ${contentArray.length}`;
}
// 创建表格文档
export async function createTableDocument(fileName, title, tableData) {
ensureOutputDir();
// 创建标题段落
const paragraphs = [
new Paragraph({
children: [
new TextRun({
text: title,
bold: true,
size: 32,
}),
],
heading: HeadingLevel.TITLE,
alignment: AlignmentType.CENTER,
}),
new Paragraph({
children: [new TextRun("")],
}),
];
// 创建表格行
const tableRows = [];
// 添加标题行
const headerRow = new TableRow({
children: tableData.headers.map(header =>
new TableCell({
children: [
new Paragraph({
children: [
new TextRun({
text: header,
bold: true,
size: 24,
}),
],
alignment: AlignmentType.CENTER,
}),
],
shading: {
fill: "e5f3ff",
},
})
),
});
tableRows.push(headerRow);
// 添加数据行
tableData.rows.forEach(row => {
const tableRow = new TableRow({
children: row.map(cell =>
new TableCell({
children: [
new Paragraph({
children: [
new TextRun({
text: cell,
size: 22,
}),
],
}),
],
})
),
});
tableRows.push(tableRow);
});
// 创建表格
const table = new Table({
rows: tableRows,
width: {
size: 100,
type: WidthType.PERCENTAGE,
},
borders: {
top: { style: BorderStyle.SINGLE, size: 1, color: "000000" },
bottom: { style: BorderStyle.SINGLE, size: 1, color: "000000" },
left: { style: BorderStyle.SINGLE, size: 1, color: "000000" },
right: { style: BorderStyle.SINGLE, size: 1, color: "000000" },
insideHorizontal: { style: BorderStyle.SINGLE, size: 1, color: "000000" },
insideVertical: { style: BorderStyle.SINGLE, size: 1, color: "000000" },
},
});
paragraphs.push(new Paragraph({ children: [new TextRun("")] }));
const doc = new Document({
sections: [{
properties: {},
children: [...paragraphs, table],
}],
});
// 保存文档
const outputPath = path.join(process.cwd(), 'documents', 'output', `${fileName}.docx`);
const buffer = await Packer.toBuffer(doc);
fs.writeFileSync(outputPath, buffer);
return `✅ 表格Word文档创建成功!\n📁 文件路径: documents/output/${fileName}.docx\n📝 标题: ${title}\n📊 表格大小: ${tableData.headers.length} 列 × ${tableData.rows.length + 1} 行`;
}