segunda-feira, 13 de setembro de 2010

Quebras de linha no Delphi 2010

Alguns tem me perguntado sobre quebra de linha no Delphi 2010, o que está dando de errado.

Antes de falar sobre isso, precisamos saber como funciona a quebra de linha.

No sistema ASCII (caracteres de um byte) a quebra de linha nos sistemas gnu linux / unix é feita somente com o caracter #10 (ou \n line feed  - LF para os íntimos). Já para sistemas windows a quebra é feita com #13#10 ou CR\LF.

Há ainda mais um agravante: assim como no GNU linux e unix o #10 sozinho é considerado como quebra de linha, há também alguns sistemas onde o #13 sozinho é considerado como quebra de linha. Então, alguns editores, como o notepad++ interpretam tanto o #10 isolado como o #13 isolado como quebras de linha, bem como a dupla #13#10, para atender a todas as demandas.

O #13 ou \r é chamado de Carriage Return (CR) e o #10 ou \n é chamado de Line Feed (LF). Nas impressoras matriciais e maquinas de escrever elétricas antigas, para se pular uma linha e se escrever na linha de baixo, eram necessarios dois comandos:

O Carriage Return retornava o cabeçote de impressão para a posição mais a esquerda e o Line Feed fazia o papel "rolar" a altura de uma linha.

Por isso os caracteres CR e LF são tratados até hoje como comandos, e também por isso que  em sistemas windows ASCII a quebra de linha é formada pelos caracteres CR e LF juntos.

O que acontece com o Delphi 2010 é que agora os caracteres, unicode, são formados por 2 bytes e não apenas um. Isso significa que o byte da direita deve ser preenchido com alguma coisa. Lembrando que, em sistemas IBM-PC, como a leitura é feita da direita para a esquerda, o byte mais significativo é o da direita e o menos significativo é o da esquerda. é como se escrevêssemos números em que a casa das unidades fica a esquerda e a casa das dezenas e centenas vão sendo postas a direita desta. (para ilustrar somente).

Então no Delphi Unicode a quebra de linha se dá não apenas com #13#10, mas com #13#0#10#0, veja os exemplos.

Para salvar os arquivos no formato correto pergunte-se qual o formato de arquivo que o destino necessita, se é unicode ou ASCII, e qual é a quebra de linha nesse sistema.

É interessante sempre salvar um arquivo como UNICODE e sempre usar a constante LineBreak (PROPRIEDADE DO TSTRINGLIST NA UNIT CLASSES) do Delphi, que fornece a quebra de linha correta de acordo com  o sistema. (CASO VOCÊ ESTEJA USANDO TSTRINGLIST)
Para analizar os arquivos gerados use um editor Hexadecimal. usarei por exemplo o HexPlorer.

Cuidado: quando usar o #0, lembre-se que ele deve ser usado apenas nos Delphis anteriores ao 2009 como o 7, ou qualquer outra linguagem que seja nativamente ASCII. Isso porque embora no Delphi 2010 a quebra de linha tenha mudado para #13#0#10#0 os chars no Delphi 2010 JÁ TEM 2 bytes JÁ VEM com o #0 no final.

Nesse caso, o #13 já é automaticamente #13#0 e o #10 já é #10#0. Na verdade o número dos caracteres especiais continuam sendo 13 e 10, isso não muda. Os zeros a esquerda, assim como são desnecessários, torna-se também desnecessário colocar o "zero à direita" em um sistema IBM-PC litle endian, onde, como já dissemos, o "byte à esquerda" (mais significativo) fica à direita.

Então se você digitar #13#0#10#0 no Delphi 2010 na verdade ele vai virar #13#0#0#0#10#0#0#0 com duas quebras de string desnecessárias e que farão com que sua string seja quebrada no meio e nem chegue ao arquivo.


Faremos 6 exemplos: dois salvando em ASCII e usando as sequencias #13#10 e a constante LineBreak. e os outros dois nesse mesmo esquema só que usando Unicode. Os últimos dois usaremos TFileStream.

procedure TForm1.Button1Click(Sender: TObject);
begin

    with TStringList.Create do
    try
      Text := 'Hello' + #13#10 + 'World';
      SaveToFile(ExtractFilePath(Application.ExeName)+'\teste1.txt', TEncoding.ASCII);
    finally
      Free;
    end;

end;

procedure TForm1.Button2Click(Sender: TObject);
begin
    with TStringList.Create do
    try
      Text := 'Hello' + LineBreak + 'World';
      SaveToFile(ExtractFilePath(Application.ExeName)+'\teste2.txt', TEncoding.ASCII);
    finally
      Free;
    end;
end;

procedure TForm1.Button3Click(Sender: TObject);
begin

    with TStringList.Create do
    try
      Text := 'Hello' + #13#10 + 'World';
      SaveToFile(ExtractFilePath(Application.ExeName)+'\teste3.txt', TEncoding.Unicode);
    finally
      Free;
    end;

