npm package: @lpen/docx
Description: A high-performance, type-safe Node.js library for parsing .docx files
Author: luopeng
Latest version: 1.0.23

# @lpen/docx

一个高性能、类型安全的 Node.js 库，用于解析 .docx 文件并将其转换为 HTML，同时也支持将 HTML 转换为 .docx 文档。

## 特性

- 🚀 **高性能**：针对大型文档进行了优化
- 📦 **类型安全**：完整的 TypeScript 支持，包含类型定义
- 🎨 **丰富的格式支持**：保留样式、图片、表格、列表等
- 🔧 **简单易用**：一行代码完成解析和转换
- 📄 **完整支持**：处理页眉、页脚、批注（包括回复和解决状态）和复杂布局
- 🔄 **双向转换**：支持 DOCX → HTML 和 HTML → DOCX
- 📊 **四向转换**：支持 DOCX ↔ JSON ↔ HTML 的完整转换链，JSON 格式为标准化 AST
- 📊 **表格支持**：完整的表格功能，包括边框、宽度、合并单元格等
- 📑 **页眉页脚支持**：完整支持页眉和页脚的转换，包括 DOCX 转 HTML 和 HTML 转 DOCX
- 📄 **分页符支持**：支持 Word 文档中的分页符，HTML 转 DOCX 时可通过样式或类名识别分页符

## 安装

```bash
npm install @lpen/docx
# 或
pnpm add @lpen/docx
# 或
yarn add @lpen/docx
```

## 快速开始

### DOCX 转 HTML

#### 最简单的方式（推荐）

```typescript
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();
```

#### 自定义选项

```typescript
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 类

如果需要更细粒度的控制，可以使用 `DocxParser` 类：

```typescript
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();
```

### HTML 转 DOCX

#### 最简单的方式（推荐）

```typescript
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();
```

#### 自定义选项

```typescript
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);
```

#### 带页眉和页脚

```typescript
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 文档

### DOCX 转 HTML

#### parseDocx（推荐）

最简单的 API，一行代码完成所有操作。

```typescript
parseDocx(input: string | Buffer, options?: ParseOptions): Promise<string>
```

**参数：**

- `input`: 文件路径（string）、HTTP/HTTPS URL 或文件 Buffer
- `options`: 可选配置项
  - `imageMode`: 图片处理方式，`'base64'`（默认）、`'skip'` 或 `'placeholder'`
  - `includeHeaders`: 是否包含页眉，默认 `true`
  - `includeFooters`: 是否包含页脚，默认 `true`
  - `includeMetadata`: 是否包含元数据，默认 `false`
  - `includeComments`: 是否包含批注，默认 `true`
  - `cssClassPrefix`: 自定义 CSS 类名前缀
  - `pageBreakClass`: 分页符的 CSS 类名，默认 `"docx-page-break"`

**返回值：**

返回 HTML 字符串

**示例：**

```typescript
// 从本地文件解析
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 类（高级用法）

如果需要更细粒度的控制，可以使用 `DocxParser` 类：

```typescript
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 转 DOCX

#### htmlToWord（推荐）

将 HTML 字符串转换为 Word (.docx) 文档。

```typescript
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`, `gutter`
  - `font`: 默认字体，默认 `'Calibri'`
  - `fontSize`: 默认字体大小（单位：pt），默认 `11`
  - `lang`: 文档语言代码，默认 `'en-US'`
  - `direction`: 文本方向，`'ltr'`（默认）或 `'rtl'`
  - `header`: 是否启用页眉，默认 `false`
  - `footer`: 是否启用页脚，默认 `false`
  - `skipFirstHeaderFooter`: 是否跳过首页的页眉页脚，默认 `false`
  - `headerType`: 页眉类型，`'default'`（默认）、`'first'`（首页）或 `'even'`（偶数页）
  - `footerType`: 页脚类型，`'default'`（默认）、`'first'`（首页）或 `'even'`（偶数页）
  - `heading`: 标题样式配置（H1-H6）
    - `font`, `fontSize`, `bold`, `spacing`, `keepLines`, `keepNext`, `outlineLevel`
  - `imageProcessing`: 图片处理选项
    - `maxRetries`: 最大重试次数
    - `downloadTimeout`: 下载超时时间（毫秒）
    - `svgHandling`: SVG 处理方式，`'convert'`（转换为 PNG）、`'native'`（原生支持）或 `'auto'`（自动）
  - `pageBreak`: 分页符配置选项
    - `enabled`: 是否启用分页符识别，默认 `true`
    - `class`: 分页符的 CSS 类名，默认 `"docx-page-break"`
- `headerHTMLString`: 页眉 HTML 字符串（可选），当 `options.header` 为 `true` 时使用
- `footerHTMLString`: 页脚 HTML 字符串（可选），当 `options.footer` 为 `true` 时使用

**返回值：**

返回 `.docx` 文件的 Buffer

**示例：**

```typescript
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：使用内联样式**

```typescript
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 类名**

