返回 ppt-master
text_fill.py
1 """apply: replace text inside cloned slide shapes while keeping frames editable.
2
3 ``_set_container_text`` is the shared text-writing primitive and is also reused
4 by ``table_fill`` for table-cell edits.
5 """
6
7 from __future__ import annotations
8
9 from typing import Any
10 from xml.etree import ElementTree as ET
11
12 from .ooxml import NS, _qn, _shape_identity, _text_containers
13 from .selectors import _replacement_text
14
15
16 def _shape_key_maps(slide_root: ET.Element, source_slide: int) -> dict[str, ET.Element]:
17 maps: dict[str, ET.Element] = {}
18 for order, container in enumerate(_text_containers(slide_root), start=1):
19 shape_id, shape_name = _shape_identity(container, order)
20 maps[f"slot_id:s{source_slide:02d}_sh{shape_id}"] = container
21 maps[f"shape_id:{shape_id}"] = container
22 if shape_name:
23 maps[f"shape_name:{shape_name}"] = container
24 return maps
25
26
27 def _ensure_text_nodes(container: ET.Element) -> list[ET.Element]:
28 text_nodes = container.findall(".//a:t", NS)
29 if text_nodes:
30 return text_nodes
31 tx_body = container.find(".//p:txBody", NS)
32 if tx_body is None:
33 tx_body = container.find(".//a:txBody", NS)
34 if tx_body is None:
35 return []
36 paragraph = tx_body.find("a:p", NS)
37 if paragraph is None:
38 paragraph = ET.SubElement(tx_body, _qn(NS["a"], "p"))
39 run = paragraph.find("a:r", NS)
40 if run is None:
41 run = ET.SubElement(paragraph, _qn(NS["a"], "r"))
42 text_node = run.find("a:t", NS)
43 if text_node is None:
44 text_node = ET.SubElement(run, _qn(NS["a"], "t"))
45 return [text_node]
46
47
48 def _ensure_paragraph_text_node(paragraph: ET.Element) -> list[ET.Element]:
49 text_nodes = paragraph.findall(".//a:t", NS)
50 if text_nodes:
51 return text_nodes
52 run = paragraph.find("a:r", NS)
53 if run is None:
54 run = ET.SubElement(paragraph, _qn(NS["a"], "r"))
55 text_node = run.find("a:t", NS)
56 if text_node is None:
57 text_node = ET.SubElement(run, _qn(NS["a"], "t"))
58 return [text_node]
59
60
61 def _set_paragraph_text(paragraph: ET.Element, text: str) -> None:
62 text_nodes = _ensure_paragraph_text_node(paragraph)
63 text_nodes[0].text = text
64 for node in text_nodes[1:]:
65 node.text = ""
66
67
68 def _set_container_text(container: ET.Element, text: str) -> None:
69 lines = text.splitlines() or [""]
70 paragraphs = container.findall(".//a:p", NS)
71 if len(lines) > 1 and paragraphs:
72 for index, paragraph in enumerate(paragraphs):
73 if index < len(lines):
74 _set_paragraph_text(paragraph, lines[index])
75 else:
76 _set_paragraph_text(paragraph, "")
77 if len(lines) > len(paragraphs):
78 _set_paragraph_text(paragraphs[-1], "\n".join(lines[len(paragraphs) - 1 :]))
79 return
80
81 text_nodes = _ensure_text_nodes(container)
82 if not text_nodes:
83 raise RuntimeError("Matched shape does not contain a text body")
84 if len(lines) <= len(text_nodes):
85 for index, node in enumerate(text_nodes):
86 node.text = lines[index] if index < len(lines) else ""
87 return
88 text_nodes[0].text = text
89 for node in text_nodes[1:]:
90 node.text = ""
91
92
93 def _apply_replacements_to_slide(
94 slide_root: ET.Element,
95 *,
96 source_slide: int,
97 replacements: list[dict[str, Any]],
98 ) -> None:
99 maps = _shape_key_maps(slide_root, source_slide)
100 errors: list[str] = []
101 for replacement in replacements:
102 selectors = []
103 if replacement.get("slot_id"):
104 selectors.append(f"slot_id:{replacement['slot_id']}")
105 if replacement.get("shape_id"):
106 selectors.append(f"shape_id:{replacement['shape_id']}")
107 if replacement.get("shape_name"):
108 selectors.append(f"shape_name:{replacement['shape_name']}")
109 container = next((maps[key] for key in selectors if key in maps), None)
110 if container is None:
111 if replacement.get("optional"):
112 continue
113 errors.append(", ".join(selectors) or "<missing selector>")
114 continue
115 _set_container_text(container, _replacement_text(replacement))
116 if errors:
117 raise RuntimeError(f"Missing replacement target(s) on slide {source_slide}: {'; '.join(errors)}")
118
118 lines PYTHON