2016-12-01 137 views
-4

当我在记事本中创建一个文件,包含(例如)字符串1d并保存为unicode文件,我得到一个6字节大小的文件包含字节#255#254#49#0#100#0如何将宽字符串转换为unicode字节的字符串?

好的。现在我需要一个Delphi 6函数,它需要(例如)输入宽字符串1d并返回包含#255#254#49#0#100#0(反之亦然)的字符串。

怎么样? 谢谢。 D

+0

看起来你需要聘请一名程序员。你有没有努力自己做这件事?我们不是代码编写服务。 –

+0

可能的重复http://stackoverflow.com/questions/12337123/widestring-to-string-conversion-in-delphi-7 – Sami

+0

这是你的特定方面卡住 –

回答

1

A WideString已经是一个Unicode字节的字符串。具体来说,在UTF16-LE编码。

你通过记事本保存在Unicode文件中看到的两个额外的字节被称为BOM - YTE Ø刻申中号方舟。这是Unicode中的一个特殊字符,用于指示后面数据中的字节顺序,以确保字符串被正确解码。

将BOM添加到字符串中(这是您要求的)仅仅是用特殊的BOM字符预先固定字符串。 BOM字符是U + FEFF(即“字符”的十六进制表示法的Unicode表示法)。

所以,你需要的功能很简单:

function WideStringWithBOM(aString: WideString): WideString; 
const 
    BOM = WideChar($FEFF); 
begin 
    result := BOM + aString; 
end; 

不过,虽然功能很简单,这可能不是问题的结束。

从该函数返回的字符串将包含BOM,并且就任何Delphi代码而言,BOM将被视为字符串的一部分。

通常通过该字符串时,一些外部接收方(例如经由文件或Web服务响应)你只添加一个BOM到字符串,如果没有其他的机构,用于指示已使用的编码。

同样,读字符串时,一些收到这可能是Unicode,你应该检查前两个字节的数据:

  • 如果您发现#255#254($ FFFE),那么你知道U + FEFF BOM中的字节已被切换(U + FFFE不是有效的Unicode字符)。即后面的字符串是UTF16- LE。因此,对于一个Delphi WideString可以抛弃那些前两个字节并加载剩余的字节直接到合适WideString变量。

  • 如果您发现#254#255然后在U + FEFF BOM字节有被周围的切换。即您知道后面的字符串是UTF16- BE。在这种情况下,您仍然需要丢弃前两个字节,但在将剩余字节加载到WideString时,必须切换每对字节以将UTF16-BE字节转换为WideString的UTF16-LE编码。

  • 如果前2个字节是#255#254(反之亦然),那么您要么处理UTF16-LE而没有BOM或可能完全使用其他编码。

祝你好运。 :)

3

如果使用十六进制,则读取字节比较容易。 #255#254#49#0#100#0被以十六进制表示为

FF FE 31 00 64 00

FF FEUTF-16LE BOM,其中确定以下字节被编码为UTF-16在小端使用的值。

31 00是ASCII字符'1'

64 00是ASCII字符'd'

要创建包含这些字节的WideString非常简单:

var 
    W: WideString; 
    S: String; 
begin 
    S := '1d'; 
    W := WideChar($FEFF) + S; 
end; 

AnsiString(这是Delphi 6中的默认串类型)被分配给一个WideString,该RTL自动AnsiString数据由8-转换bit转换为UTF-16LE,使用本地机器的默认Ansi字符集进行转换。

走另一条路也很简单:

var 
    W: WideString; 
    S: String; 
begin 
    W := WideChar($FEFF) + '1d'; 
    S := Copy(W, 2, MaxInt); 
end; 

当您将WideStringAnsiString,在RTL自动WideString数据从UTF-16LE使用默认的ANSI字符集转换为8位。

如果默认的Ansi字符集不适合您的需要(比如8位数据需要用不同的字符集编码),则必须直接使用Win32 API MultiByteToWideChar()WideCharToMultiByte()函数(或第三方库具有相同的功能),因此您可以根据需要指定所需的字符集/代码页。

那么现在,德尔福6没有提供任何有用的工具来读取Unicode文件(2009年德尔福及更高版本),所以你必须自己做手工,例如:

function ReadUnicodeFile(const FileName: string): WideString; 
const 
    cBOM_UTF8: array[0..2] of Byte = ($EF, $BB, $BF); 
    cBOM_UTF16BE: array[0..1] of Byte = ($FE, $FF); 
    cBOM_UTF16LE: array[0..1] of Byte = ($FF, $FE); 
    cBOM_UTF32BE: array[0..3] of Byte = ($00, $00, $FE, $FF); 
    cBOM_UTF32LE: array[0..3] of Byte = ($FF, $FE, $00, $00); 
var 
    FS: TFileStream; 
    BOM: array[0..3] of Byte; 
    NumRead: Integer; 
    U8: UTF8String; 
    U32: UCS4String; 
    I: Integer; 