```typescript
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：使用简化的类名**

```typescript
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);
```

**自定义分页符类名**

```typescript
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);
```

**禁用分页符识别**

```typescript
const docx = await htmlToWord(html, {
  pageBreak: {
    enabled: false, // 禁用分页符识别
  },
});
```

### 四向转换（DOCX ↔ JSON ↔ HTML）

本库支持完整的四向转换，JSON 格式为标准化 AST（抽象语法树），可以作为中间格式进行数据操作和转换。

#### 转换流程图

```
DOCX ──docxToJson──> JSON (AST) ──jsonToHtml──> HTML
  ↑                                    ↓
  │                              htmlToJson
  │                                    ↓
jsonToDocx                      JSON (AST)
```

#### 1. DOCX → JSON (AST)

将 DOCX 文件转换为 JSON 格式的 AST。

```typescript
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();
```

#### 2. HTML → JSON (AST)

将 HTML 字符串转换为 JSON 格式的 AST。

```typescript
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,
});
```

#### 3. JSON (AST) → HTML

将 JSON 格式的 AST 转换为 HTML 字符串。

```typescript
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');
```

#### 4. JSON (AST) → DOCX

将 JSON 格式的 AST 转换为 DOCX 文档。

```typescript
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);
```

#### 带批注的 JSON (AST) → DOCX 示例

以下示例展示了如何创建包含批注和回复的文档：

```typescript
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);
```

**批注结构说明：**

- **主批注**：在 AST 中使用 `commentRangeStart`、`commentRangeEnd` 和 `commentReference` 标记批注范围
- **批注定义**：通过 `CommentDefinition` 接口定义，包含：
  - `id`: 批注ID
  - `author`: 作者名称
  - `date`: 批注日期
  - `initials`: 作者缩写
  - `done`: 是否已解决（`true` 表示已解决，`false` 或 `undefined` 表示未解决）
  - `content`: 批注内容（AST节点）
  - `replies`: 批注的回复列表（可选）
- **回复批注**：在 `replies` 数组中定义，每个回复也是一个 `CommentReply` 对象，包含 `id`、`author`、`date`、`initials` 和 `content`
- **批注获取函数**：`jsonToDocx` 的第三个参数是一个函数，用于根据批注ID获取批注定义

#### 完整转换示例

以下示例展示了如何使用 JSON (AST) 作为中间格式进行复杂的转换操作：

```typescript
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 结构说明

AST（抽象语法树）是一个标准化的 JSON 格式，用于表示文档结构。主要节点类型包括：

- `document`: 文档根节点
- `paragraph`: 段落节点
- `run`: 文本运行节点
- `hyperlink`: 超链接节点
- `image`: 图片节点
- `list`: 列表节点
- `list_item`: 列表项节点
- `table`: 表格节点
- `table_row`: 表格行节点
- `table_cell`: 表格单元格节点
- `commentRangeStart`: 批注范围开始节点
- `commentRangeEnd`: 批注范围结束节点
- `commentReference`: 批注引用节点
- `pageBreak`: 分页符节点

每个节点包含：
- `type`: 节点类型
- `children`: 子节点数组（可选）
- `text`: 文本内容（可选）
- `properties`: 节点属性（样式、格式等）
- 其他特定于节点类型的属性

更多详细信息请参考 `DocxNode` 类型定义。

## 支持的 HTML 标签和 CSS 样式

### 支持的 HTML 标签

- **标题**: `<h1>` - `<h6>`
- **段落**: `<p>`
- **文本格式**: `<strong>`, `<b>`, `<em>`, `<i>`, `<u>`, `
`, `<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>`

### 支持的 CSS 样式

- `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` 表示在元素后插入分页符）

## 更多示例

### DOCX 转 HTML 示例

#### 从文件路径解析

```typescript
import { parseDocx } from '@lpen/docx';

const html = await parseDocx('./document.docx');
console.log(html);
```

#### 从 URL 下载并解析

```typescript
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');
```

#### 从 Buffer 解析

```typescript
import { parseDocx } from '@lpen/docx';
import fs from 'fs-extra';

const buffer = await fs.readFile('./document.docx');
const html = await parseDocx(buffer);
```

#### 自定义图片处理

```typescript
// 跳过图片
const html = await parseDocx('document.docx', {
  imageMode: 'skip'
});

// 使用占位符
const html = await parseDocx('document.docx', {
  imageMode: 'placeholder'
});
```

#### 不包含页眉页脚

```typescript
const html = await parseDocx('document.docx', {
  includeHeaders: false,
  includeFooters: false,
});
```

#### 不包含批注

```typescript
const html = await parseDocx('document.docx', {
  includeComments: false,
});
```

### HTML 转 DOCX 示例

#### 基本表格

```typescript
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);
```

#### 带样式的表格

```typescript
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);
```

#### 合并单元格

```typescript
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);
```

#### 列表

```typescript
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

## 联系方式

如有问题或建议，欢迎联系：

- 📧 邮箱：2736661400@qq.com

### 问题报告格式

为了更好地帮助您解决问题，请在邮件中包含以下信息：

**邮件主题格式：**
```
[@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);
```