Linux Unicode 程式設計(轉)

ba發表於2007-08-12
Linux Unicode 程式設計(轉)[@more@]作為一個計算機的多位元組字元表示系統,Unicode 支援世界上所有語言的編碼和轉換。這篇文章說明了 Linux 應用程式中的國際語言支援的重要性,以及設計 Unicode 支援並將之結合到 Linux 應用程式中去的思想。
Unicode 並不只是一個程式設計工具,它還是一個政治的、經濟的工具。沒有結合世界的語言支援的應用程式通常只能被那些能讀寫 ASCII 所支援語言的個人使用。這使得建立在 ASCII 基礎之上的計算機技術脫離了世界上大部分人。Unicode 允許程式使用世界上任何一種字符集,因此它支援所有語言。

Unicode 讓程式設計師為普通人提供用他們本國語言就能使用的軟體。這樣就不用再學一門外語了,而且更容易實現計算機技術社會和財政上的利益。很容易設想,如果使用者必須為使用因特網瀏覽器而學習烏爾都語的話,您就難以看到計算機在美國的使用。Web 就更不會出現了。

Linux 承擔了對 Unicode 很大程度上的支援。Unicode 支援被嵌入到核心和程式碼開發庫中。在很大程度上,使用程式中幾句簡單的命令就能將它們自動的結合到程式碼中。

所有現代字符集的基礎都是在 1968 年以 ANSIX3.4 版本出版的美國資訊交換標準碼(American Standard Code for Information Interchange,ASCII)。一個值得注意的例外是在 ASCII 之前定義的 IBM 的擴充的二進位制編碼的十進位制交換碼(Extended Binary Coded Decimal Information Code,EBCDIC)。ASCII 是一個編碼字符集(coded character set,CCS),換句話說,它是整數到字元表示的對映。ASCII 編碼字符集允許用一個八位(基於二進位制的,用值 0 或 1 表示的)欄位或位元組(2^8 =256)表示 256 個字元。這是一個高度受限的編碼字符集,它不能表示許多不同語言的所有字元(如中文和日文),不能表示科學符號,更不能表示古代文字(神秘符號和象形文字)和音樂符號。透過更改一個位元組的長度而使更大的字符集得以被編碼,這似乎有效但完全不切實際。所有的計算機都基於八位位元組。解決方法是一種字元編碼方案(Character encoding scheme,CES)— 用定長或變長的多位元組序列能夠表示比 256 大的數.這些數值接著透過編碼字符集被對映到它們表示的字元。

Unicode 的定義
Unicode 通常用作涉及雙位元組字元編碼方案的通用術語。Unicode CCS 3.1 的官方稱謂是 ISO10646-1 通用多八位元組編碼字符集(Universal Multiple Octet Coded Character Set,UCS)。Unicode 3.1 版本新增了 44,946 個新的編碼字元。算上 Unicode 3.0 版本已經存在的 49,194 個字元,共計 94,140 個。

Unicode 編碼字符集利用了一個由 128 個三維的組構成的四維編碼空間。其中每個組包含 256 個二維平面。每個平面由 256 個一維的行組成,並且每個行有 256 個單元。每個單元在這個編碼空間內對一個字元編碼,或者被宣告為未經使用。這種編碼概念被稱為 UCS-4;四個八位元用來表示指定組、平面、行和單元的每個字元。

第一個平面(第 00 組的第 00 平面)是基本多語言平面(Basic Multilingual Plane,BMP)。BMP 按字母、音節、表意符號和各種符號及數字定義了常規使用的字元。後續的平面用於附加字元或其它還沒有發明的編碼實體。我們需要這完整的範圍去處理世界上的所有語言;特別是擁有將近 64,000 個字元的一些東亞語言。

BMP 被用作雙位元組的編碼字符集,這種編碼字符集確定為 ISO 10646 UCS-2 格式。ISO 10646 UCS-2 就是指 Unicode(並且兩者相同)。BMP,像所有 UCS 平面那樣,包含了 256 行,其中每行包含 256 個單元,字元僅僅按照 BMP 中的行和單元的八位元在單元中被編碼。這就允許 16 位編碼字元能夠被用來書寫大多數商業上最重要的語言。UCS-2 不需要內碼表切換、程式碼擴充套件或程式碼狀態。UCS-2 是一種將 Unicode 結合到軟體中的簡單方法,但它只限於支援 Unicode BMP。

