본문 바로가기

IT 지식창고

마크다운 물결표가 취소선으로 바뀌어 문장 130자가 사라졌습니다

728x90
반응형

블로그 발행을 자동화해 두고 일주일쯤 지나 발행된 글을 다시 읽다가 문장 하나에서 멈췄습니다. 뜻이 통하지 않았습니다. 원고를 열어 보니 원고는 멀쩡했고, 발행된 글에서만 문장 한가운데가 사라져 있었습니다. 저장은 성공했고 로그에도 오류가 없었습니다.

한눈에 보기

  • 원인은 마크다운의 취소선 문법이었습니다
  • marked는 GFM 규칙대로 물결표 두 개뿐 아니라 물결표 하나로 감싼 것도 취소선으로 읽습니다
  • 한국어 글에서 물결표는 취소가 아니라 범위 기호입니다. 2~3년, 24~27일처럼 씁니다
  • 티스토리는 그렇게 만들어진 취소선 태그의 안쪽 내용을 통째로 버렸습니다
  • 발행 전 점검으로는 잡히지 않습니다. 원고에는 취소선 문법이 없기 때문입니다

문장이 어떻게 달라졌나요

원고에 쓴 문장과 발행된 글에 남은 문장입니다.

구분 문장
원고 입주 물량은 보통 2~3년 전 분양·착공된 단지가 … 다시 2~3년이 걸리는 구조라,
발행본 입주 물량은 보통 2,3년이 걸리는 구조라,

가운데가 사라지고 남은 두 조각이 그대로 이어붙었습니다. 그래서 문장은 문법적으로 멀쩡해 보이는데 뜻은 달라져 있습니다. 130자가 없어진 채 일주일 동안 공개돼 있었습니다.

다른 글에서는 다르게 나타났습니다. 날짜 범위를 적은 문장이 이렇게 됐습니다.

구분 문장
원고 9월 24일(목)~26일(토), … 실질적으로 24~27일 나흘
발행본 9월 24일(목)에서 26일(토)까지 가운데가 줄로 그어진 모양

이쪽은 눈에 보였습니다. 날짜에 줄이 그어져 있어서 "왜 선을 긋느냐"는 이야기가 나왔고 그때 처음 알았습니다.

왜 이런 일이 생기나요

마크다운의 GitHub Flavored Markdown 규칙에는 취소선이 있습니다. 문제는 물결표를 한 겹만 써도 취소선으로 읽는다는 점입니다.

물결표 두 개로 감싸면   → 취소선   (의도한 문법)
물결표 한 개로 감싸도   → 취소선   (이게 문제입니다)

한국어 글에서 물결표는 취소선 기호로 쓰이지 않습니다. 범위를 나타내는 기호입니다. 그런데 한 문장 안에 범위 표현이 두 번 나오면, 마크다운은 첫 번째 물결표와 두 번째 물결표 사이를 취소선으로 묶어 버립니다.

2~3년 전 분양·착공된 단지가 … 다시 2~3년이
  ↑                                ↑
  여는 물결표로 읽힘               닫는 물결표로 읽힘

범위 표현 두 개가 한 문장에 있으면 그 사이가 통째로 취소선이 됩니다. 우리 글은 기간과 수치를 자주 다루기 때문에 이 조건이 자주 맞았습니다.

여기까지는 보기 나쁜 정도입니다. 진짜 문제는 그다음이었습니다. 티스토리 편집기가 취소선 태그를 받아 저장하면서 태그 안쪽 내용을 남기지 않은 경우가 있었습니다. 그러면 화면에 줄이 그어지는 것이 아니라 글자가 아예 없어집니다.

왜 발행 전 점검에서 안 잡혔나요

원고를 기계로 점검하는 단계를 이미 두고 있었는데도 통과했습니다. 이유는 간단합니다.

원고에는 취소선 문법이 없습니다. 원고에 적힌 것은 2~3년이라는 평범한 범위 표현입니다. 원고만 검사하면 아무 문제가 없습니다.

취소선은 마크다운을 HTML로 바꾸는 순간에 생깁니다. 즉 변환을 지나야만 드러나는 종류의 사고입니다. 그래서 막을 수 있는 자리는 변환기 한 곳뿐이었습니다.

어떻게 고쳤나요

취소선 토크나이저를 껐습니다. 한 줄입니다.

