Linux Unicode 編程--C語言如何使用/生成UTF-8編碼格式的文件

Unicode並不只是一個編程工具,它還是一個政治的、經濟的工具。沒有結合世界的語言支持的應用程序通常只能被那些能讀寫ASCII所支持語言的個人使用。這使得建立在ASCII基礎之上的計算機技術脫離了世界上大部分人。Unicode允許程序使用世界上任何一種字符集,因此它支持所有語言。

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

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

所有現代字符集的基礎都是在1968年以ANSIX3.4版本出版的美國信息交換標準碼(AmericanStandardCodeforInformationInterchange,ASCII)。一個值得注意的例外是在ASCII之前定義的IBM的擴充的二進制編碼的十進制交換碼(ExtendedBinaryCodedDecimalInformationCode,EBCDIC)。ASCII是一個編碼字符集(codedcharacterset,CCS),換句話說,它是整數到字符表示的映射。ASCII編碼字符集允許用一個八位(基於二進制的,用值0或1表示的)字段或字節(2^8=256)表示256個字符。這是一個高度受限的編碼字符集,它不能表示許多不同語言的所有字符(如中文和日文),不能表示科學符號,更不能表示古代文字(神祕符號和象形文字)和音樂符號。通過更改一個字節的長度而使更大的字符集得以被編碼,這似乎有效但完全不切實際。所有的計算機都基於八位字節。解決方法是一種字符編碼方案(Characterencodingscheme,CES)―用定長或變長的多字節序列能夠表示比256大的數.這些數值接着通過編碼字符集被映射到它們表示的字符。

Unicode的定義

Unicode通常用作涉及雙字節字符編碼方案的通用術語。UnicodeCCS3.1的官方稱謂是ISO10646-1通用多八字節編碼字符集(UniversalMultipleOctetCodedCharacterSet,UCS)。Unicode3.1版本添加了44,946個新的編碼字符。算上Unicode3.0版本已經存在的49,194個字符,共計94,140個。

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

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

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

若要用8位字節表示一個多於2^8=256個字符的字符編碼系統(charactercodingsystem,CCS),就需要一種字符編碼方案(character-encodingscheme,CES)。


Unicode轉換

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

Unicode轉換格式(UnicodeTransformationFormats,UTFs)是一種通過映射多字節編碼中的值來支持Unicode的字符編碼方案。本文將分析最流行的格式―UTF-8字符編碼系統。

UTF-8

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

表1.UTF-8編碼

0x00000000-0x0000007F:0xxxxxxx
0x00000080-0x000007FF:110xxxxx10xxxxxx
0x00000800-0x0000FFFF:1110xxxx10xxxxxx10xxxxxx
0x00010000-0x001FFFFF:11110xxx10xxxxxx10xxxxxx10xxxxxx
0x00200000-0x03FFFFFF:111110xx10xxxxxx10xxxxxx10xxxxxx10xxxxxx
0x04000000-0x7FFFFFFF:1111110x10xxxxxx10xxxxxx10xxxxxx10xxxxxx10xxxxxx

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

UTF-8編碼示例

Unicode字符版權標記字符0xA9=10101001用UTF-8編碼如下所示:

1100001010101001=0xC20xA9

“不等於”符號字符0x2260=0010001001100000編碼如下所示:

111000101000100110100000=0xE20x890xA0

通過獲取continuationbyte的值可以看到原始數據:

[1110]0010[10]001001[10]100000
0010001001100000
0010001001100000=0x2260

第一個字節定義後面緊跟的八位元數,如果是7F或更小,這就是等價的ASCII值。每個八位字節以10xxxxxx開頭,確保字節不與ASCII的值混淆。


UTF支持

在Linux平臺上使用UTF-8之前,請確信分發包裏有glibc2.2和XFree864.0或更新的版本。早先的版本缺少UTF-8語言環境支持和ISO10646-1X11字體。

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

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

有兩種方法可以將UTF-8支持添加到Linux應用程序中。第一種方法,數據都以UTF-8形式存放在各處,這樣軟件改動很少(被動的)。另一種方法,被讀取的UTF-8數據用標準的C語言庫函數轉變成爲寬字符數組(轉換的)。在輸出時,用函數wcsrtombs()使字符串被轉變回UTF-8:


清單1.wcsrtombs()

#include <wchar.h> 
size_t wcsrtombs (char *dest, const wchar_t **src, size_t len, mbstate_t *ps);

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

通過字節計數對字符進行計數的程序需要一些小小的改動。在UTF-8中應用程序不對任何擴展的字節進行計數。如果選擇了UTF-8語言環境,C語言庫的strlen(s)函數需要用mbstowcs()函數來代替:


清單2.mbstowcs()函數

#include <stdlib.h>
size_t mbstowcs(wchar_t *pwcs, const char *s, size_t n);

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


清單3.wcwidth()函數

#include <
        wchar.h> 
int wcwidth(wchar_t wc);
      


Unicode的C語言支持

在正式情況下,從GNUglibc2.2開始,wchar_t類型只爲32位的ISO10646格式數值所特定使用,與當前使用的語言環境無關。通過ISOC99所要求的__STDC_ISO_10646__宏的定義作爲信號通知應用程序。__STDC_ISO_10646__的定義用來指出wchar_t是Unicode。精確的值是一個十進制的yyyymmL格式的常數。例如,使用:


清單4.指出wchar_t是Unicode

#define __STDC_ISO_10646__ 200104L

是爲指出wchar_t類型的值是由ISO/IEC10646和到指定的年月爲止的所有修正與技術勘誤定義的字符編碼表示。

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


清單5.確定寫雙引號的方法

#if __STDC_ISO_10646__  
   printf("%lc", 0x201c);  
#else  
   putchar('"');  
#fi

語言環境

激活UTF-8的恰當的辦法是POSIX語言環境機制。語言環境是一種包含有關軟件行爲特定文化約定的配置設定。它包含了字符編碼、日期/時間符號、分類規則以及度量系統。語言環境的名稱通常由ISO639-1語言、ISO3166-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(德語)會導致輸出按ISO8859-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 <stdio.h>
#include <locale.h>
//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_*變量可以用來覆蓋單個類別。

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

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

如果需要一個應用程序能明確的支持UTF-8(或其它編碼)轉換方法而不用libc多字節函數,則應用程序必須確定是否需要激活UTF-8模式。帶有<langinfo.h>庫頭文件的與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)函數也是由localecharmap命令調用,從而查找當前語言環境指定的編碼名稱。

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


清單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。

參考資料

FROM:http://www.ibm.com/developerworks/cn/linux/i18n/unicode/linuni/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章