quinta-feira, 28 de fevereiro de 2002

Recuperando páginas com conteúdo acentuado via XMLHTTP

Esse problema costuma aparecer periodicamente no news: como fazer para recuperar o conteúdo de uma página via XMLHTTP sem perder os caracteres acentuados ? O comportamento que é reportado se dá por conta de como os dados são transmitidos via XMLHTTP. Quando uma solicitação é feita, é aberta uma conexão com a página em questão e o conteúdo dela é retornado como uma sequência Unicode, onde cada caracter é representado em dois bytes. "Que disperdício!", você poderia pensar, mas se pensarmos que o ASCII permite representar 256 caracteres, a tabela Unicode nos permite representar 65536 caracteres (256^2), o que é muito mais interessante na Web, onde diversos idiomas coexistem e nos abre a possibilidade de explorar o potencial de internacionalização de documentos.


Voltando ao ponto, se há uma indicação que os dados que estão sendo transmitidos são XML (em outras palavras, se o Content-Type está setado para text/xml), a propriedade responseXML do XMLHTTP interpreta o encoding do documento e, caso o conteúdo do mesmo esteja bem formado, retorna o resultado na forma de um objeto DOMDocument. Caso não esteja bem formado, ou não se trate de um documento XML, o XMLHTTP vai assumir que o documento está codificado em UTF-8 e a propriedade responseText vai retornar algo esquisito: os acentos são substituídos por um ponto de interrogação e a próxima letra é perdida. Isso aconteceu na tentativa de transformar os caracteres recebidos (Unicode) para ASCII, representável no seu computador.


A "brilhante" conclusão que chegamos até aqui é que, caso estejamos transmitindo um documento XML, basta definirmos qual o encoding que está sendo utilizado (usando a famosa processing instruction <?xml version="1.0" encoding="ISO-8859-1"?> ) e que, para documentos codificados em UTF-8, as coisas funcionam bem. Isso exclui os documentos escritos em português, por exemplo, pois os caracteres acentuados fazem parte do ISO-8859-1, excedendo o UTF-8.


Isso não significa que é impossível. Há outras formas de acessar o conteúdo recebido do XMLHTTP. São elas:


- responseStream
- responseBody


A primeira, sinceramente, não consegui fazer funcionar. Mas fatalmente cairia na necessidade de algum objeto ADODB para fazer a conversão, como o Recordset ou Stream.


Já o responseBody, foi bastante útil. Segundo a documentação da Microsoft "... representa como resposta a entidade body como uma array de unsigned bytes (...) Contém os bytes não decodificados, da forma que foram recebidos pelo servidor." lembrei-me então de uma rotina que acompanha o código de upload sem componentes, que faz exatamente esse tipo de conversão. Juntando as idéias, fiquei com o código abaixo:

<%
set oXMLHTTP = Server.CreateObject("Microsoft.XMLHTTP")
oXMLHTTP.open "GET", "
http://localhost/conteudo.asp", false
oXMLHTTP.send
response.write "<xmp>" & oXMLHTTP.getAllResponseHeaders & "</xmp>" & _
    oXMLHTTP.responseText & "<br />" & _
    BinaryToString( oXMLHTTP.responseBody ) & "<br />"
'Baseado no código de Philippe Collignon (PhCollignon@email.com)
Function BinaryToString(strBinary)
 Dim intCount
 BinaryToString =""
 For intCount = 1 to LenB(strBinary)
 BinaryToString = BinaryToString & chr(AscB(MidB(strBinary,intCount,1)))
 Next
End Function
%>

Pesquisando mais um pouco, descobri que o código da BinaryToString pode ser otimizado. Ao invés de chamá-la, deveriamos substituí-la pela RSBinaryToString, desenvolvida por Antonin Foller @ PStruh, cujo fonte segue abaixo:

<%
Function RSBinaryToString(xBinary)
  Dim Binary
  If VarType(xBinary)=8 Then Binary = MultiByteToBinary(xBinary) Else Binary = xBinary
  Dim RS, LBinary
  Const adLongVarChar = 201
  Set RS = CreateObject("ADODB.Recordset")
  LBinary = LenB(Binary)
  If LBinary>0 Then
    RS.Fields.Append "mBinary", adLongVarChar, LBinary
    RS.Open
    RS.AddNew
      RS("mBinary").AppendChunk Binary
    RS.Update
    RSBinaryToString = RS("mBinary")
 Set RS=Nothing
  Else
    RSBinaryToString = ""
  End If
  Set RS=Nothing
End Function
 
Function MultiByteToBinary(MultiByte)
  Dim RS, LMultiByte, Binary
  Const adLongVarBinary = 205
  Set RS = CreateObject("ADODB.Recordset")
  LMultiByte = LenB(MultiByte)
  If LMultiByte>0 Then
    RS.Fields.Append "mBinary", adLongVarBinary, LMultiByte
    RS.Open
    RS.AddNew
    RS("mBinary").AppendChunk MultiByte & ChrB(0)
    RS.Update
    Binary = RS("mBinary").GetChunk(LMultiByte)
  End If
  Set RS = Nothing
  MultiByteToBinary = Binary
End Function
%>
Finalmente, a página conteudo.asp contém o seguinte código:
<%
'Evitar que as páginas sejam mantidas em cache
Response.AddHeader "Pragma", "No-Cache"
Response.CacheControl = "private"
Response.AddHeader "cache-control", "private"
Response.Expires = -1
Response.ExpiresAbsolute=#Jan 01,1990 00:00:01#
Response.Buffer = False
%>
áéíóú<%= timer %>àèìòù
.

Desta forma, os caracteres são convertidos para uma sequência digesta, permitindo que o conteúdo possa ser acessado em toda sua riqueza. Em outras palavras, os acentos são retornados corretamente.


Divirta-se!



Esta matéria foi postada originalmente no ASP4Developers por Rubens N. Farias (site), que na época era "pós-graduado em análise de sistemas orientados a objetos, MCP, MCSD, MCAD, MCSD.NET e consultor em TI, além de idealizador do projeto ASP4Developers. Desenvolve sistemas sob medida, focados na satisfação do usuário, com qualidade e custo realista.". Hoje, vai saber...

1 comentários:

Gilmar Trevisan disse...

[Enviado originalmente em 16/set/2002 22:20:24]:

Belíssima explicação da causa e sua correção.
Curvo-me aos teus pés meu Guru!