若要用 8 位位元組表示一個多於 2^8 =256 個字元的字元編碼系統(character coding system,CCS),就需要一種字元編碼方案(character-encoding scheme,CES)。

Unicode 轉換
在 UNIX 中,使用得最多的字元編碼方案是 UTF-8。它考慮到了對整個 Unicode 全部頁和平面的全面支援,而且它仍能正確的識別 ASCII。除了 UTF-8 的其他選擇還有:UCS-4、UTF-16、UTF-7.5、UTF-7、SCSU、HTML 和 JAVA。

Unicode 轉換格式(Unicode Transformation Formats,UTFs)是一種透過對映多位元組編碼中的值來支援 Unicode 的字元編碼方案。本文將分析最流行的格式 — UTF-8 字元編碼系統。

UTF-8
UTF-8 轉換格式正逐步成為一種占主導地位的交換國際文字資訊的方法,因為它可以支援世界上所有的語言,而且它還與 ASCII 相容。UTF-8 使用變長編碼。從 0 到 0x7f(127)的字元把自身編碼成單位元組,而將值更大的字元編碼成 2 到 6 個位元組。

表 1. UTF-8 編碼

0x00000000 - 0x0000007F: 0xxxxxxx
0x00000080 - 0x000007FF: 110xxxxx 10xxxxxx
0x00000800 - 0x0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx
0x00010000 - 0x001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
0x00200000 - 0x03FFFFFF: 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
0x04000000 - 0x7FFFFFFF: 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

位元組 10xxxxxx 是一個擴充套件位元組,它的 xxxxxx 位位置被以二進位制表示的字元程式碼號的位所填充。這是能夠代表被使用程式碼的最短的可能的多位元組序列。

UTF-8 編碼示例
Unicode 字元版權標記字元 0xA9 = 1010 1001 用 UTF-8 編碼如下所示:

11000010 10101001 = 0xC2 0xA9
“不等於”符號字元 0x2260 = 0010 0010 0110 0000 編碼如下所示:

11100010 10001001 10100000 = 0xE2 0x89 0xA0
透過獲取 continuation byte 的值可以看到原始資料:

[1110]0010 [10]001001 [10]100000
0010 001001 100000
0010 0010 0110 0000 = 0x2260
第一個位元組定義後面緊跟的八位元數,如果是 7F 或更小,這就是等價的 ASCII 值。每個八位位元組以 10xxxxxx 開頭,確保位元組不與 ASCII 的值混淆。

UTF 支援
在 Linux 平臺上使用 UTF-8 之前,請確信分發包裡有 glibc 2.2 和 XFree86 4.0 或更新的版本。早先的版本缺少 UTF-8 語言環境支援和 ISO10646-1 X11 字型。

在 UTF-8 釋出之前,Linux 使用者使用各種不同特定語言的擴充套件 ASCII,像歐洲使用者用 ISO 8859-1 或 ISO 8859-2,希臘使用者使用 ISO 8859-7,俄羅斯使用者使用 KOI-8 / ISO 8859-5/CP1251(西裡爾字母)。這使得資料交換出現了很多問題,並且需要為這些編碼之間的差異編寫應用軟體。這種語言支援是不完善的,而且資料交換沒有經過測試。Linux 主要的發行商和應用程式開發者正致力於讓主要以 UTF-8 格式表示的 Unicode 成為 Linux 中的標準。

為了識別 Unicode 檔案,Microsoft 建議所有的 Unicode 檔案應該以 ZERO WIDTH NOBREAK SPACE(U+FEFF)字元開頭。這作為一個“特徵符”或“位元組順序標記(byte-order mark,BOM)”來識別檔案中使用的編碼和位元組順序。但是,Linux/UNIX 並沒有使用 BOM,因為它會破壞現有的 ASCII 檔案的語法約定。在 POSIX 系統中,選中的語言環境識別了在一個過程中的所有輸入輸出檔案期望的編碼形式。

有兩種方法可以將 UTF-8 支援新增到 Linux 應用程式中。第一種方法,資料都以 UTF-8 形式存放在各處,這樣軟體改動很少(被動的)。另一種方法,被讀取的 UTF-8 資料用標準的 C 語言庫函式轉變成為寬字元陣列(轉換的)。在輸出時,用函式 wcsrtombs() 使字串被轉變回 UTF-8:

