링크 감사 스크립트가 표 안에서만 거짓 양성을 내는 이유, 이스케이프된 파이프
위키링크 감사에서 멀쩡한 문서가 깨졌다고 나왔는데 전부 마크다운 표 안의 링크였습니다. 표 셀의 이스케이프된 파이프가 정규식의 문서명 캡처에 백슬래시를 끼워 넣은 것이 원인이었습니다.
문서 저장소의 위키링크를 전수 스캔해서 깨진 링크를 찾는 스크립트를 돌렸더니, 이런 항목이 올라왔습니다.
1x [[01. 화면 구성 분석\]]
1x [[02. 재생 동작 분석\]]
2x [[후보 목록\]]그런데 01. 화면 구성 분석.md 는 바로 옆 폴더에 멀쩡히 있었습니다. 앱에서 링크를 눌러 봐도 잘 열립니다. 스크립트만 깨졌다고 말하고 있었어요.
공통점은 하나였습니다. 전부 마크다운 표 안에 있는 링크였습니다.
표 안의 링크는 저장된 바이트가 다릅니다
위키링크의 별칭 문법은 파이프를 씁니다.
[[실제 문서명|화면에 보일 텍스트]]그런데 마크다운 표에서 파이프는 셀 구분자입니다. 그대로 쓰면 셀이 하나 더 생겨서 행이 깨지니, 표 안에서는 이스케이프해야 합니다.
| 항목 | 근거 |
|---|---|
| 화면 정리 | [[01. 화면 구성 분석\|가능, 회귀 테스트 선행]] |같은 링크가 표 밖에서는 |, 표 안에서는 \| 로 저장되는 겁니다. 렌더러는 셀을 파싱하면서 \| 를 | 로 되돌린 뒤에 위키링크를 해석하니, 사람 눈에는 아무 문제가 없습니다.
정규식은 왜 백슬래시를 먹을까요?
감사 스크립트의 정규식은 보통 이렇게 생겼습니다.
LINK = re.compile(r'\[\[([^\]\n|#]+?)(?:\|[^\]\n]*)?(?:#[^\]\n]*)?\]\]')문서명 캡처인 [^\]\n|#] 는 파이프를 만나면 멈춥니다. 그런데 표 안의 링크는 파이프 앞에 백슬래시가 먼저 옵니다. 백슬래시는 제외 대상이 아니라서 문서명의 일부로 캡처되고, 그 다음 | 에서 별칭 분기로 넘어갑니다.
그래서 스크립트가 찾는 이름은 01. 화면 구성 분석 이 아니라 01. 화면 구성 분석\ 이 됩니다. 그런 파일이 있을 리 없죠.
왜 하필 표에서만 틀릴까요?
거짓 양성이 표 안에서만 나오니까 눈치채기가 어렵습니다.
같은 문서를 가리키는 링크가 본문에도 있으면 그쪽은 정상으로 잡힙니다. 한 문서가 "있음"과 "없음"으로 동시에 집계되는 거죠. 리포트를 보고 파일을 열어 확인해도 재현이 안 됩니다.
게다가 표는 요약, 인덱스 문서에 몰려 있습니다. 링크 밀도가 가장 높은 문서에서 집중적으로 틀리는 셈입니다. 깨진 링크 수를 지표처럼 추적하고 있었다면, 표를 많이 쓴 날 이유 없이 나빠졌을 겁니다.
파싱 전에 이스케이프부터 벗깁니다
링크 문법을 파싱하기 전 단계에서 처리하는 게 안전합니다.
# 표 셀 이스케이프를 먼저 정규화한 뒤 링크를 파싱한다
text = text.replace('\\|', '|')
for m in LINK.finditer(text):
target = m.group(1).strip()이미 나온 결과를 사후에 보정해야 한다면, 후행 백슬래시를 떼고 다시 판정해서 오탐을 걸러냅니다.
if target.endswith('\\') and resolve(target[:-1]):
continue # 표 안 이스케이프에서 온 거짓 양성다른 곳에서도 생기는 문제입니다
컨테이너 문법이 자식 문법의 바이트를 바꾸는 곳이면 어디서나 같은 일이 생깁니다.
표 셀 안의 인라인 코드도 | 를 이스케이프해야 하고, CSV 필드 안의 따옴표는 "" 로 이중화됩니다. YAML 블록 스칼라 안에 넣은 정규식이나 셸 히어독 안의 변수 표기도 비슷합니다.
문서를 데이터로 스캔할 때는 렌더러가 보는 문자열과 파일에 저장된 바이트가 다르다고 전제해야겠습니다. 화면에서는 정상인데 스캐너만 실패한다면, 그 사이에 이스케이프 계층이 하나 더 있을 가능성이 큽니다.