返回 ppt-master
table_fill.py
1 """apply: edit native PowerPoint table cell text on cloned slides.
2
3 Edits existing cells only — row / column structure is left untouched.
4 """
5
6 from __future__ import annotations
7
8 from typing import Any
9 from xml.etree import ElementTree as ET
10
11 from .edit_safety import _table_cell_merge_info
12 from .ooxml import NS, _shape_identity, _table_containers
13 from .selectors import _table_cell_text, _table_selectors
14 from .text_fill import _set_container_text
15
16
17 def _table_key_maps(slide_root: ET.Element, source_slide: int) -> dict[str, ET.Element]:
18 maps: dict[str, ET.Element] = {}
19 for order, container in enumerate(_table_containers(slide_root), start=1):
20 shape_id, shape_name = _shape_identity(container, order)
21 maps[f"table_id:s{source_slide:02d}_tbl{shape_id}"] = container
22 maps[f"shape_id:{shape_id}"] = container
23 if shape_name:
24 maps[f"shape_name:{shape_name}"] = container
25 return maps
26
27
28 def _apply_table_edits_to_slide(
29 slide_root: ET.Element,
30 *,
31 source_slide: int,
32 table_edits: list[dict[str, Any]],
33 ) -> None:
34 maps = _table_key_maps(slide_root, source_slide)
35 errors: list[str] = []
36 pending_edits: list[tuple[ET.Element, str]] = []
37 for table_edit in table_edits:
38 selectors = _table_selectors(table_edit)
39 table_frame = next((maps[key] for key in selectors if key in maps), None)
40 if table_frame is None:
41 if table_edit.get("optional"):
42 continue
43 errors.append(", ".join(selectors) or "<missing selector>")
44 continue
45 rows = table_frame.findall(".//a:tbl/a:tr", NS)
46 cells = table_edit.get("cells", [])
47 if not isinstance(cells, list):
48 raise RuntimeError(f"Slide {source_slide} table edit cells must be a list")
49 for cell_edit in cells:
50 row_index = int(cell_edit.get("row", -1))
51 col_index = int(cell_edit.get("col", -1))
52 if row_index < 0 or row_index >= len(rows):
53 errors.append(f"{selectors[0] if selectors else '<table>'} row={row_index}")
54 continue
55 row_cells = rows[row_index].findall("a:tc", NS)
56 if col_index < 0 or col_index >= len(row_cells):
57 errors.append(f"{selectors[0] if selectors else '<table>'} row={row_index} col={col_index}")
58 continue
59 target_cell = row_cells[col_index]
60 if _table_cell_merge_info(target_cell)["is_merge_slave"]:
61 target = selectors[0] if selectors else "<table>"
62 errors.append(
63 f"{target} row={row_index} col={col_index} is a merged-cell slave "
64 "[table_cell_is_merge_slave]; edit the merge anchor instead"
65 )
66 continue
67 pending_edits.append((target_cell, _table_cell_text(cell_edit)))
68 if errors:
69 raise RuntimeError(f"Invalid table edit target(s) on slide {source_slide}: {'; '.join(errors)}")
70 for target_cell, text in pending_edits:
71 _set_container_text(target_cell, text)
72
72 lines PYTHON