한글 자모 분해는 생각보다 쓸 데가 많습니다. 엑셀에서 초성 검색("ㅎㄱㄷ" 로 "홍길동" 찾기)을 만들 때, 한글로 잘못 친 비밀번호를 영타로 바꿔 보여줄 때, 이름을 자모 단위로 비교해서 오타를 잡을 때 등등. 여기 모은 함수들은 퍼온 코드를 바탕으로 용도별로 조금씩 손본 것들입니다.

계산 원리: 음절 코드 하나에 세 인덱스가 들어 있다

유니코드의 완성형 한글 11,172자는 가(U+AC00)부터 순서대로 배치되어 있고, 그 순서가 규칙적입니다. 초성 19개 × 중성 21개 × 종성 28개(받침 없음 포함)의 조합 순서 그대로라서 아래 식이 성립합니다.

광고
음절코드 = 0xAC00 + (초성 × 21 + 중성) × 28 + 종성

거꾸로 풀면 오프셋 = 음절코드 - 0xAC00 을 구한 뒤 오프셋 \ 588 이 초성, (오프셋 Mod 588) \ 28 이 중성, 오프셋 Mod 28 이 종성 인덱스가 됩니다(588 = 21 × 28). 코드의 (n Mod 21 * 28) / 28 은 괄호가 이상해 보이지만, VBA에서 * 가 Mod 보다 먼저 계산되므로 n Mod 588 과 같습니다.

VBA에서 한 가지 더 알아야 할 점이 있습니다. AscW 는 Integer(부호 있는 16비트)를 돌려주기 때문에 &H7FFF 를 넘는 한글은 음수로 나옵니다. 가 는 44032가 아니라 -21504 가 되죠. 그래서 아래 코드들은 + 21504 를 하거나, &HAC00(VBA에서는 이 리터럴 자체가 Integer -21504)을 빼서 오프셋을 구합니다. 조립할 때 ChrW(-21504 + ...) 를 쓰는 것도 같은 이유입니다.

분해: 겹자모를 그대로 둘지, 풀어 쓸지

첫 번째 fnName 은 음절을 초성·중성·종성 문자로 바꿔 이어 붙입니다. ㅘ, ㄳ 같은 겹모음·겹받침은 한 글자로 남습니다.

Function fnName(inName As String) As String
    Dim 초성 As Variant, 중성 As Variant, 종성 As Variant
    Dim vr() As Variant
    Dim i As Integer, j As Integer, n As Long, k1 As Integer, k2 As Integer, k3 As Integer
    초성 = Array("ㄱ", "ㄲ", "ㄴ", "ㄷ", "ㄸ", "ㄹ", "ㅁ", "ㅂ", "ㅃ", "ㅅ", "ㅆ", "ㅇ", "ㅈ", "ㅉ", "ㅊ", "ㅋ", "ㅌ", "ㅍ", "ㅎ")
    중성 = Array("ㅏ", "ㅐ", "ㅑ", "ㅒ", "ㅓ", "ㅔ", "ㅕ", "ㅖ", "ㅗ", "ㅘ", "ㅙ", "ㅚ", "ㅛ", "ㅜ", "ㅝ", "ㅞ", "ㅟ", "ㅠ", "ㅡ", "ㅢ", "ㅣ")
    종성 = Array("", "ㄱ", "ㄲ", "ㄳ", "ㄴ", "ㄵ", "ㄶ", "ㄷ", "ㄹ", "ㄺ", "ㄻ", "ㄼ", "ㄽ", "ㄾ", "ㄿ", "ㅀ", "ㅁ", "ㅂ", "ㅄ", "ㅅ", "ㅆ", "ㅇ", "ㅈ", "ㅊ", "ㅋ", "ㅌ", "ㅍ", "ㅎ")
    For i = 1 To Len(inName)
        If Mid(inName, i, 1) Like "[가-힇]" Then
            n = AscW(Mid(inName, i, 1)) + 21504
            k1 = Int(n / (21 * 28))
            k2 = Int((n Mod 21 * 28) / 28)
            k3 = n Mod 28
            j = j + 1
            ReDim Preserve vr(1 To j)
            vr(j) = 초성(k1) & 중성(k2) & 종성(k3)
        End If
    Next i
    '분리된 자,모음에 들어간 띄어쓰기 없애며 Join
    fnName = Replace(Join(vr), " ", "")
