[Bug]: PPTParser silently drops text and images inside grouped shapes
Prerequisites
- I searched existing issues, pull requests, and discussions and did not find this bug reported.
- This is a bug, not a usage question.
Background / Description
PPTParser._parse_slide walks slide.shapes, which only iterates the direct children of the slide's spTree (src/agentscope/rag/_parser/_ppt.py:263). A GroupShape has no has_text_frame and no has_table, so the loop skips it entirely — text frames, tables, and pictures nested inside the group are never visited.
Grouped shapes are extremely common in real decks (templates group text boxes routinely), so this is a silent content loss: the parse succeeds, the text inside the group simply never reaches the index. Pictures inside groups are also dropped even with include_image=True.
Steps to Reproduce
From a repository checkout, save the following as reproduce_ppt_group.py and run PYTHONPATH=src python reproduce_ppt_group.py. It builds a deck in memory; no files, network, or credentials are needed.
import asyncio
import base64
import io
from pptx import Presentation
from pptx.util import Inches
from agentscope.rag import PPTParser
# 1x1 transparent PNG
PNG_1X1 = base64.b64decode(
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGNgYGBgAAAABQAB"
"h6FO1AAAAABJRU5ErkJggg=="
)
async def main():
prs = Presentation()
slide = prs.slides.add_slide(prs.slide_layouts[6])
slide.shapes.add_textbox(Inches(1), Inches(1), Inches(4), Inches(1)).text_frame.text = (
"UNGROUPED TEXT"
)
group = slide.shapes.add_group_shape()
group.shapes.add_textbox(Inches(1), Inches(2), Inches(4), Inches(1)).text_frame.text = (
"GROUPED TEXT"
)
group.shapes.add_picture(io.BytesIO(PNG_1X1), Inches(1), Inches(3), Inches(1), Inches(1))
buffer = io.BytesIO()
prs.save(buffer)
sections = await PPTParser(include_image=True).parse(buffer.getvalue(), "grouped.pptx")
text = "\n".join(
s.content.text for s in sections if hasattr(s.content, "text")
)
images = sum(1 for s in sections if not hasattr(s.content, "text"))
print("extracted text:", repr(text))
print("image sections:", images)
asyncio.run(main())Actual Output
extracted text: '<slide index=1>\nUNGROUPED TEXT\n</slide>'
image sections: 0GROUPED TEXT is missing from the extracted text, and the picture inside the group produces no image section.
Expected Output
extracted text: '<slide index=1>\nUNGROUPED TEXT\nGROUPED TEXT\n</slide>'
image sections: 1Environment
- AgentScope: 2.0.8, reproduced on
main@0727cbf9 - Python: 3.13.3
- python-pptx: 1.0.2
Root Cause / Proposed Fix
_parse_slide iterates only the top-level shapes and never descends into GroupShape children (grpSp element). The fix is to walk shapes recursively: when a shape exposes .shapes (a GroupShape), apply the same text-frame / table / picture dispatch to its children in document order, so grouped text, grouped tables, and grouped pictures are extracted exactly like top-level ones. This mirrors the "sweep descendants" fix family as in #2662, and needs a regression test with an in-memory grouped deck (text + image) like the reproduction above.
Source: agentscope-ai/agentscope