플랫폼별 복붙 오류를 없애는 원고 서식 변환기 직접 만들기
한글 파일에서 5,000자를 복사해 연재 플랫폼 에디터에 붙여넣고 미리보기를 눌렀는데, 문단 사이가 전부 두 줄씩 벌어져 있었습니다. 대사 앞의 들여쓰기는 사라졌고, 큰따옴표는 어느새 모양이 다른 문자로 바뀌어 있었습니다. 결국 한 화 분량을 눈으로 훑으며 손으로 고쳤습니다.
이런 일이 한두 번이면 참고 넘어갑니다. 그런데 매주 두세 편씩 올리는 사람에게는 매번 15분씩 새는 일이 됩니다. 저는 웹소설을 써서 온라인 출판까지 해본 적이 있는데, 그때 가장 짜증났던 게 원고 자체보다 이 붙여넣기 뒤처리였습니다.
이 글은 그 뒤처리를 대신하는 작은 변환기를 만든 기록입니다. 무엇이 깨지는지 먼저 분류하고, 각각을 정규식 규칙으로 옮기고, 마지막에는 왜 이 도구를 서비스가 아니라 파일 하나짜리 HTML로 두는 편이 나은지까지 정리했습니다.
붙여넣기는 네 가지 방식으로 깨집니다
문제를 뭉뚱그려 "서식이 깨진다"고 부르면 고칠 수가 없습니다. 실제로 벌어지는 일은 성격이 다른 네 가지입니다.
- 빈 줄이 두 배로 늘어납니다: 원본에서 문단을 나누려고 넣은 줄바꿈 하나가, 에디터에서는 문단 태그로 바뀌면서 위아래 여백까지 더해집니다. 여기에 원고에 이미 빈 줄이 있었다면 화면상 세 줄 간격이 됩니다.
- 들여쓰기가 사라집니다: 원고 편집기에서 넣은 공백이나 탭이 에디터로 넘어오면서 없어지거나, 반대로 문단 앞에 공백 여러 칸이 그대로 남아 줄이 밀립니다. 같은 원고 안에서도 문단마다 결과가 다른 경우가 흔합니다.
- 문장부호가 다른 문자로 바뀝니다: 워드프로세서의 자동 교정이 곧은 따옴표를 둥근 따옴표로, 마침표 세 개를 말줄임표 한 글자로 바꿔둡니다. 화면에서는 비슷해 보이지만 코드값이 다르므로, 나중에 검색이나 일괄 수정을 할 때 걸리지 않습니다.
- 보이지 않는 서식 태그가 딸려 옵니다: 브라우저 클립보드는 글자만이 아니라 HTML 조각도 함께 나릅니다. 그래서 원본의 글꼴, 색, 줄 간격 지정이 그대로 따라 들어가고, 플랫폼 기본 스타일과 충돌해 어떤 문단만 유독 크게 보이는 현상이 생깁니다.
네 가지를 나눠 보면 처리 순서도 자연히 정해집니다. 서식 태그를 먼저 걷어내고, 줄바꿈 문자를 통일하고, 공백을 정리하고, 마지막에 문장부호를 바꿉니다. 순서를 뒤집으면 앞 단계가 만든 찌꺼기를 뒤 단계가 다시 만드는 일이 생깁니다.
정규식 규칙을 단계별로 쌓기
변환기의 본체는 텍스트 하나를 받아 텍스트 하나를 돌려주는 함수입니다. 규칙을 한 줄씩 얹는 형태로 만들면 나중에 특정 규칙만 끄고 켜기도 쉽습니다.
function normalize(text) {
return text
.replace(/\r\n?/g, "\n") // 줄바꿈 문자 통일
.replace(/\u00A0/g, " ") // 줄바꿈 없는 공백을 일반 공백으로
.replace(/[ \t]+$/gm, "") // 줄 끝 공백 제거
.replace(/^[ \t]+/gm, "") // 문단 앞 들여쓰기 제거
.replace(/\n{3,}/g, "\n\n") // 빈 줄은 최대 한 줄까지만
.trim();
}
여기서 중요한 것은 gm 플래그입니다. m이 있어야 ^와 $가 문서 전체가 아니라 각 줄의 시작과 끝을 가리킵니다. 이걸 빠뜨리면 첫 줄과 마지막 줄만 정리되고 나머지는 그대로 남습니다.
들여쓰기를 지울지 넣을지는 플랫폼마다 다릅니다. 대사 앞에 한 칸을 넣는 관습을 지키는 곳도 있고, 에디터가 알아서 여백을 주는 곳도 있습니다. 그래서 저는 일단 전부 지운 다음 필요하면 일괄로 다시 넣는 방식을 씁니다. 남아 있는 들여쓰기를 조건별로 판별하는 것보다 훨씬 단순합니다.
문장부호는 별도 함수로 뺐습니다. 작품 성격에 따라 둥근 따옴표를 그대로 두고 싶을 때가 있어서, 이 부분만 선택적으로 적용하기 위해서입니다.
const RULES = [
[/[‘’]/g, "'"], // 둥근 작은따옴표
[/[“”]/g, '"'], // 둥근 큰따옴표
[/…/g, "..."], // 말줄임표 한 글자를 마침표 세 개로
[/[–—]/g, "-"], // 엔대시, 엠대시
];
function normalizePunct(text) {
return RULES.reduce((acc, [pattern, to]) => acc.replace(pattern, to), text);
}
서식 태그 제거는 정규식으로 HTML을 파싱하려 들지 말고, 붙여넣기 이벤트에서 text/html 대신 text/plain만 읽어오는 쪽이 훨씬 안전합니다. 클립보드 데이터에서 평문만 꺼내면 스타일 정보는 애초에 들어오지 않습니다.
왜 파일 하나짜리 HTML인가
이 정도 도구는 서버가 필요 없습니다. 저는 처음에 습관대로 ASP.NET Core 프로젝트를 새로 팠는데, 반나절 만에 접고 HTML 한 장으로 다시 만들었습니다. 이유는 네 가지입니다.
- 원고가 밖으로 나가지 않습니다: 미발표 원고를 남의 서버에 올리는 일은 그 자체로 불편합니다. 브라우저 안에서만 처리하면 이 걱정이 사라지고, 설명할 필요도 없어집니다.
- 운영 비용이 0입니다: 서버도 배포도 없으니 유지비가 들지 않습니다. 개인 도구가 조용히 죽는 가장 흔한 이유가 "귀찮아서 서버를 껐다"인데, 그 경로 자체가 없어집니다.
- 어디서든 열립니다: 파일을 클라우드 드라이브에 두면 노트북에서도 태블릿에서도 더블클릭 한 번으로 씁니다. 설치나 로그인 절차가 없습니다.
- 고치기 쉽습니다: 플랫폼이 에디터를 바꿔 새로운 증상이 생기면, 파일을 열어 규칙 배열에 한 줄 추가하면 끝입니다. 빌드도 배포도 필요 없습니다.
화면 구성은 왼쪽에 입력 텍스트 영역, 오른쪽에 결과 영역, 위에 규칙별 체크박스 몇 개면 충분합니다. 여기에 결과를 클립보드로 복사하는 버튼 하나와, 변환 전후의 글자 수를 표시하는 자리를 두면 실사용에 부족함이 없습니다.
만들고 나서 알게 된 것
가장 유용했던 기능은 변환 자체가 아니라 글자 수 표시였습니다. 연재 플랫폼은 대개 공백 포함 기준으로 분량을 세는데, 변환 전후로 숫자가 어떻게 달라지는지 보이니 "빈 줄을 지우면 얼마나 줄어드는지"를 감으로 알게 됐습니다.
반대로 넣었다가 뺀 기능도 있습니다. 맞춤법 교정을 붙여볼까 했는데, 웹소설 문장은 일부러 규범을 벗어나는 경우가 많아서 오히려 방해가 됐습니다. 도구가 원고 내용에 개입하기 시작하면 신뢰가 떨어집니다. 이 변환기는 모양만 건드리고 내용은 건드리지 않는다는 선을 지켜야 계속 쓰게 됩니다.
또 하나, 변환 결과를 되돌릴 방법을 남겨두는 게 좋습니다. 저는 원본을 그대로 왼쪽에 두는 방식으로 해결했습니다. 원본이 화면에 남아 있으면 잘못 변환됐을 때 다시 복사하면 그만이라, 실행 취소 기능을 따로 만들 필요가 없었습니다.
마치며
붙여넣기가 깨지는 방식은 빈 줄, 들여쓰기, 문장부호, 서식 태그 네 가지로 나뉩니다. 처리 순서는 태그 제거, 줄바꿈 통일, 공백 정리, 문장부호 치환입니다. 이 규칙들을 함수 하나에 담고 파일 하나짜리 HTML로 두면, 서버 없이도 몇 년은 쓰는 도구가 됩니다.
지금 할 수 있는 한 가지는 이겁니다. 다음에 원고를 붙여넣다가 무언가 깨지면 고치기 전에 어떤 증상인지 한 줄로 적어두는 것입니다. 그 메모가 서너 줄 쌓이면 변환 규칙 목록이 되고, 도구를 만들 준비는 그것으로 거의 끝납니다.