LabHub
배우기 러닝패스 코스

顧客データを扱う

名前欄がすべて四角 — 判定ツールを作る

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

고객이 보낸 파일의 인코딩을 추측하지 않고 판정하는 도구 enc_probe.py 를 만든다. BOM 과 UTF-8 디코딩 가능성에서 시작해 후보 인코딩과 표본 대조로 내려가고, 이중 인코딩을 되살리고, 되살릴 수 없는 파일을 가려내고, 정규화와 보이지 않는 문자가 비교를 어떻게 망가뜨렸는지를 자기 데이터로 증명한다.

왜 중요한가

텍스트 파일 안에는 인코딩이 적혀 있지 않다. 그래서 "무슨 인코딩이냐" 는 물음에는 언제나 절차로만 답할 수 있다. 에디터 메뉴를 눌러 보는 방식은 파일이 셋일 때만 통하고, 무엇보다 맞았는지를 눈으로만 판단하게 만든다. 판정이 끝나면 파일은 세 부류로 갈린다. 제대로 읽히는 것, 한 번 잘못 읽혀 굳었지만 되돌릴 수 있는 것, 그리고 글자가 뭉개져 원래 바이트를 알 수 없는 것이다. 마지막 부류를 붙들고 시간을 쓰는 것은 노력이 아니라 낭비다 — 그 파일은 다시 받아야 한다. 이 구별을 코드로 할 수 있어야 고객에게 무엇을 요청할지 말할 수 있다. 그리고 글자가 멀쩡히 보이는데 안 맞는 자리가 있다. 같은 한글이 완성형과 자모 분해형으로 나뉘어 저장되면 화면은 같고 바이트는 다르다. 눈에 안 보이는 NBSP 와 제로폭 공백도 마찬가지다. 조인 결과가 0건인데 원인이 화면에 안 보이는 상황은 여기서 나온다. 채점기는 여러분의 문구를 믿지 않는다. 임시 디렉터리에 채점기가 만든 파일을 차려 놓고 여러분의 도구를 실제로 실행해 판정을 대조한다. 이름과 행 수는 실행마다 바뀐다.

단계

  1. /root/enc/gen_inbox.py 를 만들어 실행해 /root/enc/inbox 아래 일곱 파일을 만드세요. 같은 24명의 명부가 일곱 가지 상태로 저장됩니다.
  2. /root/enc/enc_probe.pydetect 를 만들어 BOM·UTF-8 디코딩 가능성·후보 인코딩·표본 대조 순서로 인코딩을 판정하게 하세요.
  3. decode 를 더해 판정한 인코딩으로 읽어 UTF-8 로 표준출력에 내게 하고, 수신함 전체를 /root/enc/norm 아래 같은 이름으로 통일해 저장하세요.
  4. repair 를 더해 이중 인코딩을 되살리게 하고, detect 가 그런 파일에 mojibake 를 true 로, verdict 를 mojibake 로 답하게 하세요.
  5. detectlossy 를 더해 U+FFFD 가 남은 파일의 verdict 를 lost 로 하고, 수신함 판정을 /root/enc/lost.json 에 적으세요.
  6. keys 를 더해 이름 칸을 NFC 로 정규화한 비교용 키로 내게 하고, NFD 판과 UTF-8 판의 이름이 정규화 전후로 몇 건 맞는지를 /root/enc/join_report.json 에 적으세요.
  7. scan 을 더해 보이지 않는 문자를 코드포인트별로 세게 하고, keys 가 그것들을 걷어 내게 한 뒤 /root/enc/invisible_report.json 에 적으세요.
  8. 수신함 일곱 파일을 한 번에 처리해 /root/enc/enc_report.json/root/enc/enc_report.md 를 만드세요.

참고

일곱 가지 상태의 같은 명부 만들기

/root/enc/gen_inbox.py 를 만들어 실행해 /root/enc/inbox 아래 일곱 파일을 만드세요. 내용은 같은 24명이고 저장된 바이트만 다릅니다.

먼저 /root/enc 을 만들고 그 안에서 python3 로 파일을 씁니다. 텍스트를 만든 뒤 encode 를 달리해 바이트로 저장하는 것이 전부입니다. 파일을 열 때 텍스트 모드가 아니라 바이너리 모드("wb")로 열어야 인코딩이 두 번 걸리지 않습니다.

추측하지 않고 판정하기

/root/enc/enc_probe.py 를 만들어 detect <파일> 이 BOM·UTF-8 디코딩 가능성·후보 인코딩·표본 대조 순서로 판정한 결과를 JSON 한 덩어리로 내게 하세요. 응답에는 path·bom·utf8_ok·encoding·verdict 가 있어야 합니다.

