Unicode no mundo real Normalização do conteúdo na WEB Cláudio Valente SAPO 13 de Novembro de 2007
Conteúdos 1 Introdução 2 Encodings mais comuns Relações 3 Unicode 4 Exemplos de codificações 5 Utilização prática Problemas 6 Código exemplificativo Convenções Encoding ambíguo em pedidos Vários encodings no mesmo documento 7 Conclusões Objecções ao Unicode Resumo
Conceitos Fundamentais Humanos comunicam com símbolos (letras e ideográficos) Computadores não têm a noção de símbolo, apenas números Para processar texto num computador é necessário transformar caracteres em números Um encoding consiste na atribuição de um número a cada caracter pertencente a um conjunto
Conceitos Fundamentais Humanos comunicam com símbolos (letras e ideográficos) Computadores não têm a noção de símbolo, apenas números Para processar texto num computador é necessário transformar caracteres em números Um encoding consiste na atribuição de um número a cada caracter pertencente a um conjunto
ASCII O encoding mais conhecido e usado na prática é o ASCII (American Standard Code for Information Interchange) Caracteres não acentuados da Europa Ocidental e do Norte Associa a cada caracter um número entre 0 e 127 Cada caracter ASCII pode ser representado por um byte Na realidade apenas são necessários 7 bits Exemplo, o caracter A é representado pelo número 41 16 = 65 10 = 01000001 2
ASCII O encoding mais conhecido e usado na prática é o ASCII (American Standard Code for Information Interchange) Caracteres não acentuados da Europa Ocidental e do Norte Associa a cada caracter um número entre 0 e 127 Cada caracter ASCII pode ser representado por um byte Na realidade apenas são necessários 7 bits Exemplo, o caracter A é representado pelo número 41 16 = 65 10 = 01000001 2
Encodings utilizados em Português Língua Portuguesa utiliza caracteres acentuados ASCII é insuficiente Os encodings mais utilizados em Português são: ISO-8859-1 (latin-1) ISO-8859-15 Windows-1252 UTF-8 UTF-16
Encodings utilizados em Português Língua Portuguesa utiliza caracteres acentuados ASCII é insuficiente Os encodings mais utilizados em Português são: ISO-8859-1 (latin-1) ISO-8859-15 Windows-1252 UTF-8 UTF-16
Relações entre Encodings ASCII
Relações entre Encodings ASCII àáãç ISO-8859-1 Extensão mínima com caracteres acentuados das ĺınguas latinas
Relações entre Encodings e ISO-8859-15 ASCII àáãç ISO-8859-1 e Windows-1252 O símbolo e não faz parte do latin-1 e para o incluir foram criadas várias extensões incompatíveis
Relações entre Encodings e ISO-8859-15 ASCII àáãç tudo(?) ISO-8859-1 Unicode e UTF-(8-16-32) Windows-1252 O Unicode é suposto conter todos os caracteres considerados em ĺınguas usadas actualmente bem como muitas já mortas.
O que é o Unicode Representa conteúdo escrito em (quase) qualquer sistema humano que exista A cada caracter (grifo) associa um valor numérico Define formas de processar dados tais como: Classes de caracteres (alfa-numéricos, pontuação, símbolos) Comparações entre caracteres (ordenação alfabética) Direccionalidade do texto
O que é o Unicode Representa conteúdo escrito em (quase) qualquer sistema humano que exista A cada caracter (grifo) associa um valor numérico Define formas de processar dados tais como: Classes de caracteres (alfa-numéricos, pontuação, símbolos) Comparações entre caracteres (ordenação alfabética) Direccionalidade do texto
Características técnicas do Unicode Define aproximadamente 100 000 caracteres É uma extensão do latin-1. Todos os caracteres portugueses têm a mesma representação em unicode Não é possível continuar a representar cada caracter por um byte É necessário fazer corresponder caracteres unicode a uma sequência de bytes UTF-32 UTF-16 UTF-8 UTF-7
Características técnicas do Unicode Define aproximadamente 100 000 caracteres É uma extensão do latin-1. Todos os caracteres portugueses têm a mesma representação em unicode Não é possível continuar a representar cada caracter por um byte É necessário fazer corresponder caracteres unicode a uma sequência de bytes UTF-32 UTF-16 UTF-8 UTF-7
Características técnicas do Unicode Define aproximadamente 100 000 caracteres É uma extensão do latin-1. Todos os caracteres portugueses têm a mesma representação em unicode Não é possível continuar a representar cada caracter por um byte É necessário fazer corresponder caracteres unicode a uma sequência de bytes UTF-32 UTF-16 UTF-8 UTF-7
UTF-8 UTF = Unicode Transformation Format Faz corresponder a uma sequência de caracteres unicode uma sequência de bytes Vantagens Conteúdo ASCII fica inalterado Independente de ordenação de bytes (little-endian vs big-endian) Codifica todos os caracteres Unicode Desvantagens Podem ser necessários vários bytes por caracter Não é 7bit safe O acesso aleatório a um caracter não é possível
UTF-8 UTF = Unicode Transformation Format Faz corresponder a uma sequência de caracteres unicode uma sequência de bytes Vantagens Conteúdo ASCII fica inalterado Independente de ordenação de bytes (little-endian vs big-endian) Codifica todos os caracteres Unicode Desvantagens Podem ser necessários vários bytes por caracter Não é 7bit safe O acesso aleatório a um caracter não é possível
UTF-8 UTF = Unicode Transformation Format Faz corresponder a uma sequência de caracteres unicode uma sequência de bytes Vantagens Conteúdo ASCII fica inalterado Independente de ordenação de bytes (little-endian vs big-endian) Codifica todos os caracteres Unicode Desvantagens Podem ser necessários vários bytes por caracter Não é 7bit safe O acesso aleatório a um caracter não é possível
Exemplos de codificações alfabeto latino com diacríticos e e A Ã Ç e ASCII A N.D. N.D. N.D. ISO-8859-1 A [C3] [C7] N.D. ISO-8859-15 A [C3] [C7] [A4] Windows-1252 A [C3] [C7] [80] UTF-8 A [C3][83] [C3][87] [E2][82][AC] UTF-16LE A[00] [C3][00] [C7][00] [AC][20] UTF-7 A +AMM- +AMc- +IKw- Unicode U+41 U+C3 U+C7 U+20AC ASCII não suporta acentos nem e
Exemplos de codificações alfabeto latino com diacríticos e e A Ã Ç e ASCII A N.D. N.D. N.D. ISO-8859-1 A [C3] [C7] N.D. ISO-8859-15 A [C3] [C7] [A4] Windows-1252 A [C3] [C7] [80] UTF-8 A [C3][83] [C3][87] [E2][82][AC] UTF-16LE A[00] [C3][00] [C7][00] [AC][20] UTF-7 A +AMM- +AMc- +IKw- Unicode U+41 U+C3 U+C7 U+20AC ISO-8859-1 suporta acentos, mas não o e
Exemplos de codificações alfabeto latino com diacríticos e e A Ã Ç e ASCII A N.D. N.D. N.D. ISO-8859-1 A [C3] [C7] N.D. ISO-8859-15 A [C3] [C7] [A4] Windows-1252 A [C3] [C7] [80] UTF-8 A [C3][83] [C3][87] [E2][82][AC] UTF-16LE A[00] [C3][00] [C7][00] [AC][20] UTF-7 A +AMM- +AMc- +IKw- Unicode U+41 U+C3 U+C7 U+20AC ISO-8859-1 suporta acentos, mas não o e
Exemplos de codificações alfabeto latino com diacríticos e e A Ã Ç e ASCII A N.D. N.D. N.D. ISO-8859-1 A [C3] [C7] N.D. ISO-8859-15 A [C3] [C7] [A4] Windows-1252 A [C3] [C7] [80] UTF-8 A [C3][83] [C3][87] [E2][82][AC] UTF-16LE A[00] [C3][00] [C7][00] [AC][20] UTF-7 A +AMM- +AMc- +IKw- Unicode U+41 U+C3 U+C7 U+20AC ISO-8859-15 e Windows-1252 suportam e de forma incompatível
Exemplos de codificações alfabeto latino com diacríticos e e A Ã Ç e ASCII A N.D. N.D. N.D. ISO-8859-1 A [C3] [C7] N.D. ISO-8859-15 A [C3] [C7] [A4] Windows-1252 A [C3] [C7] [80] UTF-8 A [C3][83] [C3][87] [E2][82][AC] UTF-16LE A[00] [C3][00] [C7][00] [AC][20] UTF-7 A +AMM- +AMc- +IKw- Unicode U+41 U+C3 U+C7 U+20AC O Unicode é extensão do ISO-8859-1
Exemplos de codificações alfabeto latino com diacríticos e e A Ã Ç e ASCII A N.D. N.D. N.D. ISO-8859-1 A [C3] [C7] N.D. ISO-8859-15 A [C3] [C7] [A4] Windows-1252 A [C3] [C7] [80] UTF-8 A [C3][83] [C3][87] [E2][82][AC] UTF-16LE A[00] [C3][00] [C7][00] [AC][20] UTF-7 A +AMM- +AMc- +IKw- Unicode U+41 U+C3 U+C7 U+20AC UTF-8 necessita de um número variável de bytes
Exemplos de codificações alfabeto latino com diacríticos e e A Ã Ç e ASCII A N.D. N.D. N.D. ISO-8859-1 A [C3] [C7] N.D. ISO-8859-15 A [C3] [C7] [A4] Windows-1252 A [C3] [C7] [80] UTF-8 A [C3][83] [C3][87] [E2][82][AC] UTF-16LE A[00] [C3][00] [C7][00] [AC][20] UTF-7 A +AMM- +AMc- +IKw- Unicode U+41 U+C3 U+C7 U+20AC UTF-16 e UTF-32 podem ser Little Endian ou Big Endian
Exemplos de codificações símbolos comuns 1 2 œ... ASCII N.D. N.D. N.D. N.D. ISO-8859-1 [BD] N.D. N.D. N.D. ISO-8859-15 N.D. [BD] N.D. N.D. Windows-1252 [BD] [9C] [93] [85] UTF-8 [C2][BD] [C5][93] [E2][80][9C] [E2][80][A6] UTF-16LE [BD][00] S[01] [1C][20] [26][20] UTF-7 +AL0- +AVM- +IBw- +ICY- Unicode U+BD U+153 U+201C U+2026 ISO-8859-15 não é estritamente uma extensão de ISO-8859-1
Exemplos de codificações símbolos comuns 1 2 œ... ASCII N.D. N.D. N.D. N.D. ISO-8859-1 [BD] N.D. N.D. N.D. ISO-8859-15 N.D. [BD] N.D. N.D. Windows-1252 [BD] [9C] [93] [85] UTF-8 [C2][BD] [C5][93] [E2][80][9C] [E2][80][A6] UTF-16LE [BD][00] S[01] [1C][20] [26][20] UTF-7 +AL0- +AVM- +IBw- +ICY- Unicode U+BD U+153 U+201C U+2026 smart quotes e reticências definidas no Windows-1252 mas não no ISO-8859-1(5)
Exemplos de codificações símbolos comuns 1 2 œ... ASCII N.D. N.D. N.D. N.D. ISO-8859-1 [BD] N.D. N.D. N.D. ISO-8859-15 N.D. [BD] N.D. N.D. Windows-1252 [BD] [9C] [93] [85] UTF-8 [C2][BD] [C5][93] [E2][80][9C] [E2][80][A6] UTF-16LE [BD][00] S[01] [1C][20] [26][20] UTF-7 +AL0- +AVM- +IBw- +ICY- Unicode U+BD U+153 U+201C U+2026 Unicode codifica todos os caracteres (para efeitos práticos)
HTML Identificar o conteúdo da página como estando codificado em UTF-8 nos cabeçalhos de HTTP. Content-Type: text/html; charset=utf-8 Identificar no documento. <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> </head> Forms. O encoding deve ser especificado sendo o da página por omissão <form accept-charset="utf-8">... </form>
HTML Identificar o conteúdo da página como estando codificado em UTF-8 nos cabeçalhos de HTTP. Content-Type: text/html; charset=utf-8 Identificar no documento. <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> </head> Forms. O encoding deve ser especificado sendo o da página por omissão <form accept-charset="utf-8">... </form>
HTML Identificar o conteúdo da página como estando codificado em UTF-8 nos cabeçalhos de HTTP. Content-Type: text/html; charset=utf-8 Identificar no documento. <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> </head> Forms. O encoding deve ser especificado sendo o da página por omissão <form accept-charset="utf-8">... </form>
Parâmetros em Unicode Quando um pedido GET ou POST é efectuado, os parâmetros devem ser transformados (uri escape). Infelizmente o conteúdo associado aos bytes não é transmitido no pedido obrigando a: Forçar um determinado encoding (parâmetro accept-charset no form) Utilizar argumentos heurísticos Assumir que está em UTF-8 Caso não seja um conjunto de caracteres válido assumir latin-1 ou melhor Windows-1252 Começa a ser comum enviar caracteres fora do ASCII como %uxxxx onde os quatro X representam o código unicode em hexadecimal
Parâmetros em Unicode Quando um pedido GET ou POST é efectuado, os parâmetros devem ser transformados (uri escape). Infelizmente o conteúdo associado aos bytes não é transmitido no pedido obrigando a: Forçar um determinado encoding (parâmetro accept-charset no form) Utilizar argumentos heurísticos Assumir que está em UTF-8 Caso não seja um conjunto de caracteres válido assumir latin-1 ou melhor Windows-1252 Começa a ser comum enviar caracteres fora do ASCII como %uxxxx onde os quatro X representam o código unicode em hexadecimal
Parâmetros em Unicode Quando um pedido GET ou POST é efectuado, os parâmetros devem ser transformados (uri escape). Infelizmente o conteúdo associado aos bytes não é transmitido no pedido obrigando a: Forçar um determinado encoding (parâmetro accept-charset no form) Utilizar argumentos heurísticos Assumir que está em UTF-8 Caso não seja um conjunto de caracteres válido assumir latin-1 ou melhor Windows-1252 Começa a ser comum enviar caracteres fora do ASCII como %uxxxx onde os quatro X representam o código unicode em hexadecimal
Erros comuns Páginas sem qualquer informação de encoding Conteúdo latin-1 misturado com UTF-8 GET http://some.domain.pt/ iconv -f utf-8 -t utf-8 >/dev/null iconv: illegal input sequence at position 48123 Conteúdo UTF-8 em páginas latin-1. Página que se identifica como estando em latin-1 mas que contém por exemplo Clà udio (deveria ser Cláudio).
Erros comuns Páginas sem qualquer informação de encoding Conteúdo latin-1 misturado com UTF-8 GET http://some.domain.pt/ iconv -f utf-8 -t utf-8 >/dev/null iconv: illegal input sequence at position 48123 Conteúdo UTF-8 em páginas latin-1. Página que se identifica como estando em latin-1 mas que contém por exemplo Clà udio (deveria ser Cláudio).
Erros comuns Páginas sem qualquer informação de encoding Conteúdo latin-1 misturado com UTF-8 GET http://some.domain.pt/ iconv -f utf-8 -t utf-8 >/dev/null iconv: illegal input sequence at position 48123 Conteúdo UTF-8 em páginas latin-1. Página que se identifica como estando em latin-1 mas que contém por exemplo Clà udio (deveria ser Cláudio).
Código mínimo Uma string usual é apenas um conjunto de bytes > s= Cl \ x e 1 u d i o Para interpretarmos como caracteres temos de especificar o encoding > u1 = u n i c o d e ( Cl \ x e 1 u d i o, l a t i n 1 ) Mas podemos fazer de forma equivalente > u2 = u n i c o d e ( Cl \ xc3 \ xa1udio, utf 8 ) Ou dizer logo que se trata duma string unicode > u3 = u Cl \ u00e1udio u1, u2 e u3 representam a mesma sequência de grifos. > p r i n t u1==u2 and u2==u3 True
Código mínimo Uma string usual é apenas um conjunto de bytes > s= Cl \ x e 1 u d i o Para interpretarmos como caracteres temos de especificar o encoding > u1 = u n i c o d e ( Cl \ x e 1 u d i o, l a t i n 1 ) Mas podemos fazer de forma equivalente > u2 = u n i c o d e ( Cl \ xc3 \ xa1udio, utf 8 ) Ou dizer logo que se trata duma string unicode > u3 = u Cl \ u00e1udio u1, u2 e u3 representam a mesma sequência de grifos. > p r i n t u1==u2 and u2==u3 True
Código mínimo Uma string usual é apenas um conjunto de bytes > s= Cl \ x e 1 u d i o Para interpretarmos como caracteres temos de especificar o encoding > u1 = u n i c o d e ( Cl \ x e 1 u d i o, l a t i n 1 ) Mas podemos fazer de forma equivalente > u2 = u n i c o d e ( Cl \ xc3 \ xa1udio, utf 8 ) Ou dizer logo que se trata duma string unicode > u3 = u Cl \ u00e1udio u1, u2 e u3 representam a mesma sequência de grifos. > p r i n t u1==u2 and u2==u3 True
Código mínimo Uma string usual é apenas um conjunto de bytes > s= Cl \ x e 1 u d i o Para interpretarmos como caracteres temos de especificar o encoding > u1 = u n i c o d e ( Cl \ x e 1 u d i o, l a t i n 1 ) Mas podemos fazer de forma equivalente > u2 = u n i c o d e ( Cl \ xc3 \ xa1udio, utf 8 ) Ou dizer logo que se trata duma string unicode > u3 = u Cl \ u00e1udio u1, u2 e u3 representam a mesma sequência de grifos. > p r i n t u1==u2 and u2==u3 True
Argumentos em encoding ambíguo Supor que nome contém o valor de um argumento de um pedido GET depois de url decoded. Só sabemos uma sequência de bytes mas o utilizador realmente pretendeu introduzir o nome Cláudio. http://domain.pt/?q=cl%e1udio http://domain.pt/?q=cl%c3%a1udio > nome1 = Cl \ x e 1 u d i o > nome2 = Cl \ xc3 \ xa1udio Como detectar heuristicamente o que o utilizador pretende?
Argumentos em encoding ambíguo Supor que nome contém o valor de um argumento de um pedido GET depois de url decoded. Só sabemos uma sequência de bytes mas o utilizador realmente pretendeu introduzir o nome Cláudio. http://domain.pt/?q=cl%e1udio http://domain.pt/?q=cl%c3%a1udio > nome1 = Cl \ x e 1 u d i o > nome2 = Cl \ xc3 \ xa1udio Como detectar heuristicamente o que o utilizador pretende?
Resolução Definir a função: def c l e a n a r g s ( s ) : t r y : return u n i c o d e ( s, utf 8 ) except UnicodeDecodeError : return u n i c o d e ( s, windows 1252 ) O resultado destas função em ambas alternativas tem como resultado uma string unicode que representa realmente o nome pretendido. > c l e a n a r g s ( nome1 ) u Cl \ x e 1 u d i o > c l e a n a r g s ( nome2 ) u Cl \ x e 1 u d i o
Resolução Definir a função: def c l e a n a r g s ( s ) : t r y : return u n i c o d e ( s, utf 8 ) except UnicodeDecodeError : return u n i c o d e ( s, windows 1252 ) O resultado destas função em ambas alternativas tem como resultado uma string unicode que representa realmente o nome pretendido. > c l e a n a r g s ( nome1 ) u Cl \ x e 1 u d i o > c l e a n a r g s ( nome2 ) u Cl \ x e 1 u d i o
Como tratar conteúdo com texto em vários encodings Bastantes páginas na Web em Português têm conteúdo identificado como estando em UTF-8, mas contendo caracteres representados em windows-1252 Outras identificam-se como estando em latin-1 ou windows-1252 mas contêm palavras codificadas em UTF-8 Uma solução de compromisso consiste em: Assumir que todo o conteúdo está em UTF-8 Quando um dado conjunto de bytes não corresponder a UTF-8 válido assumir que esses bytes representam caracteres em windows-1252 ou latin-1
Como tratar conteúdo com texto em vários encodings Bastantes páginas na Web em Português têm conteúdo identificado como estando em UTF-8, mas contendo caracteres representados em windows-1252 Outras identificam-se como estando em latin-1 ou windows-1252 mas contêm palavras codificadas em UTF-8 Uma solução de compromisso consiste em: Assumir que todo o conteúdo está em UTF-8 Quando um dado conjunto de bytes não corresponder a UTF-8 válido assumir que esses bytes representam caracteres em windows-1252 ou latin-1
Implementação import codecs def h a n d l e e r r o r s ( ex ) : s = ex. o b j e c t [ ex. s t a r t ] r e t = u n i c o d e ( s, windows 1252 ) return ( r e t, ex. s t a r t +1) codecs. r e g i s t e r e r r o r ( c l e a n, h a n d l e e r r o r s ) Utilizar da seguinte forma: > i n p u t = Cl \ x e 1 u d i o Cl \ xc3 \ xa1udio > u n i c o d e ( input, utf 8, c l e a n ) u Cl \ x e 1 u d i o Cl \ x e 1 u d i o > p r i n t u n i c o d e ( input, utf 8, c l e a n ) Cláudio Cláudio
Implementação import codecs def h a n d l e e r r o r s ( ex ) : s = ex. o b j e c t [ ex. s t a r t ] r e t = u n i c o d e ( s, windows 1252 ) return ( r e t, ex. s t a r t +1) codecs. r e g i s t e r e r r o r ( c l e a n, h a n d l e e r r o r s ) Utilizar da seguinte forma: > i n p u t = Cl \ x e 1 u d i o Cl \ xc3 \ xa1udio > u n i c o d e ( input, utf 8, c l e a n ) u Cl \ x e 1 u d i o Cl \ x e 1 u d i o > p r i n t u n i c o d e ( input, utf 8, c l e a n ) Cláudio Cláudio
Isto é complicar sem necessidade Basta trabalhar com bytes sempre em UTF-8 Esta abordagem não funciona. Como já vimos nome2 representa o nome Cláudio em UTF-8. Imaginemos que pretendemos imprimir este nome em maiúsculas. > nome2. upper ( ) Cl \ xc3 \ xa1udio > p r i n t nome2. upper ( ) CLáUDIO Não é o resultado pretendido. > c l e a n a r g s ( nome2 ). upper ( ) u CL\xc1UDIO > p r i n t c l e a n a r g s ( nome2 ). upper ( ) CLÁUDIO
Isto é complicar sem necessidade Basta trabalhar com bytes sempre em UTF-8 Esta abordagem não funciona. Como já vimos nome2 representa o nome Cláudio em UTF-8. Imaginemos que pretendemos imprimir este nome em maiúsculas. > nome2. upper ( ) Cl \ xc3 \ xa1udio > p r i n t nome2. upper ( ) CLáUDIO Não é o resultado pretendido. > c l e a n a r g s ( nome2 ). upper ( ) u CL\xc1UDIO > p r i n t c l e a n a r g s ( nome2 ). upper ( ) CLÁUDIO
Resumo sequências de bytes não determinam caracteres univocamente sempre que possível trabalhar com strings unicode e especificar o encoding de bytes em páginas WEB a WEB está progressivamente a migrar para UTF-8 durante a transição é publicado diverso tipo de conteúdo com erros o problema de interpretar o que se pretende transmitir vs o que é realmente transmitido necessita de abordagens heurísticas
Referências Consórcio Unicode http://www.unicode.org/ Joel on Software Informação mínima que todo o programador deve saber Characters and encodings http://www.cs.tut.fi/ jkorpela/chars/index.html
Apêndice Python Suporta Unicode out of the box Tipos distintos para strings unicode e arrays de bytes (str, unicode) Assume encodings por omissão e converte bytes para strings unicode Suporte bem integrado com a maior parte dos módulos
Apêndice Perl Suporta Unicode out of the box (Encode, perluni) Utiliza o mesmo tipo para arrays de bytes e strings unicode Distinção feita implicitamente (bit marca strings unicode) Suporte no motor de regular expressions Abordagem impĺıcita resulta em comportamentos com nuances por vezes pouco óbvias
Apêndice JAVA e C# Suporta Unicode out of the box Tipos distintos para strings unicode e arrays de bytes Integração total com todos os módulos
Apêndice PHP5 Não suporta unicode de forma integrada Não existem strings unicode, apenas bytes É possível utilizar extensões (iconv, mbstring) Todo o processamento é feito obrigatoriamente utilizando bytes, tipicamente UTF-8 Biblioteca insuficiente para processar strings multibyte
Apêndice PHP6 Promete: Integrar Unicode Tipos distintos para strings unicode e arrays de bytes binary string (default encoding) unicode Permitir desligar suporte unicode (INI) Integrar unicode na maior parte das funções built-in