end;

procedure TForm1.Button4Click(Sender: TObject);
begin
    with TStringList.Create do
    try
      Text := 'Hello' + LineBreak + 'World';
      SaveToFile(ExtractFilePath(Application.ExeName)+'\teste4.txt', TEncoding.Unicode);
    finally
      Free;
    end;
end;

procedure TForm1.Button5Click(Sender: TObject);
begin
    with TFileStream.Create(ExtractFilePath(Application.ExeName)+'\teste5.txt', fmCreate) do
    try
      Write('Hello' + #13#10 + 'World', 24);
    finally
      Free;
    end;
end;

procedure TForm1.Button6Click(Sender: TObject);
begin
    with TFileStream.Create(ExtractFilePath(Application.ExeName)+'\teste6.txt', fmCreate) do
    try
      Write('Hello' + #13#0#10#0 + 'World', 30);
    finally
      Free;
    end;
end;

Conclusão: para quebrar linha use sempre #13#10 juntos, mas tome cuidado com o encoding e com quem será o receptor. Se o destino for unicode e você utilizar o Delphi 7 (ascii) utilize widestrings, mas se o destino for ASCII e você utilizar Delphi 2010 (unicode), utilize AnsiStrings, Encoding.ASCII e quebra de linha #13#10.

Agora vamos duplicar os exemplos 5 e 6 e vamos executá-los tanto no Delphi 2010 como no Delphi 7.

procedure TForm1.Button1Click(Sender: TObject);
var
  teste: AnsiString;
begin
    with TFileStream.Create(ExtractFilePath(Application.ExeName)+'\teste1.txt', fmCreate) do
    try
      teste := 'Hello' + #13#10 + 'World';
      Write(Pointer(teste)^, Length(teste)*SizeOf(AnsiChar));
    finally
      Free;
    end;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  teste: AnsiString;
begin
    with TFileStream.Create(ExtractFilePath(Application.ExeName)+'\teste2.txt', fmCreate) do
    try
      teste := 'Hello' + #13#0#10#0 + 'World';
      Write(Pointer(teste)^, Length(teste)*SizeOf(AnsiChar));
    finally
      Free;
    end;
end;

procedure TForm1.Button3Click(Sender: TObject);
var
  teste: WideString;
begin
    with TFileStream.Create(ExtractFilePath(Application.ExeName)+'\teste3.txt', fmCreate) do
    try
      teste := 'Hello' + #13#10 + 'World';
      Write(Pointer(teste)^, Length(teste)*SizeOf(WideChar));
    finally
      Free;
    end;
end;

procedure TForm1.Button4Click(Sender: TObject);
var
  teste: WideString;
begin
    with TFileStream.Create(ExtractFilePath(Application.ExeName)+'\teste4.txt', fmCreate) do
    try
      teste := 'Hello' + #13#0#10#0 + 'World';
      Write(Pointer(teste)^, Length(teste)*SizeOf(WideChar));
    finally
      Free;
    end;
end;

O código acima, executado no Delphi 2010, cria 4 arquivos. Repare que usando AnsiString o #13#10 equivale literalmente a #13#10 e o #13#0#10#0 também é transcrito normalmente. Repare também que usando widestrings cada char terá o dobro do tamanho, sendo o mesmo código que teria em ASCII + 00. O #13#10 sai automaticamente como #13#0#10#0 e colocar #0 nessa string duplicaria o #0.

Não abra com o notepad. Principalmente os arquivos 2 e 4 com #13#0#10#0 forçados, pois eles serão interpretados erroneamente e mostrarão as letras com um espaço entre si, como se estivesse lendo um Unicode mal formatado. Use sempre o Hexplorer.

Arquivo teste1

Arquivo teste2

Arquivo teste3

Arquivo teste4


E é isso aí. Até a próxima ;)

2 comentários:

  1. hospedei eles no finado spaces, que mudou de nome e hoje é o sky drive ou coisa parecida. Devo ter eles em algum lugar, depois eu subo outro link. Vai copiando o código da página por enquanto.

    ResponderExcluir

Postagens populares

Marcadores

delphi (60) C# (31) poo (21) Lazarus (19) Site aos Pedaços (15) sql (13) Reflexões (10) .Net (9) Humor (9) javascript (9) ASp.Net (8) api (8) Básico (6) Programação (6) ms sql server (5) Web (4) banco de dados (4) HTML (3) PHP (3) Python (3) design patterns (3) jQuery (3) livros (3) metaprogramação (3) Ajax (2) Debug (2) Dicas Básicas Windows (2) Pascal (2) games (2) linguagem (2) música (2) singleton (2) tecnologia (2) Anime (1) Api do Windows (1) Assembly (1) Eventos (1) Experts (1) GNU (1) Inglês (1) JSON (1) SO (1) datas (1) developers (1) dicas (1) easter egg (1) firebird (1) interfaces (1) introspecção (1) memo (1) oracle (1) reflexão (1)