안드로이드 개발자의 창고
NFC와 NFD, 자소분리 문제 본문
자소분리란?
macOS에서는 유니코드 인코딩 방식을 NFD(조합형)로 채택하고, Windows에서는 NFC(완성형)로 채택하여 서로 다른 인코딩 방식을 사용 중이다. macOS에서 NFD로 작성된 파일을 Windows에서 확인할 경우 한글이 깨지는 문제가 발생한다.
macOS에서 채택한 NFD(조합형)
- 한글의 초성, 중성, 종성을 분리된 코드로 저장한다.
- 예시)
- 안녕하세요.txt → ㅇㅏㄴㄴㅕㅇㅎㅏㅅㅔㅇㅛ.txt
- 위 (U+C704) → ᄋ (U+110B) + ᅱ (U+1171)
Windows에서 채택한 NFC(완성형)
- 한글의 초성, 중성, 종성이 합쳐져 있는 형태의 코드로 저장한다.
- 예시)
- 안녕하세요.txt → 안녕하세요.txt
- ᄋ (U+110B) + ᅱ (U+1171) → 위 (U+C704)
회사에서 일을 하면서 Windows를 사용하는 분과 문서를 주고받는 일이 생겼는데 나는 Mac을 쓰고 있고, 내가 작성한 문서를 확인하는 분들은 대부분 윈도우를 쓰고 계셨다.
자소분리 문제가 발생해서 내가 항상 했던 일은 내 Mac에서 작성한 파일을 임시 Windows PC로 확인 후 다른 분에게 보냈었다.
매번 이 과정을 반복하다보니 간단하게 자소분리를 해결하는 프로그램을 만들기로 했었다.
실제로는 방법이 없었지만 시도할 때만 해도 방법이 있는 줄 알았다.
스크립트 또는 각종 도구를 이용하여 NFD → NFC 시도
- github에 공개 되어있는 프로젝트 또는 GoLang 스크립트를 이용하여 NFD 형식의 파일을 NFC로 변환
- 변환 후 재검사 시 NFC로 변환되어 저장되어있음
- Notion 또는 Gmail 전송 후 Windows에서 확인하였으나 NFD로 저장되어 변환의 의미가 없음
- GoLang으로 작성한 스크립트
- 해당 코드는 정해진 디렉토리의 파일 이름을 읽어 NFD인지 NFC인지 확인하여 NFD일 경우 NFC로 변환하는 기능을 가지고 있음
- 해당 코드로 변환 후 Notion 또는 Gmail 전송해도 동일하게 NFD로 자동 변환됨
package main
import (
"fmt"
"golang.org/x/text/unicode/norm" // NFC와 NFD에 관련된 golang 확장 라이브러리
"os"
)
func main() {
// 테스트 문자열
// macString := "자소분리" // NFD: mac
// windowString := "자소분리" // NFC: window
entries, _ := os.ReadDir("/test")
for _, entry := range entries {
// 읽어온 파일 이름이 NFD 형식일 경우
if norm.NFD.IsNormalString(entry.Name()) {
fmt.Println(entry.Name() + " 은 NFD 형식임")
// NFD -> NFC 변환
newString := norm.NFC.String(entry.Name())
// 변환된 것을 확인 후 파일 이름 변경
if norm.NFC.IsNormalString(newString) {
err := os.Rename("/test"+entry.Name(), "/test/" + newString)
// 오류 발생 시
if err != nil {
fmt.Println("파일 이름 변경 실패:", err)
return
}
}
fmt.Println("변경 완료")
// 읽어온 파일 이름이 NFC일 경우
} else if norm.NFC.IsNormalString(entry.Name()) {
fmt.Println(entry.Name() + " 은 NFC 형식임")
}
fmt.Println("--------------------------------------------------------------------------------------------")
}
}
코드에도 문제가 없고 다른 사람이 만든 프로그램에도 문제가 없다는 것을 확인
처음에는 내 코드의 문제인가 싶어서 다른 사람이 깃허브에 공개한 프로그램을 사용했었는데 결과는 똑같았다...
구글링 해보니 Safari에서는 정상적으로 보내진다는 말이 있었고, 내가 만든 goLang 스크립트나 다른 사람의 프로그램을 이용하여 변환 후 Safari를 사용했더니 자소분리 문제가 일어나지 않았다.
그래서 두 가지의 가설을 만들어 테스트해봤다.
1. Gmail 서버의 문제?
- Notion에 NFC로 변환된 파일을 업로드
- Windows에서 다운로드 후 확인해봤으나 한글이 깨져있는 것은 동일함
- Notion을 자체 앱이 아니라 Safari 브라우저를 이용해 페이지를 띄우고 해당 페이지에서 전송 -> 자소분리 일어나지 않음
⇒ Gmail만의 문제가 아니라고 판단
2. Chrome 브라우저의 문제? 아니면 Chorme 서버의 문제?
- 문제를 확인하기 위해 html 파일 작성. 해당 html은 2번의 console.log() 출력
- <input> 태그를 통해 파일 선택 시
- <form> 태그를 통해 파일 업로드 시
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>파일 업로드 및 다운로드</title>
</head>
<body>
<h1>파일 업로드 및 다운로드</h1>
<!-- 파일 업로드 폼 -->
<form id="uploadForm" method="post" enctype="multipart/form-data">
<label for="fileUpload">파일 선택:</label>
<input type="file" id="fileUpload" name="fileUpload">
<button type="button" onclick="uploadFile()">업로드</button>
</form>
<hr>
<!-- 다운로드 링크 -->
<h2>다운로드 가능한 파일</h2>
<div id="downloadSection">
<!-- 업로드된 파일 목록 표시 -->
</div>
<script>
// 파일 업로드 처리 (브라우저 내에서)
function uploadFile() {
const fileInput = document.getElementById('fileUpload');
const files = fileInput.files;
if (files.length === 0) {
alert('업로드할 파일을 선택하세요.');
return;
}
const file = files[0];
const downloadSection = document.getElementById('downloadSection');
// URL.createObjectURL로 파일 객체를 Blob URL로 변환
const fileURL = URL.createObjectURL(file);
// 다운로드 링크 생성
const link = document.createElement('a');
link.href = fileURL;
link.download = file.name; // 원본 파일 이름 유지
link.textContent = `다운로드: ${file.name}`;
link.style.display = 'block';
// 다운로드 섹션에 추가
downloadSection.appendChild(link);
isNormalizedTo("파일 업로드", file.name);
alert('파일이 업로드되었습니다. 아래에서 다운로드할 수 있습니다.');
}
function isNormalizedTo(funName, str) {
// 주어진 문자열이 특정 정규화 형식인지 확인
if(str === str.normalize("NFC")) {
console.log(`${funName} / ${str} : NFC 형식`)
} else if(str === str.normalize("NFD")) {
console.log(`${funName} / ${str} : NFD 형식`)
}
}
// 파일 선택 이벤트 핸들러
document.getElementById('fileUpload').addEventListener('change', function (event) {
const files = event.target.files; // 업로드된 파일 리스트
const fileInfoDiv = document.getElementById('fileInfo');
for (const file of files) {
isNormalizedTo('파일 선택', file.name);
}
});
</script>
</body>
</html>
Chrome에서의 log 확인
Safari에서의 log 확인
동일한 파일을 각각 Chrome과 Safari에서 처리했을 때
Chrome
- 파일 형식에 상관없이 Chrome을 이용하는 순간 NFD로 변환됨
Safari
- NFC → NFC, NFD → NFD
- 파일의 형식을 건드리지 않음
⇒ Chrome의 문제가 맞다면 Chromium 기반인 Notion도 운영체제가 채택한 유니코드 인코딩 방식을 따라 자체적으로 변환하는 것
Chrome 55 버전부터는 인코딩 설정 메뉴가 제거되고 자동 인코딩 감지를 수행
How do I change the character encoding for a webpage in Chrome?
Google Chrome, like almost every web browser in recent memory, used to have an option to change the character encoding for the webpage being viewed by going to Menu › Tools › Encoding (or some simi...
superuser.com
해당 문제와 관련한 Chromium Issue Tracker
- 이 외에도 NFD와 관련한 이슈가 몇개 있지만 해결되어 종료된 이슈는 없음
Chromium
issues.chromium.org
결론
- Chrome에서는 어떤 형식의 문서를 전달하든 NFD로 변환하여 전달됨
- Safari에서는 형식을 변환하지 않고 그대로 전달함
- Firefox도 확인했으나 Chrome과 마찬가지로 NFD로 변환됨
⇒ NFC로 변환하여 Safari를 이용하여야 함...
NFC로 변환하는 workflow 작성법
- macOS에서 Automator.app 실행
- 문서 유형 > 빠른 동작 선택
3. 현재 수신하는 작업흐름 > 파일 또는 폴더 / 선택 항목 위치 > Finder.app 선택
4. 왼쪽에서 ‘셸 스크립트 실행’ 검색하여 오른쪽으로 끌어당기기
5. 셸 스크립트 > 통과 입력 > ‘인수’로 설정
6. 아래 스크립트 작성
for i in "$@"; do
/opt/homebrew/bin/convmv -f utf-8 -t utf-8 --nfc --notest "$i"
done
7. 아래부터는 선택사항
- 해당 스크립트가 제대로 실행되었을 때 알림이 뜨도록 하고 싶다면
- 왼쪽 검색 > 변수 값 설정 검색하여 오른쪽에 끌어다 놓기
- 변수 클릭하여 새로운 변수(filePath) 이름 등록
- 셸 스크립트 추가하여 아래 내용 추가
- 다시 변수 설정(fileName)
- 제목과 메시지 입력
- 메시지에는 fileName을 추가하여 자소분리 변환한 파일명 기재되도록 함
for i in "$@"; do
cleanedPath=${i%\"}
basename "$cleanedPath"
done
8. 저장 후 finder에서 아무 파일 선택하여 기타 > 사용자화 클릭
9. automator로 만든 빠른 동작 체크
10. Finder에서 표시되는지 확인
기타 사용 앱들 확인 결과
- Asana, Slack은 NFC와 NFD에 상관없이 깨지지 않음
- Notion, Gmail, Chrome을 이용할 경우 NFC로 변환해야 함