fix(ppt): final critic cleanup — Medium-1 OOXML order + Info-1/2 docs
All checks were successful
CD Pipeline / deploy (push) Successful in 2m29s
All checks were successful
CD Pipeline / deploy (push) Successful in 2m29s
清掉剩餘 critic finding(Medium-1 + Info-1 + Info-2): Medium-1: _set_run_fonts 違反 OOXML CT_TextCharacterProperties 子元素順序 - 抽出 _insert_rpr_child(rPr, tag) helper:依 ECMA-376 §21.1.2.3 規定 的順序表(_RPR_CHILD_ORDER)找正確位置插入 - 找第一個排在 target 之後的子元素 → insert 前面;否則 append - 當前 from-scratch 場景安全;未來改讀模板 .pptx 時也不會踩雷 - 驗證:產生 monthly v3.1,399 個 rPr 全部符合 schema 順序,0 違反 Info-1: cleanup_expired_ppt_cache docstring 補語意說明 - 明確說明 dry_run=True/False 時 deleted_files / deleted_rows / freed_bytes 欄位語意分別為「將刪除」預估值 vs「已實刪」實際值 Info-2: TEMPLATE_VERSIONS 標記退役 type - growth / vendor / bcg 三個 type 從未實際落地(依 ADR-014 校正 2026-04-28) - 加 DEPRECATED 註解,避免後人誤以為已支援 Info-3 SKIP(評估後不做): - get_template_version import 改放模組頂部會 trigger ppt_generator 模組級 REPORTS_DIR.mkdir() 副作用,read-only filesystem 環境會掛 - 保留延遲 import 是 graceful degradation 的 intentional 設計 至此 critic38967ce審查清單全綠: - 0 critical, 2 HIGH (3b0b4b3), 4 medium (52c06f6+ 本 commit), 3 info Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2246,8 +2246,10 @@ def cleanup_expired_ppt_cache(days_old: int = 7, dry_run: bool = True) -> dict:
|
||||
launchd / cron 排程務必顯式傳:
|
||||
cleanup_expired_ppt_cache(days_old=7, dry_run=False)
|
||||
|
||||
回傳:{'deleted_files': N, 'deleted_rows': N, 'freed_bytes': N, 'errors': [...]}
|
||||
dry_run=True 時 deleted_* 為「將刪除」預估值。
|
||||
回傳統計欄位語意(critic Info-1):
|
||||
dry_run=True 時:deleted_files / deleted_rows / freed_bytes 為「將刪除」預估值
|
||||
dry_run=False 時:上述為「已實刪」實際值
|
||||
errors 為過程中發生例外的列表(id + 錯誤訊息)
|
||||
"""
|
||||
from database.manager import DatabaseManager
|
||||
from database.ppt_reports import PPTReport
|
||||
|
||||
@@ -47,9 +47,12 @@ TEMPLATE_VERSIONS = {
|
||||
'strategy': 'v3.0', # 2026-05-02 AI 頁去黑改暖紙 + 附錄頁
|
||||
'competitor': 'v3.0', # 2026-05-02 AI 頁去黑改暖紙 + 附錄頁
|
||||
'promo': 'v3.0', # 2026-05-02 AI 頁去黑改暖紙 + 附錄頁
|
||||
'growth': 'v2.0',
|
||||
'vendor': 'v2.0',
|
||||
'bcg': 'v2.0',
|
||||
# ── DEPRECATED:以下 type 從未實際落地(依 ADR-014 校正 2026-04-28)。
|
||||
# 函式 generate_growth_ppt / generate_vendor_ppt / generate_bcg_ppt 仍存在於本檔,
|
||||
# 但路由層未綁定指令;保留版本字串避免如未來重啟時快取 schema 對不上。
|
||||
'growth': 'v2.0', # DEPRECATED — 從未落地
|
||||
'vendor': 'v2.0', # DEPRECATED — 從未落地
|
||||
'bcg': 'v2.0', # DEPRECATED — 從未落地
|
||||
}
|
||||
|
||||
|
||||
@@ -156,26 +159,59 @@ def _add_rect(slide, l, t, w, h, fill_hex, line_hex=None):
|
||||
return s
|
||||
|
||||
|
||||
# OOXML CT_TextCharacterProperties 子元素順序(依 ECMA-376 第一部分 §21.1.2.3)
|
||||
# critic Medium-1:SubElement 永遠 append 會違反 schema;必須按此順序 insert
|
||||
# 否則 LibreOffice / Keynote / 嚴格驗證器會拒絕讀取或丟棄 latin/ea 元素。
|
||||
_RPR_CHILD_ORDER = [
|
||||
'ln', 'noFill', 'solidFill', 'gradFill', 'blipFill', 'pattFill',
|
||||
'effectLst', 'effectDag', 'highlight', 'uLnTx', 'uLn',
|
||||
'uFillTx', 'uFill', 'latin', 'ea', 'cs', 'sym',
|
||||
'hlinkClick', 'hlinkMouseOver', 'rtl', 'extLst',
|
||||
]
|
||||
_OOXML_DRAWING_NS = 'http://schemas.openxmlformats.org/drawingml/2006/main'
|
||||
|
||||
|
||||
def _insert_rpr_child(rPr, tag: str):
|
||||
"""在 rPr 下依 ECMA-376 schema 順序插入 <a:tag> 元素。
|
||||
若 rPr 內已有此 tag,先全部移除避免重複。
|
||||
回傳新建的元素。
|
||||
"""
|
||||
from lxml import etree
|
||||
nsmap = {'a': _OOXML_DRAWING_NS}
|
||||
# 先清掉同名舊元素
|
||||
for el in rPr.findall(f'a:{tag}', nsmap):
|
||||
rPr.remove(el)
|
||||
|
||||
target_idx = _RPR_CHILD_ORDER.index(tag) if tag in _RPR_CHILD_ORDER else len(_RPR_CHILD_ORDER)
|
||||
# 找第一個排在 target_idx 之後的子元素 → insert 在它前面
|
||||
for i, child in enumerate(rPr):
|
||||
local = etree.QName(child).localname
|
||||
if local in _RPR_CHILD_ORDER and _RPR_CHILD_ORDER.index(local) > target_idx:
|
||||
new_el = etree.Element(f'{{{_OOXML_DRAWING_NS}}}{tag}')
|
||||
rPr.insert(i, new_el)
|
||||
return new_el
|
||||
# 沒有後序元素 → append
|
||||
new_el = etree.SubElement(rPr, f'{{{_OOXML_DRAWING_NS}}}{tag}')
|
||||
return new_el
|
||||
|
||||
|
||||
def _set_run_fonts(run, latin_font: str = None, ea_font: str = None):
|
||||
"""為單一 run 同時設定 Latin 字型與 EastAsian (CJK) 字型。
|
||||
解法:直接寫入 a:rPr 下的 a:latin / a:ea 元素,避開 python-pptx
|
||||
只能設一個 font.name 的限制 — 這是「中英分軌」呈現的關鍵。
|
||||
|
||||
critic Medium-1:使用 _insert_rpr_child 維持 OOXML schema 規定的子元素順序,
|
||||
讓未來若改成讀模板 .pptx(rPr 內已有 hlinkClick/cs 等後序元素)時也能正確插入。
|
||||
"""
|
||||
if not latin_font and not ea_font:
|
||||
return
|
||||
try:
|
||||
from lxml import etree
|
||||
rPr = run._r.get_or_add_rPr()
|
||||
nsmap = {'a': 'http://schemas.openxmlformats.org/drawingml/2006/main'}
|
||||
if latin_font:
|
||||
for el in rPr.findall('a:latin', nsmap):
|
||||
rPr.remove(el)
|
||||
latin_el = etree.SubElement(rPr, '{%s}latin' % nsmap['a'])
|
||||
latin_el = _insert_rpr_child(rPr, 'latin')
|
||||
latin_el.set('typeface', latin_font)
|
||||
if ea_font:
|
||||
for el in rPr.findall('a:ea', nsmap):
|
||||
rPr.remove(el)
|
||||
ea_el = etree.SubElement(rPr, '{%s}ea' % nsmap['a'])
|
||||
ea_el = _insert_rpr_child(rPr, 'ea')
|
||||
ea_el.set('typeface', ea_font)
|
||||
except Exception:
|
||||
# 失敗時退回 font.name 單軌設定
|
||||
|
||||
Reference in New Issue
Block a user