
Research
Supply Chain Attack on Axios Pulls Malicious Dependency from npm
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.
@lpen/docx
Advanced tools
一个高性能、类型安全的 Node.js 库,用于解析 .docx 文件并将其转换为 HTML,同时也支持将 HTML 转换为 .docx 文档。
npm install @lpen/docx
# 或
pnpm add @lpen/docx
# 或
yarn add @lpen/docx
import { parseDocx } from '@lpen/docx';
import fs from 'fs-extra';
async function main() {
// 一行代码完成所有操作:加载、解析、转换为 HTML
const html = await parseDocx('path/to/document.docx');
// 保存结果
await fs.writeFile('output.html', html, 'utf-8');
}
main();
import { parseDocx } from '@lpen/docx';
// 指定输出选项
const html = await parseDocx('document.docx', {
imageMode: 'base64', // 图片处理方式:'base64'(默认)、'skip' 或 'placeholder'
includeHeaders: true, // 包含页眉,默认 true
includeFooters: true, // 包含页脚,默认 true
includeMetadata: false, // 包含元数据,默认 false
includeComments: true, // 包含批注,默认 true
cssClassPrefix: 'docx-', // 自定义 CSS 类名前缀
pageBreakClass: 'docx-page-break', // 分页符的 CSS 类名(默认 "docx-page-break")
});
如果需要更细粒度的控制,可以使用 DocxParser 类:
import { DocxParser, HtmlConverter } from '@lpen/docx';
import fs from 'fs-extra';
async function main() {
const parser = new DocxParser();
// 从文件加载
await parser.loadFromFile('document.docx');
// 或者从 Buffer 加载
// const buffer = await fs.readFile('document.docx');
// await parser.load(buffer);
// 获取 AST(抽象语法树)
const ast = parser.getAST();
// 获取元数据
const metadata = parser.getMetadata();
// 获取图片
const images = parser.getImages();
// 获取页眉
const headers = parser.getHeaders();
// 获取页脚
const footers = parser.getFooters();
// 使用 HtmlConverter 转换为 HTML
const converter = new HtmlConverter({
imageMode: 'base64',
includeMetadata: true,
});
const html = converter.convert(
ast,
images,
(id) => parser.getComment(id) // 批注获取函数
);
await fs.writeFile('output.html', html, 'utf-8');
}
main();
import { htmlToWord } from '@lpen/docx';
import fs from 'fs-extra';
async function main() {
const html = '<h1>标题</h1><p>这是段落内容。</p>';
// 一行代码完成转换
const docx = await htmlToWord(html);
// 保存文档
await fs.writeFile('output.docx', docx);
}
main();
import { htmlToWord } from '@lpen/docx';
import fs from 'fs-extra';
const html = `
<h1>我的文档</h1>
<p>这是段落内容。</p>
<table>
<tr>
<td>单元格 1</td>
<td>单元格 2</td>
</tr>
</table>
`;
const docx = await htmlToWord(html, {
title: '我的文档',
creator: 'docx-parser',
orientation: 'portrait', // 'portrait'(默认)或 'landscape'
margins: {
top: 1440, // 1 英寸 = 1440 twips
right: 1440,
bottom: 1440,
left: 1440,
header: 720, // 0.5 英寸
footer: 720,
gutter: 0,
},
font: 'Microsoft YaHei', // 默认字体
fontSize: 12, // 默认字体大小(单位:pt)
lang: 'zh-CN', // 文档语言代码
direction: 'ltr', // 文本方向:'ltr'(默认)或 'rtl'
heading: {
heading1: {
font: 'Microsoft YaHei',
fontSize: 24,
bold: true,
spacing: {
before: 240, // 段前间距(单位:twips)
after: 40, // 段后间距(单位:twips)
},
keepLines: true,
keepNext: false,
outlineLevel: 1,
},
heading2: {
fontSize: 18,
bold: true,
},
},
imageProcessing: {
maxRetries: 3, // 最大重试次数
downloadTimeout: 5000, // 下载超时时间(毫秒)
svgHandling: 'auto', // SVG 处理方式:'convert'、'native' 或 'auto'
},
pageBreak: {
enabled: true, // 是否启用分页符识别(默认 true)
class: 'docx-page-break', // 分页符的 CSS 类名(默认 "docx-page-break")
},
});
await fs.writeFile('output.docx', docx);
import { htmlToWord } from '@lpen/docx';
import fs from 'fs-extra';
const html = '<h1>标题</h1><p>这是段落内容。</p>';
// 带页眉和页脚
const docx = await htmlToWord(
html,
{
header: true, // 启用页眉
footer: true, // 启用页脚
skipFirstHeaderFooter: false, // 是否跳过首页的页眉页脚
},
'<p style="text-align: center;">这是页眉内容</p>', // 页眉 HTML
'<p style="text-align: center;">第 <span style="page-number">1</span> 页</p>' // 页脚 HTML
);
await fs.writeFile('output.docx', docx);
最简单的 API,一行代码完成所有操作。
parseDocx(input: string | Buffer, options?: ParseOptions): Promise<string>
参数:
input: 文件路径(string)、HTTP/HTTPS URL 或文件 Bufferoptions: 可选配置项
imageMode: 图片处理方式,'base64'(默认)、'skip' 或 'placeholder'includeHeaders: 是否包含页眉,默认 trueincludeFooters: 是否包含页脚,默认 trueincludeMetadata: 是否包含元数据,默认 falseincludeComments: 是否包含批注,默认 truecssClassPrefix: 自定义 CSS 类名前缀pageBreakClass: 分页符的 CSS 类名,默认 "docx-page-break"返回值:
返回 HTML 字符串
示例:
// 从本地文件解析
const html = await parseDocx('document.docx');
// 从 URL 下载并解析
const html = await parseDocx('https://example.com/document.docx');
// 使用 Buffer
const buffer = await fs.readFile('document.docx');
const html = await parseDocx(buffer);
// 自定义选项
const html = await parseDocx('document.docx', {
imageMode: 'skip',
includeHeaders: false,
});
如果需要更细粒度的控制,可以使用 DocxParser 类:
import { DocxParser, HtmlConverter } from '@lpen/docx';
const parser = new DocxParser();
// 从文件加载
await parser.loadFromFile('document.docx');
// 或从 Buffer 加载
await parser.load(buffer);
// 获取 AST
const ast = parser.getAST();
// 获取元数据
const metadata = parser.getMetadata();
// 获取图片
const images = parser.getImages();
// 获取页眉
const headers = parser.getHeaders();
// 获取页脚
const footers = parser.getFooters();
// 获取批注
const comment = parser.getComment(commentId);
// 使用 HtmlConverter 转换为 HTML
const converter = new HtmlConverter({
imageMode: 'base64',
includeMetadata: true,
});
const html = converter.convert(ast, images, (id) => parser.getComment(id));
将 HTML 字符串转换为 Word (.docx) 文档。
htmlToWord(
htmlString: string,
options?: HtmlToWordOptions,
headerHTMLString?: string,
footerHTMLString?: string
): Promise<Buffer>
参数:
htmlString: HTML 字符串options: 可选配置项
title: 文档标题creator: 创建者名称orientation: 页面方向,'portrait'(默认)或 'landscape'margins: 页边距(单位:twips,1 twip = 1/20 pt)
top, right, bottom, left, header, footer, gutterfont: 默认字体,默认 'Calibri'fontSize: 默认字体大小(单位:pt),默认 11lang: 文档语言代码,默认 'en-US'direction: 文本方向,'ltr'(默认)或 'rtl'header: 是否启用页眉,默认 falsefooter: 是否启用页脚,默认 falseskipFirstHeaderFooter: 是否跳过首页的页眉页脚,默认 falseheaderType: 页眉类型,'default'(默认)、'first'(首页)或 'even'(偶数页)footerType: 页脚类型,'default'(默认)、'first'(首页)或 'even'(偶数页)heading: 标题样式配置(H1-H6)
font, fontSize, bold, spacing, keepLines, keepNext, outlineLevelimageProcessing: 图片处理选项
maxRetries: 最大重试次数downloadTimeout: 下载超时时间(毫秒)svgHandling: SVG 处理方式,'convert'(转换为 PNG)、'native'(原生支持)或 'auto'(自动)pageBreak: 分页符配置选项
enabled: 是否启用分页符识别,默认 trueclass: 分页符的 CSS 类名,默认 "docx-page-break"headerHTMLString: 页眉 HTML 字符串(可选),当 options.header 为 true 时使用footerHTMLString: 页脚 HTML 字符串(可选),当 options.footer 为 true 时使用返回值:
返回 .docx 文件的 Buffer
示例:
import { htmlToWord } from '@lpen/docx';
import fs from 'fs-extra';
// 基本使用
const html = '<h1>标题</h1><p>这是段落内容。</p>';
const docx = await htmlToWord(html);
await fs.writeFile('output.docx', docx);
// 带选项
const docx = await htmlToWord(html, {
title: '我的文档',
creator: 'docx-parser',
orientation: 'portrait',
margins: {
top: 1440,
right: 1440,
bottom: 1440,
left: 1440,
},
font: 'Microsoft YaHei',
fontSize: 12,
lang: 'zh-CN',
});
// 处理图片(支持 data URL 和外部 URL)
const htmlWithImage = `
<h1>带图片的文档</h1>
<p>内联图片:<img src="data:image/png;base64,..." width="200" height="150" /></p>
<p>外部图片:<img src="https://example.com/image.jpg" alt="示例图片" /></p>
`;
const docx = await htmlToWord(htmlWithImage, {
imageProcessing: {
downloadTimeout: 5000,
maxRetries: 3,
},
});
await fs.writeFile('output.docx', docx);
// 带页眉和页脚
const html = '<h1>标题</h1><p>这是段落内容。</p>';
const docxWithHeaderFooter = await htmlToWord(
html,
{
header: true,
footer: true,
skipFirstHeaderFooter: false,
},
'<p style="text-align: center;">这是页眉内容</p>',
'<p style="text-align: center;">第 <span style="page-number">1</span> 页</p>'
);
await fs.writeFile('output-with-header-footer.docx', docxWithHeaderFooter);
在 HTML 中使用分页符,可以通过以下方式:
方式 1:使用内联样式
const html = `
<h1>第一页标题</h1>
<p>这是第一页的内容。</p>
<div style="page-break-after: always"></div>
<h2>第二页标题</h2>
<p>这是第二页的内容。</p>
`;
const docx = await htmlToWord(html);
await fs.writeFile('output.docx', docx);
方式 2:使用 CSS 类名
const html = `
<h1>第一页标题</h1>
<p>这是第一页的内容。</p>
<div class="docx-page-break"></div>
<h2>第二页标题</h2>
<p>这是第二页的内容。</p>
`;
const docx = await htmlToWord(html);
await fs.writeFile('output.docx', docx);
方式 3:使用简化的类名
const html = `
<h1>第一页标题</h1>
<p>这是第一页的内容。</p>
<div class="page-break"></div>
<h2>第二页标题</h2>
<p>这是第二页的内容。</p>
`;
const docx = await htmlToWord(html);
await fs.writeFile('output.docx', docx);
自定义分页符类名
const html = `
<h1>第一页标题</h1>
<p>这是第一页的内容。</p>
<div class="my-custom-page-break"></div>
<h2>第二页标题</h2>
<p>这是第二页的内容。</p>
`;
const docx = await htmlToWord(html, {
pageBreak: {
enabled: true,
class: 'my-custom-page-break', // 自定义类名
},
});
await fs.writeFile('output.docx', docx);
禁用分页符识别
const docx = await htmlToWord(html, {
pageBreak: {
enabled: false, // 禁用分页符识别
},
});
本库支持完整的四向转换,JSON 格式为标准化 AST(抽象语法树),可以作为中间格式进行数据操作和转换。
DOCX ──docxToJson──> JSON (AST) ──jsonToHtml──> HTML
↑ ↓
│ htmlToJson
│ ↓
jsonToDocx JSON (AST)
将 DOCX 文件转换为 JSON 格式的 AST。
import { docxToJson } from '@lpen/docx';
import fs from 'fs-extra';
async function main() {
// 从本地文件解析
const ast = await docxToJson('document.docx');
// 从 URL 下载并解析
const ast = await docxToJson('https://example.com/document.docx');
// 从 Buffer 解析
const buffer = await fs.readFile('document.docx');
const ast = await docxToJson(buffer);
// AST 就是 JSON 格式,可以直接序列化
const jsonString = JSON.stringify(ast, null, 2);
await fs.writeFile('document.json', jsonString, 'utf-8');
// 或者直接使用 AST 对象进行操作
console.log('文档类型:', ast.type);
console.log('子节点数量:', ast.children?.length || 0);
}
main();
将 HTML 字符串转换为 JSON 格式的 AST。
import { htmlToJson } from '@lpen/docx';
import fs from 'fs-extra';
const html = '<h1>标题</h1><p>这是段落内容。</p>';
// 基本使用
const ast = htmlToJson(html);
// AST 就是 JSON 格式,可以直接序列化
const jsonString = JSON.stringify(ast, null, 2);
await fs.writeFile('document.json', jsonString, 'utf-8');
// 带选项
const ast = htmlToJson(html, {
skipHTMLMinify: false,
decodeUnicode: true,
preserveWhitespace: false,
});
将 JSON 格式的 AST 转换为 HTML 字符串。
import { jsonToHtml } from '@lpen/docx';
import fs from 'fs-extra';
// 从 JSON 文件读取 AST
const jsonString = await fs.readFile('document.json', 'utf-8');
const ast = JSON.parse(jsonString);
// 基本使用
const html = jsonToHtml(ast);
// 带选项和图片
const html = jsonToHtml(ast, {
imageMode: 'base64',
includeMetadata: false,
cssClassPrefix: 'docx-',
}, images);
// 带批注支持
const html = jsonToHtml(ast, {}, images, (id) => {
// 返回批注定义
return commentMap.get(id);
});
await fs.writeFile('output.html', html, 'utf-8');
将 JSON 格式的 AST 转换为 DOCX 文档。
import { jsonToDocx } from '@lpen/docx';
import fs from 'fs-extra';
// 从 JSON 文件读取 AST
const jsonString = await fs.readFile('document.json', 'utf-8');
const ast = JSON.parse(jsonString);
// 基本使用
const docx = await jsonToDocx(ast);
// 带选项
const docx = await jsonToDocx(ast, {
title: '我的文档',
creator: 'docx-parser',
orientation: 'portrait',
margins: { top: 1440, bottom: 1440 },
font: 'Microsoft YaHei',
fontSize: 12,
});
await fs.writeFile('output.docx', docx);
以下示例展示了如何创建包含批注和回复的文档:
import { jsonToDocx } from '@lpen/docx';
import fs from 'fs-extra';
// 批注类型定义(如果需要类型提示)
// 注意:实际使用时,CommentDefinition 和 CommentReply 类型可以从 '@lpen/docx/types' 导入
interface CommentDefinition {
id: number;
author: string;
date: Date;
initials?: string;
done?: boolean; // true 表示已解决,false 或 undefined 表示未解决
content?: any; // DocxNode 类型,批注内容
replies?: CommentReply[]; // 批注的回复列表
}
interface CommentReply {
id?: number; // 可选,如果不提供则自动生成
author: string;
date: Date;
initials?: string;
content: any; // DocxNode 类型,回复内容
}
// 1. 创建包含批注的 JSON AST
const ast = {
type: "document",
children: [
// 第一段:包含主批注和两个回复批注
{
type: "paragraph",
children: [
{
type: "commentRangeStart",
commentId: 0,
properties: {
commentId: 0,
commentType: "commentRangeStart",
},
},
{
type: "commentRangeStart",
commentId: 1,
properties: {
commentId: 1,
commentType: "commentRangeStart",
},
},
{
type: "commentRangeStart",
commentId: 2,
properties: {
commentId: 2,
commentType: "commentRangeStart",
},
},
{
type: "run",
text: "本项目计划于2025年第一季度完成,涉及多个模块的开发与集成工作。",
properties: {},
},
{
type: "commentRangeEnd",
commentId: 0,
properties: {
commentId: 0,
commentType: "commentRangeEnd",
},
},
{
type: "commentReference",
commentId: 0,
properties: {
commentId: 0,
commentType: "commentReference",
},
children: [],
},
{
type: "commentRangeEnd",
commentId: 1,
properties: {
commentId: 1,
commentType: "commentRangeEnd",
},
},
{
type: "commentReference",
commentId: 1,
properties: {
commentId: 1,
commentType: "commentReference",
},
children: [],
},
{
type: "commentRangeEnd",
commentId: 2,
properties: {
commentId: 2,
commentType: "commentRangeEnd",
},
},
{
type: "commentReference",
commentId: 2,
properties: {
commentId: 2,
commentType: "commentReference",
},
children: [],
},
],
properties: {
fontFamily: "微软雅黑",
},
},
// 第二段:包含主批注和一个回复批注
{
type: "paragraph",
children: [
{
type: "commentRangeStart",
commentId: 3,
properties: {
commentId: 3,
commentType: "commentRangeStart",
},
},
{
type: "commentRangeStart",
commentId: 4,
properties: {
commentId: 4,
commentType: "commentRangeStart",
},
},
{
type: "run",
text: "技术方案需要进一步优化,建议增加性能测试环节以确保系统稳定性。",
properties: {},
},
{
type: "commentRangeEnd",
commentId: 3,
properties: {
commentId: 3,
commentType: "commentRangeEnd",
},
},
{
type: "commentReference",
commentId: 3,
properties: {
commentId: 3,
commentType: "commentReference",
},
children: [],
},
{
type: "commentRangeEnd",
commentId: 4,
properties: {
commentId: 4,
commentType: "commentRangeEnd",
},
},
{
type: "commentReference",
commentId: 4,
properties: {
commentId: 4,
commentType: "commentReference",
},
children: [],
},
],
properties: {
fontFamily: "微软雅黑",
},
},
],
};
// 2. 创建批注定义 Map
const commentMap = new Map<number, CommentDefinition>();
// 批注0:第一段的主批注(已解决,有2个回复)
commentMap.set(0, {
id: 0,
author: "张明",
date: new Date("2025-01-15T10:00:00Z"),
initials: "ZM",
done: true, // 已解决
content: {
type: "document",
children: [
{
type: "paragraph",
children: [
{
type: "run",
text: "该计划时间安排合理,建议按此执行。",
properties: {},
},
],
properties: {},
},
],
},
replies: [
{
id: 1, // 批注1是批注0的回复
author: "李华",
date: new Date("2025-01-15T10:30:00Z"),
initials: "LH",
content: {
type: "document",
children: [
{
type: "paragraph",
children: [
{
type: "run",
text: "同意,我会协调各部门配合完成。",
properties: {},
},
],
properties: {},
},
],
},
},
{
id: 2, // 批注2是批注0的回复
author: "王芳",
date: new Date("2025-01-15T11:00:00Z"),
initials: "WF",
content: {
type: "document",
children: [
{
type: "paragraph",
children: [
{
type: "run",
text: "已收到,我会跟进项目进度。",
properties: {},
},
],
properties: {},
},
],
},
},
],
});
// 批注3:第二段的主批注(未解决,有1个回复)
commentMap.set(3, {
id: 3,
author: "陈强",
date: new Date("2025-01-15T14:00:00Z"),
initials: "CQ",
done: false, // 未解决
content: {
type: "document",
children: [
{
type: "paragraph",
children: [
{
type: "run",
text: "性能测试环节确实很重要,需要尽快落实。",
properties: {},
},
],
properties: {},
},
],
},
replies: [
{
id: 4, // 批注4是批注3的回复
author: "刘静",
date: new Date("2025-01-15T14:30:00Z"),
initials: "LJ",
content: {
type: "document",
children: [
{
type: "paragraph",
children: [
{
type: "run",
text: "我会在本周内完成测试方案的制定。",
properties: {},
},
],
properties: {},
},
],
},
},
],
});
// 3. 转换为 DOCX(传入批注获取函数)
const getComment = (id: number) => commentMap.get(id);
const docx = await jsonToDocx(ast, {
title: '项目计划文档',
creator: 'docx-parser',
font: 'Microsoft YaHei',
fontSize: 12,
}, getComment);
await fs.writeFile('output-with-comments.docx', docx);
批注结构说明:
commentRangeStart、commentRangeEnd 和 commentReference 标记批注范围CommentDefinition 接口定义,包含:
id: 批注IDauthor: 作者名称date: 批注日期initials: 作者缩写done: 是否已解决(true 表示已解决,false 或 undefined 表示未解决)content: 批注内容(AST节点)replies: 批注的回复列表(可选)replies 数组中定义,每个回复也是一个 CommentReply 对象,包含 id、author、date、initials 和 contentjsonToDocx 的第三个参数是一个函数,用于根据批注ID获取批注定义以下示例展示了如何使用 JSON (AST) 作为中间格式进行复杂的转换操作:
import { docxToJson, jsonToHtml, htmlToJson, jsonToDocx } from '@lpen/docx';
import fs from 'fs-extra';
async function complexConversion() {
// 1. DOCX → JSON
const ast1 = await docxToJson('input.docx');
// 2. 对 AST 进行操作(例如:修改内容、添加节点等)
// ast1.children?.push({ ... });
// 3. JSON → HTML
const html = jsonToHtml(ast1);
// 4. 修改 HTML
const modifiedHtml = html.replace('旧文本', '新文本');
// 5. HTML → JSON
const ast2 = htmlToJson(modifiedHtml);
// 6. JSON → DOCX
const docx = await jsonToDocx(ast2);
await fs.writeFile('output.docx', docx);
}
complexConversion();
AST(抽象语法树)是一个标准化的 JSON 格式,用于表示文档结构。主要节点类型包括:
document: 文档根节点paragraph: 段落节点run: 文本运行节点hyperlink: 超链接节点image: 图片节点list: 列表节点list_item: 列表项节点table: 表格节点table_row: 表格行节点table_cell: 表格单元格节点commentRangeStart: 批注范围开始节点commentRangeEnd: 批注范围结束节点commentReference: 批注引用节点pageBreak: 分页符节点每个节点包含:
type: 节点类型children: 子节点数组(可选)text: 文本内容(可选)properties: 节点属性(样式、格式等)更多详细信息请参考 DocxNode 类型定义。
<h1> - <h6><p><strong>, <b>, <em>, <i>, <u>, <s>, <del><ul>, <ol>, <li><table>, <tr>, <td>, <th>
colspan 和 rowspan 属性width 样式(表格和单元格)<a href="..."><img src="...">(支持 data URL 和外部 URL)<br>, <br/><hr><blockquote><code>, <pre><div style="page-break-after: always"></div> 或 <div class="docx-page-break"></div>color: 文本颜色background-color: 背景颜色font-size: 字体大小font-family: 字体族font-weight: 字体粗细(bold, normal)font-style: 字体样式(italic, normal)text-align: 文本对齐(left, center, right, justify)text-decoration: 文本装饰(underline, line-through)margin: 外边距padding: 内边距border: 边框(支持 border-top, border-right, border-bottom, border-left)width, height: 尺寸(用于图片、表格和单元格)vertical-align: 垂直对齐(用于表格单元格)page-break-after: 分页符样式(always 表示在元素后插入分页符)import { parseDocx } from '@lpen/docx';
const html = await parseDocx('./document.docx');
console.log(html);
import { parseDocx } from '@lpen/docx';
// 从 HTTPS URL 下载并解析
const html = await parseDocx('https://example.com/document.docx');
// 从 HTTP URL 下载并解析
const html = await parseDocx('http://example.com/document.docx');
import { parseDocx } from '@lpen/docx';
import fs from 'fs-extra';
const buffer = await fs.readFile('./document.docx');
const html = await parseDocx(buffer);
// 跳过图片
const html = await parseDocx('document.docx', {
imageMode: 'skip'
});
// 使用占位符
const html = await parseDocx('document.docx', {
imageMode: 'placeholder'
});
const html = await parseDocx('document.docx', {
includeHeaders: false,
includeFooters: false,
});
const html = await parseDocx('document.docx', {
includeComments: false,
});
const html = `
<table>
<tr>
<th>姓名</th>
<th>年龄</th>
</tr>
<tr>
<td>张三</td>
<td>25</td>
</tr>
</table>
`;
const docx = await htmlToWord(html);
await fs.writeFile('output.docx', docx);
const html = `
<table style="width: 432pt; border-collapse: collapse;">
<tr>
<td style="width: 186.75pt; border: 1pt solid #000000;">单元格 1</td>
<td style="width: 186.75pt; border: 1pt solid #000000;">单元格 2</td>
</tr>
</table>
`;
const docx = await htmlToWord(html);
await fs.writeFile('output.docx', docx);
const html = `
<table>
<tr>
<td rowspan="2">合并行</td>
<td>单元格 1</td>
</tr>
<tr>
<td>单元格 2</td>
</tr>
</table>
`;
const docx = await htmlToWord(html);
await fs.writeFile('output.docx', docx);
const html = `
<ul>
<li>项目 1</li>
<li>项目 2</li>
</ul>
<ol>
<li>有序项目 1</li>
<li>有序项目 2</li>
</ol>
`;
const docx = await htmlToWord(html);
await fs.writeFile('output.docx', docx);
MIT
如有问题或建议,欢迎联系:
为了更好地帮助您解决问题,请在邮件中包含以下信息:
邮件主题格式:
[@lpen/docx] 问题描述
邮件内容模板:
问题描述:
[请简要描述您遇到的问题]
复现步骤:
1. [步骤 1]
2. [步骤 2]
3. [步骤 3]
预期行为:
[描述您期望的行为]
实际行为:
[描述实际发生的行为]
环境信息:
- Node.js 版本:[例如:v18.0.0]
- 操作系统:[例如:Windows 10 / macOS 13 / Linux Ubuntu 22.04]
- 库版本:[例如:@lpen/docx@1.0.8]
代码示例:
[请提供相关的代码示例,如果可能的话]
错误信息:
[如果有错误信息,请完整粘贴]
附加信息:
[任何其他可能有助于解决问题的信息]
示例:
问题描述:
表格宽度在 HTML 转 DOCX 时没有正确保留
复现步骤:
1. 创建一个包含表格的 HTML 文件
2. 使用 htmlToWord 函数转换
3. 打开生成的 DOCX 文件
预期行为:
表格宽度应该与 HTML 中的 width 样式一致
实际行为:
表格宽度变成了默认值,没有保留原始宽度
环境信息:
- Node.js 版本:v18.17.0
- 操作系统:Windows 10
- 库版本:@lpen/docx@1.0.8
代码示例:
const html = '<table style="width: 432pt;"><tr><td>内容</td></tr></table>';
const docx = await htmlToWord(html);
FAQs
A high-performance, type-safe Node.js library for parsing .docx files
We found that @lpen/docx demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.

Research
Malicious versions of the Telnyx Python SDK on PyPI delivered credential-stealing malware via a multi-stage supply chain attack.

Security News
TeamPCP is partnering with ransomware group Vect to turn open source supply chain attacks on tools like Trivy and LiteLLM into large-scale ransomware operations.