清單 1. wcsrtombs()
#include
size_t wcsrtombs (char *dest, const wchar_t **src, size_t len, mbstate_t *ps);


方法的選擇取決於應用程式的性質。大多數應用程式可以使用被動的方法操作。這就是在 UNIX 平臺上使用 UTF-8 會如此流行的原因。像 cat 和 echo 那樣的程式就不需要修改。位元組流仍只是位元組流,並沒有對它進行任何處理。ASCII 字元和控制程式碼在 UTF-8 語言環境中不改變。

透過位元組計數對字元進行計數的程式需要一些小小的改動。在 UTF-8 中應用程式不對任何擴充套件的位元組進行計數。如果選擇了 UTF-8 語言環境,C 語言庫的 strlen(s) 函式需要用 mbstowcs() 函式來代替:

清單 2. mbstowcs() 函式
#include
size_t mbstowcs(wchar_t *pwcs, const char *s, size_t n);


strlen 的一種常見用法是估算顯示寬度。中文和其它表意符號將佔用兩列位置。wcwidth() 函式用來測試每個字元的顯示寬度:

清單 3. wcwidth() 函式
#include
int wcwidth(wchar_t wc);


Unicode 的 C 語言支援
在正式情況下,從 GNU glibc 2.2 開始,wchar_t 型別只為 32 位的 ISO 10646 格式數值所特定使用,與當前使用的語言環境無關。透過 ISO C99 所要求的 __STDC_ISO_10646__ 宏的定義作為訊號通知應用程式。 __STDC_ISO_10646__ 的定義用來指出 wchar_t 是 Unicode。精確的值是一個十進位制的 yyyymmL 格式的常數。例如,使用:

清單 4. 指出 wchar_t 是 Unicode
#define __STDC_ISO_10646__ 200104L


是為指出 wchar_t 型別的值是由 ISO/IEC 10646 和到指定的年月為止的所有修正與技術勘誤定義的字元編碼表示。

對 wchar_t 的利用如這個示例所示,使用宏確定在 ISO C99 可移植程式碼中寫雙引號的方法。

清單 5. 確定寫雙引號的方法
#if __STDC_ISO_10646__
printf("%lc", 0x201c);
#else
putchar('"');
#fi


語言環境
啟用 UTF-8 的恰當的辦法是 POSIX 語言環境機制。語言環境是一種包含有關軟體行為特定文化約定的配置設定。它包含了字元編碼、日期/時間符號、分類規則以及度量系統。語言環境的名稱通常由 ISO 639-1 語言、ISO 3166-1 國家或地區程式碼以及可選的編碼名稱和其它限定符組成。您可以用命令 locale -a 獲取所有安裝在系統上的語言環境列表(通常在 /usr/lib/locale/)。

如果沒有預安裝 UTF-8 語言環境,你可以用 localedef 命令生成它。若要為某個特定使用者生成並啟用一個德語的 UTF-8 語言環境,請使用如下語句:

清單 6. 為特定使用者生成語言環境
localedef -v -c -i de_DE -f UTF-8 $HOME/local/locale/de_DE.UTF-8
export LOCPATH=$HOME/local/locale
export LANG=de_DE.UTF-8


有時候為所有使用者新增 UTF-8 語言環境會很有用。root 使用者使用如下指令就可以完成:

清單 7. 為每個使用者生成語言環境
localedef -v -c -i de_DE -f UTF-8 /usr/share/locale/de_DE.UTF-8


若要為每個使用者將這個語言環境設為預設值,可以將以下行新增到 /etc/profile 檔案中:

清單 8. 為所有使用者設定預設的語言環境
export LANG=de_DE.UTF-8


處理多位元組字元程式碼序列的函式行為依賴於當前語言環境的 LC_CTYPE 類別;它確定了依賴語言環境的多位元組編碼。值 LANG=de_DE(德語)會導致輸出按 ISO 8859-1 被格式化。值 LANG=de_DE.UTF-8 會把輸出格式化成 UTF-8。語言環境設定會導致 printf 中的 %ls 格式說明符呼叫 wcsrtombs() 函式以便於將寬字元的引數字串轉換成依賴語言環境的多位元組編碼。語言環境中的國家或地區識別符號如:LC_CTYPE= en_GB (英國英語)和 LC_CTYPE= en_AU(澳大利亞英語),它們之間的差異只在 LC_MONETARY 類別中,原因在於貨幣的名稱和列印貨幣數量的規則不同。

