feat: add YAML validation with check command and auto-validation
Implements comprehensive validation before PPTX conversion to catch errors early. Includes element-level validation (colors, fonts, table consistency) and system-level validation (geometry, resources). Supports standalone check command and automatic validation during conversion.
This commit is contained in:
220
yaml2pptx.py
220
yaml2pptx.py
@@ -14,6 +14,11 @@ YAML to PPTX Converter
|
||||
将 YAML 格式的演示文稿源文件转换为 PPTX 文件
|
||||
|
||||
使用方法:
|
||||
# 子命令模式(推荐)
|
||||
uv run yaml2pptx.py convert input.yaml output.pptx
|
||||
uv run yaml2pptx.py check input.yaml
|
||||
|
||||
# 传统模式(向后兼容)
|
||||
uv run yaml2pptx.py input.yaml output.pptx
|
||||
uv run yaml2pptx.py input.yaml # 自动生成 input.pptx
|
||||
"""
|
||||
@@ -28,42 +33,78 @@ from loaders.yaml_loader import YAMLError
|
||||
from core.presentation import Presentation
|
||||
from renderers.pptx_renderer import PptxGenerator
|
||||
from preview.server import start_preview_server
|
||||
from validators import Validator
|
||||
|
||||
|
||||
def parse_args():
|
||||
"""解析命令行参数"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="将 YAML 格式的演示文稿源文件转换为 PPTX 文件"
|
||||
)
|
||||
parser.add_argument(
|
||||
"input",
|
||||
type=str,
|
||||
help="输入的 YAML 文件路径"
|
||||
)
|
||||
parser.add_argument(
|
||||
"output",
|
||||
type=str,
|
||||
nargs="?",
|
||||
help="输出的 PPTX 文件路径(可选,默认为输入文件名.pptx)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--template-dir",
|
||||
type=str,
|
||||
default=None,
|
||||
help="模板文件目录路径(如果 YAML 中使用了模板则必须指定)。可以是绝对路径或相对路径(相对于当前工作目录)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--preview",
|
||||
action="store_true",
|
||||
help="启动浏览器预览模式,而不是生成 PPTX 文件"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--port",
|
||||
type=int,
|
||||
default=None,
|
||||
help="预览服务器端口(默认随机选择 20000-30000 之间的端口)"
|
||||
)
|
||||
return parser.parse_args()
|
||||
# 检查是否使用了子命令
|
||||
if len(sys.argv) > 1 and sys.argv[1] in ['check', 'convert']:
|
||||
# 使用子命令解析
|
||||
parser = argparse.ArgumentParser(
|
||||
description="将 YAML 格式的演示文稿源文件转换为 PPTX 文件"
|
||||
)
|
||||
subparsers = parser.add_subparsers(dest='command')
|
||||
|
||||
# check 子命令
|
||||
check_parser = subparsers.add_parser('check', help='验证 YAML 文件')
|
||||
check_parser.add_argument("input", type=str, help="输入的 YAML 文件路径")
|
||||
check_parser.add_argument("--template-dir", type=str, default=None, help="模板文件目录路径")
|
||||
|
||||
# convert 子命令
|
||||
convert_parser = subparsers.add_parser('convert', help='转换 YAML 为 PPTX')
|
||||
convert_parser.add_argument("input", type=str, help="输入的 YAML 文件路径")
|
||||
convert_parser.add_argument("output", type=str, nargs="?", help="输出的 PPTX 文件路径(可选,默认为输入文件名.pptx)")
|
||||
convert_parser.add_argument("--template-dir", type=str, default=None, help="模板文件目录路径")
|
||||
convert_parser.add_argument("--preview", action="store_true", help="启动浏览器预览模式")
|
||||
convert_parser.add_argument("--port", type=int, default=None, help="预览服务器端口")
|
||||
convert_parser.add_argument("--no-check", action="store_true", help="跳过自动验证")
|
||||
|
||||
return parser.parse_args()
|
||||
else:
|
||||
# 使用传统解析(转换命令)- 向后兼容
|
||||
parser = argparse.ArgumentParser(
|
||||
description="将 YAML 格式的演示文稿源文件转换为 PPTX 文件"
|
||||
)
|
||||
parser.add_argument("input", type=str, help="输入的 YAML 文件路径")
|
||||
parser.add_argument("output", type=str, nargs="?", help="输出的 PPTX 文件路径(可选,默认为输入文件名.pptx)")
|
||||
parser.add_argument("--template-dir", type=str, default=None, help="模板文件目录路径")
|
||||
parser.add_argument("--preview", action="store_true", help="启动浏览器预览模式")
|
||||
parser.add_argument("--port", type=int, default=None, help="预览服务器端口")
|
||||
parser.add_argument("--no-check", action="store_true", help="跳过自动验证")
|
||||
args = parser.parse_args()
|
||||
args.command = None # 标记为非子命令(向后兼容模式)
|
||||
return args
|
||||
|
||||
|
||||
def handle_check(args):
|
||||
"""处理 check 子命令"""
|
||||
try:
|
||||
input_path = Path(args.input)
|
||||
|
||||
# 处理模板目录
|
||||
template_dir = None
|
||||
if args.template_dir:
|
||||
template_dir = Path(args.template_dir)
|
||||
|
||||
# 执行验证
|
||||
validator = Validator()
|
||||
result = validator.validate(input_path, template_dir)
|
||||
|
||||
# 输出验证结果
|
||||
print(result.format_output())
|
||||
|
||||
# 返回退出码
|
||||
if result.has_errors():
|
||||
sys.exit(1)
|
||||
else:
|
||||
sys.exit(0)
|
||||
|
||||
except Exception as e:
|
||||
log_error(f"验证失败: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
@@ -71,47 +112,16 @@ def main():
|
||||
try:
|
||||
args = parse_args()
|
||||
|
||||
# 处理输入文件路径
|
||||
input_path = Path(args.input)
|
||||
|
||||
# 预览模式
|
||||
if args.preview:
|
||||
start_preview_server(input_path, args.template_dir, args.port)
|
||||
# 处理 check 子命令
|
||||
if args.command == 'check':
|
||||
handle_check(args)
|
||||
return
|
||||
|
||||
# PPTX 生成模式
|
||||
# 处理输出文件路径
|
||||
if args.output:
|
||||
output_path = Path(args.output)
|
||||
else:
|
||||
# 自动生成输出文件名
|
||||
output_path = input_path.with_suffix(".pptx")
|
||||
|
||||
log_info(f"开始转换: {input_path}")
|
||||
log_info(f"输出文件: {output_path}")
|
||||
|
||||
# 1. 加载演示文稿
|
||||
log_info("加载演示文稿...")
|
||||
pres = Presentation(input_path, templates_dir=args.template_dir)
|
||||
|
||||
# 2. 创建 PPTX 生成器
|
||||
log_info(f"创建演示文稿 ({pres.size})...")
|
||||
generator = PptxGenerator(pres.size)
|
||||
|
||||
# 3. 渲染所有幻灯片
|
||||
slides_data = pres.data.get('slides', [])
|
||||
total_slides = len(slides_data)
|
||||
|
||||
for i, slide_data in enumerate(slides_data, 1):
|
||||
log_progress(i, total_slides, f"处理幻灯片")
|
||||
rendered_slide = pres.render_slide(slide_data)
|
||||
generator.add_slide(rendered_slide, input_path.parent)
|
||||
|
||||
# 4. 保存 PPTX 文件
|
||||
log_info("保存 PPTX 文件...")
|
||||
generator.save(output_path)
|
||||
|
||||
log_success(f"转换完成: {output_path}")
|
||||
# 处理 convert 子命令或传统模式(向后兼容)
|
||||
# convert 子命令和无子命令的行为相同
|
||||
if args.command == 'convert' or args.command is None:
|
||||
handle_convert(args)
|
||||
return
|
||||
|
||||
except YAMLError as e:
|
||||
log_error(str(e))
|
||||
@@ -123,5 +133,73 @@ def main():
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def handle_convert(args):
|
||||
"""处理 convert 子命令或传统转换模式"""
|
||||
# 检查是否提供了输入文件
|
||||
if not args.input:
|
||||
log_error("错误: 缺少输入文件参数")
|
||||
sys.exit(1)
|
||||
|
||||
# 处理输入文件路径
|
||||
input_path = Path(args.input)
|
||||
|
||||
# 预览模式
|
||||
if args.preview:
|
||||
start_preview_server(input_path, args.template_dir, args.port)
|
||||
return
|
||||
|
||||
# PPTX 生成模式
|
||||
# 处理输出文件路径
|
||||
if args.output:
|
||||
output_path = Path(args.output)
|
||||
else:
|
||||
# 自动生成输出文件名
|
||||
output_path = input_path.with_suffix(".pptx")
|
||||
|
||||
log_info(f"开始转换: {input_path}")
|
||||
log_info(f"输出文件: {output_path}")
|
||||
|
||||
# 自动验证(除非使用 --no-check)
|
||||
if not args.no_check:
|
||||
log_info("验证 YAML 文件...")
|
||||
template_dir = Path(args.template_dir) if args.template_dir else None
|
||||
validator = Validator()
|
||||
result = validator.validate(input_path, template_dir)
|
||||
|
||||
# 如果有错误,输出并终止
|
||||
if result.has_errors():
|
||||
print("\n" + result.format_output())
|
||||
log_error("验证失败,转换已终止")
|
||||
sys.exit(1)
|
||||
|
||||
# 如果有警告,输出但继续
|
||||
if result.warnings:
|
||||
print("\n" + result.format_output())
|
||||
print() # 空行
|
||||
|
||||
# 1. 加载演示文稿
|
||||
log_info("加载演示文稿...")
|
||||
pres = Presentation(input_path, templates_dir=args.template_dir)
|
||||
|
||||
# 2. 创建 PPTX 生成器
|
||||
log_info(f"创建演示文稿 ({pres.size})...")
|
||||
generator = PptxGenerator(pres.size)
|
||||
|
||||
# 3. 渲染所有幻灯片
|
||||
slides_data = pres.data.get('slides', [])
|
||||
total_slides = len(slides_data)
|
||||
|
||||
for i, slide_data in enumerate(slides_data, 1):
|
||||
log_progress(i, total_slides, f"处理幻灯片")
|
||||
rendered_slide = pres.render_slide(slide_data)
|
||||
generator.add_slide(rendered_slide, input_path.parent)
|
||||
|
||||
# 4. 保存 PPTX 文件
|
||||
log_info("保存 PPTX 文件...")
|
||||
generator.save(output_path)
|
||||
|
||||
log_success(f"转换完成: {output_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user