- 后端
- AI 应用
- NLP
【免费下载链接】xberg
Polyglot document intelligence with a Rust core: extract text, metadata, images, tables, and structured data from 106 formats across 140 file extensions, plus code intelligence for 371 languages. Fifteen bindings, with CLI, REST API, and MCP server.
本篇技术指南围绕 Xberg(Rust 核心的 Polyglot 文档智能引擎)的 C 语言绑定,讲解如何仅用xberg_extract_input_from_json与xberg_extract两个函数完成 HWPX(Hangul Word Processor XML)文档的独立(standalone)提取。读者将掌握 C 侧 ExtractInput 的 JSON 契约、HWPX 的 MIME 与格式识别规则、底层HwpxExtractor对正文/标题/表格/公式/图片/脚注的完整解析链路,以及如何在测试夹具中验证提取结果。示例代码可直接复制编译运行。
HWPX 是什么,Xberg 如何支持它
HWPX(.hwpx,Open HWPML)是韩国 Hancom 公司韩文办公套件 HWP 的 XML 格式,是二进制 HWP 5.0 的 ZIP 容器后继者。与 DOCX/OOXML 类似,HWPX 是一个以Contents/content.hpf为清单、以Contents/section*.xml承载正文分节的 ZIP 包。Xberg 通过 HwpxExtractor 插件支持该格式,其supported_mime_types()返回两个值:
application/haansofthwpx—— 对外标准 MIME,也是最常用的输入标识;application/hwp+zip—— HWPX 包内mimetype条目自声明的媒体类型。
该提取器注册优先级为50,插件名hwpx-extractor(见 插件接口实现 与 注册测试)。
在格式识别层面,core/mime.rs 做了双层探测:扩展名.hwpx直接映射到application/haansofthwpx;对字节内容,则检测 ZIP 内是否存在Contents/content.hpf清单文件,或读取包内未压缩mimetype条目是否为application/hwp+zip且伴随清单存在,从而把这类包从普通 ZIP 路由中接管过来。
最小可运行的 C 示例
关联文档 format_hwpx_standalone.md 给出了一段完整的、可编译运行的 C 程序——这也是本指南的核心骨架。它通过 JSON 构造一个uri类型的输入,然后以默认配置调用提取:
#include <assert.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "xberg.h" int main(void) { XBERGAlefHandle input_handle = xberg_extract_input_from_json("{\"filename\":\"simple.hwpx\",\"kind\":\"uri\",\"mime_type\":\"application/haansofthwpx\",\"uri\":\"https://example.com/hwpx/simple.hwpx\"}"); XBERGAlefHandle result = xberg_extract(input_handle, 0); xberg_extract_input_free(input_handle); xberg_extraction_result_free(result); return EXIT_SUCCESS; }要点拆解:
#include "xberg.h"来自 C 绑定头文件 include/xberg.h,其中XBERGAlefHandle定义为uint64_t类型句柄(见该文件第 3042 行附近),所有对象都以不透明句柄形式传递。xberg_extract_input_from_json(const char *json)从 JSON 构造输入对象,声明见 xberg_extract_input_from_json;返回值必须用xberg_extract_input_free释放。xberg_extract(XBERGAlefHandle input, XBERGAlefHandle config)为单文档提取入口,声明见 xberg_extract。第二个参数传0表示使用默认提取配置,等价于xberg_extraction_config_default()。xberg_extraction_result_free释放结果句柄,对应 xberg_extraction_result_free;结果内容可通过xberg_extraction_result_results(handle)(声明)或xberg_extraction_result_to_json(handle)(声明)取出。
这段示例是"独立提取"(standalone)语义的典型用法:输入直接指向一个 HWPX 文档 URI,不依赖任何前置的 MIME 探测或格式判别流程——调用方显式声明 MIME 后即可一次性拿到提取结果。更完整的 C 侧生命周期范例(含错误分支与字符串释放)可以参考 xberg-ffi/README.md 中的xberg_extract_input_from_uri示例。
ExtractInput 的 JSON 契约
xberg_extract_input_from_json接受的 JSON 在本例中共五个字段,其含义与底层ExtractInput一一对应(字段访问器见 xberg_extract_input_kind / _uri / _mime_type / _filename):
| 字段 | 取值(示例) | 说明 |
|---|---|---|
kind | "uri" | 输入类型。除uri外还支持bytes等类型,uri接受本地路径、file://URI 或 HTTP(S) URL |
uri | "https://example.com/hwpx/simple.hwpx" | 文档地址。示例中指向由测试桩返回的simple.hwpx |
mime_type | "application/haansofthwpx" | 显式声明的媒体类型。对 HWPX 声明此值可跳过内容嗅探,直接路由到HwpxExtractor |
filename | "simple.hwpx" | 文件名提示,用于辅助格式判定与结果元数据 |
config | (省略) | 可选;本例用xberg_extract的第二个参数传0代替 |
等价地,C 侧也可用xberg_extract_input_from_uri(uri)快速构造输入(声明),或对字节流使用xberg_extract_input_from_bytes(bytes, len, mime_type, filename)(声明),后者适合从内存缓冲区直接读取 .hwpx 文件内容。
测试夹具:如何验证一次成功的 HWPX 提取
仓库中与本文档配套的夹具 fixtures/format_specific/format_hwpx_standalone.json 完整定义了一次extract调用的服务端模拟与断言条件,可以当作"可运行的验收标准"来理解:
{ "id": "format_hwpx_standalone", "category": "format_specific", "call": "extract", "input": { "mock_responses": [ { "path": "/hwpx/simple.hwpx", "status_code": 200, "headers": { "content-type": "application/haansofthwpx" }, "body_file": "../test_documents/hwpx/simple.hwpx" } ], "extract_input": { "kind": "uri", "uri": "$mock_url/hwpx/simple.hwpx", "mime_type": "application/haansofthwpx", "filename": "simple.hwpx" } }, "assertions": [ { "type": "not_error" }, { "type": "min_length", "field": "results[0].content", "value": 20 }, { "type": "contains", "field": "results[0].content", "value": "Hello from HWPX" } ] }三个断言依次验证:调用不报错;提取出的文本内容长度至少 20 字符;且内容包含真实文档正文Hello from HWPX。真实样本文件位于test_documents/hwpx/simple.hwpx,提取器自身的端到端单测test_hwpx_extract_real_document也直接读取该文件并断言文本包含Hello from HWPX document(见 hwpx.rs 测试),与本夹具互相印证。
底层解析链路:HwpxExtractor 做了什么
当输入被路由到 HwpxExtractor 后,extract_content 实现 依次执行:
- 安全预检:检查文件总大小是否超过
security_limits.max_archive_size,再以ZipBombValidator校验压缩比与包内文件数,防止恶意 ZIP 包耗尽内存(详见下文"安全边界"一节); - 公式预扫描:
collect_section_formulas逐节读取Contents/section*.xml,把文档内所有数学公式的 HWP EQEdit 脚本收集起来(细节见下文"公式"小节); - 正式解析:调用
unhwp::parse_bytes(content)生成结构化的Document模型(标题、作者、正文分节、表格、资源表等); - 构建内部文档:
build_hwpx_internal_document将模型转换为统一的InternalDocument,供后续渲染 Markdown、抽取表格/图片/元数据等下游环节使用。
元数据提取
从doc.metadata中映射标题、作者、主题、关键词、创建/修改时间,并把creator_app(创建程序)、format_version分别写入additional与document_version字段(见 元数据构建)。这意味着 HWPX 的文档属性会原样出现在提取结果的 metadata 中。
正文、标题与页眉页脚
解析按节(section)遍历,每一节的正文块分为段落与表格两类:
- 带标题样式的段落(
p.style.is_heading())推入 heading 元素,并保留标题层级; - 普通非空段落推入 paragraph 元素;
- 每节的
header/footer页眉页脚段落被完整读取,并通过ContentLayer::Header/ContentLayer::Footer打上内容层标记(见 push_header_footer_paragraphs),这样下游include_headers/include_footers过滤配置可以精确控制是否输出。
对应测试test_section_header_and_footer_are_extracted验证了页眉页脚文本确实进入输出(测试)。
公式:EQEdit 脚本到 LaTeX
这是 HWPX 提取中最有特色的部分。HWP 的公式使用独立的 EQEdit 脚本 DSL(而非 MathML/OMML),且 Hancom 会把公式写进 run 内,常规解析会将其丢弃。Xberg 采用"预扫描 + 二次回填"策略:
collect_section_formulas用EntityReader解析节 XML(保证&这类实体引用被正确还原——矩阵列分隔符就是&),定位<hp:equation>/<hp:eqEdit>内的<hp:script>文本;- 通过
unhwp::equation::to_latex将脚本转换为 LaTeX; - 公式以独立的 Formula 元素输出,并保证其紧跟在引入它的段落之后(公式回填逻辑)。
相关测试覆盖:公式跟随其段落(test_a_section_formula_follows_its_paragraph)、run 内公式可被发现(test_scan_reads_an_equation_inside_a_run)、&实体必须还原(test_scan_resolves_entity_references_in_a_script)、公式移出后句子只保留一个空格(test_removing_an_equation_leaves_one_space)、纯公式段落不丢失数学内容(test_equation_only_paragraph_keeps_its_math)。这些测试全部位于 hwpx.rs 测试模块。
表格
表格块被展平为单元格网格:每个单元格内的段落文本用\n连接,并仍然走build_paragraph_content,因此单元格内的脚注、链接、公式不会被丢弃(cell_plain_text)。表格输出同时包含cells网格与渲染好的 Markdown;当unhwp报告存在表头行时,第一行会被写入columns字段(push_table)。测试test_table_header_row_populates_columns_and_cell_content与test_table_cell_footnote_is_extracted_not_dropped分别验证了这两点。
图片与资源
段落内联的图片引用通过doc.resources资源表解析二进制数据,并按 MIME 映射输出格式。mime_to_format特别处理了 HWPX 中常见的矢量/旧式 Windows 图元文件:image/svg+xml→svg、image/x-wmf→wmf、image/x-emf→emf,其余回退为bin(见 mime_to_format)。若某个图片引用的 id 在资源表中不存在,会输出带hwpx来源标识的处理警告,而不会静默崩溃(警告逻辑)。注意:表格单元格内的图片当前不提取,并会给出明确警告(相关测试)。
脚注与链接
脚注在正文中以[^n]标记占位,同时以独立FootnoteDefinition元素输出正文内容,并标记为ContentLayer::Footnote层;超链接则被记录为带起始/结束字节偏移的Link注解,且偏移量在段落文本 trim 后会被重算,确保始终与最终文本对齐(build_paragraph_content、trim_text_and_annotations)。测试test_footnote_produces_marker_and_definition与test_link_produces_link_annotation_with_correct_offsets给出了具体断言。
安全边界:防 ZipBomb 与大小限制
HWPX 本质是 ZIP 包,因此 Xberg 在解包前做多层防护(实现见 extract_content 的预检段):
| 防护项 | 依据 | 默认行为 |
|---|---|---|
| 归档总大小 | security_limits.max_archive_size | 超限即返回validation错误 |
| 压缩比 / 文件数 | ZipBombValidator::validate | 依据 central directory 声明值校验,超阈值拒绝 |
| 单节 XML 读取上限 | MAX_HWPX_MEMBER_SIZE = 100 * 1024 * 1024 | 公式预扫描用Read::take硬性封顶读入字节数,不信任声明值 |
其中MAX_HWPX_MEMBER_SIZE的注释明确指出:ZipBombValidator只检查 ZIP 中央目录的声明大小,从不解压;恶意条目可以低估声明大小而让 deflate 流远超声明,因此预扫描阶段必须自行用Read::take限制读取(常量与注释)。测试test_scan_bounds_the_read_of_an_oversized_section_member用超过上限的 XML 注释填充验证了该边界。此外,损坏的非 ZIP 输入会返回解析错误而不是 panic(test_hwpx_extract_corrupted_returns_err),保证 C 侧调用方拿到的是可诊断的错误而非进程崩溃。
结果读取与资源释放
提取完成后,C 调用方应通过xberg_extraction_result_results或xberg_extraction_result_to_json读取结果,并严格遵守"谁创建、谁释放"的句柄生命周期约定:
xberg_extract_input_free(input_handle)释放输入;xberg_extraction_result_free(result)释放结果;- 若通过字符串访问器取回
char *,需用xberg_free_string释放(参见 xberg_extract_input_uri 的注释约定)。
扩展阅读
- 关联文档(本文骨架来源):C 版 HWPX standalone 提取片段,同一主题还生成了 Rust、Python、Go 等十余种语言版本,可直接对照学习各自语言的 FFI 惯用法;
- 提取器源码:crates/xberg/src/extractors/hwpx.rs(含 1300 余行实现与测试);
- 格式识别:crates/xberg/src/core/mime.rs(
.hwpx扩展名、content.hpf清单、包内mimetype三重识别); - C ABI 头文件与用法:crates/xberg-ffi/include/xberg.h、crates/xberg-ffi/README.md;
- 验收夹具:fixtures/format_specific/format_hwpx_standalone.json 与真实样本
test_documents/hwpx/simple.hwpx。
至此,你已经掌握了一条完整的 HWPX 独立提取链路:从 C 侧 JSON 输入契约,到提取器的逐节解析与公式/表格/图片处理,再到安全校验与结果释放。将此模式迁移到 bytes 输入、批量提取(xberg_extract_batch)或其他 106 种支持格式,只是替换输入类型与 MIME 的问题。
- 后端
- AI 应用
- NLP
【免费下载链接】xberg
Polyglot document intelligence with a Rust core: extract text, metadata, images, tables, and structured data from 106 formats across 140 file extensions, plus code intelligence for 371 languages. Fifteen bindings, with CLI, REST API, and MCP server.
相关推荐
如何使用extract-text-webpack-plugin:5步快速提取CSS到独立文件
如何使用extract text webpack plugin:5步快速提取CSS到独立文件 extract text webpack plugin是webpa
开发工具Docz Monorepo 独立文档包实战:用单独 docs 包为同级 peer 组件生成 API 文档
Docz Monorepo 独立文档包实战:用单独 docs 包为同级 peer 组件生成 API 文档 Docz 官方示例 monorepo separate
文档静态站点开发工具在 ASP.NET Core 中配置多个 Scalar API References:独立端点与独立 OpenAPI 文档实战
在 ASP.NET Core 中配置多个 Scalar API References:独立端点与独立 OpenAPI 文档实战 本篇技术指南讲解如何基于 Sca
开发工具API 工具前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考