성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>"Hanja Hangul Project v1.01 (파이썬)"의 C# 버전"</h1> <p> 아래와 같은 프로젝트가 있군요. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Hanja Hangul Project v1.01 (파이썬) ; <a target='tab' href='https://blog.daum.net/masoris20/187'>https://blog.daum.net/masoris20/187</a> ; <a target='tab' href='https://blog.daum.net/masoris20/186'>https://blog.daum.net/masoris20/186</a> </pre> <br /> 재미있어서 C#으로도 포팅해봤습니다.<br /> <br /> 파이썬 코드를 포팅하는데 결정적인 요소는 "unicodedata.name(i)"의 기능을 구현하는 것입니다. 사실 꼭 그것과 동일하게 구현할 필요는 없지만, 어쨌든 <a target='tab' href='https://www.sysnet.pe.kr/2/0/1294#2'>유니코드 문자에 대한 코드 포인트</a> 이름을 알려주는 것은 기존 BCL 라이브러리에는 없기 때문에 흥미롭습니다.<br /> <br /> 검색해 보면, 다행히 이에 대한 프로젝트가 이미 있어서 굳이 만들 필요는 없습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > UnicodeInformation ; <a target='tab' href='https://www.nuget.org/packages/UnicodeInformation/'>https://www.nuget.org/packages/UnicodeInformation/</a> GoldenCrystal/NetUnicodeInfo ; <a target='tab' href='https://github.com/GoldenCrystal/NetUnicodeInfo'>https://github.com/GoldenCrystal/NetUnicodeInfo</a> </pre> <br /> 따라서 이 패키지를 참조 추가해,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // Install-Package UnicodeInformation -Version 2.6.0 Install-Package UnicodeInformation </pre> <br /> 유니코드 문자에 대해 다음과 같은 결과를 얻을 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Console.WriteLine(UnicodeInfo.GetName('蠶')); // CJK IDEOGRAPH-8836 Console.WriteLine(UnicodeInfo.GetName('가')); // HANGUL SYLLABLE GA Console.WriteLine(UnicodeInfo.GetName('ㄱ')); // HANGUL LETTER KIYEOK </pre> <br /> 위의 메서드가 파이썬의 "unicodedata.name(i)" 함수와 동일한 결과를 반환하기 때문에 이제 남은 작업은 단순히 소스코드 변환만 하면 됩니다.<br /> <br /> <pre style='height: 400px; margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System.Diagnostics; using System.Text; using System.Unicode; namespace HanConv { public static class Hanja2Hangul { public static List<string> MakeSplittedHanjaList(this string str) { List<string> list = new List<string>(); if (string.IsNullOrEmpty(str)) { return list; } str = str.Normalize(NormalizationForm.FormC); bool previous = str[0].IsHanja(); List<char> word = new List<char>(); word.Add(str[0]); foreach (char ch in str[1..]) { bool current = ch.IsHanja(); if (current != previous) { list.Add(new string(word.ToArray())); word.Clear(); } word.Add(ch); previous = current; } if (word.Count > 0) { list.Add(new string(word.ToArray())); } return list; } public static string HanjaToHangulDueum(this string str) { str = str.Normalize(NormalizationForm.FormC); List<string> splitted = MakeSplittedHanjaList(str); StringBuilder sb = new StringBuilder(); foreach (string word in splitted) { if (word.IsHanja()) { sb.Append(MakeDueum(HanjaToHangulSimple(word))); } else { sb.Append(word); } } return sb.ToString(); } private static string MakeDueum(string str) { if (string.IsNullOrEmpty(str)) { return ""; } char[] chArray = str.ToArray(); chArray[0] = make_dueum_a_char(chArray[0]); if (chArray.Length == 1) { return $"{chArray[0]}"; } char previous = chArray[0]; for (int i = 1; i < chArray.Length; i++) { char ch = chArray[i]; if (ch == '렬' && is_vowel_or_nieun(previous)) { chArray[i] = '열'; } else if (ch == '률' && is_vowel_or_nieun(previous)) { chArray[i] = '율'; } previous = ch; } return new string(chArray); } private static bool is_vowel_or_nieun(char ch) { if (ch.IsHangul() == false) { return false; } int n = ((int)ch - 0xAC00) % 28; if (n == 0 || n == 4) { return true; } return false; } private static char make_dueum_a_char(char ch) { if (ch.IsHangul() == false) { return ch; } if (HanConv.HanDict.Dueum.ContainsKey(ch)) { return HanConv.HanDict.Dueum[ch]; } return ch; } private static string HanjaToHangulSimple(string str) { char[] chArray = new char[str.Length]; for (int i = 0; i < chArray.Length; i ++) { chArray[i] = HanConv.HanDict.Hanja[str[i]]; } return new string(chArray); } public static bool IsHangul(this char ch) { return UnicodeInfo.GetName(ch).IndexOf("HANGUL") != -1; } public static bool IsHangul(this string str) { if (string.IsNullOrEmpty(str)) { return false; } return str.Length == len_hangul(str); } private static int len_hangul(string str) { int count = 0; foreach (char c in str) { if (c.IsHangul()) { count++; } } return count; } public static bool IsHanja(this char ch) { return UnicodeInfo.GetName(ch) switch { String txt when txt.Length >= 3 && txt[0..3] == "CJK" => true, String txt when txt.Length >= 6 && txt[0..6] == "KANGXI" => true, _ => false, }; } public static bool IsHanja(this string str) { if (string.IsNullOrEmpty(str)) { return false; } return str.Length == len_hanja(str); } private static int len_hanja(string str) { int count = 0; foreach (char c in str) { if (c.IsHanja()) { count++; } } return count; } } } </pre> <br /> 그런데, 실제로 위의 코드를 수행해 보면 처음 한 번의 GetName 호출에서 약간의 멈춤 현상이 발생합니다. 최초 한 번의 호출에서만 참으면 되는 건데, 이게 좀... 은근히 신경 쓰입니다. ^^; 이것의 원인은 UnicodeInformation 프로젝트가 내부에서 유니코드 정보를 가지고 있는 파일인 "ucd.dat"를 압축해서 보관했다가, 최초 GetName 호출에서 압축을 해제하느라 시간이 걸리기 때문입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public static UnicodeData ReadFromResources() { <span style='color: blue; font-weight: bold'>using (var stream = new DeflateStream(typeof(UnicodeData).GetTypeInfo().Assembly.GetManifestResourceStream("ucd.dat"), CompressionMode.Decompress, false))</span> { return ReadFromStream(stream); } } </pre> <br /> Ucd.dat 파일은 압축 해제 시 3MB가 넘는 파일인데, 사실 근래의 스토리지/네트워크 환경에서 그다지 신경 쓰이는 크기는 아닙니다. 따라서, 그냥 저 파일을 압축하지 않고 저장하고 읽으면 되는데요, 재미있는 것은, UnicodeInformation 프로젝트가 저 파일을 수작업으로 생성하지 않고 빌드 프로세스에서 자동으로 생성/압축한다는 점입니다.<br /> <br /> 그래서, 단순히 저 파일을 압축만 해제해서 저장한다고 해도 다음번 빌드에서 다시 압축이 되기 때문에 그런 식으로 수정하면 안 되고, 근본적으로 System.Unicode.Build.Core 프로젝트의 UnicodeDatabaseGenerator.cs 파일의 코드를 (압축하지 않도록) 수정해야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public static async ValueTask GenerateDatabase(HttpClient httpClient, string baseDirectory, string outputFilePath, bool? shouldDownloadFiles, bool? shouldSaveFiles, bool? shouldExtractFiles) { UnicodeInfoBuilder data; baseDirectory = string.IsNullOrWhiteSpace(baseDirectory) ? Environment.CurrentDirectory : Path.GetFullPath(baseDirectory); using (var ucdSource = await GetDataSourceAsync(httpClient, UnicodeCharacterDataUri, baseDirectory, UcdDataSourceName, UcdRequiredFiles, true, shouldDownloadFiles, shouldSaveFiles, shouldExtractFiles)) using (var unihanSource = await GetDataSourceAsync(httpClient, UnicodeCharacterDataUri, baseDirectory, UnihanDataSourceName, UnihanRequiredFiles, true, shouldDownloadFiles, shouldSaveFiles, shouldExtractFiles)) using (var ucdEmojiSource = await GetDataSourceAsync(httpClient, UcdEmojiDataUri, baseDirectory, EmojiDataSourceName, UcdEmojiRequiredFiles, false, shouldDownloadFiles, shouldSaveFiles, shouldExtractFiles)) //using (var emojiSource = await GetDataSourceAsync(httpClient, EmojiDataUri, baseDirectory, EmojiDataSourceName, EmojiRequiredFiles, false, shouldDownloadFiles, shouldSaveFiles, shouldExtractFiles)) { data = await UnicodeDataProcessor.BuildDataAsync(ucdSource, unihanSource, ucdEmojiSource); } // This part is actually highly susceptible to framework version. Different frameworks give a different results. // In order to consistently produce the same result, the framework executing this code must be fixed. <span style='color: blue; font-weight: bold'>using (var stream = File.Create(outputFilePath)) data.WriteToStream(stream);</span> } </pre> <br /> 이와 함께, System.Unicode.Build.Tasks 프로젝트의 GetUnicodeDatabaseVersion.cs 파일의 코드에서 압축 해제 코드도 없애고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > protected override async Task<bool> ExecuteAsync(CancellationToken cancellationToken) { var buffer = new byte[8]; <span style='color: blue; font-weight: bold'>using (var file = File.OpenRead(DatabasePath)) { await file.ReadAsync(buffer, 0, buffer.Length); }</span> if (TryReadHeader(buffer, out var version)) { UnicodeDatabaseVersion = version.ToString(3); return true; } Log.LogError("The database contained an invalid header."); return false; } </pre> <br /> 끝으로 System.Unicode 프로젝트의 UnicodeData.cs 파일에서 리소스에 대한 압축 해제 코드를 없애면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public static UnicodeData ReadFromResources() { <span style='color: blue; font-weight: bold'>using (var stream = typeof(UnicodeData).GetTypeInfo().Assembly.GetManifestResourceStream("ucd.dat")) { return ReadFromStream(stream); }</span> } </pre> <br /> 이후 아주 빠르게 GetName 메서드가 수행되는 것을 확인할 수 있습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 현재 위와 같이 변경한 UnicodeInformation 어셈블리를 참조하는 패키지(nuget)와 소스코드(github)를 올려 두었습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > HanConv ; <a target='tab' href='https://www.nuget.org/packages/HanConv/'>https://www.nuget.org/packages/HanConv/</a> ; <a target='tab' href='https://github.com/stjeong/HanjaHangul'>https://github.com/stjeong/HanjaHangul</a> // Install-Package HanConv -Version 1.0.1 Install-Package HanConv </pre> <br /> 따라서 이를 참조하고 실행하면 다음과 같은 결과를 얻을 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using HanConv; string text = "蠶段陽物是乎等用良水氣乙厭却桑葉叱分喫破爲遣飮水不冬"; Console.WriteLine(text.HanjaToHangulDueum()); // 잠단양물시호등용량수기을염각상엽질분끽파위견음수불동 </pre> <br /> 단지 아쉬운 것은 두음법칙에 대한 처리가 완벽하지 않습니다. 가령 다음과 같은 테스트는 통과하지만,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [<a target='tab' href='https://www.sysnet.pe.kr/2/0/12749'>DataRow</a>("羅列", "나열")] [DataRow("雲量", "운량")] [DataRow("羅州", "나주")] [DataRow("靈巖", "영암")] [DataRow("男女", "남녀")] [DataRow("讀者欄", "독자란")] [DataRow("隱匿", "은닉")] [DataRow("李成桂", "이성계")] public void HanjaToHangulDueumTest(string input, string expected) { Assert.AreEqual(input.HanjaToHangulDueum(), expected); } </pre> <br /> 아래와 같이 중간에 들어가는 두음법칙 단어는 처리하지 않고 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [TestMethod()] [DataRow("新女性", "신여성", "신녀성")] [DataRow("國際聯合", "국제연합", "국제련합")] [DataRow("空念佛", "공염불", "공념불")] [DataRow("會計年度", "회계연도", "회계년도")] [DataRow("許蘭雪軒", "허난설헌", "허란설헌")] [DataRow("失樂園", "실낙원", "실락원")] [DataRow("銃榴彈", "총유탄", "총류탄")] public void HanjaToHangulDueumTest_ToImprove(string input, string myExpected, string result) { Assert.AreEqual(input.HanjaToHangulDueum(), result); Assert.AreNotEqual(input.HanjaToHangulDueum(), myExpected); } </pre> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1805
(왼쪽의 숫자를 입력해야 합니다.)