Files
ppt/ppt_manager_v2/core/conditional_renderer.py

138 lines
5.6 KiB
Python
Raw Normal View History

from typing import Dict, Any, List, Optional, Callable
from pptx import Presentation
from loguru import logger
import ast
import operator
class ConditionalRenderer:
def __init__(self, presentation: Presentation = None):
self.prs = presentation
self.context = {}
self._slides_to_remove = []
self.operators = {
ast.Add: operator.add,
ast.Sub: operator.sub,
ast.Mult: operator.mul,
ast.Div: operator.truediv,
ast.Gt: operator.gt,
ast.GtE: operator.ge,
ast.Lt: operator.lt,
ast.LtE: operator.le,
ast.Eq: operator.eq,
ast.NotEq: operator.ne,
ast.And: lambda a, b: a and b,
ast.Or: lambda a, b: a or b,
}
def set_context(self, context: Dict[str, Any]):
self.context = context
logger.info(f"条件渲染上下文已设置: {list(context.keys())}")
def update_context(self, key: str, value: Any):
self.context[key] = value
def evaluate_condition(self, condition_expr: str, context: Dict[str, Any] = None) -> bool:
eval_context = {**self.context, **(context or {})}
try:
if "{" in condition_expr:
for key, value in eval_context.items():
condition_expr = condition_expr.replace(f"{{{key}}}", str(value))
result = self._safe_eval(condition_expr, eval_context)
logger.info(f"条件 [{condition_expr}] 评估结果: {result}")
return bool(result)
except Exception as e:
logger.warning(f"条件表达式评估失败 [{condition_expr}]: {e}")
return False
def _safe_eval(self, expr: str, context: Dict) -> Any:
try:
node = ast.parse(expr, mode='eval')
return self._eval_ast(node.body, context)
except:
safe_dict = {k: v for k, v in context.items() if isinstance(k, str)}
safe_dict['__builtins__'] = {}
return eval(expr, safe_dict)
def _eval_ast(self, node, context):
if isinstance(node, ast.Constant):
return node.value
elif isinstance(node, ast.Num):
return node.n
elif isinstance(node, ast.Str):
return node.s
elif isinstance(node, ast.Name):
return context.get(node.id, node.id)
elif isinstance(node, ast.BinOp):
op_type = type(node.op)
if op_type in self.operators:
return self.operators[op_type](
self._eval_ast(node.left, context),
self._eval_ast(node.right, context)
)
elif isinstance(node, ast.Compare):
left_val = self._eval_ast(node.left, context)
for op, comparator in zip(node.ops, node.comparators):
op_type = type(op)
if op_type in self.operators:
if not self.operators[op_type](left_val, self._eval_ast(comparator, context)):
return False
return True
elif isinstance(node, ast.BoolOp):
op_type = type(node.op)
if op_type == ast.And:
for value in node.values:
if not self._eval_ast(value, context):
return False
return True
elif op_type == ast.Or:
for value in node.values:
if self._eval_ast(value, context):
return True
return False
return None
def process_slide_conditions(self, slide_configs: List[Dict], presentation: Presentation = None) -> Presentation:
prs = presentation or self.prs
if not prs:
logger.error("没有提供Presentation对象")
return prs
logger.info(f"开始处理条件渲染,当前页数: {len(prs.slides)}")
slides_to_keep = []
for idx, slide_config in enumerate(slide_configs):
if 'condition' in slide_config:
condition = slide_config['condition']
if not self.evaluate_condition(condition):
logger.info(f"跳过第 {idx+1} 页,条件不满足: {condition}")
continue
if 'action' in slide_config and slide_config['action'] == 'insert_slide':
logger.info(f"执行插入幻灯片操作: {slide_config.get('template_source')}")
if idx < len(prs.slides):
slides_to_keep.append(idx)
logger.info(f"条件渲染完成,保留 {len(slides_to_keep)} / {len(slide_configs)} 页配置")
return prs
def insert_new_slide(self, presentation: Presentation,
slide_layout_idx: int = 6,
position: int = None) -> int:
if position is None:
position = len(presentation.slides)
slide_layout = presentation.slide_layouts[slide_layout_idx] if slide_layout_idx < len(presentation.slide_layouts) else presentation.slide_layouts[0]
xml_slides = presentation.slides._sldIdLst
slides = list(xml_slides)
xml_slides.insert(position, slides[0] if slides else None)
new_slide = presentation.slides.add_slide(slide_layout)
logger.info(f"已在位置 {position} 插入新页面")
return position