Files
lyxy-document/openspec/changes/archive/2026-03-08-unify-document-readers/design.md
lanyuanxiaoyao 833018d451 feat: 统一文档解析器项目 - 迁移 lyxy-reader-office 和 lyxy-reader-html
## 功能特性

- 建立统一的项目结构,包含 core/、readers/、utils/、tests/ 模块
- 迁移 lyxy-reader-office 的所有解析器(docx、xlsx、pptx、pdf)
- 迁移 lyxy-reader-html 的所有解析器(html、url 下载)
- 统一 CLI 入口为 lyxy_document_reader.py
- 统一 Markdown 后处理逻辑
- 按文件类型组织 readers,每个解析器独立文件
- 依赖分组按文件类型细分(docx、xlsx、pptx、pdf、html、http)
- PDF OCR 解析器优先,无参数控制
- 使用 logging 模块替代简单 print
- 设计完整的单元测试结构
- 重写项目文档

## 新增目录/文件

- core/ - 核心模块(异常体系、Markdown 工具、解析调度器)
- readers/ - 格式阅读器(base.py + docx/xlsx/pptx/pdf/html)
- utils/ - 工具函数(文件类型检测)
- tests/ - 测试(conftest.py + test_core/ + test_readers/ + test_utils/)
- lyxy_document_reader.py - 统一 CLI 入口

## 依赖分组

- docx - DOCX 文档解析支持
- xlsx - XLSX 文档解析支持
- pptx - PPTX 文档解析支持
- pdf - PDF 文档解析支持(含 OCR)
- html - HTML/URL 解析支持
- http - HTTP/URL 下载支持
- office - Office 格式组合(docx/xlsx/pptx/pdf)
- web - Web 格式组合(html/http)
- full - 完整功能
- dev - 开发依赖
2026-03-08 13:46:37 +08:00

7.9 KiB
Raw Blame History

Context

当前存在两个独立的文档解析 skilllyxy-reader-office支持 docx/xlsx/pptx/pdf和 lyxy-reader-html支持 html/url。两个项目存在大量重复代码common.py 中的 Markdown 处理函数完全相同),维护成本高,且无法统一扩展。

本项目 lyxy-document 是一个新的 Python 项目,用于统一这两个 skill 的能力。项目当前只有基础结构,需要完整迁移两个 skill 的核心功能。

项目规范约束:

  • 语言:仅中文(代码注释、文档、交流)
  • Python始终用 uv 运行
  • 依赖pyproject.toml 声明,使用 uv 安装
  • 模块文件150-300 行
  • 错误:需自定义异常 + 清晰信息 + 位置上下文
  • 测试:所有需求必须设计全面测试

Goals / Non-Goals

Goals:

  • 建立统一的项目结构,合并两个 skill 的能力
  • 提供一致的 CLI 入口 lyxy_document_reader.py
  • 按文件类型组织 readers每个解析器独立文件
  • 建立自定义异常体系
  • 使用 logging 模块替代简单 print
  • 迁移所有解析器,保持原有功能和解析器优先级
  • PDF OCR 优先,无参数控制
  • 按文件类型细分依赖分组
  • 设计完整的测试覆盖

Non-Goals:

  • 保持原有两个 skill 的兼容入口(不需要)
  • 保留 --high-res 参数(取消)
  • 重写或优化解析器逻辑(仅迁移,保持原样)
  • 实现新的解析器(仅迁移现有)
  • 迁移原有 references 文档(文档重写)

Decisions

1. 目录结构

决定: 扁平化目录结构,不使用 src/lyxy_document/ 双层目录。

lyxy-document/
├── lyxy_document_reader.py    # 统一 CLI 入口
├── core/                       # 核心模块
├── readers/                    # 格式阅读器
├── utils/                      # 工具函数
└── tests/                      # 测试

替代方案:

  • src/lyxy_document/ 双层包结构(更标准的 Python 包结构)
  • 所有文件平铺在根目录(过于混乱)

理由: 用户明确要求减少目录层级,扁平化结构更简单直接,符合当前项目的规模。


2. Reader 注册机制

决定: 显式导入注册,不使用动态发现。

# readers/__init__.py
from .base import BaseReader
from .docx import DocxReader
from .xlsx import XlsxReader
from .pptx import PptxReader
from .pdf import PdfReader
from .html import HtmlReader

READERS = [
    DocxReader,
    XlsxReader,
    PptxReader,
    PdfReader,
    HtmlReader,
]

替代方案:

  • 通过文件名匹配自动发现(*_reader.py 自动导入)
  • 入口点entry points插件机制

理由: 用户明确要求显式导入,不考虑动态发现。显式导入更清晰、更可预测,符合项目"未上线、无用户"的阶段特点。


3. 解析器组织

决定: 每个解析器独立文件,按文件类型分目录组织。

