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:
125
core/elements.py
125
core/elements.py
@@ -5,7 +5,22 @@
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
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
|
||||
@@ -21,6 +36,41 @@ class TextElement:
|
||||
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:
|
||||
@@ -36,6 +86,12 @@ class ImageElement:
|
||||
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:
|
||||
@@ -51,6 +107,42 @@ class ShapeElement:
|
||||
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:
|
||||
@@ -68,6 +160,37 @@ class TableElement:
|
||||
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):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user