請給您首選的語言環境設定環境變數 LANG。當一個 C 程式執行 setlocale() 函式時:

清單 9. setlocale() 函式
#include
#include
//char *setlocale(int category, const char *locale);
int main()
{
if (!setlocale(LC_CTYPE, ""))
{
fprintf(stderr, "Locale not specified. Check LANG, LC_CTYPE, LC_ALL.
");
return 1;
}


C 語言庫將會依次測試環境變數 LC_ALL、LC_CTYPE 和 LANG。其中第一個含值的環境變數將決定為 LC_CTYPE 類別裝入哪種語言環境資料。語言環境資料分裂成獨立的類別。值 LC_CTYPE 定義了字元編碼,而 LC_COLLATE 定義了排序順序。我們用 LANG 環境變數為所有類別設定預設語言環境,但 LC_* 變數可以用來覆蓋單個類別。

您可以用命令 locale charmap 查詢當前語言環境中字元編碼的名稱。如果您從 LC_CTYPE 類別中成功選取了 UTF-8 語言環境,會輸出 UTF-8。命令 locale -m 提供一張已安裝的所有字元編碼名稱的列表。

如果您使用專門的 C 語言庫的多位元組函式來完成所有外部字元編碼和內部使用的 wchar_t 編碼之間的轉換,那麼 C 語言庫將承擔責任,根據 LC_CTYPE 使用正確的編碼方式。這甚至不需要程式被明確的編碼成當前的多位元組編碼。

如果需要一個應用程式能明確的支援 UTF-8(或其它編碼)轉換方法而不用 libc 多位元組函式,則應用程式必須確定是否需要啟用 UTF-8 模式。帶有 庫標頭檔案的與 X/Open 相容系統可以用如下程式碼:

清單 10. 檢測當前的語言環境是否使用了 UTF-8 編碼
BOOL utf8_mode = FALSE;

if( ! strcmp(nl_langinfo(CODESET), "UTF-8")
utf8_mode = TRUE;


為檢測當前語言環境是否使用了 UTF-8 編碼。首先必須呼叫 setlocale(LC_CTYPE, "") 函式,依據環境變數設定語言環境。nl_langinfo(CODESET) 函式也是由 locale charmap 命令呼叫,從而查詢當前語言環境指定的編碼名稱。

另一種可以使用的方法是查詢語言環境變數:

清單 11. 查詢語言環境變數
char *s;
BOOL utf8_mode = FALSE;

if ((s = getenv("LC_ALL")) || (s = getenv("LC_CTYPE")) || (s = getenv ("LANG")))

{
if (strstr(s, "UTF-8"))
utf8_mode = TRUE;
}


這項測試假設 UTF-8 語言環境名稱中有值“UTF-8”,但實際情況並不總是如此,所以應該使用 nl_langinfo() 方法。

總結
為支援世界上的所有語言,需要一種具有八位位元組字元編碼策略的字元編碼系統,它的字元應多於 ASCII(一種使用無符號位元組的擴充套件版本)的 2^8 = 256 個字元。Unicode 就是這樣一種字元編碼系統,它具有由 128 個三維組(帶有由大量字元編碼方案的方法支援的 94,140 個定義好的字元值)組成的四維編碼空間,在 Linux 中更流行的字元編碼方案是 Unicode 轉換格式 UTF-8。

參考資料

請訪問 Unicode 聯盟的 Unicode 主頁,這裡定義了 Unicode 字元之間的行為和關係,併為實現者提供了技術資訊。
國際標準組織(International Organization for Standardization,ISO)是一個由 140 個國家組成的全球性的國家標準社團聯盟。
ANSI 是個私有的、非營利組織,它管理並調整 U.S. 的志願標準化以及一致性評價系統。
ISO C99 Draft(Acrobat PDF 格式,556 頁),是新的 C 語言標準,來自 Calgary 大學 Ben 的 C 程式設計課程。
C 語言的新 ISO 標準討論了 C9x 標準。
請閱讀 Roman Czyborra 的 Unix 環境下的 Unicode。
請查閱由 David A. Wheeler 撰寫的 Secure Programming for Linux and Unix HOWTO 中的 Character Encoding 章節。
請閱讀 IANA(Internet Assigned Numbers Authority)中的 IANA Charset Registration Procedures。
請參閱 Virginia 大學圖書館 Robertson Media 中心的 Unicode Music Symbols。
請看看 graphic representation of the Roadmap to the BMP, Plane 0 of the UCS。這些表包含了由 0 號,也就是通用字符集(Universal Character Set,UCS)的基本多語言平面(Basic Multilingual Plane,BMP)實際大小的對映組成的。Everson Gunn Teoranta 是一個自 1990 年開辦的支援少數民族語言團體的軟體和出版公司,由 Michael Everson 和 Marion Gunn 共同建立。
請瀏覽 UTF-8 and Unicode FAQ for UNIX/Linux,Markus Kuhn 的綜合性的 one-stop 資訊資源,關於您如何在 POSIX 系統(Linux,UNIX)使用 Unicode/UTF-8。
請檢查 Technology Appraisals Ltd 的 Solution Given by the Universal Character Set,其中提供了獨立的、高質量的有關電子商務系統、電子資訊傳遞、XML、網路和 IT 安全的資訊、教育和培訓。
請閱讀 Mulberry Technologies, Inc 的 Unicode presentation titled“10646 and All That”,一個專攻基於 SGML 和 XML 系統的電子出版物的諮詢公司。
UTF-8, a transformation format of ISO 10646 是由俄亥俄州立大學的計算機和資訊科學系指定的因特網社群的因特網標準跟蹤協議。
請諮詢 Linux 程式設計師手冊上的 UTF-8 — an ASCII compatible multi-byte Unicode encoding。
請閱讀 Unicode Standard Annex#15 Unicode Normalization Forms,一篇描寫了四種 Unicode 文字標準化格式規範的文件。有了這些格式,等價的(規範或是相容的)文字將會有同樣的二進位制表式。當實現工具在標準化的格式中保留了一個字串,可以確保有一個以二進位制形式表現的獨一無二的等價字串。
請閱讀 man-pages.net 上的 mbstowcs,它把多位元組字串轉換成了寬字元的字串,man-pages.net 為 Linux 手冊頁面提供了永久的基於 Web 的歸檔檔案。
請閱讀 Menlo 學校的主頁上的 wcwidth,它能決定一個寬字元程式碼值的所佔列位置的列數。
請閱讀 Hewlett Packard 的開發者資源站點的 Linux 程式設計師手冊上的 wcsrtombs,它能將寬字元的字串轉化為多位元組字串。
請閱讀 MKS 工具箱文件中的 setlocale(),它能改變或查詢語言環境。MKS 軟體公司是在 Windows 環境或混合 UNIX/Linux 和 Windows 環境中用於系統管理和開發的 Windows 自動化工具的領先供應商。
請學習 IBM Classes for Unicode (ICU),一個 C 語言和 C++ 語言庫,它在許多平臺上提供了健壯的和功能完善的 Unicode 支援。
請參閱 IBM 的“Introduction to Unicode”站點,這裡深入涵蓋了 Unicode 基礎知識。
在 IBM 的關於新興技術的 alphaWorks 站點。請參閱:
UnicodeCompressor,這裡提供了使用標準 Unicode 壓縮方案的壓縮和解壓縮 Unicode 文字的工具
Unicode Normalizer,為實現快速排序和搜尋將 Java 字串物件轉換為標準 Unicode 格式。
請閱讀 TW Burger 撰寫的“Cyrillic in Unicode”和 Jim Melnick 撰寫的“Multilingual forms in Unicode”,也在 developerWorks 上。
請在 developerWorks 上瀏覽更多 Linux 參考資料。
請在 developerWorks 上瀏覽更多 Unicode 參考資料。

關於作者
TW Burger 從 1979 年起曾經做過程式設計、講授中等計算機課程以及撰寫有關計算機技術方面的書。他正在經營一個資訊科技諮詢公司。您可以透過 與他聯絡。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10617731/viewspace-950663/,如需轉載,請註明出處,否則將追究法律責任。

相關文章