순서가 곧 판정입니다. 앞 세 바이트가 EF BB BF 인지 먼저 보고, 아니면 엄격 UTF-8 디코딩을 시도하고, 그것도 실패하면 CP949 로 읽어 봅니다. 마지막으로 읽어 낸 글자가 말이 되는지를 완성형 한글 비율로 확인합니다. 에러 없이 디코딩됐다는 것만으로는 모자랍니다.

판정한 대로 읽어 UTF-8 로 통일하기

decode <파일> 을 더해 판정한 인코딩으로 읽은 본문을 UTF-8 로 표준출력에 내게 하세요(BOM 은 떼고 냅니다). 그 결과로 /root/enc/norm 아래에 같은 이름으로 통일본을 저장하세요.

판정 결과를 그대로 재사용하면 됩니다. BOM 이 붙은 파일은 utf-8-sig 로 읽어야 첫 칼럼 이름에 보이지 않는 글자가 남지 않습니다. 원본은 그대로 두고 통일본은 다른 디렉터리에 만듭니다 — 판정이 틀렸을 때 돌아갈 곳이 있어야 합니다.

한 번 잘못 읽혀 굳은 글자 되살리기

repair <파일> 을 더해 이중 인코딩을 되살려 표준출력에 내게 하고, detect 응답에 mojibake 를 더해 그런 파일의 verdict 를 mojibake 로 하세요. 되살릴 수 없으면 종료 코드 3 입니다.

UTF-8 바이트를 latin-1 로 읽으면 한글 한 글자가 알파벳 세 글자가 됩니다. 바이트는 하나도 잃지 않았으니 같은 길로 되돌리면 됩니다. 되살렸다고 판정하는 근거는 되살린 쪽의 완성형 한글 비율이 더 높아졌다는 것입니다 — 이 검사를 빼면 멀쩡한 파일까지 건드리게 됩니다.

되살릴 수 있는 것과 이미 잃은 것 가르기

detect 응답에 lossy 를 더해 U+FFFD 가 남은 파일의 verdict 를 lost 로 하세요. 그리고 수신함을 훑어 /root/enc/lost.json 에 lost·recoverable 두 목록과 reason 을 적으세요.

잘못 읽는 쪽이 관대하면 읽을 수 없는 바이트가 대체 문자 하나로 뭉개집니다. 서로 다른 바이트 여럿이 같은 글자가 되므로 되돌릴 정보가 남지 않습니다. 이것은 노력의 문제가 아니라 판정입니다 — 그 파일은 다시 받아야 합니다. lost 와 recoverable 은 파일 이름만 담은 목록입니다.

같아 보이는데 안 맞는 이름

keys <파일> 을 더해 두 번째 칸(이름)을 NFC 로 정규화한 비교용 키 배열로 내게 하세요. 그리고 NFD 판과 UTF-8 판의 이름이 정규화 전에 몇 건, 정규화 뒤에 몇 건 맞는지를 /root/enc/join_report.json 에 rows·matched_raw·matched_nfc 로 적으세요.

한글 한 글자는 완성된 음절 하나로도, 자모 세 개로도 저장됩니다. 화면에는 같아 보이지만 다른 문자열입니다. 정규화는 비교를 위한 것이지 원본을 고치는 것이 아닙니다 — 원본 파일은 그대로 둡니다. matched_raw 는 정규화하지 않은 NFD 이름이 UTF-8 판 이름 집합에 그대로 들어 있는 건수입니다.

눈에 안 보이는 글자가 키에 섞였다

scan <파일> 을 더해 보이지 않는 문자를 코드포인트별로 세게 하고, keys 가 NBSP 를 보통 공백으로 바꾸고 제로폭 문자를 걷어 내게 하세요. 결과는 /root/enc/invisible_report.json 에 counts·rows_affected·keys_fixed 로 적습니다.

제로폭 문자는 strip() 으로 지워지지 않습니다. 지울 것을 코드포인트 목록으로 정해 두고 한 글자씩 훑어 걸러야 합니다. rows_affected 는 보이지 않는 문자가 하나라도 들어 있던 줄의 수이고, keys_fixed 는 정리 뒤에 UTF-8 판의 이름과 맞게 된 키의 수입니다.

수신함 한 장으로 보고하기

수신함 일곱 파일을 한 번에 처리해 /root/enc/enc_report.json 에 files·ok·mojibake·lost 를 적고, /root/enc/enc_report.md## 무엇을 받았나 ## 어떻게 판정했나 ## 되살린 것과 잃은 것 ## 보내는 쪽에 요청할 것 네 절로 쓰세요.

files 는 파일 이름을 키로 두고 encoding 과 verdict 를 담은 객체입니다. 보고서에는 되살린 파일 수와 잃은 파일 수를 숫자로 적으세요 — 고객이 다시 보내 줘야 할 것이 무엇인지가 이 문서의 목적입니다. 앞 단계에서 만든 판정 함수를 그대로 부르면 됩니다.