138 lines
5.6 KiB
Python
138 lines
5.6 KiB
Python
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
|