Skip to content
Back to skills

Korean Encoding

ASecurity

Use when 엑셀 CSV 한글 깨짐, BOM, CP949/EUC-KR 인코딩 문제, 초성 검색, 한글 정렬, 파일명 NFC/NFD 정규화, 자모 분해, 을/를·이/가 조사 선택을 다룰 때 — Korean text encoding and search, legacy CP949/EUC-KR files, Excel CSV BOM mojibake, NFC/NFD normalization for macOS-uploaded filenames, initial-consonant (초성) search, Korean collation, particle selection in generated messages.

  • 2 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added October 15, 2026
developmentnodegit

Security analysis

A100/100

Pro scans all 4 files and shows the line behind each finding

Scanned October 15, 2026

npx -y skills add SungJun1217/kimchi-claude --skill korean-encoding --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Korean Encoding?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Korean Encoding
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/sungjun1217-korean-encoding/badge)](https://www.skillsdirectory.com/skills/sungjun1217-korean-encoding)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: korean-encoding
description: Use when 엑셀 CSV 한글 깨짐, BOM, CP949/EUC-KR 인코딩 문제, 초성 검색, 한글 정렬, 파일명 NFC/NFD 정규화, 자모 분해, 을/를·이/가 조사 선택을 다룰 때 — Korean text encoding and search, legacy CP949/EUC-KR files, Excel CSV BOM mojibake, NFC/NFD normalization for macOS-uploaded filenames, initial-consonant (초성) search, Korean collation, particle selection in generated messages.
---
<!-- kimchi-ignore-file 나쁜 예를 그대로 인용한다 -->

# 한글 인코딩과 문자 처리

예제 파일을 열 수 있으면 Read 로 열고, 권한 때문에 막히면 아래 코드를 그대로 쓰십시오.

## 인코딩이 깨지는 세 자리

**엑셀에서 CSV 가 깨진다.** 엑셀은 UTF-8 파일에 BOM 이 없으면 시스템 기본 인코딩으로
읽습니다. 한국어 윈도에서는 CP949 라서 한글이 전부 깨집니다. 해결은 BOM 을 앞에 붙이는
것입니다. 파일 내용을 CP949 로 바꾸지 마십시오 — 엑셀은 열리지만 다른 도구가 깨집니다.

**BOM 을 `"\uFEFF"` 이스케이프로 쓰지 마십시오.** 도구 호출 인자는 JSON 이라 `\uFEFF`가
파싱 단계에서 바로 진짜 BOM 문자로 풀립니다. 그러면 에디터 화면에 아무것도 안 보이는
글자가 소스에 그대로 박혀서, 나중에 지우거나 옮기다가 없어져도 아무도 알아채지 못합니다.
코드 포인트로 만드십시오.

```js
const csv = String.fromCharCode(0xfeff) + rows.map((row) => row.join(",")).join("\r\n");
```

줄바꿈도 `\r\n` 이어야 엑셀이 제대로 나눕니다. `toExcelCsv()` 가 둘을 함께 처리합니다.

`toExcelCsv()` 도 **앞자리 0 은 지키지 못합니다.** CSV 에는 셀 서식이 없어서 엑셀이 열 때
숫자로만 보이는 값을 다시 숫자로 해석합니다. 앞자리 0 을 지켜야 하면 CSV 가 아니라 xlsx 를
만들고 해당 열을 문자열 서식으로 지정하십시오.

**그리고 수식 주입을 막아야 합니다.** `=`, `+`, `-`, `@` 로 시작하는 값을 엑셀과 구글
스프레드시트가 **수식으로 실행합니다.** 상호나 비고란에 실제로 들어오고, 받는 사람의
기기에서 실행되므로 보안 문제입니다. 앞에 홑따옴표를 붙이면 문자열로 다룹니다.
`escapeForSpreadsheet()` 를 쓰십시오. 아래는 `examples/tabular.mjs` 원본과 어긋나지
않는 전체 구현입니다.

```js
// inline:tabular.mjs
const FORMULA_STARTERS = /^[=+\-@\t\r]/;

export function escapeForSpreadsheet(value) {
  const text = String(value ?? "");
  return FORMULA_STARTERS.test(text) ? `'${text}` : text;
}

