## Purpose 提供 Reader 内部共享工具模块,包含解析器包装函数、格式化工具、ZIP 安全处理和 unstructured 库集成。此模块仅供 readers 包内部使用,不作为公共 API。 ## Requirements ### Requirement: 解析器包装函数 系统 SHALL 提供统一的解析器包装函数,封装第三方库的调用细节。 #### Scenario: 使用 MarkItDown 解析 - **WHEN** 调用 `parse_via_markitdown(file_path)` - **THEN** 系统使用 MarkItDown 库解析文件 - **AND** 成功时返回 `(markdown_content, None)` - **AND** 失败时返回 `(None, error_message)` #### Scenario: 使用 docling 解析 - **WHEN** 调用 `parse_via_docling(file_path)` - **THEN** 系统使用 docling 库解析文件 - **AND** 成功时返回 `(markdown_content, None)` - **AND** 失败时返回 `(None, error_message)` #### Scenario: 库未安装时返回友好错误 - **WHEN** 调用解析器包装函数但对应库未安装 - **THEN** 系统返回 `(None, "<库名> 库未安装")` ### Requirement: Markdown 表格格式化 系统 SHALL 提供将二维列表格式化为 Markdown 表格的工具函数。 #### Scenario: 格式化标准表格 - **WHEN** 调用 `build_markdown_table(rows_data)` 且 rows_data 包含表头和数据行 - **THEN** 系统生成标准 Markdown 表格格式 - **AND** 第一行前生成分隔行(`| --- | --- |`) #### Scenario: 空数据返回空字符串 - **WHEN** 调用 `build_markdown_table([])` 或 `build_markdown_table([[]])` - **THEN** 系统返回空字符串 ### Requirement: 列表堆栈处理 系统 SHALL 提供列表堆栈处理工具函数,用于处理嵌套列表的格式化输出。 #### Scenario: 刷新列表堆栈 - **WHEN** 调用 `flush_list_stack(list_stack, target)` - **THEN** 系统将 list_stack 中所有非空项添加到 target 列表 - **AND** 每个项末尾添加换行符 - **AND** 清空 list_stack #### Scenario: 跳过空项 - **WHEN** list_stack 中包含空字符串 - **THEN** 系统跳过空项,不添加到 target ### Requirement: ZIP 文件安全打开 系统 SHALL 提供安全的 ZIP 文件打开函数,防止路径遍历攻击。 #### Scenario: 打开合法文件 - **WHEN** 调用 `safe_open_zip(zip_file, "valid/file.txt")` - **THEN** 系统返回对应的 ZipExtFile 对象 #### Scenario: 拒绝路径遍历攻击 - **WHEN** 路径包含 ".." 在 Path.parts 中 - **THEN** 系统返回 None #### Scenario: 拒绝绝对路径 - **WHEN** 路径为绝对路径 - **THEN** 系统返回 None #### Scenario: 处理路径异常 - **WHEN** Path() 抛出 ValueError 或 OSError - **THEN** 系统捕获异常并返回 None ### Requirement: unstructured 元素转换 系统 SHALL 提供将 unstructured 库解析的元素转换为 Markdown 的工具函数。 #### Scenario: 转换标准元素 - **WHEN** 调用 `convert_unstructured_to_markdown(elements, trust_titles=True)` - **THEN** 系统跳过 Header、Footer、PageBreak、PageNumber 元素 - **AND** 跳过 RGB 颜色值和页码噪声 - **AND** Table 元素转换为 Markdown 表格 - **AND** Title 元素转换为 # 标题(根据 category_depth 确定级别) - **AND** ListItem 元素转换为 - 列表项 - **AND** Image 元素转换为 ![image](path) 格式 #### Scenario: 库未安装时回退 - **WHEN** markdownify 或 unstructured 库未安装 - **THEN** 系统提取所有元素的 text 属性并用双换行连接 ### Requirement: 噪声模式匹配 系统 SHALL 定义 unstructured 库的噪声匹配模式。 #### Scenario: 匹配 RGB 颜色值 - **WHEN** 文本匹配 `_UNSTRUCTURED_RGB_PATTERN`(如 "R:255 G:128 B:0") - **THEN** 系统将其识别为噪声并过滤 #### Scenario: 匹配页码 - **WHEN** 文本匹配 `_UNSTRUCTURED_PAGE_NUMBER_PATTERN`(如 "— 3 —") - **THEN** 系统将其识别为噪声并过滤 ### Requirement: 通用 LibreOffice 格式转换 系统 SHALL 提供通用的 LibreOffice 格式转换函数,支持在不同格式间转换。 #### Scenario: 转换文件到指定格式 - **WHEN** 调用 `convert_via_libreoffice(input_path, target_format, output_dir)` - **THEN** 系统使用 soffice --headless --convert-to 进行转换 - **AND** 输出文件写入 output_dir - **AND** 成功时返回 (output_path, None) - **AND** 失败时返回 (None, error_message) #### Scenario: LibreOffice 未安装 - **WHEN** soffice 未在 PATH 中 - **THEN** 系统返回 (None, "LibreOffice 未安装") #### Scenario: 转换超时 - **WHEN** soffice 执行超过 timeout 秒(默认 60 秒) - **THEN** 系统返回 (None, "LibreOffice 转换超时") #### Scenario: 转换失败 - **WHEN** soffice 返回非零退出码 - **THEN** 系统返回 (None, "LibreOffice 转换失败 (code: {code})") #### Scenario: 输出文件未生成 - **WHEN** soffice 执行成功但未生成输出文件 - **THEN** 系统返回 (None, "LibreOffice 未生成输出文件") #### Scenario: 可自定义输出后缀 - **WHEN** 提供 output_suffix 参数 - **THEN** 系统使用该后缀作为输出文件后缀,而不是 target_format #### Scenario: 调用者管理输出目录生命周期 - **WHEN** convert_via_libreoffice 执行完成 - **THEN** 输出文件保留在 output_dir 中,由调用者负责清理