本来这一篇是讲关于XML字符编码的,我觉得写着写着好像与XML的关系不大了,就改了标题
。所以,看的时候如果感觉到摸不到头脑,那就对了,如果感觉和你的认识不一样,欢迎批
评指正。
这里还有一个字符编码的问题。字符编码在Delphi7中已经得到了很大提高。
Delphi7自己的IDE虽然不能读取Unicode编码的源代码文件,但编译器已经支持
AnsiString和WideString的转换。也就是说,只要定义的时候定义WideString,
那么在后面直接给他赋值时,AnsiString自动转换为WideString,反之亦然。
这样有好处也有坏处,好处是在快速开发中,不需要考虑更多的字符转换问题,
能够比较平顺地从Win98向NT字符集转换,坏处是混淆了字符界限,深入看下
去,有时候搞不清我的内存里究竟是Ansi还是Wide,特别是希望仅仅使用宽字
符的情况下,更要留意字符格式的定义。
WideString保存为文本文件时,常用的有UTF-8、Unicode、Ansi、Unicode Big Endian,
其中 UTF-8 的格式,从文件读取的时候,需要利用 Delphi7 提供的 Utf8ToUnicode
转换一下全部编码,其他几种编码本身都不需要转换(BigEndian编码是摩托罗拉规范,是
intel 规范的 Unicode (即我们现在说的 WideString)编码的字符按字节反转,这符合摩
托罗拉生产的计算机芯片的构造特点,所以读取后要按 WORD 反转),但保存为相应格式的
文本文件时,必须按要求在文件头部写入一个编码识别记号,他们分别为:
Ansi:不需要
Unicode:$FEFF (十六进制编辑器看到的是高位在前显示$FFFE,以下同)
BigEndian:$FFFE (正好是上面 Unicode 的反转)
UTF-8:$BBEF $BF (三字节,十六进制编辑器里显示 $EFBB BF)
这样,其他编辑器读取时就可以识别出保存者把文本翻译成了什么编码。
Unicode(即WideString)只要写好文件头,后面的就按照保存Ansi文本一样把
文本写入文件,保存为Big Endian,则按WORD逐字节反转写入,保存为UTF-8
要利用UnicodeToUtf8转换后写入。
在XML解析中,如果带有非ASCII编码的文字,MS默认使用UTF-16编码,如果
原始文本是Ansi编码,这时将获得乱码的字符。这个编码不是Delphi造成的,是
MS的XML库所致,所以在使用非ASCII字符前,建议转换成UTF-8编码,上面例
子中我没有使用WideString,所以没有实现编码转换。
编码转换有很多现成的开源代码可以利用,其中影响最深远的就是JEDI的Unicoee.pas,
但这个文件很庞大,大约有250K大小,它还带有一个转换表的资源文件,如果
处理一些小型的字符转换就显得杀鸡用牛刀了。当然我们可以直接利用Delphi7
提供给我们的函数,比如:
function PUCS4Chars(const S: UCS4String): PUCS4Char;
function WideStringToUCS4String(const S: WideString): UCS4String;
function UCS4StringToWidestring(const S: UCS4String): WideString;
function UnicodeToUtf8(Dest: PChar; Source: PWideChar; MaxBytes: Integer): Integer;
function UnicodeToUtf8(Dest: PChar; MaxDestBytes: Cardinal; Source: PWideChar; SourceChars: Cardinal): Cardinal;
function Utf8ToUnicode(Dest: PWideChar; Source: PChar; MaxChars: Integer): Integer;
function Utf8ToUnicode(Dest: PWideChar; MaxDestChars: Cardinal; Source: PChar; SourceBytes: Cardinal): Cardinal;
function Utf8Encode(const WS: WideString): UTF8String;
function Utf8Decode(const S: UTF8String): WideString;
function AnsiToUtf8(const S: string): UTF8string;
function Utf8ToAnsi(const S: UTF8string): string;
等等。这些已经足够使用了。轻量级的代码是OmniXML中的TGpTextStream,
不过这个代码有不少BUG,并且不支持BigEndian的写入(读取部分也因忘了使
用临时变量而错误)。这些都可以利用。
在Delphi7中,Edit等控件不支持WideString,但有一组TnTWare的开源控件可
以直接支持WideString。
所以,了解了这些内容后,就可以明确这么多编码在读入内存后变成了什么。
读入内存中的字符其实已经只剩下二种格式了:
要么是 AnsiString,
要么是WideString。
因此,对于认识字符编码的关键就是理解读取和理解保存,只有这二个地方需
要对编码有了解才能正确地完成工作。
哦,对了,还要补充一下Delphi中比较特殊的一个事情:本来我们全程使用了
WideString后,在NT系统下应该可以不考虑处于哪种语言环境的,但是Delphi
的全部控件都是基于Ansi的,因此,除非使用了象Tnt控件一样的显示控件,
否则都要注意字符集的定义。象Edit,如果要显示WideString,Edit的Line.Text
会自动转换为AnsiString,这个转换的依据是活动文档的键盘定义或者活动文档
的字符集定义(字符集定义优先),因此一定不要忘记把Edit字符集设置为与
文本相适应的标志,比如中文,就设置为GB2313_CHARSET,这样,转换时会
使用936的中文字符集。这个设置与具体使用的字体无关,只要强制把这个属
性设置好了,字体是否支持这个集合由系统自动转换。
因为 WideString 中最需要转换的编码就是 UTF-8,所以演示了 UTF-8 就可以应用到所有
WideString 编码。
下面的演示代码是把 UTF-8 格式的文本装入只能显示 AnsiString 的 Delphi 自带的 Memo
中,并且可以再将这个 Memo 中的 AnsiString 取出来保存为 UTF-8 格式文本,并且支持
在任何语种的 Windows NT 操作系统上显示中文。
unit frmUnit;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, Dialogs, ComCtrls, Menus, StdCtrls;
type
TEncodeFlags = (efUnknown, efAnsi, efUnicode, efUncodeBigEn, efUTF8);
TUniEditFrm = class(TForm)
MainMenu1: TMainMenu;
mnuFileItem: TMenuItem;
mnuOpen: TMenuItem;
mnuSpace1: TMenuItem;
mnuSaveAs: TMenuItem;
mnuSpace2: TMenuItem;
mnuExit: TMenuItem;
StatusBar: TStatusBar;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
FStream: TStream;
OpenDlg: TOpenDialog;
SaveDlg: TSaveDialog;
UnicoMemo: TMemo;
procedure SetMemoCharset;
procedure LoadFromFile(fName: string);
procedure SaveToFile(fName: string);
procedure SetStatusMessage(Msg: string);
procedure MenuItemOnClick(Sender: TObject);
function ChWideToAnsi(const StrW: WideString): AnsiString;
function ChAnsiToWide(const StrA: AnsiString): WideString;
function UTF8ToWideString(const Stream: TStream): WideString;
procedure TextToUTF8Stream(const Text: string; var Stream: TStream);
function GetEncodeFromStream(const Stream: TStream): TEncodeFlags;
public
{ Public declarations }
end;
var
UniEditFrm: TUniEditFrm;
implementation
{$R *.dfm}
type
TUTF8Falg = packed record
EF, BB, BF: Byte;
end;
const
Encode: TUTF8Falg = (EF: $EF; BB: $BB; BF: $BF);
MenuActSpace = 0;
MenuActOpen = 1;
MenuActSaveAs = 2;
MenuActExit = 3;
{ TUniEditFrm }
procedure TUniEditFrm.FormCreate(Sender: TObject);
var
n: integer;
begin
mnuOpen.Tag := MenuActOpen;
mnuSaveAs.Tag := MenuActSaveAs;
mnuExit.Tag := MenuActExit;
for n := 0 to mnuFileItem.Count - 1 do
if mnuFileItem.Items[n].Caption <> '-' then
mnuFileItem.Items[n].OnClick := MenuItemOnClick;
OpenDlg := TOpenDialog.Create(Self);
OpenDlg.Filter := 'UTF8 Text File|*.txt';
SaveDlg := TSaveDialog.Create(Self);
SaveDlg.Filter := 'UTF8 Text File|*.txt';
SaveDlg.DefaultExt := '.txt';
UnicoMemo := TMemo.Create(Self);
UnicoMemo.Parent := Self;
UnicoMemo.Align := alClient;
UnicoMemo.ScrollBars := ssVertical;
SetMemoCharset;
end;
procedure TUniEditFrm.FormDestroy(Sender: TObject);
begin
OpenDlg.Free; SaveDlg.Free; UnicoMemo.Free;
if Assigned(FStream) then FStream.Free;
end;
procedure TUniEditFrm.MenuItemOnClick(Sender: TObject);
begin
case TComponent(Sender).tag of
MenuActOpen: if OpenDlg.Execute then LoadFromFile(OpenDlg.FileName);
MenuActSaveAs: if SaveDlg.Execute then SaveToFile(SaveDlg.FileName);
MenuActExit: Close;
end;
end;
procedure TUniEditFrm.SetMemoCharset;
begin
UnicoMemo.Font.Charset := GB2312_CHARSET;
UnicoMemo.Font.Size := 12;
end;
procedure TUniEditFrm.SetStatusMessage(Msg: string);
begin
SendMessage(StatusBar.Handle, WM_USER + 1, 0, DWord(PChar(Msg)));
end;
procedure TUniEditFrm.LoadFromFile(fName: string);
begin
if not Assigned(FStream) then FStream := TMemoryStream.Create;
TMemoryStream(FStream).LoadFromFile(fName);
if GetEncodeFromStream(FStream) = efUTF8 then
begin
SetStatusMessage(Format('File: %s ,Size:%d Byte', [fName, FStream.Size]));
UnicoMemo.Lines.BeginUpdate;
UnicoMemo.Clear;
try
UnicoMemo.Lines.Add(ChWideToAnsi(UTF8ToWideString(FStream)));
finally
UnicoMemo.Lines.EndUpdate;
end;
end
else SetStatusMessage(Format('File: %s ,Unknown Encode', [fName]));
FStream.Size := 0;
end;
procedure TUniEditFrm.SaveToFile(fName: string);
begin
try
if not Assigned(FStream) then FStream := TMemoryStream.Create;
TextToUTF8Stream(UnicoMemo.Lines.Text, FStream);
TMemoryStream(FStream).SaveToFile(fName);
SetStatusMessage(Format('Save File: %s ,Size:%d Byte', [fName, FStream.Size]));
finally
FStream.Size := 0;
end;
end;
function TUniEditFrm.ChWideToAnsi(const StrW: WideString): AnsiString;
var
nLen: integer;
begin
Result := StrW;
if Result <> '' then
begin
nLen := WideCharToMultiByte(936, 624, @StrW[1], -1, nil, 0, nil, nil);
SetLength(Result, nLen - 1);
if nLen > 1 then
WideCharToMultiByte(936, 624, @StrW[1], -1, @Result[1], nLen - 1, nil, nil);
end;
end;
function TUniEditFrm.ChAnsiToWide(const StrA: AnsiString): WideString;
var
nLen: integer;
begin
Result := StrA;
if Result <> '' then
begin
nLen := MultiByteToWideChar(936, 1, PChar(@StrA[1]), -1, nil, 0);
SetLength(Result, nLen - 1);
if nLen > 1 then
MultiByteToWideChar(936, 1, PChar(@StrA[1]), -1, PWideChar(@Result[1]), nLen - 1);
end;
end;
function TUniEditFrm.UTF8ToWideString(const Stream: TStream): WideString;
var
nLen: Cardinal;
begin
try
SetLength(Result, Stream.Size div SizeOf(WideChar) * 3);
nLen := Utf8ToUnicode(@Result[1], Length(Result),
Pointer(DWord(TMemoryStream(Stream).Memory) + Stream.Position),
Stream.Size - Stream.Position);
SetLength(Result, nLen);
except
SetLength(Result, 0);
end;
end;
procedure TUniEditFrm.TextToUTF8Stream(const Text: string; var Stream: TStream);
var
StringW, StrW: WideString;
nLen: Cardinal;
begin
try
if Text <> '' then
begin
StrW := ChAnsiToWide(Text);
nLen := Length(StrW) * 3;
SetLength(StringW, nLen);
nLen := UnicodeToUtf8(@StringW[1], nLen, @StrW[1], Length(StrW));
SetLength(StringW, nLen);
Stream.Write(Encode, SizeOf(Encode));
Stream.Write(StringW[1], Length(StringW));
end
else
Stream.Write(Encode, SizeOf(Encode));
except
SetLength(StrW, 0);
SetLength(StringW, 0);
end;
end;
function TUniEditFrm.GetEncodeFromStream(const Stream: TStream): TEncodeFlags;
var
FEncode: TUTF8Falg;
begin
Result := efUnknown;
Stream.Read(FEncode, SizeOf(FEncode));
if (FEncode.EF = Encode.EF) and (FEncode.BB = Encode.BB)
and (FEncode.BF = Encode.BF) then Result := efUTF8;
end;
end.
代码中有几个控件是动态创建的,其中有对话框和 Memo。这是为了随时改变使用不同控件
库进行测试观察的需要,如果你使用支持宽字符的控件,不用改界面,直接把创建实例改一
改就可以测试观察了。这种写法不是最好的,如果使用接口来描述就会更随意些。如果有时
间,下一次再乱谈Delphi的接口在编程中的应用吧。这一篇就到这里了。
http://blog.csdn.net/xwchen/archive/2007/03/21/1536829.aspx