End Function

Join 은 구분자를 생략하면 공백으로 이어 붙이므로 마지막에 Replace 로 공백을 지웁니다. 이 함수는 한글이 아닌 문자는 결과에서 아예 빠진다는 점, 그리고 Like "[가-힇]" 의 끝 글자가 힣 이 아니라 힇 이라서 그 뒤의 히 ~ 힣 28글자가 범위에서 빠진다는 점을 주의하세요("희" 는 들어가지만 "힘", "힙" 은 빠집니다). 다음 Jamo 함수는 [가-힣] 으로 제대로 되어 있습니다.

키보드로 실제 타이핑하는 순서를 흉내 내려면 겹자모를 풀어 써야 합니다. ㅘ 는 ㅗ+ㅏ, ㄳ 은 ㄱ+ㅅ 으로 치니까요. 아래는 배열만 그렇게 바꾼 버전입니다. 위 함수와 이름이 같으니 둘 중 하나만 모듈에 넣어야 합니다(같이 두면 "이름이 중복되었습니다" 컴파일 오류).

'// 실제 타이핑에 사용 가능하도록 수정
Function fnName(inName As String) As String
    Dim 초성 As Variant, 중성 As Variant, 종성 As Variant
    Dim vr() As Variant
    Dim i As Integer, j As Integer, n As Long, k1 As Integer, k2 As Integer, k3 As Integer
    초성 = Array("ㄱ", "ㄲ", "ㄴ", "ㄷ", "ㄸ", "ㄹ", "ㅁ", "ㅂ", "ㅃ", "ㅅ", "ㅆ", "ㅇ", "ㅈ", "ㅉ", "ㅊ", "ㅋ", "ㅌ", "ㅍ", "ㅎ")
    중성 = Array("ㅏ", "ㅐ", "ㅑ", "ㅒ", "ㅓ", "ㅔ", "ㅕ", "ㅖ", "ㅗ", "ㅗㅏ", "ㅗㅐ", "ㅗㅣ", "ㅛ", "ㅜ", "ㅜㅓ", "ㅜㅔ", "ㅜㅣ", "ㅠ", "ㅡ", "ㅡㅣ", "ㅣ")
    종성 = Array("", "ㄱ", "ㄲ", "ㄱㅅ", "ㄴ", "ㄴㅈ", "ㄴㅎ", "ㄷ", "ㄹ", "ㄹㄱ", "ㄹㅁ", "ㄹㅂ", "ㄹㅅ", "ㄹㅌ", "ㄹㅍ", "ㄹㅎ", "ㅁ", "ㅂ", "ㅂㅅ", "ㅅ", "ㅆ", "ㅇ", "ㅈ", "ㅊ", "ㅋ", "ㅌ", "ㅍ", "ㅎ")
    For i = 1 To Len(inName)
        If Mid(inName, i, 1) Like "[가-힇]" Then
            n = AscW(Mid(inName, i, 1)) + 21504
            k1 = Int(n / (21 * 28))
            k2 = Int((n Mod 21 * 28) / 28)
            k3 = n Mod 28
            j = j + 1
            ReDim Preserve vr(1 To j)
            vr(j) = 초성(k1) & 중성(k2) & 종성(k3)
        End If
    Next i
    '분리된 자,모음에 들어간 띄어쓰기 없애며 Join
    fnName = Replace(Join(vr), " ", "")
End Function

실제로 제가 주로 쓰는 건 아래 Jamo 입니다. 한글이 아닌 문자(영문, 숫자, 공백)는 그대로 통과시키기 때문에 "VB.NET 2개" 같은 섞인 문자열을 넣어도 원래 모양이 유지됩니다.

