fix(utils): recurse into PPTX groups and tables when extracting text (#3250)

This commit is contained in:
04cb
2026-04-18 12:30:42 +08:00
committed by Xubin Ren
parent 34fccb2ee9
commit c27b4d07c4
2 changed files with 69 additions and 2 deletions
+26 -2
View File
@@ -159,8 +159,7 @@ def _extract_pptx(path: Path) -> str:
for i, slide in enumerate(prs.slides, 1):
slide_text: list[str] = []
for shape in slide.shapes:
if hasattr(shape, "text") and shape.text:
slide_text.append(shape.text)
_collect_pptx_shape_text(shape, slide_text)
if slide_text:
slides.append(f"--- Slide {i} ---\n" + "\n".join(slide_text))
return _truncate("\n\n".join(slides), _MAX_TEXT_LENGTH)
@@ -169,6 +168,31 @@ def _extract_pptx(path: Path) -> str:
return f"[error: failed to extract PPTX: {e!s}]"
def _collect_pptx_shape_text(shape, out: list[str]) -> None:
"""Collect text from a PPTX shape, recursing into groups and tables.
Groups have ``has_text_frame=False`` and must be walked via ``.shapes``;
tables are GraphicFrame objects whose cell text lives under ``.table``.
"""
sub_shapes = getattr(shape, "shapes", None)
if sub_shapes is not None:
for sub in sub_shapes:
_collect_pptx_shape_text(sub, out)
return
if getattr(shape, "has_table", False):
for row in shape.table.rows:
cells = [cell.text.strip() for cell in row.cells]
line = "\t".join(cell for cell in cells if cell)
if line:
out.append(line)
return
text = getattr(shape, "text", "")
if text:
out.append(text)
def _extract_text_file(path: Path) -> str:
"""Extract text from a plain text file."""
try:
+43
View File
@@ -209,6 +209,49 @@ class TestExtractText:
# Text content may vary depending on PowerPoint layout defaults
assert len(result) > 0
def test_extract_text_pptx_table(self, tmp_path: Path):
"""Table cells should be extracted, not silently dropped."""
from pptx import Presentation
from pptx.util import Inches
pptx_file = tmp_path / "table.pptx"
prs = Presentation()
slide = prs.slides.add_slide(prs.slide_layouts[5])
table = slide.shapes.add_table(
2, 2, Inches(1), Inches(1), Inches(4), Inches(1)
).table
table.cell(0, 0).text = "Header A"
table.cell(0, 1).text = "Header B"
table.cell(1, 0).text = "Alice"
table.cell(1, 1).text = "Bob"
prs.save(pptx_file)
result = extract_text(pptx_file)
assert result is not None
assert "Header A" in result
assert "Header B" in result
assert "Alice" in result
assert "Bob" in result
def test_extract_text_pptx_grouped_shapes(self, tmp_path: Path):
"""Text inside grouped shapes must be extracted recursively."""
from pptx import Presentation
from pptx.util import Inches
pptx_file = tmp_path / "grouped.pptx"
prs = Presentation()
slide = prs.slides.add_slide(prs.slide_layouts[5])
group = slide.shapes.add_group_shape()
inner = group.shapes.add_textbox(
Inches(1), Inches(1), Inches(3), Inches(1)
)
inner.text_frame.text = "Inside group"
prs.save(pptx_file)
result = extract_text(pptx_file)
assert result is not None
assert "Inside group" in result
def test_extract_text_pdf_not_found(self, tmp_path: Path):
"""Test that missing PDF files return error string."""
missing_pdf = tmp_path / "nonexistent.pdf"