readers/
├── docx/
│   ├── docling.py
│   ├── unstructured.py
│   ├── markitdown.py
│   ├── pypandoc.py
│   ├── python_docx.py
│   └── native_xml.py
├── xlsx/
│   └── ...
├── pptx/
│   └── ...
├── pdf/
│   ├── docling_ocr.py
│   ├── unstructured_ocr.py
│   ├── docling.py
│   ├── unstructured.py
│   ├── markitdown.py
│   └── pypdf.py
└── html/
    ├── downloader.py      # 不拆分
    ├── cleaner.py
    ├── trafilatura.py
    ├── domscribe.py
    ├── markitdown.py
    └── html2text.py

替代方案:

  • 所有解析器在同一文件(原有方式,文件太大)
  • 按解析库类型分目录(不如按文件类型直观)

理由: 用户明确要求每个解析器拆分到单个脚本,方便对单个解析器进行优化。按文件类型分目录符合逻辑,下载器和清理器作为 html reader 的辅助模块,不拆分。


4. 异常体系

决定: 自定义异常继承自基异常 LyxyDocumentError。

# core/exceptions.py
class LyxyDocumentError(Exception):
    """文档处理基异常"""
    pass

class FileDetectionError(LyxyDocumentError):
    """文件类型检测失败"""
    pass

class ReaderNotFoundError(LyxyDocumentError):
    """未找到适配的阅读器"""
    pass

class ParseError(LyxyDocumentError):
    """解析失败"""
    pass

class DownloadError(LyxyDocumentError):
    """下载失败"""
    pass

替代方案:

  • 直接使用内置异常(不够清晰)
  • 更细粒度的异常(过度设计)

理由: 符合项目规范"错误需自定义异常 + 清晰信息 + 位置上下文"5 个异常覆盖主要场景。


5. 依赖分组

决定: 按文件类型细分依赖分组。

[project.optional-dependencies]
docx = [...]
xlsx = [...]
pptx = [...]
pdf = [...]
html = [...]
http = [...]
office = ["lyxy-document[docx,xlsx,pptx,pdf]"]
web = ["lyxy-document[html,http]"]
full = ["lyxy-document[office,web]"]
dev = [...]

替代方案:

  • 按原有 skill 分组office/html不够细
  • 所有依赖在 dependencies太重

理由: 用户明确要求分组更细按文件类型分组让用户可以按需安装组合分组office/web/full提供便利性。


6. PDF OCR 策略

决定: OCR 解析器加入解析器链,优先级最高,无参数控制。

# readers/pdf/__init__.py
PARSERS = [
    ("docling OCR", docling_ocr.parse),
    ("unstructured OCR", unstructured_ocr.parse),
    ("docling", docling.parse),
    ("unstructured", unstructured.parse),
    ("MarkItDown", markitdown.parse),
    ("pypdf", pypdf.parse),
]

替代方案:

  • 保留 --high-res 参数(用户要求取消)
  • OCR 单独接口(不必要)
  • 后续智能决策(留待未来)

理由: 用户明确要求取消 --high-res 参数OCR 与非 OCR 同级OCR 优先。后续可考虑更智能的方式(如先判断是否需要 OCR


7. 模块文件行数

决定: 严格控制单文件行数在 150-300 行。

替代方案:

  • 不限制行数(可能导致过大文件)
  • 更严格限制(过度拆分)

理由: 符合项目规范"模块文件 150-300 行"。原有单个 parser.py 文件过大,通过拆分为独立解析器文件来满足要求。


8. 临时文件

决定: 正式代码使用系统临时目录tempfile 标准库),项目 temp/ 目录仅用于开发过程中的临时文档。

替代方案:

  • 所有临时文件放项目 temp/(用户明确表示不需要)

理由: 用户明确说明 temp/ 目录是开发过程中大模型或 AI 创建与代码无关的文档时使用,正式代码不使用这个目录。

Risks / Trade-offs

风险 影响 缓解措施
解析器拆分为独立文件后,代码复用需仔细设计 通用工具函数(如 parse_with_markitdown、parse_with_docling放在 core/markdown.py 或 utils/ 中复用
PDF OCR 优先可能导致非 OCR 文档解析变慢 后续可考虑增加智能判断(检测文档是否包含文本层),当前按用户要求保持简单
依赖分组过细可能导致用户困惑 提供 office/web/full 组合分组,文档中说明清楚
没有向后兼容,原有 skill 调用会失效 用户明确表示不需要兼容,项目阶段"未上线、无用户"
缺少 --high-res 参数,用户无法控制是否使用 OCR 用户明确要求取消,后续可考虑更智能的方式

Migration Plan

本项目是新项目,无现有用户,无需迁移。

实施步骤(详见 tasks.md

  1. 搭建核心模块core/
  2. 搭建基础架构readers/base.py、utils/
  3. 迁移各个 readerdocx/xlsx/pptx/pdf/html
  4. 实现统一 CLI 入口
  5. 编写测试
  6. 更新文档

Open Questions

无。所有决策已与用户确认。