'// 사용하는 소스
Function Jamo(T As String)
    Dim ChoSung As Variant, JOONGSUNG As Variant, JongSung As Variant
    Dim i As Integer, K As Long
    Dim T1 As String, T2 As String, T3 As String
    ChoSung = Array("ㄱ", "ㄲ", "ㄴ", "ㄷ", "ㄸ", "ㄹ", "ㅁ", "ㅂ", "ㅃ", "ㅅ", "ㅆ", "ㅇ", "ㅈ", "ㅉ", "ㅊ", "ㅋ", "ㅌ", "ㅍ", "ㅎ")
    JOONGSUNG = Array("ㅏ", "ㅐ", "ㅑ", "ㅒ", "ㅓ", "ㅔ", "ㅕ", "ㅖ", "ㅗ", "ㅗㅏ", "ㅗㅐ", "ㅗㅣ", "ㅛ", "ㅜ", "ㅜㅓ", "ㅜㅔ", "ㅜㅣ", "ㅠ", "ㅡ", "ㅡㅣ", "ㅣ")
    JongSung = Array(Empty, "ㄱ", "ㄲ", "ㄱㅅ", "ㄴ", "ㄴㅈ", "ㄴㅎ", "ㄷ", "ㄹ", "ㄹㄱ", "ㄹㅁ", "ㄹㅂ", "ㄹㅅ", "ㄹㅌ", "ㄹㅍ", "ㄹㅎ", "ㅁ", "ㅂ", "ㅂㅅ", "ㅅ", "ㅆ", "ㅇ", "ㅈ", "ㅊ", "ㅋ", "ㅌ", "ㅍ", "ㅎ")
    For i = 1 To Len(T)
        If Mid(T, i, 1) Like "[가-힣]" Then
            K = AscW(Mid(T, i, 1))
            K = K - &HAC00
            T1 = Int(K / (21 * 28))
            T2 = Int((K Mod 21 * 28) / 28)
            T3 = K Mod 28
            Jamo = Jamo & ChoSung(T1) & JOONGSUNG(T2) & JongSung(T3)
        Else
            Jamo = Jamo + Mid(T, i, 1)
        End If
    Next
End Function

T1~T3 를 String 으로 선언해 두고 배열 인덱스로 쓰는데, VBA가 숫자 문자열을 알아서 정수로 바꿔 주기 때문에 동작은 합니다. 새로 짠다면 Integer 로 두는 게 깔끔합니다.

영타로 바로 바꾸기

한/영 전환을 깜빡하고 "ㅔㄱㅐㅎㄱ묘1472" 처럼 입력했을 때 영문으로 돌려주는 용도입니다. 배열 값만 두벌식 자판의 영문 키로 바꿨을 뿐 구조는 Jamo 와 같습니다. 반대로 "dkssud" 를 "안녕"으로 바꾸고 싶다면 이 표를 거꾸로 써서 조립 함수와 엮으면 됩니다.

'// 영타로 바로출력
Function SplitEnglish(T As String)
    Dim ChoSung As Variant, JoongSung As Variant, JongSung As Variant
    Dim vR()
    Dim i As Integer, j As Integer, n As Long, k1 As Integer, k2 As Integer, k3 As Integer
    ChoSung = Array("r", "R", "s", "e", "E", "f", "a", "q", "Q", "t", "T", "o", "w", "W", "c", "z", "x", "v", "g")
    JoongSung = Array("k", "o", "i", "il", "j", "p", "u", "ul", "h", "hk", "ho", "hl", "y", "n", "nj", "np", "nl", "b", "m", "ml", "l")
    JongSung = Array("", "r", "R", "rt", "s", "sw", "sg", "e", "f", "fr", "fa", "fq", "ft", "fx", "fv", "fg", "a", "q", "qt", "t", "T", "d", "w", "c", "z", "x", "v", "g")
    For i = 1 To Len(T)
        If Mid(T, i, 1) Like "[가-힣]" Then
            K = AscW(Mid(T, i, 1))
            K = K - &HAC00
            T1 = Int(K / (21 * 28))
            T2 = Int((K Mod 21 * 28) / 28)
            T3 = K Mod 28
            SplitEnglish = SplitEnglish & ChoSung(T1) & JoongSung(T2) & JongSung(T3)
        Else
            SplitEnglish = SplitEnglish + Mid(T, i, 1)
        End If
    Next
End Function

주의: 이 함수는 K, T1, T2, T3 를 선언하지 않고 씁니다(대신 쓰지 않는 k1~k3, vR 이 선언되어 있음). 모듈 맨 위에 Option Explicit 가 있으면 "변수가 정의되지 않았습니다" 오류가 나니, 그럴 땐 Dim K As Long, T1 As Integer, T2 As Integer, T3 As Integer 를 추가하세요.

