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 ;)

Comentários

  1. Boa tarde
    Os arquivos testes não abrem!

    ResponderExcluir
  2. 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

Postar um comentário

Postagens mais visitadas deste blog

Detectar o encoding de um arquivo para não corromper ao transformá-lo

erro "ora-12154: tns: não foi possível resolver o identificador de conexão especificado"