O objetivo deste trabalho é implementar, na linguagem C, duas funções
(utf_varint
e varint_utf
), que convertem,
respectivamente,
arquivos em formato UTF-8 para o formato varint e
arquivos em formato varint para o formato UTF-8
Leia com atenção o enunciado do trabalho e as instruções para a entrega.
Em caso de dúvidas, não invente. Pergunte!
Em computação, caracteres de texto são tipicamente representados por códigos especificados por algum padrão de codificação. Um padrão bastante conhecido é a codificação ASCII, que utiliza valores inteiros de 0 a 127 para representar letras, dígitos e alguns outros símbolos. Algumas extensões dessa codificação utilizam também a faixa de valores de 128 a 255 para representar, por exemplo, caracteres acentuados e alguns outros símbolos adicionais.
A codificação ASCII e outros padrões de codificação que se limitem a um único byte para representar caracteres ficam restritos a apenas 256 símbolos diferentes. A codificação Unicode foi criada, no final da década de 1980, para permitir a representação de um conjunto maior de símbolos. A versão corrente dessa codificação é capaz de representar os caracteres utilizados por todos os idiomas conhecidos, além de diversos outros símbolos.
Cada caractere em Unicode é associado a um código (code point) na faixa de 0 a 0x10FFFF, o que, com a exclusão de alguns códigos não utilizados, permite a representação de 1.111.998 símbolos diferentes. Na notação adotada pelo padrão Unicode, U+xxxx identifica o código com valor hexadecimal xxxx. Por exemplo, o código U+00A9 (o código do símbolo ©) corresponde ao valor hexadecimal 0x00A9.
Uma faixa de valores (o intervalo 0xD800 a 0xDFFF) não deve ser utilizada para a representação de caracteres ou símbolos. Essa faixa é reservada para uso como surrogates na codificação UTF-16, permitindo a representação de códigos superiores a 0xFFFF. Contudo, na prática essa regra é muitas vezes ignorada em outras codificações UTF, que não UTF-16.
Codepoints Unicode podem ser representados através de diferentes tipos de
codificação de caracteres.
O padrão Unicode define as codificações UTF-8, UTF-16 e UTF-32.
Para este trabalho, a codificação de interesse é a UTF-8.
Essa codificação é a mais utilizada atualmente em sistemas Linux,
e é também a codificação dominante na Web.
Uma característica importante é que a codificação UTF-8 é compatível com o padrão ASCII, ou seja, os 128 caracteres associados aos códigos de 0 a 0x7F em ASCII tem a mesma representação (em um único byte) em UTF-8.
A tabela a seguir indica para cada faixa de valores de códigos Unicode o número de bytes necessários para representá-los e a codificação usada para essa faixa.
Código Unicode | Representação UTF-8 (byte a byte) |
---|---|
U+0000 a U+007F | 0xxxxxxx |
U+0080 a U+07FF | 110xxxxx 10xxxxxx |
U+0800 a U+FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
U+10000 a U+10FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
Note que:
11000010 10101001 = 0xC2 0xA9
O primeiro byte começa com 110, indicando que a sequência é composta por dois bytes. A seguir vêm os cinco primeiros bits do código Unicode (note o preenchimento com zeros à esquerda para completar a porção do código do caractere colocada no primeiro byte da sequência).
O segundo byte começa com 10, indicando que é um byte de continuação. A seguir vêm os próximos seis bits do código Unicode.
11100010 10001001 10100000 = 0xE2 0x89 0xA0
O primeiro byte começa com 1110, indicando que a sequência é composta por três bytes. A seguir vêm os quatro primeiros bits do código Unicode
O segundo e o terceiro bytes começam com 10, indicando que são bytes de continuação. Cada um deles tem, em seguida, os próximos seis bits do código Unicode.
Varints são um método de serialização de inteiros que, assim como UTF-8, utiliza um número variável de bytes, de forma que quanto menor o valor a ser codificado, menor o número de bytes. Esse tipo de codificação é utilizado, por exemplo, no padrão Protocol Buffers, um mecanismo de serialização de dados definido pela Google para uso em protocolos de comunicação e armazenamento de dados.
Para fazer a codificação varint de um valor inteiro, o valor original é dividido em grupos de 7 bits, começando com os bits menos significativos. Cada um desses grupos gera um byte da codificação; o bit mais significativo de cada byte gerado indica se ele é o último byte do valor codificado (0) ou se há mais bytes em seguida (1).
Vejamos um exemplo, a codificação do inteiro 300 como um varint. Em binário, temos:
00000000 00000000 00000001 00101100
Pegamos o primeiro grupo de 7 bits (os menos significativos, em azul), e acrescentamos um oitavo bit para indicar que haverá mais bytes a seguir:
10101100
00000010
10101100 00000010que corresponde, em hexadecimal, à sequência 0xAC 0x02.
O objetivo deste trabalho é implementar, na linguagem C, as funções
utf_varint
e varint_utf
, que realizam
respectivamente, a conversão de
arquivos em formato UTF-8 para o formato varint e
arquivos em formato varint para o formato UTF-8.
utf_varint
deve ler o conteúdo de um arquivo de
entrada codificado em UTF-8, obter os valores dos codepoints
representados, e gravar em um arquivo de saída esses valores codificados
como varints.
O protótipo (cabeçalho) dessa função é o
seguinte:
int utf_varint(FILE *arq_entrada, FILE *arq_saida);Os dois parâmetros da função são dois arquivos abertos em modo binário: o arquivo de entrada (
arq_entrada
) e o arquivo de
saída (arq_saida
).
O valor de retorno da função utf8_var
é 0, em caso de
sucesso e -1, em caso de erro de E/S.
Em caso de erro, a função deve emitir, na saída de erro (stderr),
uma mensagem indicando qual o tipo de erro ocorrido (leitura ou gravação)
e retornar imediatamente.
Por simplicidade, você pode considerar que o arquivo de entrada sempre estará CORRETAMENTE CODIFICADO. Dessa forma, você não precisa implementar o tratamento de erros de codificação do arquivo de entrada, apenas erros de E/S.
varint_utf
deve ler o conteúdo de um arquivo de
entrada contendo valores inteiros codificados como varints,
obter os valores inteiros originais,
e gravar em um arquivo de saída os valores obtidos, codificados em UTF-8.
O protótipo da função é o seguinte:
int varint_utf(FILE *arq_entrada, FILE *arq_saida);
Novamente, os parâmetros da função são dois arquivos abertos em modo binário:
o arquivo de entrada (arq_entrada
) e o arquivo de
saída (arq_saida
).
Assim como na função anterior, o valor de retorno é 0, em caso de sucesso e -1, em caso de erro de E/S. Em caso de erro, a função deve retornar após emitir, na saída de erro (stderr) uma mensagem indicando o tipo de erro ocorrido (leitura ou gravação).
Por simplicidade, você pode considerar que o arquivo de entrada sempre estará CORRETAMENTE CODIFICADO, ou seja, todos os valores inteiros codificados no arquivo correspondem a codepoints válidos.
Você deve criar um arquivo fonte chamado
converte.c
contendo as duas funções descritas acima,
e funções auxiliares, se for o caso.
Esse arquivo não
deve conter uma função main
!
O arquivo converte.c
deverá incluir o arquivo
de cabeçalho
converte.h
,
fornecido aqui.
Além desse arquivo de cabeçalho, seu arquivo fonte somente deverá incluir
arquivos de cabeçalho relativos a funções de bibliotecas padrão de C.
Para testar seu programa, crie um outro arquivo, por exemplo,
teste.c
,
contendo uma função main
.
Crie seu programa executável, por exemplo teste
, com a linha:
gcc -Wall -o teste converte.c teste.c
Implemente seu trabalho por partes, testando cada parte implementada antes de prosseguir.
Por exemplo, você pode implementar primeiro a leitura de um arquivo UTF-8, decodificando os caracteres para obter o valor dos codepoints correspondentes, exibindo esses valores na tela. Quando essa parte estiver funcionando, implemente a codificação varint sobre os valores, gerando um arquivo de saída.
Para verificar o conteúdo do arquivo gravado, você pode usar o utilitário
hexdump
. Por exemplo, o comando
hexdump -C <nome-do-arquivo>
exibe o conteúdo do arquivo especificado byte a byte, em hexadecimal
(16 bytes por linha). A segunda coluna de cada linha (entre '|') exibe
os caracteres ASCII correspondentes a esses bytes, se eles existirem.
Experimente inspecionar os arquivos dados como exemplo com o hexdump!
Para abrir um arquivo para gravação ou leitura em formato binário,
use a função
FILE *fopen(char *path, char *mode);
descrita em stdio.h
. Seus argumentos são:
path
: nome do arquivo a ser aberto
mode
: uma string que, no nosso caso, será "rb"
para abrir o arquivo para leitura em modo binário ou "wb"
para abrir o arquivo para escrita em modo binário.
Para fazer a leitura e gravação do arquivo,
pesquise as funções fwrite
/fread
e/ou
fputc
/fgetc
.
Deverão ser entregues via Moodle dois arquivos:
converte.c
, contendo as
funções utf_varint
e
varint_utf
(e funções auxiliares, se for o caso).
main
, e só
deve incluir o arquivo de cabeçalho converte.h
e
arquivos de cabeçalho das bibliotecas padrão de C.
/* Nome_do_Aluno1 Matricula Turma */ /* Nome_do_Aluno2 Matricula Turma */
relatorio.txt
,
contendo um pequeno
relatório.