다시 합치기 두 가지

PlusKorean 은 "ㅎㅏㄴㄱㅡㄹ" 처럼 자모가 나열된 문자열을 받아 음절로 조립합니다. 자음 뒤에 모음이 오면 초성, 자음 뒤에 자음이 오거나 문자열 끝이면 받침으로 판단하는 단순한 규칙입니다.

'// 초성, 중성, 종성 Join
Function PlusKorean(inName As String) As String
    Dim 초성, 중성, 종성
    Dim vr() As String
    Dim worD As String
    Dim i%, j%
    Dim k1%, k2%, k3%
    Dim wkF As WorksheetFunction
    Set wkF = WorksheetFunction
    초성 = Array("ㄱ", "ㄲ", "ㄴ", "ㄷ", "ㄸ", "ㄹ", "ㅁ", "ㅂ", "ㅃ", "ㅅ", "ㅆ", "ㅇ", "ㅈ", "ㅉ", "ㅊ", "ㅋ", "ㅌ", "ㅍ", "ㅎ")
    중성 = Array("ㅏ", "ㅐ", "ㅑ", "ㅒ", "ㅓ", "ㅔ", "ㅕ", "ㅖ", "ㅗ", "ㅘ", "ㅙ", "ㅚ", "ㅛ", "ㅜ", "ㅝ", "ㅞ", "ㅟ", "ㅠ", "ㅡ", "ㅢ", "ㅣ")
    종성 = Array("", "ㄱ", "ㄲ", "ㄳ", "ㄴ", "ㄵ", "ㄶ", "ㄷ", "ㄹ", "ㄺ", "ㄻ", "ㄼ", "ㄽ", "ㄾ", "ㄿ", "ㅀ", "ㅁ", "ㅂ", "ㅄ", "ㅅ", "ㅆ", "ㅇ", "ㅈ", "ㅊ", "ㅋ", "ㅌ", "ㅍ", "ㅎ")
    For i = 1 To Len(inName)
        worD = Mid(inName, i, 1)
        '초성, 종성 구분(뒤에 오는 자,모의 종류에 따라서 구분)
        If worD Like "[ㄱ-ㅎ]" Then
            '자음 다음에 모음이 오면 초성
            If Mid(inName, i + 1, 1) Like "[ㅏ-ㅣ]" Then
                k1 = wkF.Match(worD, 초성) - 1
            '자음 다음에 자음이 오거나 글자 길이와 같으면 종성
            ElseIf Mid(inName, i + 1, 1) Like "[ㄱ-ㅎ]" Or i = Len(inName) Then
                k3 = wkF.Match(worD, 종성) - 1
                '종성이 끝나면 배열에 값 삽입
                ReDim Preserve vr(j)
                vr(j) = ChrW(-21504 + (28 * 21 * k1) + (28 * k2) + k3)
                j = j + 1
            End If
        '모음은 중성
        ElseIf worD Like "[ㅏ-ㅣ]" Then
            k2 = wkF.Match(worD, 중성) - 1
            '모음의 두칸 뒤가 또 모음이거나 글자의 마지막이면 종성이 없는 것으로 보고 배열에 값 삽입
            If Mid(inName, i + 2, 1) Like "[ㅏ-ㅣ]" Or i = Len(inName) Then
                k3 = 0  '모음으로 끝나는 글자이므로 종성 초기화
                ReDim Preserve vr(j)
                vr(j) = ChrW(-21504 + (28 * 21 * k1) + (28 * k2) + k3)
                j = j + 1
            End If
        End If
    Next i
    PlusKorean = Replace(Join(vr), " ", "")
End Function

WorksheetFunction.Match 를 쓰므로 엑셀 VBA 전용이고(VB6 에서는 배열 루프로 바꿔야 함), 겹모음·겹받침은 ㅘ, ㄳ 처럼 한 글자로 들어와야 합니다. "ㅗㅏ" 처럼 풀어 쓴 입력은 처리하지 못하고, 찾지 못한 자모가 있으면 Match 에서 런타임 오류가 납니다. 이름 몇 글자 정도를 다시 붙이는 용도로는 충분하지만, 자유 입력을 받는 곳에 쓰려면 예외 처리를 보강해야 합니다.

