虽然Excel95格式的文件现在用的少了,但是有些老系统导出的仍然是这种格式的Excel。最近要读取这样格式的文件,发现读出的中文全部乱码。试过XLSReadWriteII 4.00.62、XLSReadWriteII v5.10.25 Cracked for XE2-XE4、TMS FlexCel 5.5和NativeExcel 3.1.0 Beta with XE2 Support,都存在这个问题。以前一直用的是XLSReadWriteII,所以还得用它了。跟踪了好长时间,才发现问题的根本原因在于Excel95格式的文件内部汉字编码为GBK(代码页为936),而Excel97及以上版本采用的是Unicode编码(UTF16,代码页为1200),XLSReadWriteII 4.00.62读取Excel97及以上版本没问题,但是当读取Excel95格式的Excel文件时会产生乱码。
于是解决起来就简单了。方法见下。
顺便说一下,在网上搜索到的解决方案(包括盒子里的)都是修改AddString为AddUnicode,其实这不是根本原因,修改后也没用的(至少对我来说)。
开发环境:简体中文Windows XP SP3,Delphi XE2。
一、读取单元格内容时乱码。这是因为在XLSReadII4.pas单元中的过程"procedure TXLSReadII.RREC_LABEL;"的第1024行有错。原来的第1024行为:
----------
for i := 0 to Len - 1 do
WS[i + 1] := WideChar(Data[i]);
----------
此行有错。此时汉字的GBK码已经读出来了(如“入学”两字的十六进制GBK编码为C8EB D1A7),但是用了WideChar以后,是按Unicode码处理的。修改方法如下:
第一步:在出错的行所在的函数体上方加入以下GBK转Unicode代码:
//GBK码转为Unicode(UTF16)码
function GBK_Code_To_Unicode(const S: AnsiString; CodePage: Cardinal = 936) : WideString;//GBK编码的代码页为936
var
InputLength, OutputLength: Integer;
begin
InputLength := Length(S);
OutputLength := MultiByteToWideChar(CodePage, 0, PAnsiChar(S),InputLength, nil, 0);
SetLength(Result, OutputLength);
MultiByteToWideChar(CodePage, 0, PAnsiChar(S), InputLength, PWideChar(Result),OutputLength);
end;
第二步:修改上述有错的过程 TXLSReadII.RREC_LABEL 以下所示:
procedure TXLSReadII.RREC_LABEL;
var
i: integer;
P: PWordArray;
WS: WideString;
ByteArray: array of byte;//存放要显示的字符(或汉字)的字节数组
begin
InsertRecord := False;
with PRecLABEL(PBuf)^ do
begin
if FXLS.Version > xvExcel97 then
FXLS.Sheets[FCurrSheet].IntWriteSSTString(Col, Row, FormatIndex,
ByteStrToWideString(@Data[0], Len))
else
begin
SetLength(WS, Len);
if Data[0] = 0 then
begin
for i := 1 to Len do
WS[i] := WideChar(Data[i]);
end
else if Data[0] = 1 then
begin
P := @Data[1];
for i := 0 to Len - 1 do
WS[i + 1] := WideChar(P[i]);
end
else
begin
if FXLS.Version <= xvExcel50 then //added by seven,2013.6.22
begin
//复制提取出来的字节
SetLength(ByteArray, Len);
for i := 0 to Len-1 do
ByteArray[i]:=Data[i];
WS:=GBK_Code_To_Unicode(AnsiString(ByteArray));//GBK码转为Unicode(UTF16)码.转换后就显示为正确的字符或汉字了
end
else
begin
for i := 0 to Len - 1 do
WS[i + 1] := WideChar(Data[i]); //原控件的此行在读取Excel95格式的文件时有问题
end;
end;
FXLS.Sheets[FCurrSheet].IntWriteSSTString(Col, Row, FormatIndex, WS);
end;
end;
end;
二、读取Sheet的名称时(即Sheet[n].Name)时乱码的修改方法(同样需要GBK_Code_To_Unicode方法)
修改XLSReadII4.pas单元中的 procedure TXLSReadII.RREC_BOUNDSHEET 过程为以下代码:
procedure TXLSReadII.RREC_BOUNDSHEET;
var
i: integer;
WS: WideString;
Hidden: byte;
ByteArray: array of byte;//存放要显示的字符(或汉字)的字节数组
begin
TRecordStorageGlobals(CurrRecs).UpdateInternal(INTERNAL_BOUNDSHEETS);
InsertRecord := False;
if FXLS.Version < xvExcel97 then
with PRecBOUNDSHEET7(PBuf)^ do
begin
SetLength(WS, NameLen);
{
for i := 1 to NameLen do
WS[i] := WideChar(Name[i - 1]); //此行有问题,故注释掉不用
}
//复制提取出来的字节
SetLength(ByteArray, NameLen);
for i := 0 to NameLen-1 do
ByteArray[i]:=Name[i];
WS:=GBK_Code_To_Unicode(AnsiString(ByteArray));//GBK码转为Unicode(UTF16)码.转换后就显示为正确的字符或汉字了
Hidden := (Options and $0300) shr 8;
end
else
with PRecBOUNDSHEET8(PBuf)^ do
begin
WS := ByteToWideString(@Name, NameLen);
Hidden := Options and $0003;
end;
FBoundsheets.AddObject(WS, TObject(Hidden));
end;
三、读取字体名称时乱码的修改方法
XLSFonts4.pas单元的第310行 “function TXFont.CopyFromBuf(Buf: PByteArray): integer; ”调用的ByteStrToWideString函数有问题。这个ByteStrToWideString 方法影响字体名称、公式、单元格注释、打印设置的页头和页脚、单元格的格式化字符。由于我目前只需要读取单元格内容及工作表名,所以这个函数没有进行修改,有需要的请自己解决。
这样修改后,经过测试,Excel95格式、Excel2003和Excel2007及Excel2010格式的文件都可以正常读取出来。希望对大家有点帮助。
seven_14
2013.6.23
于是解决起来就简单了。方法见下。
顺便说一下,在网上搜索到的解决方案(包括盒子里的)都是修改AddString为AddUnicode,其实这不是根本原因,修改后也没用的(至少对我来说)。
开发环境:简体中文Windows XP SP3,Delphi XE2。
一、读取单元格内容时乱码。这是因为在XLSReadII4.pas单元中的过程"procedure TXLSReadII.RREC_LABEL;"的第1024行有错。原来的第1024行为:
----------
for i := 0 to Len - 1 do
WS[i + 1] := WideChar(Data[i]);
----------
此行有错。此时汉字的GBK码已经读出来了(如“入学”两字的十六进制GBK编码为C8EB D1A7),但是用了WideChar以后,是按Unicode码处理的。修改方法如下:
第一步:在出错的行所在的函数体上方加入以下GBK转Unicode代码:
//GBK码转为Unicode(UTF16)码
function GBK_Code_To_Unicode(const S: AnsiString; CodePage: Cardinal = 936) : WideString;//GBK编码的代码页为936
var
InputLength, OutputLength: Integer;
begin
InputLength := Length(S);
OutputLength := MultiByteToWideChar(CodePage, 0, PAnsiChar(S),InputLength, nil, 0);
SetLength(Result, OutputLength);
MultiByteToWideChar(CodePage, 0, PAnsiChar(S), InputLength, PWideChar(Result),OutputLength);
end;
第二步:修改上述有错的过程 TXLSReadII.RREC_LABEL 以下所示:
procedure TXLSReadII.RREC_LABEL;
var
i: integer;
P: PWordArray;
WS: WideString;
ByteArray: array of byte;//存放要显示的字符(或汉字)的字节数组
begin
InsertRecord := False;
with PRecLABEL(PBuf)^ do
begin
if FXLS.Version > xvExcel97 then
FXLS.Sheets[FCurrSheet].IntWriteSSTString(Col, Row, FormatIndex,
ByteStrToWideString(@Data[0], Len))
else
begin
SetLength(WS, Len);
if Data[0] = 0 then
begin
for i := 1 to Len do
WS[i] := WideChar(Data[i]);
end
else if Data[0] = 1 then
begin
P := @Data[1];
for i := 0 to Len - 1 do
WS[i + 1] := WideChar(P[i]);
end
else
begin
if FXLS.Version <= xvExcel50 then //added by seven,2013.6.22
begin
//复制提取出来的字节
SetLength(ByteArray, Len);
for i := 0 to Len-1 do
ByteArray[i]:=Data[i];
WS:=GBK_Code_To_Unicode(AnsiString(ByteArray));//GBK码转为Unicode(UTF16)码.转换后就显示为正确的字符或汉字了
end
else
begin
for i := 0 to Len - 1 do
WS[i + 1] := WideChar(Data[i]); //原控件的此行在读取Excel95格式的文件时有问题
end;
end;
FXLS.Sheets[FCurrSheet].IntWriteSSTString(Col, Row, FormatIndex, WS);
end;
end;
end;
二、读取Sheet的名称时(即Sheet[n].Name)时乱码的修改方法(同样需要GBK_Code_To_Unicode方法)
修改XLSReadII4.pas单元中的 procedure TXLSReadII.RREC_BOUNDSHEET 过程为以下代码:
procedure TXLSReadII.RREC_BOUNDSHEET;
var
i: integer;
WS: WideString;
Hidden: byte;
ByteArray: array of byte;//存放要显示的字符(或汉字)的字节数组
begin
TRecordStorageGlobals(CurrRecs).UpdateInternal(INTERNAL_BOUNDSHEETS);
InsertRecord := False;
if FXLS.Version < xvExcel97 then
with PRecBOUNDSHEET7(PBuf)^ do
begin
SetLength(WS, NameLen);
{
for i := 1 to NameLen do
WS[i] := WideChar(Name[i - 1]); //此行有问题,故注释掉不用
}
//复制提取出来的字节
SetLength(ByteArray, NameLen);
for i := 0 to NameLen-1 do
ByteArray[i]:=Name[i];
WS:=GBK_Code_To_Unicode(AnsiString(ByteArray));//GBK码转为Unicode(UTF16)码.转换后就显示为正确的字符或汉字了
Hidden := (Options and $0300) shr 8;
end
else
with PRecBOUNDSHEET8(PBuf)^ do
begin
WS := ByteToWideString(@Name, NameLen);
Hidden := Options and $0003;
end;
FBoundsheets.AddObject(WS, TObject(Hidden));
end;
三、读取字体名称时乱码的修改方法
XLSFonts4.pas单元的第310行 “function TXFont.CopyFromBuf(Buf: PByteArray): integer; ”调用的ByteStrToWideString函数有问题。这个ByteStrToWideString 方法影响字体名称、公式、单元格注释、打印设置的页头和页脚、单元格的格式化字符。由于我目前只需要读取单元格内容及工作表名,所以这个函数没有进行修改,有需要的请自己解决。
这样修改后,经过测试,Excel95格式、Excel2003和Excel2007及Excel2010格式的文件都可以正常读取出来。希望对大家有点帮助。
seven_14
2013.6.23