export function toExcelCsv(rows) {
  const BOM = String.fromCharCode(0xfeff);
  const body = rows
    .map((row) =>
      row
        .map((cell) => {
          const safe = escapeForSpreadsheet(cell);
          return /[",\r\n]/.test(safe) ? `"${safe.replace(/"/g, '""')}"` : safe;
        })
        .join(",")
    )
    .join("\r\n");

  return BOM + body;
}
```

**전각 문자와 보이지 않는 글자가 섞인다.** 관공서 자료와 웹에서 복사한 값에
`123`(전각 숫자), 전각 하이픈, U+00A0(줄바꿈 없는 공백), 폭 없는 글자가 들어옵니다.
눈으로는 구별할 수 없고 정규식 `\d` 에도 걸리지 않아서 **검증이 조용히 실패합니다.**

```js
import { cleanField, normalizeWidth } from "./examples/tabular.mjs";

cleanField(row["사업자등록번호"]); // 폭·공백·정규화를 한 번에
```

**옛 파일이 CP949 다.** 관공서 자료, 은행 거래 내역, 레거시 시스템 내보내기가 아직
CP949/EUC-KR 입니다. Node 는 CP949 를 기본으로 지원하지 않아 `iconv-lite` 같은 것이 필요합니다.
바이트를 UTF-8 로 가정해 읽으면 조용히 깨진 문자열이 됩니다.

**macOS 에서 올린 파일 이름이 다르다.** 이게 가장 찾기 어렵습니다. macOS 는 파일 이름을
NFD(자모 분리)로 저장하고 리눅스·윈도는 NFC(음절 결합)로 저장합니다. 눈으로는 똑같은
`한글.txt` 인데 문자열이 달라서 조회가 실패합니다.

규칙은 하나입니다. **경계를 넘는 모든 문자열은 NFC 로 정규화합니다.** 업로드를 받을 때,
데이터베이스에 넣을 때, 비교할 때.

```js
import { normalizeForStorage, differsOnlyByNormalization } from "./examples/hangul-jamo.mjs";

normalizeForStorage(uploadedName); // 저장 전에 반드시
differsOnlyByNormalization(a, b); // 버그를 진단할 때
```

## 앞자리 0 이 날아간다

한국 자료의 식별자는 대개 0 으로 시작합니다. 사업자번호, 우편번호, 전화번호, 계좌번호.
숫자로 읽으면 앞자리 0 이 사라집니다.

더 나쁜 경우가 있습니다. **받은 파일이 이미 엑셀을 거쳤으면 원본이 이미 망가져 있습니다.**
담당자가 열어 저장한 순간 0 이 없어지거나 지수 표기(`1.23457E+09`)로 바뀝니다.

**0 을 채워 복구하려 들지 마십시오.** 잘못 채우면 다른 사업자의 번호가 됩니다.
이상 자료로 표시해 담당자에게 되돌리는 것이 유일한 해결입니다.

```js
import { checkDigitCount } from "./examples/tabular.mjs";

const check = checkDigitCount(row["사업자등록번호"], 10);
if (!check.ok) reject(row, check.reason); // 채우지 않고 되돌린다
```

내보낼 때는 xlsx 셀 서식을 문자열로 지정하십시오. 아니면 받는 쪽에서 다시 날아갑니다.

## 초성 검색

한국 서비스의 검색창에서는 사실상 필수입니다. "김치"를 "ㄱㅊ"로 찾는 기능입니다.

한글 음절은 `(초성 × 21 × 28) + (중성 × 28) + 종성 + 0xAC00` 으로 만들어지므로 초성 색인은
`(코드 - 0xAC00) / 588` 입니다. 직접 짜면 이 산술을 틀리기 쉽습니다.

동작하는 구현: `examples/chosung-search.mjs`

**놓치기 쉬운 것**: 질의가 초성만일 때에만 초성으로 찾아야 합니다. 사용자가 "김"을
입력했는데 초성으로 처리하면 "ㄱ"으로 시작하는 모든 것이 걸려 오히려 방해가 됩니다.
그리고 한글이 아닌 글자는 그대로 통과시켜야 "iOS앱"을 "iOSㅇ"로 찾을 수 있습니다.

**입력하는 도중의 질의도 받아야 합니다.** 한글 입력기는 "김치"를 치는 동안
`기 → 김 → 김ㅊ → 김치`를 차례로 보냅니다. 글자를 칠 때마다 결과를 보여 주는 검색창에서
완성된 질의만 받으면 결과가 사라졌다가 마지막 글자에서 다시 나타납니다. 그래서

- 음절과 초성이 섞인 질의(`김ㅊ`)는 자리마다 따로 비교합니다
- 마지막 음절은 앞부분만 맞아도 통과시킵니다. `기`는 `김`의, `갑`은 `가방`의, `달`은 `닭`의 앞부분입니다

그리고 대상과 질의를 모두 NFC로 정규화하십시오. 맥에서 올린 이름은 NFD라서 초성이 하나도 뽑히지 않습니다.

## 한글 정렬

**현대 한글 음절끼리는 코드포인트 순서가 가나다순과 같습니다.** 유니코드가 11,172자를 사전순으로
배열했고, 겹받침도 맞습니다(`달 < 달걀 < 닭`). 한글만 있는 목록에 정렬 규칙을 직접 짜지 마십시오.

어긋나는 곳은 섞였을 때입니다.

| 섞인 것 | 코드포인트 정렬 | 한국어 화면에서 기대하는 것 |
|---|---|---|
| 영문 | `Apple` 이 한글보다 앞 | 한글이 앞 |
| 숫자 | `파일10` 이 `파일2` 보다 앞 | `파일2` 가 앞 |
| NFD 이름 | 맥에서 올린 이름이 맨 앞으로 몰림 | 제자리 |

동작하는 구현: `examples/hangul-jamo.mjs` 의 `sortKorean` — NFC로 모은 뒤 `Intl.Collator("ko", { numeric: true })`
하나를 재사용합니다. 데이터베이스에서 정렬한다면 한국어 콜레이션을 지정하십시오.

```js
names.sort((a, b) => a.localeCompare(b, "ko"));
```

직접 자모를 분해해 비교하지 마십시오. 표준 콜레이션이 이미 옳게 합니다.

## 생성 문구의 조사

`"${name}을 삭제했습니다"` 처럼 문자열을 이어붙이면 이름에 따라 틀립니다.
"파일을"은 맞고 "캐시을"은 틀립니다. 받침으로 골라야 합니다.

```js
import { particleFor } from "./examples/hangul-jamo.mjs";

`${name}${particleFor(name, "을/를")} 삭제했습니다`;
```

`으로/로` 는 예외가 있습니다. ㄹ 받침 뒤에는 "로"를 씁니다. "서울로", "제주로".

**영어나 숫자로 끝나면 자동으로 알 수 없습니다.** 발음으로 판단해야 하는데 철자로는
알 수 없습니다. `particleFor` 가 `null` 을 돌려주면 조사를 쓰지 않는 문장으로 바꾸십시오.
"commit을 확인하세요" 대신 "다음 커밋을 확인하세요".

## 자주 틀리는 것

- CSV 에 BOM 없이 UTF-8 로 쓰기 — 엑셀에서 깨집니다
- 업로드 파일 이름을 정규화하지 않기 — macOS 에서 올린 것이 조회되지 않습니다
- 음절 질의를 초성 검색으로 처리하기 — 결과가 쏟아집니다
- 한글만 있는 목록에 정렬 규칙을 직접 짜기 — 코드포인트 순서가 이미 가나다순입니다. 영문·숫자·NFD가 섞일 때만 콜레이션이 필요합니다
- 완성된 질의만 검색하기 — 입력 도중인 `김ㅊ`에서 결과가 사라집니다
- 문자열 이어붙이기로 조사 만들기 — 절반은 틀립니다
- `slice` 로 한글 자르기 — 자모 분리(NFD)된 문자열에서는 `[...text]` 도 깨집니다. 음절 하나가
  여러 코드포인트로 흩어져 있어서 스프레드도 코드포인트 단위로 자르기 때문입니다. NFC 로
  정규화하고 나서 자르거나, 자소 단위가 그대로 필요하면 `Intl.Segmenter` 를 쓰십시오
- 전각 숫자를 정제하지 않기 — `\d` 에 걸리지 않아 검증이 조용히 실패합니다
- 식별자를 숫자로 읽기 — 앞자리 0 이 날아갑니다. 문자열로 다루십시오
- 날아간 0 을 채워 복구하기 — 다른 사업자의 번호가 됩니다. 되돌려야 합니다
- 스프레드시트로 내보낼 때 수식 주입을 막지 않기

Files in this skill

  • SKILL.md10 KB
  • examples/chosung-search.mjs5.6 KB
  • examples/hangul-jamo.mjs5.6 KB
  • examples/tabular.mjs6 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.