begin 
    Result := ''; 
    FS := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite); 
    try 
    NumRead := FS.Read(BOM, 4); 

    // UTF-8 
    if (NumRead >= 3) and CompareMem(@BOM, @cBOM_UTF8, 3) then 
    begin 
     if NumRead > 3 then 
     FS.Seek(-(NumRead-3), soCurrent); 
     SetLength(U8, FS.Size - FS.Position); 
     if Length(U8) > 0 then 
     begin 
     FS.ReadBuffer(PAnsiChar(U8)^, Length(U8)); 
     Result := UTF8Decode(U8); 
     end; 
    end 

    // the UTF-16LE and UTF-32LE BOMs are ambiguous! Check for UTF-32 first... 

    // UTF-32 
    else if (NumRead = 4) and (CompareMem(@BOM, cBOM_UTF32LE, 4) or CompareMem(@BOM, cBOM_UTF32BE, 4)) then 
    begin 
     // UCS4String is not a true string type, it is a dynamic array, so 
     // it must include room for a null terminator... 
     SetLength(U32, ((FS.Size - FS.Position) div SizeOf(UCS4Char)) + 1); 
     if Length(U32) > 1 then 
     begin 
     FS.ReadBuffer(PUCS4Chars(U32)^, (Length(U32) - 1) * SizeOf(UCS4Char)); 
     if CompareMem(@BOM, cBOM_UTF32BE, 4) then 
     begin 
      for I := Low(U32) to High(U32) do 
      begin 
      U32[I] := ((U32[I] and $000000FF) shl 24) or 
         ((U32[I] and $0000FF00) shl 8) or 
         ((U32[I] and $00FF0000) shr 8) or 
         ((U32[I] and $FF000000) shr 24); 
      end; 
     end; 
     U32[High(U32)] := 0; 
     // Note: UCS4StringToWidestring() does not actually support UTF-16, 
     // only UCS-2! If you need to handle UTF-16 surrogates, you will 
     // have to convert from UTF-32 to UTF-16 manually, there is no RTL 
     // or Win32 function that will do it for you... 
     Result := UCS4StringToWidestring(U32); 
     end; 
    end 

    // UTF-16 
    else if (NumRead >= 2) and (CompareMem(@BOM, cBOM_UTF16LE, 2) or CompareMem(@BOM, cBOM_UTF16BE, 2)) then 
    begin 
     if NumRead > 2 then 
     FS.Seek(-(NumRead-2), soCurrent); 
     SetLength(Result, (FS.Size - FS.Position) div SizeOf(WideChar)); 
     if Length(Result) > 0 then 
     begin 
     FS.ReadBuffer(PWideChar(Result)^, Length(Result) * SizeOf(WideChar)); 
     if CompareMem(@BOM, cBOM_UTF16BE, 2) then 
     begin 
      for I := 1 to Length(Result) then 
      begin 
      Result[I] := WideChar(
          ((Word(Result[I]) and $00FF) shl 8) or 
          ((Word(Result[I]) and $FF00) shr 8) 
         ); 
      end; 
     end; 
     end; 
    end 

    // something else, assuming UTF-8 
    else 
    begin 
     if NumRead > 0 then 
     FS.Seek(-NumRead, soCurrent); 
     SetLength(U8, FS.Size - FS.Position); 
     if Length(U8) > 0 then 
     begin 
     FS.ReadBuffer(PAnsiChar(U8)^, Length(U8)); 
     Result := UTF8Decode(U8); 
     end; 
    end; 
    finally 
    FS.Free; 
    end; 
end; 

更新:如果你想在AnsiString变量内存储UTF-16LE编码字节(为什么?),那么你可以Move()一个WideString的字符数据的原始字节成的AnsiString的内存块:如:

function WideStringAsAnsi(const AValue: WideString): AnsiString; 
begin 
    SetLength(Result, Length(AValue) * SizeOf(WideChar)); 
    Move(PWideChar(AValue)^, PAnsiChar(Result)^, Length(Result)); 
end; 

var 
    W: WideString; 
    S: AnsiString; 
begin 
    W := WideChar($FEFF) + '1d'; 
    S := WideStringAsAnsi(W); 
end; 

我不会建议滥用AnsiString这个样子,虽然。如果您需要字节,操作上字节,例如:

type 
    TBytes = array of Byte; 

function WideStringAsBytes(const AValue: WideString): TBytes; 
begin 
    SetLength(Result, Length(AValue) * SizeOf(WideChar)); 
    Move(PWideChar(AValue)^, PByte(Result)^, Length(Result)); 
end; 

var 
    W: WideString; 
    B: TBytes; 
begin 
    W := WideChar($FEFF) + '1d'; 
    B := WideStringAsBytes(W); 
end; 
+0

谢谢。你可以给一个例子使用MultiBytetowidechar(),反之亦然这个问题的API(其他字符集)? –

+0

对不起,在你的第二个例子中,length(s)返回2,而我期望4! (#49#0#100#0)。 –

+0

特别是我的问题预计6字符串! –