1
0
Files
PPTX/validators/validator.py
lanyuanxiaoyao 19d6661381 feat: 添加图片适配模式支持
- 支持四种图片适配模式:stretch、contain、cover、center
- 支持背景色填充功能(contain 和 center 模式)
- 支持文档级 DPI 配置(metadata.dpi)
- PPTX 渲染器集成 Pillow 实现高质量图片处理
- HTML 渲染器使用 CSS object-fit 实现相同效果
- 添加完整的单元测试、集成测试和端到端测试
- 更新 README 文档和架构文档
- 模块化设计:utils/image_utils.py 图片处理工具模块
- 添加图片配置验证器:validators/image_config.py
- 向后兼容:未指定 fit 时默认使用 stretch 模式
2026-03-04 10:29:21 +08:00

173 lines
6.0 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.
"""
主验证器
协调各子验证器,执行完整的 YAML 文件验证。
"""
from pathlib import Path
from loaders.yaml_loader import load_yaml_file, validate_presentation_yaml, YAMLError
from validators.result import ValidationResult, ValidationIssue
from validators.geometry import GeometryValidator
from validators.resource import ResourceValidator
from validators.image_config import validate_dpi_value
from core.elements import create_element
class Validator:
"""主验证器"""
# 幻灯片尺寸映射(英寸)
SLIDE_SIZES = {
"16:9": (10, 5.625),
"4:3": (10, 7.5),
}
def __init__(self):
"""初始化验证器"""
pass
def validate(self, yaml_path: Path, template_dir: Path = None) -> ValidationResult:
"""
验证 YAML 文件
Args:
yaml_path: YAML 文件路径
template_dir: 模板文件目录(可选)
Returns:
验证结果
"""
errors = []
warnings = []
infos = []
# 1. 加载 YAML
try:
data = load_yaml_file(yaml_path)
validate_presentation_yaml(data, str(yaml_path))
except YAMLError as e:
errors.append(
ValidationIssue(
level="ERROR", message=str(e), location="", code="YAML_ERROR"
)
)
return ValidationResult(
valid=False, errors=errors, warnings=warnings, infos=infos
)
except Exception as e:
errors.append(
ValidationIssue(
level="ERROR",
message=f"加载 YAML 文件失败: {str(e)}",
location="",
code="YAML_LOAD_ERROR",
)
)
return ValidationResult(
valid=False, errors=errors, warnings=warnings, infos=infos
)
# 获取幻灯片尺寸
size_str = data.get("metadata", {}).get("size", "16:9")
slide_width, slide_height = self.SLIDE_SIZES.get(size_str, (10, 5.625))
# 验证 DPI 配置
dpi = data.get("metadata", {}).get("dpi")
if dpi is not None:
dpi_issues = validate_dpi_value(dpi)
self._categorize_issues(dpi_issues, errors, warnings, infos)
# 初始化子验证器
geometry_validator = GeometryValidator(slide_width, slide_height)
resource_validator = ResourceValidator(
yaml_dir=yaml_path.parent, template_dir=template_dir, yaml_data=data
)
# 2. 验证每个幻灯片
slides = data.get("slides", [])
for slide_index, slide_data in enumerate(slides, start=1):
# 验证模板
template_issues = resource_validator.validate_template(
slide_data, slide_index
)
self._categorize_issues(template_issues, errors, warnings, infos)
# 验证模板变量
template_var_issues = resource_validator.validate_template_vars(
slide_data, slide_index
)
self._categorize_issues(template_var_issues, errors, warnings, infos)
# 验证元素
elements = slide_data.get("elements", [])
for elem_index, elem_dict in enumerate(elements, start=1):
# 3. 元素级验证
try:
element = create_element(elem_dict)
# 调用元素的 validate() 方法
if hasattr(element, "validate"):
elem_issues = element.validate()
# 填充位置信息
for issue in elem_issues:
issue.location = f"幻灯片 {slide_index}, 元素 {elem_index}"
self._categorize_issues(elem_issues, errors, warnings, infos)
# 4. 几何验证
geom_issues = geometry_validator.validate_element(
element, slide_index, elem_index
)
self._categorize_issues(geom_issues, errors, warnings, infos)
# 5. 资源验证(图片)
if elem_dict.get("type") == "image":
img_issues = resource_validator.validate_image(
element, slide_index, elem_index
)
self._categorize_issues(img_issues, errors, warnings, infos)
except ValueError as e:
# 元素创建失败__post_init__ 中的验证)
errors.append(
ValidationIssue(
level="ERROR",
message=str(e),
location=f"幻灯片 {slide_index}, 元素 {elem_index}",
code="ELEMENT_VALIDATION_ERROR",
)
)
except Exception as e:
errors.append(
ValidationIssue(
level="ERROR",
message=f"验证元素时出错: {str(e)}",
location=f"幻灯片 {slide_index}, 元素 {elem_index}",
code="ELEMENT_VALIDATION_ERROR",
)
)
# 返回验证结果
return ValidationResult(
valid=len(errors) == 0, errors=errors, warnings=warnings, infos=infos
)
def _categorize_issues(
self, issues: list, errors: list, warnings: list, infos: list
):
"""
将问题按级别分类
Args:
issues: 问题列表
errors: 错误列表
warnings: 警告列表
infos: 提示列表
"""
for issue in issues:
if issue.level == "ERROR":
errors.append(issue)
elif issue.level == "WARNING":
warnings.append(issue)
elif issue.level == "INFO":
infos.append(issue)