1
0
Files
PPTX/core/elements.py
lanyuanxiaoyao 83ff827ad1 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.
2026-03-02 18:14:45 +08:00

220 lines
7.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
元素抽象层模块
定义统一的元素数据类,支持元素验证和未来扩展。
"""
from dataclasses import dataclass, field
from typing import Optional, List
import re
def _is_valid_color(color: str) -> bool:
"""
验证颜色格式是否正确
Args:
color: 颜色字符串
Returns:
是否为有效的颜色格式(#RGB 或 #RRGGBB
"""
pattern = r'^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$'
return bool(re.match(pattern, color))
@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 个数字的列表")
def validate(self) -> List:
"""验证元素自身的完整性"""
from validators.result import ValidationIssue
issues = []
# 检查颜色格式
if self.font.get('color'):
if not _is_valid_color(self.font['color']):
issues.append(ValidationIssue(
level="ERROR",
message=f"无效的颜色格式: {self.font['color']} (应为 #RRGGBB)",
location="",
code="INVALID_COLOR_FORMAT"
))
# 检查字体大小
if self.font.get('size'):
size = self.font['size']
if size < 8:
issues.append(ValidationIssue(
level="WARNING",
message=f"字体太小: {size}pt (建议 >= 8pt)",
location="",
code="FONT_TOO_SMALL"
))
elif size > 100:
issues.append(ValidationIssue(
level="WARNING",
message=f"字体太大: {size}pt (建议 <= 100pt)",
location="",
code="FONT_TOO_LARGE"
))
return issues
@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 个数字的列表")
def validate(self) -> List:
"""验证元素自身的完整性"""
# ImageElement 的必需字段已在 __post_init__ 中检查
# 这里返回空列表,资源验证由 ResourceValidator 负责
return []
@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 个数字的列表")
def validate(self) -> List:
"""验证元素自身的完整性"""
from validators.result import ValidationIssue
issues = []
# 检查形状类型枚举
valid_shapes = ['rectangle', 'ellipse', 'rounded_rectangle']
if self.shape not in valid_shapes:
issues.append(ValidationIssue(
level="ERROR",
message=f"不支持的形状类型: {self.shape} (支持: {', '.join(valid_shapes)})",
location="",
code="INVALID_SHAPE_TYPE"
))
# 检查填充颜色格式
if self.fill and not _is_valid_color(self.fill):
issues.append(ValidationIssue(
level="ERROR",
message=f"无效的填充颜色格式: {self.fill} (应为 #RRGGBB)",
location="",
code="INVALID_COLOR_FORMAT"
))
# 检查线条颜色格式
if self.line and self.line.get('color'):
if not _is_valid_color(self.line['color']):
issues.append(ValidationIssue(
level="ERROR",
message=f"无效的线条颜色格式: {self.line['color']} (应为 #RRGGBB)",
location="",
code="INVALID_COLOR_FORMAT"
))
return issues
@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 validate(self) -> List:
"""验证元素自身的完整性"""
from validators.result import ValidationIssue
issues = []
# 检查表格数据行列数一致性
if self.data:
first_row_cols = len(self.data[0]) if isinstance(self.data[0], list) else 0
for i, row in enumerate(self.data[1:], start=1):
if isinstance(row, list):
if len(row) != first_row_cols:
issues.append(ValidationIssue(
level="ERROR",
message=f"表格数据行列数不一致: 第 {i+1} 行有 {len(row)} 列,期望 {first_row_cols}",
location="",
code="TABLE_INCONSISTENT_COLUMNS"
))
# 检查 col_widths 与列数是否匹配
if self.col_widths and self.data:
first_row_cols = len(self.data[0]) if isinstance(self.data[0], list) else 0
if len(self.col_widths) != first_row_cols:
issues.append(ValidationIssue(
level="WARNING",
message=f"col_widths 数量 ({len(self.col_widths)}) 与表格列数 ({first_row_cols}) 不匹配",
location="",
code="TABLE_COL_WIDTHS_MISMATCH"
))
return issues
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}")