From 56166abff6f65e6e8ad7751b7edd0eda3253e142 Mon Sep 17 00:00:00 2001 From: Aw <62973442+Awei-996@users.noreply.github.com> Date: Mon, 11 May 2026 21:54:25 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(parser):=20=E4=BF=AE=E5=A4=8D=20Docling?= =?UTF-8?q?=20=E8=A7=A3=E6=9E=90=E6=96=87=E6=A1=A3=E6=97=B6=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E9=94=99=E4=BD=8D=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 遍历所有图片生成等长替换列表,非 data URI 图片填空字符串, 确保占位符替换严格 1:1,避免混合图片类型时顺序错乱。 --- .../package/yuxi/plugins/parser/unified.py | 35 ++++++++----------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/backend/package/yuxi/plugins/parser/unified.py b/backend/package/yuxi/plugins/parser/unified.py index 7e263256..a7b82ad6 100644 --- a/backend/package/yuxi/plugins/parser/unified.py +++ b/backend/package/yuxi/plugins/parser/unified.py @@ -127,31 +127,24 @@ def _convert_with_docling(file_path: Path, params: dict | None = None) -> str: doc = result.document if hasattr(doc, "pictures") and doc.pictures: - image_refs: list[tuple[str, bytes]] = [] - + replacements: list[str] = [] for pic in doc.pictures: - if hasattr(pic, "image") and hasattr(pic.image, "uri"): - uri = str(pic.image.uri) - if uri.startswith("data:"): + uri = str(pic.image.uri) if hasattr(pic, "image") and hasattr(pic.image, "uri") else "" + if uri.startswith("data:"): + try: image_data, mime_type = _parse_data_uri(uri) - timestamp = int(time.time() * 1000000) - filename = f"image_{timestamp}.{mime_type.split('/')[-1]}" - image_refs.append((filename, image_data)) - - image_urls: list[str] = [] - for filename, image_data in image_refs: - try: - url = _upload_image_to_minio(image_data, filename, image_bucket, image_prefix) - image_urls.append(f"![{filename}]({url})") - except Exception as e: # noqa: BLE001 - logger.error(f"上传图片失败 {filename}: {e}") - image_urls.append(f"[图片: {filename}]") + filename = f"image_{int(time.time() * 1000000)}.{mime_type.split('/')[-1]}" + url = _upload_image_to_minio(image_data, filename, image_bucket, image_prefix) + replacements.append(f"![{filename}]({url})") + except Exception as e: # noqa: BLE001 + logger.error(f"上传图片失败 {filename}: {e}") + replacements.append("") + else: + replacements.append("") markdown = doc.export_to_markdown() - - for url in image_urls: - markdown = re.sub(r"", url, markdown, count=1) - + for replacement in replacements: + markdown = re.sub(r"", replacement, markdown, count=1) return markdown return doc.export_to_markdown() From bdf983f1e09be4e16e247efb70d3639ad929df0b Mon Sep 17 00:00:00 2001 From: Wenjie Zhang Date: Tue, 12 May 2026 10:17:44 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(parser):=20=E4=BF=AE=E5=A4=8D=20Docling?= =?UTF-8?q?=20=E5=9B=BE=E7=89=87=E5=BC=82=E5=B8=B8=E5=8D=A0=E4=BD=8D?= =?UTF-8?q?=E5=9B=9E=E5=A1=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 保留上传失败的图片占位说明,并补充混合图片类型回填顺序的回归测试,避免占位符错位或异常路径掩盖原始错误。 Taskr: 2026-05-12-fix-docling-image-replacement-regression --- .../package/yuxi/plugins/parser/unified.py | 3 +- .../test/unit/plugins/test_parser_facade.py | 35 ++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/backend/package/yuxi/plugins/parser/unified.py b/backend/package/yuxi/plugins/parser/unified.py index a7b82ad6..77cbf488 100644 --- a/backend/package/yuxi/plugins/parser/unified.py +++ b/backend/package/yuxi/plugins/parser/unified.py @@ -131,6 +131,7 @@ def _convert_with_docling(file_path: Path, params: dict | None = None) -> str: for pic in doc.pictures: uri = str(pic.image.uri) if hasattr(pic, "image") and hasattr(pic.image, "uri") else "" if uri.startswith("data:"): + filename = "image" try: image_data, mime_type = _parse_data_uri(uri) filename = f"image_{int(time.time() * 1000000)}.{mime_type.split('/')[-1]}" @@ -138,7 +139,7 @@ def _convert_with_docling(file_path: Path, params: dict | None = None) -> str: replacements.append(f"![{filename}]({url})") except Exception as e: # noqa: BLE001 logger.error(f"上传图片失败 {filename}: {e}") - replacements.append("") + replacements.append(f"[图片: {filename}]") else: replacements.append("") diff --git a/backend/test/unit/plugins/test_parser_facade.py b/backend/test/unit/plugins/test_parser_facade.py index 21857308..e3d1d4ce 100644 --- a/backend/test/unit/plugins/test_parser_facade.py +++ b/backend/test/unit/plugins/test_parser_facade.py @@ -76,9 +76,10 @@ def test_convert_with_docling_reinserts_image_links_in_document_order( fake_doc = SimpleNamespace( pictures=[ SimpleNamespace(image=SimpleNamespace(uri=f"data:image/png;base64,{first_image}")), + SimpleNamespace(image=SimpleNamespace(uri="https://example.test/remote.png")), SimpleNamespace(image=SimpleNamespace(uri=f"data:image/png;base64,{second_image}")), ], - export_to_markdown=lambda: "before\n\nbetween\n\nafter", + export_to_markdown=lambda: "before\n\nremote\n\nbetween\n\nafter", ) fake_result = SimpleNamespace(status=SimpleNamespace(name="SUCCESS"), document=fake_doc) uploaded_images: list[bytes] = [] @@ -103,12 +104,44 @@ def test_convert_with_docling_reinserts_image_links_in_document_order( assert markdown == ( "before\n" "![image_1000000.png](https://example.test/1.png)\n" + "remote\n" + "\n" "between\n" "![image_2000000.png](https://example.test/2.png)\n" "after" ) +def test_convert_with_docling_keeps_image_placeholder_when_upload_fails( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +): + file_path = tmp_path / "parser_test.docx" + file_path.write_bytes(b"fake docx") + image = base64.b64encode(b"image data").decode() + fake_doc = SimpleNamespace( + pictures=[SimpleNamespace(image=SimpleNamespace(uri=f"data:image/png;base64,{image}"))], + export_to_markdown=lambda: "before\n\nafter", + ) + fake_result = SimpleNamespace(status=SimpleNamespace(name="SUCCESS"), document=fake_doc) + + class FakeConverter: + def convert(self, path: Path): + assert path == file_path + return fake_result + + def _raise_upload_error(*args, **kwargs): + raise RuntimeError("upload failed") + + monkeypatch.setattr(parser_unified, "_get_docling_converter", lambda: FakeConverter()) + monkeypatch.setattr(parser_unified, "_upload_image_to_minio", _raise_upload_error) + monkeypatch.setattr(parser_unified.time, "time", lambda: 1.0) + + markdown = parser_unified._convert_with_docling(file_path) + + assert markdown == "before\n[图片: image_1000000.png]\nafter" + + def test_parser_parse_png_file_returns_markdown_text_with_mocked_ocr( tmp_path: Path, monkeypatch: pytest.MonkeyPatch,