pptx 화면설계서 버전 diff — 텍스트 추출과 도형 좌표로 실제 변경점 찾기
슬라이드 100장짜리 설계서가 v5.2 에서 v5.3 으로 올라왔다. 눈으로 대조하는 대신 슬라이드 단위로 텍스트를 뽑아 diff 를 뜨고, 도형 좌표로 항목 순서를 판정한다.
기획에서 화면설계서 pptx 를 새 버전으로 던져줬다. 100장이 넘는다. “뭐가 바뀌었는지” 를 코드에 반영해야 하는데 두 가지가 막혔다.
변경 요약만으로는 실제 수정 지점을 못 찾는다. “A 라는 용어를 B 로 전반 변경”이라고 하면, “전반”이 어디까지인지 알 수 없다. 비슷하게 생긴 다른 라벨은 그대로 둬야 하는 경우가 많다.
pptx 는 바이너리라 git diff 가 안 된다. 보통 .gitignore 에 들어가 있어 히스토리도 없다.
결국 텍스트로 뽑아 비교했다. 세 단계다.
1. 슬라이드 단위 텍스트 추출
python-pptx 를 쓴다. 두 가지를 지켜야 diff 가 쓸모 있게 나온다.
그룹 도형은 재귀로 들어간다. shape_type == 6 이 그룹이다. 안 들어가면 그룹 안의 텍스트가 통째로 빠진다.
줄바꿈을 치환해 한 도형을 한 줄로 만든다. 이걸 안 하면 도형 하나가 여러 줄로 쪼개져서, 한 글자만 바뀌어도 diff 가 엉뚱하게 벌어진다.
# extract.py
import sys
from pptx import Presentation
def walk(shapes, out):
for sh in shapes:
if sh.shape_type == 6: # GROUP
walk(sh.shapes, out); continue
if sh.has_text_frame:
t = sh.text_frame.text.strip()
if t: out.append(t)
if getattr(sh, "has_table", False) and sh.has_table:
for r in sh.table.rows:
out.append(" | ".join(c.text.strip() for c in r.cells))
prs = Presentation(sys.argv[1])
for i, slide in enumerate(prs.slides, 1):
out = []
walk(slide.shapes, out)
print(f"===== SLIDE {i} =====")
for line in out:
print(line.replace("\n", " ⏎ "))
python3 extract.py spec_v5.2.pptx > v52.txt
python3 extract.py spec_v5.3.pptx > v53.txt
diff v52.txt v53.txt > spec.diff
2. 변경된 슬라이드 번호를 뽑는다
raw diff 는 줄 번호만 알려준다. “몇 번 슬라이드냐”를 모른다. 슬라이드별로 비교해서 변경된 슬라이드 목록과 제목을 뽑는다.
import re
def load(p):
slides, cur = {}, None
for line in open(p, encoding='utf-8'):
m = re.match(r'^===== SLIDE (\d+) =====', line)
if m: cur = int(m.group(1)); slides[cur] = []
elif cur: slides[cur].append(line.rstrip('\n'))
return slides
a, b = load('v52.txt'), load('v53.txt')
# 전 슬라이드 공통 오타 수정 같은 건 노이즈로 걸러낸다
noise = lambda s: s.strip() in ('Summery. |', 'Summary. |')
for i in sorted(a):
aa = [l for l in a[i] if not noise(l)]
bb = [l for l in b[i] if not noise(l)]
if aa != bb:
title = next((l for l in b[i] if re.search(r'(SCR|USR)-\d', l)), b[i][0] if b[i] else '?')
print(f"SLIDE {i:>3} :: {title[:80]}")
노이즈 필터가 없으면 Summery → Summary 같은 전역 오타 수정 하나가 전 슬라이드를 변경으로 만든다. 실제로 이것 때문에 첫 diff 결과가 쓸모없었다. 필터를 먼저 걸고 시작한다.
여기서 기획이 알려준 슬라이드 번호가 실제와 맞는지도 확인된다. 대체로 한두 개는 어긋나 있다.
3. 도형 좌표로 항목 순서를 판정한다
여기가 가장 비자명한 부분이다.
pptx 의 도형 나열 순서는 z-order(추가된 순서)이지 화면상 위치가 아니다. 새 버전에서 추가된 도형은 항상 리스트 끝에 붙는다. 텍스트 추출만 보면 “새 항목이 맨 아래에 추가됐다”고 오해한다.
실제로는 2열 그리드 배치라서 시각적 순서가 전혀 다를 수 있다. shape.top / shape.left 를 인치로 변환해 확인한다.
from pptx import Presentation
from pptx.util import Emu
KEYS = ['항목 A', '항목 B', '항목 C', '항목 D']
def walk(shapes, out):
for sh in shapes:
if sh.shape_type == 6: walk(sh.shapes, out); continue
if sh.has_text_frame and sh.top is not None:
t = sh.text_frame.text.strip()
if any(k in t for k in KEYS):
out.append((round(Emu(sh.top).inches, 2), round(Emu(sh.left).inches, 2), t[:40]))
prs = Presentation('spec_v5.3.pptx')
out = []
walk(prs.slides[107 - 1].shapes, out) # 0-indexed
for r in sorted(out): print(r)
찍어보면 배치가 드러난다.
(1.89, 2.85, '항목 A') (1.88, 5.65, '항목 B') ← row 1
(2.45, 2.86, '항목 C') (2.45, 5.69, '항목 D') ← row 2
2열 그리드다. 이걸 세로 리스트 컴포넌트로 옮긴다면 순서는 A / C / D / B 가 아니라 시각적 읽기 순서를 따라야 한다.
같은 문서에 표(table) 도형이 있는 슬라이드가 있다면 그쪽이 순서 판정의 authoritative source 다. 표는 행 순서가 곧 시각 순서라 좌표를 볼 필요가 없다.
“전반 변경”을 전역 치환으로 처리하면 안 된다
기획 문구에 “전반”이 붙어 있어도 sed 로 일괄 치환하면 사고가 난다.
실제 사례에서, 용어 A 를 B 로 바꾸라는 지시였지만 비슷한 형태의 다른 라벨 넷은 그대로 유지해야 했다. 문자열만 보면 구분이 안 되고, 도메인 의미가 다른 것들이었다.
diff 로 실제 변경된 라인만 확인해야 오탐이 0 이 된다. 이게 이 작업 전체의 목적이기도 하다.
슬라이드에 버전 주석 도형이 박혀 있는 경우도 있다. v5.3 ⏎ [수정] ... 같은 것들이다. 있으면 그게 곧 변경 근거라 grep 대상 1순위다.
슬라이드 삭제는 diff 로 안 보인다
다음 버전에서 한 번 더 걸렸다. 이건 따로 알아둘 만하다.
개수가 같아도 삭제와 분할이 동시에 일어나면 diff 가 정렬을 잃는다.
실측한 경우가 이랬다. 슬라이드 하나가 두 장으로 분할되면서 뒤가 한 칸씩 밀렸고, 그만큼 끝쪽 슬라이드 하나가 소리 없이 사라졌다. 총 118장 그대로라 개수로는 탐지할 수 없다.
diff 출력만 보면 “슬라이드 번호가 하나씩 밀렸네”로 읽힌다. 삭제를 놓친다.
탐지법 — ID 를 집합으로 비교
슬라이드 번호와 무관한 화면 ID 집합을 뽑아 대조한다.
grep -oE '(SCR|USR)-[0-9]{2}-[A-Z]' v53.txt | sort -u > ids53.txt
grep -oE '(SCR|USR)-[0-9]{2}-[A-Z]' v54.txt | sort -u > ids54.txt
comm -23 ids53.txt ids54.txt # 사라진 화면 ID
comm -13 ids53.txt ids54.txt # 새로 생긴 화면 ID
순서가 밀려도 집합은 안 변한다. 그래서 삭제와 추가만 정확히 남는다.
사라진 ID 가 나오면 다른 슬라이드가 그 ID 를 여전히 참조하는지 확인한다.
grep -n 'SCR-22-D' v54.txt
참조가 남아 있으면 의도적 삭제가 아니라 편집 사고다. 기획에 확인을 요청해야 한다. 실제로 본문이 없어진 화면을 계속 참조하고 있는 경우가 나왔다.
정리
pptx 설계서 diff 는 세 층으로 본다.
- 텍스트 diff — 무엇이 바뀌었나
- 도형 좌표 — 시각적 순서가 어떻게 되나 (나열 순서와 다르다)
- ID 집합 비교 — 무엇이 사라졌나 (diff 가 못 잡는다)
셋 다 필요하다. 하나라도 빼면 다른 종류의 변경을 놓친다.
추출한 텍스트(v52.txt)는 버려도 되지만, 남겨두면 다음 버전 diff 의 기준선이 된다. pptx 자체가 gitignore 대상인 경우가 많으니 이 텍스트가 사실상 유일한 히스토리다.