diff --git a/deepdoc/parser/docling_parser.py b/deepdoc/parser/docling_parser.py index e0f4ad72ac..468f3e4775 100644 --- a/deepdoc/parser/docling_parser.py +++ b/deepdoc/parser/docling_parser.py @@ -174,6 +174,26 @@ class DoclingParser(RAGFlowPdfParser): if not poss: return (None, None) if need_position else None + # a position tag is emitted even when page rendering failed (__images__ leaves + # page_images empty), and a tag can name a page beyond the rendered range, so + # indexing page_images below would raise IndexError. mirror + # cropout_docling_table and the sibling parsers: bail out when there are no + # page images and drop positions that fall outside them. + if not getattr(self, "page_images", None): + self.logger.warning("[Docling] crop called without page images; skipping image generation.") + return (None, None) if need_position else None + + page_count = len(self.page_images) + valid_poss = [] + for p in poss: + if p[0] and all(0 <= pn < page_count for pn in p[0]): + valid_poss.append(p) + else: + self.logger.warning(f"[Docling] Position on pages {p[0]} is out of range for {page_count} rendered page(s); skipping it.") + poss = valid_poss + if not poss: + return (None, None) if need_position else None + GAP = 6 pos = poss[0] poss.insert(0, ([pos[0][0]], pos[1], pos[2], max(0, pos[3] - 120), max(pos[3] - GAP, 0))) diff --git a/test/unit_test/deepdoc/parser/test_docling_parser_remote.py b/test/unit_test/deepdoc/parser/test_docling_parser_remote.py index 87bd44115d..aced0dcbb0 100644 --- a/test/unit_test/deepdoc/parser/test_docling_parser_remote.py +++ b/test/unit_test/deepdoc/parser/test_docling_parser_remote.py @@ -159,3 +159,32 @@ def test_remote_chunked_request_with_ignored_flag_does_not_log_success(monkeypat flat = " ".join(record.getMessage() for record in caplog.records) assert "Successfully used native chunking" not in flat assert "Server ignored chunking request" in flat + + +@pytest.mark.p2 +def test_crop_without_page_images_returns_none(monkeypatch): + """Position tags are emitted even when page rendering failed and left + ``page_images`` empty, so ``crop`` must bail out instead of raising + IndexError while indexing the empty list.""" + module = _load_docling_parser(monkeypatch) + parser = module.DoclingParser(docling_server_url="http://docling.local") + parser.page_images = [] + + tag = "@@1\t1.0\t2.0\t3.0\t4.0##" + assert parser.crop(tag, need_position=True) == (None, None) + assert parser.crop(tag) is None + + +@pytest.mark.p2 +def test_crop_drops_positions_beyond_rendered_pages(monkeypatch): + """A tag naming a page past the rendered range must be filtered out rather + than indexed, mirroring the range check in cropout_docling_table.""" + module = _load_docling_parser(monkeypatch) + parser = module.DoclingParser(docling_server_url="http://docling.local") + # a single rendered page; the sentinel is never indexed because the + # out-of-range position is dropped first. + parser.page_images = [object()] + + tag = "@@5\t1.0\t2.0\t3.0\t4.0##" + assert parser.crop(tag, need_position=True) == (None, None) + assert parser.crop(tag) is None