mcpcn-office-powerpoint-mcp-server 2.0.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcpcn_office_powerpoint_mcp_server-2.0.7.dist-info/METADATA +1023 -0
- mcpcn_office_powerpoint_mcp_server-2.0.7.dist-info/RECORD +25 -0
- mcpcn_office_powerpoint_mcp_server-2.0.7.dist-info/WHEEL +4 -0
- mcpcn_office_powerpoint_mcp_server-2.0.7.dist-info/entry_points.txt +2 -0
- mcpcn_office_powerpoint_mcp_server-2.0.7.dist-info/licenses/LICENSE +21 -0
- ppt_mcp_server.py +450 -0
- slide_layout_templates.json +3690 -0
- tools/__init__.py +28 -0
- tools/chart_tools.py +82 -0
- tools/connector_tools.py +91 -0
- tools/content_tools.py +593 -0
- tools/hyperlink_tools.py +138 -0
- tools/master_tools.py +114 -0
- tools/presentation_tools.py +212 -0
- tools/professional_tools.py +290 -0
- tools/structural_tools.py +373 -0
- tools/template_tools.py +521 -0
- tools/transition_tools.py +75 -0
- utils/__init__.py +69 -0
- utils/content_utils.py +579 -0
- utils/core_utils.py +55 -0
- utils/design_utils.py +689 -0
- utils/presentation_utils.py +217 -0
- utils/template_utils.py +1143 -0
- utils/validation_utils.py +323 -0
utils/content_utils.py
ADDED
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Content management utilities for PowerPoint MCP Server.
|
|
3
|
+
Functions for slides, text, images, tables, charts, and shapes.
|
|
4
|
+
"""
|
|
5
|
+
from pptx import Presentation
|
|
6
|
+
from pptx.chart.data import CategoryChartData
|
|
7
|
+
from pptx.enum.chart import XL_CHART_TYPE
|
|
8
|
+
from pptx.enum.text import PP_ALIGN
|
|
9
|
+
from pptx.util import Inches, Pt
|
|
10
|
+
from pptx.dml.color import RGBColor
|
|
11
|
+
from typing import Dict, List, Tuple, Optional, Any
|
|
12
|
+
import tempfile
|
|
13
|
+
import os
|
|
14
|
+
import base64
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def add_slide(presentation: Presentation, layout_index: int = 1) -> Tuple:
|
|
18
|
+
"""
|
|
19
|
+
Add a slide to the presentation.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
presentation: The Presentation object
|
|
23
|
+
layout_index: Index of the slide layout to use
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
A tuple containing the slide and its layout
|
|
27
|
+
"""
|
|
28
|
+
layout = presentation.slide_layouts[layout_index]
|
|
29
|
+
slide = presentation.slides.add_slide(layout)
|
|
30
|
+
return slide, layout
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_slide_info(slide, slide_index: int) -> Dict:
|
|
34
|
+
"""
|
|
35
|
+
Get information about a specific slide.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
slide: The slide object
|
|
39
|
+
slide_index: Index of the slide
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Dictionary containing slide information
|
|
43
|
+
"""
|
|
44
|
+
try:
|
|
45
|
+
placeholders = []
|
|
46
|
+
for placeholder in slide.placeholders:
|
|
47
|
+
placeholder_info = {
|
|
48
|
+
"idx": placeholder.placeholder_format.idx,
|
|
49
|
+
"type": str(placeholder.placeholder_format.type),
|
|
50
|
+
"name": placeholder.name
|
|
51
|
+
}
|
|
52
|
+
placeholders.append(placeholder_info)
|
|
53
|
+
|
|
54
|
+
shapes = []
|
|
55
|
+
for i, shape in enumerate(slide.shapes):
|
|
56
|
+
shape_info = {
|
|
57
|
+
"index": i,
|
|
58
|
+
"name": shape.name,
|
|
59
|
+
"shape_type": str(shape.shape_type),
|
|
60
|
+
"left": shape.left,
|
|
61
|
+
"top": shape.top,
|
|
62
|
+
"width": shape.width,
|
|
63
|
+
"height": shape.height
|
|
64
|
+
}
|
|
65
|
+
shapes.append(shape_info)
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
"slide_index": slide_index,
|
|
69
|
+
"layout_name": slide.slide_layout.name,
|
|
70
|
+
"placeholder_count": len(placeholders),
|
|
71
|
+
"placeholders": placeholders,
|
|
72
|
+
"shape_count": len(shapes),
|
|
73
|
+
"shapes": shapes
|
|
74
|
+
}
|
|
75
|
+
except Exception as e:
|
|
76
|
+
raise Exception(f"Failed to get slide info: {str(e)}")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def set_title(slide, title: str) -> None:
|
|
80
|
+
"""
|
|
81
|
+
Set the title of a slide.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
slide: The slide object
|
|
85
|
+
title: The title text
|
|
86
|
+
"""
|
|
87
|
+
if slide.shapes.title:
|
|
88
|
+
slide.shapes.title.text = title
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def populate_placeholder(slide, placeholder_idx: int, text: str) -> None:
|
|
92
|
+
"""
|
|
93
|
+
Populate a placeholder with text.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
slide: The slide object
|
|
97
|
+
placeholder_idx: The index of the placeholder
|
|
98
|
+
text: The text to add
|
|
99
|
+
"""
|
|
100
|
+
placeholder = slide.placeholders[placeholder_idx]
|
|
101
|
+
placeholder.text = text
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def add_bullet_points(placeholder, bullet_points: List[str]) -> None:
|
|
105
|
+
"""
|
|
106
|
+
Add bullet points to a placeholder.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
placeholder: The placeholder object
|
|
110
|
+
bullet_points: List of bullet point texts
|
|
111
|
+
"""
|
|
112
|
+
text_frame = placeholder.text_frame
|
|
113
|
+
text_frame.clear()
|
|
114
|
+
|
|
115
|
+
for i, point in enumerate(bullet_points):
|
|
116
|
+
p = text_frame.add_paragraph()
|
|
117
|
+
p.text = point
|
|
118
|
+
p.level = 0
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def add_textbox(slide, left: float, top: float, width: float, height: float, text: str,
|
|
122
|
+
font_size: int = None, font_name: str = None, bold: bool = None,
|
|
123
|
+
italic: bool = None, underline: bool = None,
|
|
124
|
+
color: Tuple[int, int, int] = None, bg_color: Tuple[int, int, int] = None,
|
|
125
|
+
alignment: str = None, vertical_alignment: str = None,
|
|
126
|
+
auto_fit: bool = True) -> Any:
|
|
127
|
+
"""
|
|
128
|
+
Add a textbox to a slide with formatting options.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
slide: The slide object
|
|
132
|
+
left: Left position in inches
|
|
133
|
+
top: Top position in inches
|
|
134
|
+
width: Width in inches
|
|
135
|
+
height: Height in inches
|
|
136
|
+
text: Text content
|
|
137
|
+
font_size: Font size in points
|
|
138
|
+
font_name: Font name
|
|
139
|
+
bold: Whether text should be bold
|
|
140
|
+
italic: Whether text should be italic
|
|
141
|
+
underline: Whether text should be underlined
|
|
142
|
+
color: RGB color tuple (r, g, b)
|
|
143
|
+
bg_color: Background RGB color tuple (r, g, b)
|
|
144
|
+
alignment: Text alignment ('left', 'center', 'right', 'justify')
|
|
145
|
+
vertical_alignment: Vertical alignment ('top', 'middle', 'bottom')
|
|
146
|
+
auto_fit: Whether to auto-fit text
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
The created textbox shape
|
|
150
|
+
"""
|
|
151
|
+
textbox = slide.shapes.add_textbox(
|
|
152
|
+
Inches(left), Inches(top), Inches(width), Inches(height)
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
textbox.text_frame.text = text
|
|
156
|
+
|
|
157
|
+
# Apply formatting if provided
|
|
158
|
+
if any([font_size, font_name, bold, italic, underline, color, bg_color, alignment, vertical_alignment]):
|
|
159
|
+
format_text_advanced(
|
|
160
|
+
textbox.text_frame,
|
|
161
|
+
font_size=font_size,
|
|
162
|
+
font_name=font_name,
|
|
163
|
+
bold=bold,
|
|
164
|
+
italic=italic,
|
|
165
|
+
underline=underline,
|
|
166
|
+
color=color,
|
|
167
|
+
bg_color=bg_color,
|
|
168
|
+
alignment=alignment,
|
|
169
|
+
vertical_alignment=vertical_alignment
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
return textbox
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def format_text(text_frame, font_size: int = None, font_name: str = None,
|
|
176
|
+
bold: bool = None, italic: bool = None, color: Tuple[int, int, int] = None,
|
|
177
|
+
alignment: str = None) -> None:
|
|
178
|
+
"""
|
|
179
|
+
Format text in a text frame.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
text_frame: The text frame to format
|
|
183
|
+
font_size: Font size in points
|
|
184
|
+
font_name: Font name
|
|
185
|
+
bold: Whether text should be bold
|
|
186
|
+
italic: Whether text should be italic
|
|
187
|
+
color: RGB color tuple (r, g, b)
|
|
188
|
+
alignment: Text alignment ('left', 'center', 'right', 'justify')
|
|
189
|
+
"""
|
|
190
|
+
alignment_map = {
|
|
191
|
+
'left': PP_ALIGN.LEFT,
|
|
192
|
+
'center': PP_ALIGN.CENTER,
|
|
193
|
+
'right': PP_ALIGN.RIGHT,
|
|
194
|
+
'justify': PP_ALIGN.JUSTIFY
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
for paragraph in text_frame.paragraphs:
|
|
198
|
+
if alignment and alignment in alignment_map:
|
|
199
|
+
paragraph.alignment = alignment_map[alignment]
|
|
200
|
+
|
|
201
|
+
for run in paragraph.runs:
|
|
202
|
+
font = run.font
|
|
203
|
+
|
|
204
|
+
if font_size is not None:
|
|
205
|
+
font.size = Pt(font_size)
|
|
206
|
+
if font_name is not None:
|
|
207
|
+
font.name = font_name
|
|
208
|
+
if bold is not None:
|
|
209
|
+
font.bold = bold
|
|
210
|
+
if italic is not None:
|
|
211
|
+
font.italic = italic
|
|
212
|
+
if color is not None:
|
|
213
|
+
r, g, b = color
|
|
214
|
+
font.color.rgb = RGBColor(r, g, b)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def format_text_advanced(text_frame, font_size: int = None, font_name: str = None,
|
|
218
|
+
bold: bool = None, italic: bool = None, underline: bool = None,
|
|
219
|
+
color: Tuple[int, int, int] = None, bg_color: Tuple[int, int, int] = None,
|
|
220
|
+
alignment: str = None, vertical_alignment: str = None) -> Dict:
|
|
221
|
+
"""
|
|
222
|
+
Advanced text formatting with comprehensive options.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
text_frame: The text frame to format
|
|
226
|
+
font_size: Font size in points
|
|
227
|
+
font_name: Font name
|
|
228
|
+
bold: Whether text should be bold
|
|
229
|
+
italic: Whether text should be italic
|
|
230
|
+
underline: Whether text should be underlined
|
|
231
|
+
color: RGB color tuple (r, g, b)
|
|
232
|
+
bg_color: Background RGB color tuple (r, g, b)
|
|
233
|
+
alignment: Text alignment ('left', 'center', 'right', 'justify')
|
|
234
|
+
vertical_alignment: Vertical alignment ('top', 'middle', 'bottom')
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
Dictionary with formatting results
|
|
238
|
+
"""
|
|
239
|
+
result = {
|
|
240
|
+
'success': True,
|
|
241
|
+
'warnings': []
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
try:
|
|
245
|
+
alignment_map = {
|
|
246
|
+
'left': PP_ALIGN.LEFT,
|
|
247
|
+
'center': PP_ALIGN.CENTER,
|
|
248
|
+
'right': PP_ALIGN.RIGHT,
|
|
249
|
+
'justify': PP_ALIGN.JUSTIFY
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
# Enable text wrapping
|
|
253
|
+
text_frame.word_wrap = True
|
|
254
|
+
|
|
255
|
+
# Apply formatting to all paragraphs and runs
|
|
256
|
+
for paragraph in text_frame.paragraphs:
|
|
257
|
+
if alignment and alignment in alignment_map:
|
|
258
|
+
paragraph.alignment = alignment_map[alignment]
|
|
259
|
+
|
|
260
|
+
for run in paragraph.runs:
|
|
261
|
+
font = run.font
|
|
262
|
+
|
|
263
|
+
if font_size is not None:
|
|
264
|
+
font.size = Pt(font_size)
|
|
265
|
+
if font_name is not None:
|
|
266
|
+
font.name = font_name
|
|
267
|
+
if bold is not None:
|
|
268
|
+
font.bold = bold
|
|
269
|
+
if italic is not None:
|
|
270
|
+
font.italic = italic
|
|
271
|
+
if underline is not None:
|
|
272
|
+
font.underline = underline
|
|
273
|
+
if color is not None:
|
|
274
|
+
r, g, b = color
|
|
275
|
+
font.color.rgb = RGBColor(r, g, b)
|
|
276
|
+
|
|
277
|
+
return result
|
|
278
|
+
|
|
279
|
+
except Exception as e:
|
|
280
|
+
result['success'] = False
|
|
281
|
+
result['error'] = str(e)
|
|
282
|
+
return result
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def add_image(slide, image_path: str, left: float, top: float, width: float = None, height: float = None) -> Any:
|
|
286
|
+
"""
|
|
287
|
+
Add an image to a slide.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
slide: The slide object
|
|
291
|
+
image_path: Path to the image file
|
|
292
|
+
left: Left position in inches
|
|
293
|
+
top: Top position in inches
|
|
294
|
+
width: Width in inches (optional)
|
|
295
|
+
height: Height in inches (optional)
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
The created image shape
|
|
299
|
+
"""
|
|
300
|
+
if width is not None and height is not None:
|
|
301
|
+
return slide.shapes.add_picture(
|
|
302
|
+
image_path, Inches(left), Inches(top), Inches(width), Inches(height)
|
|
303
|
+
)
|
|
304
|
+
elif width is not None:
|
|
305
|
+
return slide.shapes.add_picture(
|
|
306
|
+
image_path, Inches(left), Inches(top), Inches(width)
|
|
307
|
+
)
|
|
308
|
+
elif height is not None:
|
|
309
|
+
return slide.shapes.add_picture(
|
|
310
|
+
image_path, Inches(left), Inches(top), height=Inches(height)
|
|
311
|
+
)
|
|
312
|
+
else:
|
|
313
|
+
return slide.shapes.add_picture(
|
|
314
|
+
image_path, Inches(left), Inches(top)
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def add_table(slide, rows: int, cols: int, left: float, top: float, width: float, height: float) -> Any:
|
|
319
|
+
"""
|
|
320
|
+
Add a table to a slide.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
slide: The slide object
|
|
324
|
+
rows: Number of rows
|
|
325
|
+
cols: Number of columns
|
|
326
|
+
left: Left position in inches
|
|
327
|
+
top: Top position in inches
|
|
328
|
+
width: Width in inches
|
|
329
|
+
height: Height in inches
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
The created table shape
|
|
333
|
+
"""
|
|
334
|
+
return slide.shapes.add_table(
|
|
335
|
+
rows, cols, Inches(left), Inches(top), Inches(width), Inches(height)
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def format_table_cell(cell, font_size: int = None, font_name: str = None,
|
|
340
|
+
bold: bool = None, italic: bool = None,
|
|
341
|
+
color: Tuple[int, int, int] = None, bg_color: Tuple[int, int, int] = None,
|
|
342
|
+
alignment: str = None, vertical_alignment: str = None) -> None:
|
|
343
|
+
"""
|
|
344
|
+
Format a table cell.
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
cell: The table cell object
|
|
348
|
+
font_size: Font size in points
|
|
349
|
+
font_name: Font name
|
|
350
|
+
bold: Whether text should be bold
|
|
351
|
+
italic: Whether text should be italic
|
|
352
|
+
color: RGB color tuple (r, g, b)
|
|
353
|
+
bg_color: Background RGB color tuple (r, g, b)
|
|
354
|
+
alignment: Text alignment
|
|
355
|
+
vertical_alignment: Vertical alignment
|
|
356
|
+
"""
|
|
357
|
+
# Format text
|
|
358
|
+
if any([font_size, font_name, bold, italic, color, alignment]):
|
|
359
|
+
format_text_advanced(
|
|
360
|
+
cell.text_frame,
|
|
361
|
+
font_size=font_size,
|
|
362
|
+
font_name=font_name,
|
|
363
|
+
bold=bold,
|
|
364
|
+
italic=italic,
|
|
365
|
+
color=color,
|
|
366
|
+
alignment=alignment
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
# Set background color
|
|
370
|
+
if bg_color:
|
|
371
|
+
cell.fill.solid()
|
|
372
|
+
cell.fill.fore_color.rgb = RGBColor(*bg_color)
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def add_chart(slide, chart_type: str, left: float, top: float, width: float, height: float,
|
|
376
|
+
categories: List[str], series_names: List[str], series_values: List[List[float]]) -> Any:
|
|
377
|
+
"""
|
|
378
|
+
Add a chart to a slide.
|
|
379
|
+
|
|
380
|
+
Args:
|
|
381
|
+
slide: The slide object
|
|
382
|
+
chart_type: Type of chart ('column', 'bar', 'line', 'pie', etc.)
|
|
383
|
+
left: Left position in inches
|
|
384
|
+
top: Top position in inches
|
|
385
|
+
width: Width in inches
|
|
386
|
+
height: Height in inches
|
|
387
|
+
categories: List of category names
|
|
388
|
+
series_names: List of series names
|
|
389
|
+
series_values: List of value lists for each series
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
The created chart object
|
|
393
|
+
"""
|
|
394
|
+
# Map chart type names to enum values
|
|
395
|
+
chart_type_map = {
|
|
396
|
+
'column': XL_CHART_TYPE.COLUMN_CLUSTERED,
|
|
397
|
+
'stacked_column': XL_CHART_TYPE.COLUMN_STACKED,
|
|
398
|
+
'bar': XL_CHART_TYPE.BAR_CLUSTERED,
|
|
399
|
+
'stacked_bar': XL_CHART_TYPE.BAR_STACKED,
|
|
400
|
+
'line': XL_CHART_TYPE.LINE,
|
|
401
|
+
'line_markers': XL_CHART_TYPE.LINE_MARKERS,
|
|
402
|
+
'pie': XL_CHART_TYPE.PIE,
|
|
403
|
+
'doughnut': XL_CHART_TYPE.DOUGHNUT,
|
|
404
|
+
'area': XL_CHART_TYPE.AREA,
|
|
405
|
+
'stacked_area': XL_CHART_TYPE.AREA_STACKED,
|
|
406
|
+
'scatter': XL_CHART_TYPE.XY_SCATTER,
|
|
407
|
+
'radar': XL_CHART_TYPE.RADAR,
|
|
408
|
+
'radar_markers': XL_CHART_TYPE.RADAR_MARKERS
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
xl_chart_type = chart_type_map.get(chart_type.lower(), XL_CHART_TYPE.COLUMN_CLUSTERED)
|
|
412
|
+
|
|
413
|
+
# Create chart data
|
|
414
|
+
chart_data = CategoryChartData()
|
|
415
|
+
chart_data.categories = categories
|
|
416
|
+
|
|
417
|
+
for i, series_name in enumerate(series_names):
|
|
418
|
+
if i < len(series_values):
|
|
419
|
+
chart_data.add_series(series_name, series_values[i])
|
|
420
|
+
|
|
421
|
+
# Add chart to slide
|
|
422
|
+
chart_shape = slide.shapes.add_chart(
|
|
423
|
+
xl_chart_type, Inches(left), Inches(top), Inches(width), Inches(height), chart_data
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
return chart_shape.chart
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def format_chart(chart, has_legend: bool = True, legend_position: str = 'right',
|
|
430
|
+
has_data_labels: bool = False, title: str = None,
|
|
431
|
+
x_axis_title: str = None, y_axis_title: str = None,
|
|
432
|
+
color_scheme: str = None) -> None:
|
|
433
|
+
"""
|
|
434
|
+
Format a chart with various options.
|
|
435
|
+
|
|
436
|
+
Args:
|
|
437
|
+
chart: The chart object
|
|
438
|
+
has_legend: Whether to show legend
|
|
439
|
+
legend_position: Position of legend ('right', 'top', 'bottom', 'left')
|
|
440
|
+
has_data_labels: Whether to show data labels
|
|
441
|
+
title: Chart title
|
|
442
|
+
x_axis_title: X-axis title
|
|
443
|
+
y_axis_title: Y-axis title
|
|
444
|
+
color_scheme: Color scheme to apply
|
|
445
|
+
"""
|
|
446
|
+
try:
|
|
447
|
+
# Set chart title
|
|
448
|
+
if title:
|
|
449
|
+
chart.chart_title.text_frame.text = title
|
|
450
|
+
|
|
451
|
+
# Configure legend
|
|
452
|
+
if has_legend:
|
|
453
|
+
chart.has_legend = True
|
|
454
|
+
# Note: Legend position setting may vary by chart type
|
|
455
|
+
else:
|
|
456
|
+
chart.has_legend = False
|
|
457
|
+
|
|
458
|
+
# Configure data labels
|
|
459
|
+
if has_data_labels:
|
|
460
|
+
for series in chart.series:
|
|
461
|
+
series.has_data_labels = True
|
|
462
|
+
|
|
463
|
+
# Set axis titles if available
|
|
464
|
+
try:
|
|
465
|
+
if x_axis_title and hasattr(chart, 'category_axis'):
|
|
466
|
+
chart.category_axis.axis_title.text_frame.text = x_axis_title
|
|
467
|
+
if y_axis_title and hasattr(chart, 'value_axis'):
|
|
468
|
+
chart.value_axis.axis_title.text_frame.text = y_axis_title
|
|
469
|
+
except:
|
|
470
|
+
pass # Axis titles may not be available for all chart types
|
|
471
|
+
|
|
472
|
+
except Exception:
|
|
473
|
+
pass # Graceful degradation for chart formatting
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
def extract_slide_text_content(slide) -> Dict:
|
|
477
|
+
"""
|
|
478
|
+
Extract all text content from a slide including placeholders and text shapes.
|
|
479
|
+
|
|
480
|
+
Args:
|
|
481
|
+
slide: The slide object to extract text from
|
|
482
|
+
|
|
483
|
+
Returns:
|
|
484
|
+
Dictionary containing all text content organized by source type
|
|
485
|
+
"""
|
|
486
|
+
try:
|
|
487
|
+
text_content = {
|
|
488
|
+
"slide_title": "",
|
|
489
|
+
"placeholders": [],
|
|
490
|
+
"text_shapes": [],
|
|
491
|
+
"table_text": [],
|
|
492
|
+
"all_text_combined": ""
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
all_texts = []
|
|
496
|
+
|
|
497
|
+
# Extract title from slide if available
|
|
498
|
+
if hasattr(slide, 'shapes') and hasattr(slide.shapes, 'title') and slide.shapes.title:
|
|
499
|
+
try:
|
|
500
|
+
title_text = slide.shapes.title.text_frame.text.strip()
|
|
501
|
+
if title_text:
|
|
502
|
+
text_content["slide_title"] = title_text
|
|
503
|
+
all_texts.append(title_text)
|
|
504
|
+
except:
|
|
505
|
+
pass
|
|
506
|
+
|
|
507
|
+
# Extract text from all shapes
|
|
508
|
+
for i, shape in enumerate(slide.shapes):
|
|
509
|
+
shape_text_info = {
|
|
510
|
+
"shape_index": i,
|
|
511
|
+
"shape_name": shape.name,
|
|
512
|
+
"shape_type": str(shape.shape_type),
|
|
513
|
+
"text": ""
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
try:
|
|
517
|
+
# Check if shape has text frame
|
|
518
|
+
if hasattr(shape, 'text_frame') and shape.text_frame:
|
|
519
|
+
text = shape.text_frame.text.strip()
|
|
520
|
+
if text:
|
|
521
|
+
shape_text_info["text"] = text
|
|
522
|
+
all_texts.append(text)
|
|
523
|
+
|
|
524
|
+
# Categorize by shape type
|
|
525
|
+
if hasattr(shape, 'placeholder_format'):
|
|
526
|
+
# This is a placeholder
|
|
527
|
+
placeholder_info = shape_text_info.copy()
|
|
528
|
+
placeholder_info["placeholder_type"] = str(shape.placeholder_format.type)
|
|
529
|
+
placeholder_info["placeholder_idx"] = shape.placeholder_format.idx
|
|
530
|
+
text_content["placeholders"].append(placeholder_info)
|
|
531
|
+
else:
|
|
532
|
+
# This is a regular text shape
|
|
533
|
+
text_content["text_shapes"].append(shape_text_info)
|
|
534
|
+
|
|
535
|
+
# Extract text from tables
|
|
536
|
+
elif hasattr(shape, 'table'):
|
|
537
|
+
table_texts = []
|
|
538
|
+
table = shape.table
|
|
539
|
+
for row_idx, row in enumerate(table.rows):
|
|
540
|
+
row_texts = []
|
|
541
|
+
for col_idx, cell in enumerate(row.cells):
|
|
542
|
+
cell_text = cell.text_frame.text.strip()
|
|
543
|
+
if cell_text:
|
|
544
|
+
row_texts.append(cell_text)
|
|
545
|
+
all_texts.append(cell_text)
|
|
546
|
+
if row_texts:
|
|
547
|
+
table_texts.append({
|
|
548
|
+
"row": row_idx,
|
|
549
|
+
"cells": row_texts
|
|
550
|
+
})
|
|
551
|
+
|
|
552
|
+
if table_texts:
|
|
553
|
+
text_content["table_text"].append({
|
|
554
|
+
"shape_index": i,
|
|
555
|
+
"shape_name": shape.name,
|
|
556
|
+
"table_content": table_texts
|
|
557
|
+
})
|
|
558
|
+
|
|
559
|
+
except Exception as e:
|
|
560
|
+
# Skip shapes that can't be processed
|
|
561
|
+
continue
|
|
562
|
+
|
|
563
|
+
# Combine all text
|
|
564
|
+
text_content["all_text_combined"] = "\n".join(all_texts)
|
|
565
|
+
|
|
566
|
+
return {
|
|
567
|
+
"success": True,
|
|
568
|
+
"text_content": text_content,
|
|
569
|
+
"total_text_shapes": len(text_content["placeholders"]) + len(text_content["text_shapes"]),
|
|
570
|
+
"has_title": bool(text_content["slide_title"]),
|
|
571
|
+
"has_tables": len(text_content["table_text"]) > 0
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
except Exception as e:
|
|
575
|
+
return {
|
|
576
|
+
"success": False,
|
|
577
|
+
"error": f"Failed to extract text content: {str(e)}",
|
|
578
|
+
"text_content": None
|
|
579
|
+
}
|
utils/core_utils.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core utility functions for PowerPoint MCP Server.
|
|
3
|
+
Basic operations and error handling.
|
|
4
|
+
"""
|
|
5
|
+
from typing import Any, Callable, List, Tuple, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def try_multiple_approaches(operation_name: str, approaches: List[Tuple[Callable, str]]) -> Tuple[Any, Optional[str]]:
|
|
9
|
+
"""
|
|
10
|
+
Try multiple approaches to perform an operation, returning the first successful result.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
operation_name: Name of the operation for error reporting
|
|
14
|
+
approaches: List of (approach_func, description) tuples to try
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
Tuple of (result, None) if any approach succeeded, or (None, error_messages) if all failed
|
|
18
|
+
"""
|
|
19
|
+
error_messages = []
|
|
20
|
+
|
|
21
|
+
for approach_func, description in approaches:
|
|
22
|
+
try:
|
|
23
|
+
result = approach_func()
|
|
24
|
+
return result, None
|
|
25
|
+
except Exception as e:
|
|
26
|
+
error_messages.append(f"{description}: {str(e)}")
|
|
27
|
+
|
|
28
|
+
return None, f"Failed to {operation_name} after trying multiple approaches: {'; '.join(error_messages)}"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def safe_operation(operation_name: str, operation_func: Callable, error_message: Optional[str] = None, *args, **kwargs) -> Tuple[Any, Optional[str]]:
|
|
32
|
+
"""
|
|
33
|
+
Execute an operation safely with standard error handling.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
operation_name: Name of the operation for error reporting
|
|
37
|
+
operation_func: Function to execute
|
|
38
|
+
error_message: Custom error message (optional)
|
|
39
|
+
*args, **kwargs: Arguments to pass to the operation function
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
A tuple (result, error) where error is None if operation was successful
|
|
43
|
+
"""
|
|
44
|
+
try:
|
|
45
|
+
result = operation_func(*args, **kwargs)
|
|
46
|
+
return result, None
|
|
47
|
+
except ValueError as e:
|
|
48
|
+
error_msg = error_message or f"Invalid input for {operation_name}: {str(e)}"
|
|
49
|
+
return None, error_msg
|
|
50
|
+
except TypeError as e:
|
|
51
|
+
error_msg = error_message or f"Type error in {operation_name}: {str(e)}"
|
|
52
|
+
return None, error_msg
|
|
53
|
+
except Exception as e:
|
|
54
|
+
error_msg = error_message or f"Failed to execute {operation_name}: {str(e)}"
|
|
55
|
+
return None, error_msg
|