HanJoin 은 초성·중성·종성을 따로 받아 한 글자를 만드는 함수라서 더 단순하고 안전합니다. 배열에 없는 자모가 들어오면 빈 문자열을 돌려줍니다.

'// 자음, 모음 합치기
Private Const HAN_FIRST As Double = 44032#

Public Function HanJoin(ByVal Cho As String, ByVal Jung As String, Optional ByVal Jong As String = "") As String
    Dim ChoSeong  As Integer
    Dim JungSeong As Integer
    Dim JongSeong As Integer
    Dim i As Integer
    Dim Ch As Variant, Ju As Variant, Jo As Variant
    Ch = Array("ㄱ", "ㄲ", "ㄴ", "ㄷ", "ㄸ", "ㄹ", "ㅁ", "ㅂ", "ㅃ", "ㅅ", "ㅆ", "ㅇ", "ㅈ", "ㅉ", "ㅊ", "ㅋ", "ㅌ", "ㅍ", "ㅎ")
    Ju = Array("ㅏ", "ㅐ", "ㅑ", "ㅐ", "ㅓ", "ㅔ", "ㅕ", "ㅖ", "ㅗ", "ㅘ", "ㅙ", "ㅚ", "ㅛ", "ㅜ", "ㅟ", "ㅞ", "ㅟ", "ㅠ", "ㅡ", "ㅢ", "ㅣ")
    Jo = Array("", "ㄱ", "ㄲ", "ㄳ", "ㄴ", "ㄵ", "ㄶ", "ㄷ", "ㄹ", "ㄺ", "ㄻ", "ㄼ", "ㄽ", "ㄾ", "ㄿ", "ㅀ", "ㅁ", "ㅂ", "ㅄ", "ㅅ", "ㅆ", "ㅇ", "ㅈ", "ㅊ", "ㅋ", "ㅌ", "ㅍ", "ㅎ")
    ChoSeong = -1
    For i = 0 To 18
        If Cho = Ch(i) Then ChoSeong = i: Exit For
    Next i
    If ChoSeong < 0 Then Exit Function
    JungSeong = -1
    For i = 0 To 20
        If Jung = Ju(i) Then JungSeong = i: Exit For
    Next i
    If JungSeong < 0 Then Exit Function
    JongSeong = -1
    For i = 0 To 27
        If Jong = Jo(i) Then JongSeong = i: Exit For
    Next i
    If JongSeong < 0 Then Exit Function
    HanJoin = ChrW((ChoSeong * 21 + JungSeong) * 28 + JongSeong + HAN_FIRST)
End Function

주의: 위 코드의 중성 배열 Ju 에는 오타가 두 군데 있습니다. 4번째(인덱스 3)가 ㅒ 여야 하는데 ㅐ 로, 15번째(인덱스 14)가 ㅝ 여야 하는데 ㅟ 로 들어가 있습니다. 그래서 HanJoin("ㄱ", "ㅒ") 는 빈 문자열이 나오고, HanJoin("ㄱ", "ㅟ") 는 "귀" 가 아니라 "궈" 가 나옵니다(배열을 앞에서부터 찾다가 인덱스 14에서 먼저 걸리기 때문). 바로잡은 배열은 이렇습니다.

    Ju = Array("ㅏ", "ㅐ", "ㅑ", "ㅒ", "ㅓ", "ㅔ", "ㅕ", "ㅖ", "ㅗ", "ㅘ", "ㅙ", "ㅚ", "ㅛ", "ㅜ", "ㅝ", "ㅞ", "ㅟ", "ㅠ", "ㅡ", "ㅢ", "ㅣ")

HAN_FIRST 를 Double 로 둔 건 44032 가 Integer 범위를 넘기 때문입니다. 결과를 ChrW 에 넘기면 VBA가 범위 안의 값으로 받아 줍니다. 이 함수는 VB.NET 으로 옮길 때도 거의 그대로 쓸 수 있는데, .NET 의 AscW/ChrW 는 Char 를 0~65535 로 다루므로 앞의 + 21504 같은 음수 보정은 빼고 - &HAC00 만 하면 됩니다.

참고: 한글을 자음 모음으로 나누기 + 합치기