deckbuilder 1.0.0b1__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.
- deckbuilder/__init__.py +22 -0
- deckbuilder/cli.py +544 -0
- deckbuilder/cli_tools.py +739 -0
- deckbuilder/engine.py +1546 -0
- deckbuilder/image_handler.py +291 -0
- deckbuilder/layout_intelligence.json +288 -0
- deckbuilder/layout_intelligence.py +398 -0
- deckbuilder/naming_conventions.py +541 -0
- deckbuilder/placeholder_types.py +101 -0
- deckbuilder/placekitten_integration.py +280 -0
- deckbuilder/structured_frontmatter.py +862 -0
- deckbuilder/table_styles.py +37 -0
- deckbuilder-1.0.0b1.dist-info/METADATA +378 -0
- deckbuilder-1.0.0b1.dist-info/RECORD +37 -0
- deckbuilder-1.0.0b1.dist-info/WHEEL +5 -0
- deckbuilder-1.0.0b1.dist-info/entry_points.txt +3 -0
- deckbuilder-1.0.0b1.dist-info/licenses/LICENSE +201 -0
- deckbuilder-1.0.0b1.dist-info/top_level.txt +4 -0
- mcp_server/__init__.py +9 -0
- mcp_server/content_analysis.py +436 -0
- mcp_server/content_optimization.py +822 -0
- mcp_server/layout_recommendations.py +595 -0
- mcp_server/main.py +550 -0
- mcp_server/tools.py +492 -0
- placekitten/README.md +561 -0
- placekitten/__init__.py +44 -0
- placekitten/core.py +184 -0
- placekitten/filters.py +183 -0
- placekitten/images/ACuteKitten-1.png +0 -0
- placekitten/images/ACuteKitten-2.png +0 -0
- placekitten/images/ACuteKitten-3.png +0 -0
- placekitten/images/TwoKitttens Playing-1.png +0 -0
- placekitten/images/TwoKitttens Playing-2.png +0 -0
- placekitten/images/TwoKitttensSleeping-1.png +0 -0
- placekitten/processor.py +262 -0
- placekitten/smart_crop.py +314 -0
- shared/__init__.py +9 -0
mcp_server/tools.py
ADDED
@@ -0,0 +1,492 @@
|
|
1
|
+
import json
|
2
|
+
import os
|
3
|
+
from typing import Dict, Optional
|
4
|
+
|
5
|
+
from pptx import Presentation
|
6
|
+
|
7
|
+
|
8
|
+
class TemplateAnalyzer:
|
9
|
+
"""Analyzes PowerPoint templates to extract raw layout and placeholder information."""
|
10
|
+
|
11
|
+
def __init__(self):
|
12
|
+
self.template_path = os.getenv("DECK_TEMPLATE_FOLDER")
|
13
|
+
self.output_folder = os.getenv("DECK_OUTPUT_FOLDER")
|
14
|
+
|
15
|
+
def analyze_pptx_template(self, template_name: str) -> Dict:
|
16
|
+
"""
|
17
|
+
Analyze a PowerPoint template and extract raw layout information.
|
18
|
+
|
19
|
+
Args:
|
20
|
+
template_name: Name of the template file (with or without .pptx extension)
|
21
|
+
|
22
|
+
Returns:
|
23
|
+
Dictionary containing template structure with placeholder indices
|
24
|
+
"""
|
25
|
+
# Ensure template name has .pptx extension
|
26
|
+
if not template_name.endswith(".pptx"):
|
27
|
+
template_name += ".pptx"
|
28
|
+
|
29
|
+
# Build template path
|
30
|
+
if not self.template_path:
|
31
|
+
raise RuntimeError("DECK_TEMPLATE_FOLDER environment variable not set")
|
32
|
+
|
33
|
+
template_path = os.path.join(self.template_path, template_name)
|
34
|
+
|
35
|
+
if not os.path.exists(template_path):
|
36
|
+
raise FileNotFoundError(f"Template file not found: {template_path}")
|
37
|
+
|
38
|
+
try:
|
39
|
+
prs = Presentation(template_path)
|
40
|
+
|
41
|
+
# Extract basic template info
|
42
|
+
base_name = os.path.splitext(template_name)[0]
|
43
|
+
template_info = {"name": base_name.replace("_", " ").title(), "version": "1.0"}
|
44
|
+
|
45
|
+
# Extract raw layout data
|
46
|
+
layouts = self._extract_layouts(prs)
|
47
|
+
|
48
|
+
# Validate template and generate warnings
|
49
|
+
validation_results = self._validate_template(layouts)
|
50
|
+
|
51
|
+
# Generate basic aliases structure (empty for user to fill)
|
52
|
+
aliases = self._generate_aliases_template()
|
53
|
+
|
54
|
+
result = {"template_info": template_info, "layouts": layouts, "aliases": aliases}
|
55
|
+
|
56
|
+
# Add validation results
|
57
|
+
if validation_results["warnings"] or validation_results["errors"]:
|
58
|
+
result["validation"] = validation_results
|
59
|
+
|
60
|
+
# Save to output folder as .g.json
|
61
|
+
self._save_json_mapping(base_name, result)
|
62
|
+
|
63
|
+
# Print validation results
|
64
|
+
self._print_validation_results(validation_results)
|
65
|
+
|
66
|
+
return result
|
67
|
+
|
68
|
+
except Exception as e:
|
69
|
+
raise RuntimeError(f"Error analyzing template: {str(e)}")
|
70
|
+
|
71
|
+
def _extract_layouts(self, presentation: Presentation) -> Dict:
|
72
|
+
"""Extract raw layout data from all slide layouts."""
|
73
|
+
layouts = {}
|
74
|
+
|
75
|
+
for idx, layout in enumerate(presentation.slide_layouts):
|
76
|
+
layout_info = self._extract_single_layout(layout, idx)
|
77
|
+
if layout_info:
|
78
|
+
# Use actual PowerPoint layout name
|
79
|
+
layout_name = f"layout_{idx}" # fallback
|
80
|
+
try:
|
81
|
+
if hasattr(layout, "name") and layout.name:
|
82
|
+
layout_name = layout.name
|
83
|
+
except Exception:
|
84
|
+
pass # Keep fallback name
|
85
|
+
|
86
|
+
layouts[layout_name] = layout_info
|
87
|
+
|
88
|
+
return layouts
|
89
|
+
|
90
|
+
def _extract_single_layout(self, layout, index: int) -> Optional[Dict]:
|
91
|
+
"""
|
92
|
+
Extract raw placeholder data from a single slide layout.
|
93
|
+
|
94
|
+
Args:
|
95
|
+
layout: PowerPoint slide layout object
|
96
|
+
index: Layout index in the template
|
97
|
+
|
98
|
+
Returns:
|
99
|
+
Dictionary with raw layout information
|
100
|
+
"""
|
101
|
+
try:
|
102
|
+
placeholders = {}
|
103
|
+
|
104
|
+
# Extract each placeholder's index and available properties
|
105
|
+
for shape in layout.placeholders:
|
106
|
+
placeholder_idx = shape.placeholder_format.idx
|
107
|
+
|
108
|
+
# Try to get more information about the placeholder
|
109
|
+
placeholder_info = f"placeholder_{placeholder_idx}"
|
110
|
+
|
111
|
+
# Check if placeholder has a name or type information
|
112
|
+
try:
|
113
|
+
if hasattr(shape, "name") and shape.name:
|
114
|
+
placeholder_info = shape.name
|
115
|
+
elif hasattr(shape.placeholder_format, "type"):
|
116
|
+
placeholder_type = shape.placeholder_format.type
|
117
|
+
placeholder_info = f"type_{placeholder_type}"
|
118
|
+
except Exception:
|
119
|
+
pass # Keep default name if we can't get more info
|
120
|
+
|
121
|
+
placeholders[str(placeholder_idx)] = placeholder_info
|
122
|
+
|
123
|
+
return {"index": index, "placeholders": placeholders}
|
124
|
+
|
125
|
+
except Exception as e:
|
126
|
+
print(f"Warning: Could not analyze layout {index}: {str(e)}")
|
127
|
+
return None
|
128
|
+
|
129
|
+
def _generate_aliases_template(self) -> Dict:
|
130
|
+
"""Generate basic aliases template for user configuration."""
|
131
|
+
return {
|
132
|
+
"table": "Title and Content",
|
133
|
+
"bullets": "Title and Content",
|
134
|
+
"content": "Title and Content",
|
135
|
+
"title": "Title Slide",
|
136
|
+
}
|
137
|
+
|
138
|
+
def _validate_template(self, layouts: Dict) -> Dict:
|
139
|
+
"""
|
140
|
+
Validate template layouts and detect common issues.
|
141
|
+
|
142
|
+
Args:
|
143
|
+
layouts: Dictionary of layout definitions
|
144
|
+
|
145
|
+
Returns:
|
146
|
+
Dictionary containing validation results with warnings and errors
|
147
|
+
"""
|
148
|
+
validation_results = {"warnings": [], "errors": [], "layout_analysis": {}}
|
149
|
+
|
150
|
+
all_placeholder_names = []
|
151
|
+
|
152
|
+
for layout_name, layout_info in layouts.items():
|
153
|
+
placeholders = layout_info.get("placeholders", {})
|
154
|
+
layout_warnings = []
|
155
|
+
layout_errors = []
|
156
|
+
|
157
|
+
# Check for duplicate placeholder names within layout
|
158
|
+
placeholder_names = list(placeholders.values())
|
159
|
+
unique_names = set(placeholder_names)
|
160
|
+
|
161
|
+
if len(placeholder_names) != len(unique_names):
|
162
|
+
for name in unique_names:
|
163
|
+
count = placeholder_names.count(name)
|
164
|
+
if count > 1:
|
165
|
+
layout_errors.append(
|
166
|
+
f"Duplicate placeholder name '{name}' appears {count} times"
|
167
|
+
)
|
168
|
+
|
169
|
+
# Check for column layouts with inconsistent naming
|
170
|
+
if "column" in layout_name.lower():
|
171
|
+
self._validate_column_layout(
|
172
|
+
layout_name, placeholders, layout_warnings, layout_errors
|
173
|
+
)
|
174
|
+
|
175
|
+
# Check for comparison layouts
|
176
|
+
if "comparison" in layout_name.lower():
|
177
|
+
self._validate_comparison_layout(
|
178
|
+
layout_name, placeholders, layout_warnings, layout_errors
|
179
|
+
)
|
180
|
+
|
181
|
+
# Track all placeholder names across layouts
|
182
|
+
all_placeholder_names.extend(placeholder_names)
|
183
|
+
|
184
|
+
# Store layout-specific validation results
|
185
|
+
if layout_warnings or layout_errors:
|
186
|
+
validation_results["layout_analysis"][layout_name] = {
|
187
|
+
"warnings": layout_warnings,
|
188
|
+
"errors": layout_errors,
|
189
|
+
}
|
190
|
+
validation_results["warnings"].extend(
|
191
|
+
[f"{layout_name}: {w}" for w in layout_warnings]
|
192
|
+
)
|
193
|
+
validation_results["errors"].extend([f"{layout_name}: {e}" for e in layout_errors])
|
194
|
+
|
195
|
+
# Global validation checks
|
196
|
+
self._validate_global_consistency(all_placeholder_names, validation_results, layouts)
|
197
|
+
|
198
|
+
return validation_results
|
199
|
+
|
200
|
+
def _validate_column_layout(
|
201
|
+
self, layout_name: str, placeholders: Dict, warnings: list, errors: list
|
202
|
+
) -> None:
|
203
|
+
"""Validate column-based layouts for consistent naming patterns."""
|
204
|
+
placeholder_names = list(placeholders.values())
|
205
|
+
|
206
|
+
# Check for expected column patterns
|
207
|
+
col_titles = [
|
208
|
+
name for name in placeholder_names if "col" in name.lower() and "title" in name.lower()
|
209
|
+
]
|
210
|
+
col_contents = [
|
211
|
+
name
|
212
|
+
for name in placeholder_names
|
213
|
+
if "col" in name.lower() and ("text" in name.lower() or "content" in name.lower())
|
214
|
+
]
|
215
|
+
|
216
|
+
# Extract column numbers from names and track specific placeholders that need fixing
|
217
|
+
title_cols = []
|
218
|
+
content_cols = []
|
219
|
+
fix_suggestions = []
|
220
|
+
|
221
|
+
for name in col_titles:
|
222
|
+
try:
|
223
|
+
# Extract number from names like "Col 1 Title" or "Col 2 Title Placeholder"
|
224
|
+
parts = name.lower().split()
|
225
|
+
for i, part in enumerate(parts):
|
226
|
+
if part == "col" and i + 1 < len(parts):
|
227
|
+
col_num = int(parts[i + 1])
|
228
|
+
title_cols.append((col_num, name))
|
229
|
+
break
|
230
|
+
except (ValueError, IndexError):
|
231
|
+
warnings.append(f"Could not parse column number from title placeholder: '{name}'")
|
232
|
+
|
233
|
+
for name in col_contents:
|
234
|
+
try:
|
235
|
+
parts = name.lower().split()
|
236
|
+
for i, part in enumerate(parts):
|
237
|
+
if part == "col" and i + 1 < len(parts):
|
238
|
+
col_num = int(parts[i + 1])
|
239
|
+
content_cols.append((col_num, name))
|
240
|
+
break
|
241
|
+
except (ValueError, IndexError):
|
242
|
+
warnings.append(f"Could not parse column number from content placeholder: '{name}'")
|
243
|
+
|
244
|
+
# Check for consistent column numbering
|
245
|
+
title_nums = sorted([col[0] for col in title_cols])
|
246
|
+
content_nums = sorted([col[0] for col in content_cols])
|
247
|
+
|
248
|
+
# For layouts with titles, check title/content pairs match
|
249
|
+
if "title" in layout_name.lower() and title_cols:
|
250
|
+
if title_nums != content_nums:
|
251
|
+
# Find content placeholders that need fixing
|
252
|
+
expected_content_nums = title_nums
|
253
|
+
for expected_num in expected_content_nums:
|
254
|
+
if expected_num not in content_nums:
|
255
|
+
# Find what column number this content actually has
|
256
|
+
for actual_num, content_name in content_cols:
|
257
|
+
if actual_num != expected_num and expected_num not in [
|
258
|
+
c[0] for c in content_cols
|
259
|
+
]:
|
260
|
+
# This content placeholder has wrong number
|
261
|
+
correct_name = content_name.replace(
|
262
|
+
f"Col {actual_num}", f"Col {expected_num}"
|
263
|
+
)
|
264
|
+
fix_suggestions.append(
|
265
|
+
f"In PowerPoint: Rename '{content_name}' → '{correct_name}'"
|
266
|
+
)
|
267
|
+
break
|
268
|
+
|
269
|
+
# Also check for duplicate column numbers in content
|
270
|
+
content_num_counts = {}
|
271
|
+
for num, name in content_cols:
|
272
|
+
if num not in content_num_counts:
|
273
|
+
content_num_counts[num] = []
|
274
|
+
content_num_counts[num].append(name)
|
275
|
+
|
276
|
+
for num, names in content_num_counts.items():
|
277
|
+
if len(names) > 1:
|
278
|
+
# Multiple content placeholders have same column number
|
279
|
+
for i, name in enumerate(
|
280
|
+
names[1:], start=2
|
281
|
+
): # Skip first one, fix the rest
|
282
|
+
# Find the next available column number
|
283
|
+
next_num = num + i - 1
|
284
|
+
while next_num in content_num_counts and next_num != num:
|
285
|
+
next_num += 1
|
286
|
+
if next_num <= len(title_nums):
|
287
|
+
correct_name = name.replace(f"Col {num}", f"Col {next_num}")
|
288
|
+
fix_suggestions.append(
|
289
|
+
f"In PowerPoint: Rename '{name}' → '{correct_name}'"
|
290
|
+
)
|
291
|
+
|
292
|
+
error_msg = (
|
293
|
+
f"Column title numbers {title_nums} don't match content numbers {content_nums}"
|
294
|
+
)
|
295
|
+
if fix_suggestions:
|
296
|
+
error_msg += (
|
297
|
+
f". Required fixes in '{layout_name}' layout: {'; '.join(fix_suggestions)}"
|
298
|
+
)
|
299
|
+
errors.append(error_msg)
|
300
|
+
|
301
|
+
# Check for proper sequential numbering
|
302
|
+
if title_nums and title_nums != list(range(1, len(title_nums) + 1)):
|
303
|
+
warnings.append(
|
304
|
+
f"Column numbers not sequential: {title_nums} (expected: {list(range(1, len(title_nums) + 1))})"
|
305
|
+
)
|
306
|
+
|
307
|
+
# Check for proper content numbering in content-only layouts
|
308
|
+
elif content_cols:
|
309
|
+
if content_nums != list(range(1, len(content_nums) + 1)):
|
310
|
+
warnings.append(
|
311
|
+
f"Column numbers not sequential: {content_nums} (expected: {list(range(1, len(content_nums) + 1))})"
|
312
|
+
)
|
313
|
+
|
314
|
+
def _validate_comparison_layout(
|
315
|
+
self, layout_name: str, placeholders: Dict, warnings: list, errors: list
|
316
|
+
) -> None:
|
317
|
+
"""Validate comparison layouts for proper left/right structure."""
|
318
|
+
placeholder_names = list(placeholders.values())
|
319
|
+
|
320
|
+
text_placeholders = [
|
321
|
+
name
|
322
|
+
for name in placeholder_names
|
323
|
+
if "text" in name.lower() and "placeholder" in name.lower()
|
324
|
+
]
|
325
|
+
content_placeholders = [
|
326
|
+
name
|
327
|
+
for name in placeholder_names
|
328
|
+
if "content" in name.lower() and "placeholder" in name.lower()
|
329
|
+
]
|
330
|
+
|
331
|
+
if len(text_placeholders) < 2:
|
332
|
+
errors.append(
|
333
|
+
"Comparison layout should have at least 2 text placeholders for left/right titles"
|
334
|
+
)
|
335
|
+
|
336
|
+
if len(content_placeholders) < 2:
|
337
|
+
errors.append(
|
338
|
+
"Comparison layout should have at least 2 content placeholders for left/right content"
|
339
|
+
)
|
340
|
+
|
341
|
+
def _validate_global_consistency(
|
342
|
+
self, all_placeholder_names: list, validation_results: Dict, layouts: Dict = None
|
343
|
+
) -> None:
|
344
|
+
"""Validate global consistency across all layouts."""
|
345
|
+
# Track patterns by layout for more specific reporting
|
346
|
+
layout_patterns = {}
|
347
|
+
unique_patterns = set()
|
348
|
+
|
349
|
+
if layouts:
|
350
|
+
for layout_name, layout_info in layouts.items():
|
351
|
+
layout_patterns[layout_name] = set()
|
352
|
+
placeholders = layout_info.get("placeholders", {})
|
353
|
+
|
354
|
+
for name in placeholders.values():
|
355
|
+
if "placeholder" in str(name).lower():
|
356
|
+
# Extract pattern like "Text Placeholder", "Content Placeholder"
|
357
|
+
parts = str(name).split()
|
358
|
+
if len(parts) >= 2:
|
359
|
+
pattern = f"{parts[0]} {parts[1]}"
|
360
|
+
layout_patterns[layout_name].add(pattern)
|
361
|
+
unique_patterns.add(pattern)
|
362
|
+
else:
|
363
|
+
# Fallback to old method if layouts not provided
|
364
|
+
for name in all_placeholder_names:
|
365
|
+
if "placeholder" in name.lower():
|
366
|
+
parts = name.split()
|
367
|
+
if len(parts) >= 2:
|
368
|
+
pattern = f"{parts[0]} {parts[1]}"
|
369
|
+
unique_patterns.add(pattern)
|
370
|
+
|
371
|
+
# Check for mixed naming conventions
|
372
|
+
if len(unique_patterns) > 1:
|
373
|
+
warning_msg = (
|
374
|
+
f"Multiple placeholder naming patterns detected: {sorted(unique_patterns)}"
|
375
|
+
)
|
376
|
+
|
377
|
+
# Add specific layout information if available
|
378
|
+
if layouts and layout_patterns:
|
379
|
+
layouts_with_patterns = []
|
380
|
+
for layout_name, patterns in layout_patterns.items():
|
381
|
+
if patterns: # Only include layouts that have patterns
|
382
|
+
pattern_list = sorted(patterns)
|
383
|
+
layouts_with_patterns.append(f"{layout_name} ({', '.join(pattern_list)})")
|
384
|
+
|
385
|
+
if layouts_with_patterns:
|
386
|
+
warning_msg += f". Affected layouts: {'; '.join(layouts_with_patterns)}"
|
387
|
+
|
388
|
+
validation_results["warnings"].append(warning_msg)
|
389
|
+
|
390
|
+
def _print_validation_results(self, validation_results: Dict) -> None:
|
391
|
+
"""Print validation results to console with formatting."""
|
392
|
+
if not validation_results["warnings"] and not validation_results["errors"]:
|
393
|
+
print("\n✓ Template validation passed - no issues detected")
|
394
|
+
return
|
395
|
+
|
396
|
+
print("\n" + "=" * 60)
|
397
|
+
print("TEMPLATE VALIDATION RESULTS")
|
398
|
+
print("=" * 60)
|
399
|
+
|
400
|
+
if validation_results["errors"]:
|
401
|
+
print(f"\n❌ ERRORS ({len(validation_results['errors'])}):")
|
402
|
+
for i, error in enumerate(validation_results["errors"], 1):
|
403
|
+
print(f" {i}. {error}")
|
404
|
+
|
405
|
+
if validation_results["warnings"]:
|
406
|
+
print(f"\n⚠️ WARNINGS ({len(validation_results['warnings'])}):")
|
407
|
+
for i, warning in enumerate(validation_results["warnings"], 1):
|
408
|
+
print(f" {i}. {warning}")
|
409
|
+
|
410
|
+
print("\n" + "=" * 60)
|
411
|
+
print("RECOMMENDED ACTIONS:")
|
412
|
+
print("=" * 60)
|
413
|
+
|
414
|
+
if validation_results["errors"]:
|
415
|
+
print("\n🔧 Fix these errors in your PowerPoint template:")
|
416
|
+
print(" • Open your PowerPoint template file")
|
417
|
+
print(" • Open View > Slide Master to edit the template layouts")
|
418
|
+
print(" • On Mac: Open Arrange > Selection Pane to see all placeholder objects")
|
419
|
+
print(" • Select the placeholder objects and rename them in the Selection Pane")
|
420
|
+
print(" • Rename placeholders as specified in the error messages above")
|
421
|
+
print(" • Ensure column layouts have consistent numbering (Col 1, Col 2, etc.)")
|
422
|
+
print(" • Verify comparison layouts have proper left/right structure")
|
423
|
+
print(" • Close Slide Master view when finished")
|
424
|
+
|
425
|
+
if validation_results["warnings"]:
|
426
|
+
print("\n💡 Consider these improvements:")
|
427
|
+
print(" • Use consistent placeholder naming patterns")
|
428
|
+
print(" • Ensure column numbers are sequential (1, 2, 3, 4)")
|
429
|
+
print(" • Follow naming conventions like 'Col 1 Title Placeholder 2'")
|
430
|
+
|
431
|
+
print("\n📝 After fixing placeholder names in PowerPoint, regenerate the template mapping:")
|
432
|
+
print(" python src/deckbuilder/cli_tools.py analyze default --verbose")
|
433
|
+
print("\n💡 The analyzer will show ✅ validation passed when all issues are resolved")
|
434
|
+
print("=" * 60)
|
435
|
+
|
436
|
+
def _save_json_mapping(self, template_name: str, data: Dict) -> None:
|
437
|
+
"""
|
438
|
+
Save the JSON mapping to output folder as .g.json file.
|
439
|
+
|
440
|
+
Args:
|
441
|
+
template_name: Base name of the template (without extension)
|
442
|
+
data: Dictionary to save as JSON
|
443
|
+
"""
|
444
|
+
if not self.output_folder:
|
445
|
+
print("Warning: DECK_OUTPUT_FOLDER not set, saving to current directory")
|
446
|
+
output_folder = "."
|
447
|
+
else:
|
448
|
+
output_folder = self.output_folder
|
449
|
+
|
450
|
+
# Ensure output folder exists
|
451
|
+
os.makedirs(output_folder, exist_ok=True)
|
452
|
+
|
453
|
+
# Create output filename
|
454
|
+
output_filename = f"{template_name}.g.json"
|
455
|
+
output_path = os.path.join(output_folder, output_filename)
|
456
|
+
|
457
|
+
# Save JSON (overwrite if exists)
|
458
|
+
with open(output_path, "w", encoding="utf-8") as f:
|
459
|
+
json.dump(data, f, indent=2, ensure_ascii=False)
|
460
|
+
|
461
|
+
print(f"\nTemplate mapping saved to: {output_path}")
|
462
|
+
print(f"Rename to {template_name}.json when ready to use with deckbuilder")
|
463
|
+
|
464
|
+
|
465
|
+
def analyze_pptx_template(template_name: str) -> Dict:
|
466
|
+
"""
|
467
|
+
Convenience function to analyze a PowerPoint template.
|
468
|
+
|
469
|
+
Args:
|
470
|
+
template_name: Name of the template file (with or without .pptx extension)
|
471
|
+
|
472
|
+
Returns:
|
473
|
+
Dictionary containing raw template structure for user mapping
|
474
|
+
"""
|
475
|
+
analyzer = TemplateAnalyzer()
|
476
|
+
return analyzer.analyze_pptx_template(template_name)
|
477
|
+
|
478
|
+
|
479
|
+
def test_with_default_template():
|
480
|
+
"""Test the analyzer with the default template."""
|
481
|
+
try:
|
482
|
+
result = analyze_pptx_template("default")
|
483
|
+
print("Raw Template Structure:")
|
484
|
+
print(json.dumps(result, indent=2))
|
485
|
+
return result
|
486
|
+
except Exception as e:
|
487
|
+
print(f"Error analyzing template: {str(e)}")
|
488
|
+
return None
|
489
|
+
|
490
|
+
|
491
|
+
if __name__ == "__main__":
|
492
|
+
test_with_default_template()
|