refactor: modularize yaml2pptx into layered architecture
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)
This commit is contained in:
96
core/elements.py
Normal file
96
core/elements.py
Normal file
@@ -0,0 +1,96 @@
|
||||
"""
|
||||
元素抽象层模块
|
||||
|
||||
定义统一的元素数据类,支持元素验证和未来扩展。
|
||||
"""
|
||||
|
||||
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}")
|
||||
Reference in New Issue
Block a user