skills/ppt-station-skill/scripts/generate_ppt.py

189 lines
6.1 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""根据模板 + 数据 JSON 配置文件 → 生成 PPT
用法: python generate_ppt.py <template.pptx> <config.json> <output.pptx>
config.json 格式:
{
"text_data": { "标题": "xxx", "日期": "2024-12-05" },
"chart_configs": {
"图表名": {
"csv_path": "data.csv",
"categories_col": "日期",
"series_config": [
{"key": "列名", "name": "显示名", "type": "bar", "axis": "primary"},
{"key": "列名2", "name": "显示名2", "type": "line", "axis": "secondary"}
],
"style": {
"color_scheme": "aim00",
"line_width_pt": 2.0,
"marker_style": "none"
},
"layout": {
"title": "图表标题",
"legend": {"position": "top", "font_size_pt": 9, "font_name": "黑体"},
"value_axis": {"number_format": "0%", "font_size_pt": 9},
"secondary_value_axis": {"number_format": "#,##0", "font_size_pt": 9},
"date_axis": {"number_format": "yyyy/mm", "base_unit": "days"}
}
}
}
}
"""
import sys
import json
import argparse
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
import pandas as pd
from pptx.enum.chart import XL_LEGEND_POSITION
from ppt_station.template.replacer import TemplateReplacer
from ppt_station.chart_builder.styles import StyleConfig
from ppt_station.chart_builder.layout import ChartLayoutConfig, LegendConfig, ValueAxisConfig
from ppt_station.chart_builder.date_axis import DateAxisConfig
LEGEND_POSITION_MAP = {
"top": XL_LEGEND_POSITION.TOP,
"bottom": XL_LEGEND_POSITION.BOTTOM,
"left": XL_LEGEND_POSITION.LEFT,
"right": XL_LEGEND_POSITION.RIGHT,
"corner": XL_LEGEND_POSITION.CORNER,
}
def build_chart_config(chart_name: str, chart_def: dict, config_dir: Path) -> dict:
"""从 JSON 配置构建图表配置字典(供 TemplateReplacer 使用)"""
# 加载 CSV 数据
csv_path = config_dir / chart_def["csv_path"]
if not csv_path.exists():
print(f" 错误: CSV 文件不存在: {csv_path}", file=sys.stderr)
sys.exit(1)
df = pd.read_csv(str(csv_path))
# 转换日期列
categories_col = chart_def["categories_col"]
if categories_col in df.columns:
try:
df[categories_col] = pd.to_datetime(df[categories_col])
except Exception:
pass # 非日期列保持原样
series_config = chart_def["series_config"]
# 构建样式配置
style_def = chart_def.get("style", {})
style_config = StyleConfig(
color_scheme=style_def.get("color_scheme", "aim00"),
line_width_pt=style_def.get("line_width_pt", 2.0),
marker_style=style_def.get("marker_style", "none"),
)
# 构建布局配置
layout_def = chart_def.get("layout", {})
legend_def = layout_def.get("legend", {})
legend_pos = LEGEND_POSITION_MAP.get(legend_def.get("position", "top"), XL_LEGEND_POSITION.TOP)
legend_config = LegendConfig(
position=legend_pos,
font_size_pt=legend_def.get("font_size_pt", 9),
font_name=legend_def.get("font_name", "黑体"),
)
va_def = layout_def.get("value_axis", {})
value_axis_config = ValueAxisConfig(
number_format=va_def.get("number_format", "0%"),
font_size_pt=va_def.get("font_size_pt", 9),
font_name=va_def.get("font_name", "黑体"),
has_major_gridlines=va_def.get("has_major_gridlines", False),
)
sva_def = layout_def.get("secondary_value_axis", {})
secondary_value_axis_config = ValueAxisConfig(
number_format=sva_def.get("number_format", "#,##0"),
font_size_pt=sva_def.get("font_size_pt", 9),
font_name=sva_def.get("font_name", "黑体"),
has_major_gridlines=sva_def.get("has_major_gridlines", False),
)
layout_config = ChartLayoutConfig(
title=layout_def.get("title", ""),
legend_config=legend_config,
value_axis_config=value_axis_config,
secondary_value_axis_config=secondary_value_axis_config,
)
# 日期轴配置
da_def = layout_def.get("date_axis", {})
if da_def:
label_interval = da_def.get("major_unit", len(df) // 7)
date_axis_config = DateAxisConfig(
base_unit=da_def.get("base_unit", "days"),
major_unit=label_interval,
number_format=da_def.get("number_format", "yyyy/mm"),
)
layout_config.date_axis_config = date_axis_config
return {
"df": df,
"categories_col": categories_col,
"series_config": series_config,
"style_config": style_config,
"layout_config": layout_config,
}
def main():
parser = argparse.ArgumentParser(description="模板替换生成 PPT")
parser.add_argument("template", help="模板 .pptx 文件路径")
parser.add_argument("config", help="配置 .json 文件路径")
parser.add_argument("output", help="输出 .pptx 文件路径")
args = parser.parse_args()
template_path = Path(args.template)
config_path = Path(args.config)
output_path = Path(args.output)
if not template_path.exists():
print(f"错误: 模板文件不存在: {template_path}", file=sys.stderr)
sys.exit(1)
if not config_path.exists():
print(f"错误: 配置文件不存在: {config_path}", file=sys.stderr)
sys.exit(1)
with open(config_path, "r", encoding="utf-8") as f:
config = json.load(f)
config_dir = config_path.parent
# 重定向 print 到 stderr
old_stdout = sys.stdout
sys.stdout = sys.stderr
# 构建图表配置
chart_configs = {}
for chart_name, chart_def in config.get("chart_configs", {}).items():
print(f"构建图表配置: {chart_name}")
chart_configs[chart_name] = build_chart_config(chart_name, chart_def, config_dir)
# 执行替换
text_data = config.get("text_data", {})
replacer = TemplateReplacer(template_path)
replacer.replace(data=text_data, chart_configs=chart_configs)
output_path.parent.mkdir(parents=True, exist_ok=True)
replacer.save(output_path)
sys.stdout = old_stdout
print(json.dumps({"status": "ok", "output": str(output_path)}))
if __name__ == "__main__":
main()