marked.use({ tokenizer: { del: () => undefined } });

이 한 줄이면 물결표는 그냥 물결표로 남습니다. 망설일 이유도 없었습니다. 원고 작성 규칙에 취소선을 쓰지 말라고 이미 적어 둔 상태였기 때문입니다. 고쳐 나가는 과정은 원고에 남길 일이지 발행된 글에 남길 일이 아니라는 이유였습니다.

쓸 일이 없는 문법이 멀쩡한 날짜와 문장을 먹고 있었던 셈입니다.

이미 나간 글은 어떻게 찾았나요

고치는 것보다 이쪽이 더 중요했습니다. 이미 공개된 글 중에 피해를 입은 것이 몇 편인지 알아야 했습니다.

방법은 이렇습니다. 발행된 원고를 옛 설정 그대로 다시 변환해서, 결과에 취소선 태그가 나오는지 봅니다.

// 취소선을 끄기 전과 같은 조건으로 다시 그려 본다
  // 취소선 태그가 섞여 있으면 확인 대상이다
const html = await marked(원고본문);
if (html.includes('del>')) {
  console.log('피해 의심:', 파일이름);
}

이렇게 돌려 보니 네 편이 나왔습니다. 한 편씩 열어 확인했고, 태그만 남고 내용은 살아 있는 글은 물결표 자리로 되돌렸습니다. 내용까지 사라진 글은 되돌릴 방법이 없어서 원고에서 문장을 다시 넣어야 했습니다.

내 글도 걸리는지 확인하는 법

한 줄이면 확인할 수 있습니다. 평소 쓰시는 변환기에 문장을 그대로 넣고 결과를 보시면 됩니다.

import { marked } from 'marked';

const 문장 = '보통 2~3년 전에 시작해 다시 2~3년이 걸립니다';
console.log(await marked(문장));

결과에 취소선 태그가 섞여 나오면 그 문장은 그대로 발행하시면 안 됩니다. 범위 표현이 한 문장에 두 번 이상 나오는 문장부터 넣어 보시는 것이 빠릅니다.

당장 고치기 어려우시면 두 가지로 피해 갈 수 있습니다.

  • 물결표 대신 물결표가 아닌 기호를 씁니다. "2에서 3년", "2-3년"처럼 적으면 걸리지 않습니다
  • 한 문장에 범위 표현을 하나만 둡니다. 문장을 나누면 짝이 맞지 않아 취소선이 생기지 않습니다

다만 둘 다 사람이 매번 신경 써야 하는 방법입니다. 글을 계속 쓰실 거라면 변환기에서 막는 쪽이 확실합니다. 사람이 기억해야 하는 규칙은 언젠가 잊힙니다.

여기서 배운 것

이 저장소에서 되풀이해 물린 것은 늘 조용한 실패였습니다. 성공했다는 로그, 세션이 죽은 채 계속 도는 예약 작업, 그리고 이번 일까지 전부 같은 종류입니다.

공통점이 있습니다. 터지지 않습니다. 오류가 나면 로그를 보고 고치면 되는데, 조용한 실패는 사람이 우연히 발견할 때까지 그대로 갑니다. 이번 것은 일주일이 걸렸고, 그것도 글을 다시 읽다가 알았습니다.

그래서 기준을 이렇게 바꿨습니다. 막을 수 없으면, 적어도 다음 실행이 알아채게 만든다. 이번 경우에는 변환기에서 아예 막을 수 있어서 막았지만, 막을 수 없는 종류라면 검사하는 코드를 남겨 둡니다.

정리하면

  • 마크다운은 물결표 하나로 감싼 것도 취소선으로 읽습니다
  • 한국어 글의 범위 표현이 한 문장에 둘 이상 있으면 그 사이가 취소선이 됩니다
  • 티스토리는 그 태그의 안쪽 내용을 버리는 경우가 있어 글자가 아예 사라집니다
  • 원고만 검사해서는 잡히지 않습니다. 변환을 지나야 드러나기 때문입니다
  • marked를 쓴다면 취소선 토크나이저를 끄는 한 줄로 막을 수 있습니다

한국어로 마크다운을 쓰고 계신다면 한 번 확인해 보시길 권합니다. 기간이나 수치 범위를 다루는 글일수록 걸릴 확률이 높습니다.

반응형