Refactor yaml2pptx.py from a 1,245-line monolithic script into a modular architecture with clear separation of concerns. The entry point is now 127 lines, with business logic distributed across focused modules. Architecture: - core/: Domain models (elements, template, presentation) - loaders/: YAML loading and validation - renderers/: PPTX and HTML rendering - preview/: Flask preview server - utils.py: Shared utilities Key improvements: - Element abstraction layer using dataclass with validation - Renderer logic built into generator classes - Single-direction dependencies (no circular imports) - Each module 150-300 lines for better readability - Backward compatible CLI interface Documentation: - README.md: User-facing usage guide - README_DEV.md: Developer documentation OpenSpec: - Archive refactor-yaml2pptx-modular change (63/70 tasks complete) - Sync 5 delta specs to main specs (2 new + 3 updated)
97 lines
2.7 KiB
Python
97 lines
2.7 KiB
Python
"""
|
|
元素抽象层模块
|
|
|
|
定义统一的元素数据类,支持元素验证和未来扩展。
|
|
"""
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import Optional
|
|
|
|
|
|
@dataclass
|
|
class TextElement:
|
|
"""文本元素"""
|
|
type: str = 'text'
|
|
content: str = ''
|
|
box: list = field(default_factory=lambda: [1, 1, 8, 1])
|
|
font: dict = field(default_factory=dict)
|
|
|
|
def __post_init__(self):
|
|
"""创建时验证"""
|
|
if not isinstance(self.box, list) or len(self.box) != 4:
|
|
raise ValueError("box 必须是包含 4 个数字的列表")
|
|
|
|
|
|
@dataclass
|
|
class ImageElement:
|
|
"""图片元素"""
|
|
type: str = 'image'
|
|
src: str = ''
|
|
box: list = field(default_factory=lambda: [1, 1, 4, 3])
|
|
|
|
def __post_init__(self):
|
|
"""创建时验证"""
|
|
if not self.src:
|
|
raise ValueError("图片元素必须指定 src")
|
|
if not isinstance(self.box, list) or len(self.box) != 4:
|
|
raise ValueError("box 必须是包含 4 个数字的列表")
|
|
|
|
|
|
@dataclass
|
|
class ShapeElement:
|
|
"""形状元素"""
|
|
type: str = 'shape'
|
|
shape: str = 'rectangle'
|
|
box: list = field(default_factory=lambda: [1, 1, 2, 1])
|
|
fill: Optional[str] = None
|
|
line: Optional[dict] = None
|
|
|
|
def __post_init__(self):
|
|
"""创建时验证"""
|
|
if not isinstance(self.box, list) or len(self.box) != 4:
|
|
raise ValueError("box 必须是包含 4 个数字的列表")
|
|
|
|
|
|
@dataclass
|
|
class TableElement:
|
|
"""表格元素"""
|
|
type: str = 'table'
|
|
data: list = field(default_factory=list)
|
|
position: list = field(default_factory=lambda: [1, 1])
|
|
col_widths: list = field(default_factory=list)
|
|
style: dict = field(default_factory=dict)
|
|
|
|
def __post_init__(self):
|
|
"""创建时验证"""
|
|
if not self.data:
|
|
raise ValueError("表格数据不能为空")
|
|
if not isinstance(self.position, list) or len(self.position) != 2:
|
|
raise ValueError("position 必须是包含 2 个数字的列表")
|
|
|
|
|
|
def create_element(elem_dict: dict):
|
|
"""
|
|
元素工厂函数,从字典创建对应类型的元素对象
|
|
|
|
Args:
|
|
elem_dict: 元素字典,必须包含 type 字段
|
|
|
|
Returns:
|
|
对应类型的元素对象
|
|
|
|
Raises:
|
|
ValueError: 不支持的元素类型
|
|
"""
|
|
elem_type = elem_dict.get('type')
|
|
|
|
if elem_type == 'text':
|
|
return TextElement(**elem_dict)
|
|
elif elem_type == 'image':
|
|
return ImageElement(**elem_dict)
|
|
elif elem_type == 'shape':
|
|
return ShapeElement(**elem_dict)
|
|
elif elem_type == 'table':
|
|
return TableElement(**elem_dict)
|
|
else:
|
|
raise ValueError(f"不支持的元素类型: {elem_type}")
|