2025年6月23日 星期一

關於 Unicode等價性 (Unicode Normalization) - Java - 如何將被拆分的韓文字母正確合併 - Normalizer - NFD, NFC, NFKD, NFKC

因為工作上碰到在儲存使用者輸入的韓文時,得到的是拆分的子音母音而非完整的字,
例如:패스워드 變成 ᄑ ᅢ ᄉ ᅳ ᄋ ᅯ ᄃ ᅳ ,
所以在這裡紀錄一下關於 Java 是如何處理 Unicode等價性 (Unicode Normalization) 的。

先貼上 Wiki 的 Unicode等價性 (Unicode Normalization)  說明描述:

Unicode等價性(英語:Unicode equivalence)是為和許多現存的標準能夠相容,Unicode(統一碼)包含了許多特殊字元。在這些字元中,有些在功能上會和其它字元或字元序列等價。因此,Unicode將一些碼位序列定義成相等的。Unicode提供了兩種等價概念:標準等價和相容等價。前者是後者的一個子集。例如,字元n後接著組合字元~標準等價和相容等價於Unicode字元ñ。而合字ff則只有相容等價於兩個f字元。

Unicode正規化(英語:Unicode normalization)是文字正規化的一種形式,是指將彼此等價的序列轉成同一列序。此序列在Unicode標準中稱作正規形式。對於每種等價概念,Unicode又定義兩種形式,一種是完全合成的,一種是完全分解的。因此,最後會有四種形式,其縮寫分別為:NFC、NFD、NFKC、NFKD。對於Unicode的文字處理程式而言,正規化是很重要的。因為它影響了比較、搜尋和排序的意義。

簡單地說,就是 Unicode等價性定義了有些字元 (或一組字元) 在意義或視覺上可以等價於另一個字元 (或一組字元)。

而 Unicode 正規化 就是字元之前的轉換方式,又分為

  1. NFC (Normalization Form Canonical Composition) :以標準等價方式來分解,然後以標準等價重組之。若是singleton的話,重組結果有可能和分解前不同。
  2. NFD (Normalization Form Canonical Decomposition):以標準等價方式來分解。
  3. NFKC (Normalization Form Compatibility Composition):以相容等價方式來分解。
  4. NFKD (Normalization Form Compatibility Decomposition):以相容等價方式來分解,然後以標準等價重組之。

相容等價標準較寬鬆,函蓋範圍比標準等價大。

例如:

  1. 韓文的字可由母音子音組合而成:ᄑ + ᅢ =  패
  2. 中文有些部首可以跟某些字等價:對 部首的  "⽅" (\u2F45) 做 NFKC 會得到 一般的字 "方" (\u65B9)
  3. 日文的一些複合字:對 "㍿" 做 NFKC 會得到 "株式会社"
  4. 一些符號:對 ㊋ 做 NFKC 會得到 一般的字 "火"
  5. 上標和下標:對 A² 中的 "上標的2" (\u00B2) 做 NFKC 會得到 一般的數字 "2"

在 Java 中可以使用 
Normalizer.normalize("要處理的文字", Normalizer.Form.NFC (或 NFD, NFKC, NFKD));
來處理,下面直接用程式碼示範。

package test;

import java.io.IOException;
import java.text.Normalizer;

public class UnicodeNormalizationTest {

	public static void main(String[] args) throws IOException {
		String str1 = "패스워드";
		String str2 = "패스워드";
		
		System.out.println(str1 + ", " + str2);
		System.out.println(Normalizer.normalize(str1, Normalizer.Form.NFC).equals(str2)); // true
		System.out.println(Normalizer.normalize(str1, Normalizer.Form.NFKC).equals(str2)); // true
		
		/////////////////////
		str1 = "⽅"; // \u2f45
		str2 = "方"; // \u65b9
		
		System.out.println(str1 + ", " + str2);
		System.out.println(Normalizer.normalize(str1, Normalizer.Form.NFC).equals(str2)); // false
		System.out.println(Normalizer.normalize(str1, Normalizer.Form.NFKC).equals(str2)); // true
		
		/////////////////////
		str1 = "㍿"; // \u337f
		str2 = "株式会社";
		
		System.out.println(str1 + ", " + str2);
		System.out.println(Normalizer.normalize(str1, Normalizer.Form.NFC).equals(str2)); // false
		System.out.println(Normalizer.normalize(str1, Normalizer.Form.NFKC).equals(str2)); // true
		
		/////////////////////
		str1 = "²"; // \u00b2
		str2 = "2";
		
		System.out.println(str1 + ", " + str2);
		System.out.println(Normalizer.normalize(str1, Normalizer.Form.NFC).equals(str2)); // false
		System.out.println(Normalizer.normalize(str1, Normalizer.Form.NFKC).equals(str2)); // true
		/////////////////////
		str1 = "㊋"; // \u328b
		str2 = "火";
		
		System.out.println(str1 + ", " + str2);
		System.out.println(Normalizer.normalize(str1, Normalizer.Form.NFC).equals(str2)); // false
		System.out.println(Normalizer.normalize(str1, Normalizer.Form.NFKC).equals(str2)); // true
	}
}

參考資料:

  1. Unicode等價性 - 維基百科,自由的百科全書
  2. Unicode Normalization 文字標準化 | Sean's Note
  3. 从⽅不是方到Unicode正规化NFD, NFC, NFKD, NFKC | 小不的笔记 | 时间之外的往事

沒有留言 :

張貼留言