|

楼主 |
发表于 2012-12-25 23:01:31
|
显示全部楼层
转一篇:http://www.pascalbbs.com/article-56216.html
Lazarus 對於 String 編碼的 BUG 與處理
Lazarus 0.9.24 以前的版本中, String 是 ANSI 編碼,一個英文是 1 Byte,一個中文字是 2 Byte。Lazarus 0.9.26 版以後, 開始使用 UTF8 編碼,英文字母佔據的空間不變,中文字佔據的空間將是 3~6 個 Byte 不等。但在某些場合中我捫需要精確計算字串位置與長度, 如發票印表機自己有帶字型, 根據 ANSI STRING "一個英文是 1 Byte,漢字是 2 Byte" 的規則及使用習慣, 傳統上 BCB 5/6 或 Delphi 7 把中文字的 String (ANSI CODE) 丟到 ComPort 即可列印, 但 Lazarus 0.9.28 的 String 是 UTF8 CODE , 若是直接將中文的 String ( UTF8 CODE ) 丟到 ComPort , 會讓發票印表機印出亂碼, 故要特別處理 UTF8 -> ANSI 的 String 轉換
Lazarus 0.9.28 在 LCLProc單元中提供了幾個與 UTF8 字串相關的函數,例如有 Utf8ToAnsi(), AnsiToUtf8, UTF8Length()、UTF8CharAt() 等等,在一定程度上可以給我們提供幫助 , 我自己也寫了一些公用函式, 來加強 UTF8 與 ANSI 字串編碼的處理
BUG (以下都是在 Lazarus 0.9.28 之 WIN32 版本下測試發現的, 其它平台如 Linux 版不一定會有相同狀況)
雖然 Lazarus 0.9.28 已全面使用 UTF8 編碼, 但仍有些函式取回的 String 仍為 ANSI String , 混合處理的情況下會造成許多困擾, 所以在此整理一下
▲ INI FILE
//TIniFile 物件字串的 "讀出值" 及 "讀出預設值" 仍為 ansi, 故要轉換
MyIni:TIniFile;
procedure TForm1.FormCreate(Sender: TObject);
begin
MyIni := TIniFile.Create(ChangeFileExt(Application.ExeName,'.ini'));
MyPath := ExtractFilePath(Application.ExeName);
MyPath:=ansitoutf8(MyPath); //改為 UTF8
MyList := TStringList.Create;
MyList.Clear;
tmp:=MyPath+'POSDB.FDB'; //路徑可能夾帶中文
FDB_FN:=ansitoutf8(MyIni.ReadString('PARAMETER', 'FDB_FILENAME', utf8toansi(tmp)));
ShowMessage(FDB_FN);
end;
procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
MyIni.WriteString('PARAMETER', 'FDB_FILENAME', utf8toansi(FDB_FN));
MyList.Free;
MyIni.Free;
end;
▲ ExtractFilePath() 函式 : 取出某檔案的所在路徑
我發現它取出路徑的字串仍是 ANSI String 編碼, 要與一般 String 變數 (default 是 UTF8 編碼) 做 "+" 號處理會有問題, 所以每次取出後要用 AnsiToUtf8() 轉成 UTF8 String
▲ FileExists() 函式 : 判斷檔案存不存在
if (FileExists(Utf8ToAnsi(SDB_FN))) then
begin
.....
end;
▲ TStringList 物件的主要屬性仍為 Ansi String
//以下為正確處理示範
procedure TForm1.Button1Click(Sender: TObject);
var TmpList: TStringList;
i:integer;
begin
TmpList := TStringList.Create;
TmpList.LoadFromFile(Utf8ToAnsi('C:\新資料夾\新增文字文件.txt'));
for i:=0 to TmpList.Count-1 do
begin
ShowMessage(AnsiToUtf8(TmpList.Strings));
end;
TmpList.SaveToFile(Utf8ToAnsi('C:\新資料夾\新增文字文件2.txt'));
TmpList.Free;
end;
▲ 某些控件 String 屬性需為 Ansi String
如 要設定資料庫控件 TDbf 的 TableName 屬性, 需傳入 Ansi String
My_Path:=ExtractFilePath(Application.ExeName); //ExtractFilePath() 取回的是 ANSI
Dbf1.TableName:=My_Path+'myTest.DBF'; //TDbf 的 TableName 可直接餵入 ANSI STRING
而指定另一種資料庫控件 SQLite3Connection1 的 DatabaseName 屬性 卻又須使用標準 UTF8 String
My_Path:=ExtractFilePath(Application.ExeName); //ExtractFilePath() 取回的是 ANSI
My_Path:=AnsiToUtf8(My_Path); //改為 UTF8
SQLite3Connection1.DatabaseName:=My_Path+'myTest.db'; //指定一個 DATABASE 要餵入 UTF8 STRING
所以以後若指定某控件的字串屬性, 明明是正確的字串內容, 但卻出現編譯錯誤或是控件無法正確動作, 可能就是 String 編碼問題
自己寫的公用函式
//---------------------------------------------------------------------------
//Length(), Copy(), Pos() 傳入 Ansi String 才會準確
//UTF8Length(), UTF8Copy() 傳入 UTF8 String 才會準確
//---------------------------------------------------------------------------
//傳入傳出都以 UTF8 字串為準
function _Length(str: String):Integer; //或稱 AnsiLength()
var tmp:String;
begin
tmp:=UTF8ToAnsi(str);
result:=Length(tmp);
end;
//---------------------------------------------------------------------------
//傳入傳出都以 UTF8 字串為準
function _Copy(str: String; idx,len: integer):String; //或稱 AnsiCopy()
var tmp,tmp2:String;
begin
tmp:=UTF8ToAnsi(str);
tmp2:=Copy(tmp,idx,len);
result:=AnsiToUTF8(tmp2);
end;
//---------------------------------------------------------------------------
//傳入傳出都以 UTF8 字串為準
function _Pos(substr,str: String):Integer; //或稱 AnsiPos()
var tmp,tmp2:String;
begin
tmp:=UTF8ToAnsi(substr);
tmp2:=UTF8ToAnsi(str);
result:=Pos(tmp,tmp2);
end;
常用的 String 處理相關函式有 Length(), Copy(), Pos() 等, 都是以 Ansi String 角度來看結果的, 但在 Lazarus 中 utf8 跟 ansi 混用的情況下, 只好 "以UTF8 String 為參數, 而以 Ansi String 為角度(一個英文是 1 Byte,一個中文字是 2 Byte) " 來編寫新的函式
例如
var tmp: String ;
tmp2: String ;
begin
tmp:='字串處理測試函式'; //default 都是 UTF8 編碼的 String
tmp2:=_Copy(tmp,5,4); //以UTF8 String 為參數, 而以 Ansi String 為角度, 取出 tmp 中 第 5 byte 起連續 4 個 byte 的字串 , 取回值也為 UTF8 編碼 String
ShowMessage(tmp2); //傳入 ShowMessage() 的 String 需為 UTF8, 故 tmp2 可直接傳入
end;
若要以 Lazarus 內定的 Copy() 取出正確位置的字串, 需要轉來轉去的, 很麻煩
var tmp: String ;
tmp2: String ;
begin
tmp:='字串處理測試函式'; //default 都是 UTF8 編碼的 String
tmp:=Utf8ToAnsi(tmp); //先把 UTF8 編碼的 String 轉成 Ansi 編碼
tmp2:=Copy(tmp,5,4); //用 Lazarus 內建的 Copy() 處理 Ansi String, 取回值也為 Ansi String
ShowMessage(AnsiToUtf8(tmp2)); //傳入 ShowMessage() 的 String 需為 UTF8, 故需再把 Ansi 再轉回 UTF8
end;
補充說明 : 我個人認為 Lazarus 既然要全面使用 UTF8 String , 就不應該再提供像是 Copy() 等這些需傳入 Ansi String 的函式, 而是應提供像我寫的 _Copy() 這種函式 , 傳入傳出都是 UTF8 String , 但內部是以 Ansi String 的角度 (一個英文是 1 Byte,一個中文字是 2 Byte) 來處理字串
|
|