|
|
@@ -0,0 +1,92 @@
|
|
|
+"""mineru_vl_utils 运行时补丁集合。
|
|
|
+
|
|
|
+集中存放对第三方库 ``mineru_vl_utils`` 的运行时修补(monkey-patch),
|
|
|
+目的是在**不修改第三方源码**的前提下修复其在 PaddleOCR-VL 模型上的兼容性问题,
|
|
|
+并保证补丁随本仓库一起版本化、可随时开关、升级第三方库后不会丢失。
|
|
|
+
|
|
|
+当前包含的补丁:
|
|
|
+
|
|
|
+1. ``patch_convert_otsl_to_html``
|
|
|
+ 修复 PaddleOCR-VL 输出的 OTSL「整表首个单元格缺少前导 ``<fcel>`` token」
|
|
|
+ 导致 ``otsl_parse_texts`` 文本错位、所有单元格文字丢失的问题。
|
|
|
+
|
|
|
+统一通过 :func:`apply_once` 应用,幂等且仅在首次调用时生效。
|
|
|
+"""
|
|
|
+
|
|
|
+from __future__ import annotations
|
|
|
+
|
|
|
+from loguru import logger
|
|
|
+
|
|
|
+# OTSL 结构 token;与 mineru_vl_utils.post_process 内部定义保持一致
|
|
|
+_OTSL_STRUCT_TOKENS = ("<nl>", "<fcel>", "<ecel>", "<lcel>", "<ucel>", "<xcel>")
|
|
|
+
|
|
|
+_applied = False
|
|
|
+
|
|
|
+
|
|
|
+def _make_otsl_normalizer(orig_convert):
|
|
|
+ """生成一个在调用原始 convert_otsl_to_html 前先归一化 OTSL 的包装函数。"""
|
|
|
+
|
|
|
+ def _normalize_then_convert(otsl_content):
|
|
|
+ if isinstance(otsl_content, str):
|
|
|
+ stripped = otsl_content.lstrip()
|
|
|
+ # 整表首格缺少前导结构 token(如 PaddleOCR-VL)时补 <fcel>,
|
|
|
+ # 否则 otsl_parse_texts 的 text_idx 会永久错位,导致全部单元格文字丢失。
|
|
|
+ if (
|
|
|
+ stripped
|
|
|
+ and not stripped.startswith("<table")
|
|
|
+ and not stripped.startswith(_OTSL_STRUCT_TOKENS)
|
|
|
+ ):
|
|
|
+ otsl_content = "<fcel>" + stripped
|
|
|
+ return orig_convert(otsl_content)
|
|
|
+
|
|
|
+ # 记录原始函数,便于排查与还原
|
|
|
+ _normalize_then_convert.__wrapped__ = orig_convert
|
|
|
+ return _normalize_then_convert
|
|
|
+
|
|
|
+
|
|
|
+def _patch_convert_otsl_to_html():
|
|
|
+ """替换 post_process 命名空间中的 convert_otsl_to_html。
|
|
|
+
|
|
|
+ mineru_vl_utils.post_process.__init__ 通过
|
|
|
+ ``from .otsl2html import convert_otsl_to_html`` 导入该函数,
|
|
|
+ 其内部 simple_process / _convert_pure_table_content_to_html 在调用时
|
|
|
+ 按 post_process 模块全局名查找,因此覆盖该命名空间即可拦截全部内部调用。
|
|
|
+ """
|
|
|
+ import mineru_vl_utils.post_process as pp
|
|
|
+ from mineru_vl_utils.post_process import otsl2html
|
|
|
+
|
|
|
+ orig = getattr(otsl2html, "convert_otsl_to_html", None)
|
|
|
+ if orig is None:
|
|
|
+ # 上游接口变更时大声报错,避免补丁静默失效后又开始丢字
|
|
|
+ raise RuntimeError(
|
|
|
+ "mineru_vl_utils 接口已变更:找不到 otsl2html.convert_otsl_to_html,"
|
|
|
+ "请检查第三方库版本并更新补丁。"
|
|
|
+ )
|
|
|
+
|
|
|
+ wrapped = _make_otsl_normalizer(orig)
|
|
|
+ # 关键:post_process 内部调用按此命名空间查找
|
|
|
+ pp.convert_otsl_to_html = wrapped
|
|
|
+ # 兜底:若有代码直接 import otsl2html.convert_otsl_to_html
|
|
|
+ otsl2html.convert_otsl_to_html = wrapped
|
|
|
+
|
|
|
+
|
|
|
+def apply_once() -> bool:
|
|
|
+ """应用全部 mineru_vl_utils 运行时补丁,幂等。
|
|
|
+
|
|
|
+ 应在任何 ``content_extract`` / ``batch_content_extract`` 调用之前执行一次
|
|
|
+ (通常放在 VL 识别器/检测器的 ``initialize()`` 内、获取模型之前)。
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ bool: 本次调用是否真正应用了补丁(首次为 True,后续为 False)。
|
|
|
+ """
|
|
|
+ global _applied
|
|
|
+ if _applied:
|
|
|
+ return False
|
|
|
+ try:
|
|
|
+ _patch_convert_otsl_to_html()
|
|
|
+ _applied = True
|
|
|
+ logger.info("已应用 mineru_vl_utils 补丁:OTSL 整表首格 <fcel> 归一化")
|
|
|
+ return True
|
|
|
+ except Exception as e: # 补丁失败不应阻断主流程,但需明确告警
|
|
|
+ logger.error(f"应用 mineru_vl_utils 补丁失败:{e}")
|
|
|
+ raise
|