Files
C.E.L_Slide_test2/src/html_validator.py
T
kmlee1andClaude Opus 4.6 0e4b8c091c Phase S: Claude HTML 직접 생성 + 독립 검증 시스템 도입
블록 선택 방식(Phase P/Q/R) 폐기 → Claude Sonnet이 영역별 HTML 직접 생성.
생성-검증 분리: content_verifier.py로 텍스트 보존/금지 콘텐츠/구조를 코드 검증.

주요 변경:
- src/html_generator.py: 4개 프롬프트 템플릿(BG/CORE/SIDEBAR/FOOTER) + 영역별 Claude 호출
- src/content_verifier.py: L1 텍스트 보존, L2 금지 콘텐츠, L3 구조 검증 + 재시도 루프
- src/html_validator.py: 보안 검증(script/iframe 제거)
- src/renderer.py: render_slide_from_html() 추가, area div overflow:hidden
- scripts/test_phase_s.py: generate_with_retry() 통합, step2b_verification 결과 저장
- 배경 라이트 디자인(#f8fafc), 개조식 어미 변환, 축약 금지 규칙

다음 과제: 폰트 위계(핵심14>본문12>배경10-12>첨부9-11) + 동적 컨테이너 계산

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 08:37:05 +09:00

80 lines
2.7 KiB
Python

"""Phase R'-6: AI 생성 HTML 정화 + 토큰 위반 검증.
AI가 생성한 HTML에서:
1. 위험 태그 제거 (<script>, <iframe> 등)
2. CSS에 하드코딩 값이 있는지 경고 (토큰 위반)
"""
from __future__ import annotations
import logging
import re
logger = logging.getLogger(__name__)
def validate_and_clean_html(generated: dict[str, str]) -> dict[str, str]:
"""AI 생성 HTML을 정화하고 검증한다.
Args:
generated: {"body_html": "...", "sidebar_html": "...", "footer_html": "..."}
Returns:
정화된 generated dict
"""
cleaned = {}
for key in ("body_html", "sidebar_html", "footer_html"):
html = generated.get(key, "")
html = _remove_dangerous_tags(html)
_check_token_violations(html, key)
cleaned[key] = html
# reasoning은 그대로 유지
cleaned["reasoning"] = generated.get("reasoning", "")
return cleaned
def _remove_dangerous_tags(html: str) -> str:
"""위험 태그 제거."""
# <script>...</script> 제거
html = re.sub(r"<script[^>]*>.*?</script>", "", html, flags=re.DOTALL | re.IGNORECASE)
# <iframe> 제거
html = re.sub(r"<iframe[^>]*>.*?</iframe>", "", html, flags=re.DOTALL | re.IGNORECASE)
# on* 이벤트 핸들러 제거
html = re.sub(r'\s+on\w+="[^"]*"', "", html, flags=re.IGNORECASE)
html = re.sub(r"\s+on\w+='[^']*'", "", html, flags=re.IGNORECASE)
return html
def _check_token_violations(html: str, area_name: str) -> None:
"""CSS에서 디자인 토큰을 사용하지 않고 하드코딩한 값을 경고한다.
정보용 경고만. 생성을 차단하지는 않음 (AI가 블록 CSS 패턴을 참고하므로 하드코딩이 일부 있을 수 있음).
"""
# <style> 블록 추출
style_blocks = re.findall(r"<style[^>]*>(.*?)</style>", html, re.DOTALL | re.IGNORECASE)
if not style_blocks:
return
all_css = "\n".join(style_blocks)
# 허용된 하드코딩 색상 (블록 CSS 패턴에서 자주 쓰이는 것)
allowed_colors = {
"#1e293b", "#0f172a", "#2563eb", "#93c5fd", "#60a5fa",
"#ffffff", "#f8fafc", "#e2e8f0", "#475569", "#64748b",
"#94a3b8", "#cbd5e1", "#1e40af", "#bae6fd", "#f0f9ff",
"#006aff", "#00aaff", "#dc2626", "#fbbf24", "#0c4a6e",
"#334155", "#0088cc", "#86efac", "#fde047", "#16a34a",
"#fecaca", "#fef2f2",
}
# 하드코딩 색상 검출 (#으로 시작하는 hex)
found_colors = set(re.findall(r"#[0-9a-fA-F]{3,8}", all_css))
unknown_colors = found_colors - allowed_colors
if unknown_colors:
logger.info(
f"[R' 검증] {area_name}: 디자인 토큰 외 색상 {len(unknown_colors)}개 "
f"(참고: {list(unknown_colors)[:5]})"
)