O Elixir oferece uma sintaxe elegante para trabalhar com dados binários, como já vimos com a forma especial <<>> disponibilizada para trabalhar com bitstrings.
O tipo binário é uma especialização do tipo bitstring. Enquanto os bitstrings podem ter qualquer comprimento (qualquer número de bits), os binários são aqueles em que o número de bits é divisível por 8. Ou seja, quando trabalhamos com binários, pensamos muitas vezes em termos de bytes (8 bits). Um byte pode representar números inteiros de 0 a 255. É comum trabalhar com valores de byte em hexadecimal, 0x00 - 0xFF.
Os literais binários definem-se com a forma especial de bitstring <<>>. Ao definir um literal binário, podemos usar literais inteiros e de string. Valores inteiros superiores a 255 sofrem overflow e só os últimos 8 bits do inteiro são usados. Por predefinição, o modificador ::binary é aplicado ao valor. Podemos concatenar binários com o operador <>/2.
<<255>> == <<0xFF>>
# Overflowing bits are truncated
<<256>> == <<0>>
<<2, 4, 6, 8, 10, 12, 14, 16>> == <<0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x10>>
Um null-byte é outro nome para <<0>>.
O pattern matching é até estendido aos binários, e podemos fazer pattern matching numa porção de dados binários, tal como faríamos com uma lista.
# Ignore the first 8 bytes, match and bind the remaining to `body`
<<_::binary-size(8), body::binary>>
Tal como acontece com outros tipos de pattern matching, podemos usar isto em assinaturas de funções para corresponder ao selecionar entre várias cláusulas de função.
Tens estado a trabalhar num projeto que permite aos utilizadores carregar ficheiros para o servidor, para serem partilhados com outros utilizadores. Foi-te pedido que escrevas uma função para verificar se um carregamento corresponde ao seu tipo de media. Fazes alguma pesquisa e descobres que os primeiros bytes de um ficheiro são geralmente únicos desse tipo de ficheiro, o que lhe dá uma espécie de assinatura.
Usa a seguinte tabela como referência:
| Tipo de ficheiro | Extensão comum | Tipo de media | «assinatura» binária |
|---|---|---|---|
| ELF | "exe" |
"application/octet-stream" |
0x7F, 0x45, 0x4C, 0x46 |
| BMP | "bmp" |
"image/bmp" |
0x42, 0x4D |
| PNG | "png" |
"image/png" |
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A |
| JPG | "jpg" |
"image/jpg" |
0xFF, 0xD8, 0xFF |
| GIF | "gif" |
"image/gif" |
0x47, 0x49, 0x46 |
Implementa a função type_from_extension/1. Deve receber uma extensão de ficheiro (string) e devolver o tipo de media (string), ou nil se a extensão não corresponder às esperadas.
FileSniffer.type_from_extension("exe")
# => "application/octet-stream"
FileSniffer.type_from_extension("txt")
# => nil
Implementa a função type_from_binary/1. Deve receber um ficheiro (binário) e devolver o tipo de media (string), ou nil se a assinatura do ficheiro não corresponder às esperadas.
file = File.read!("application.exe")
FileSniffer.type_from_binary(file)
# => "application/octet-stream"
file = File.read!("example.txt")
FileSniffer.type_from_binary(file)
# => nil
Não te preocupes em ler o ficheiro como binário. Assume que isso já foi feito por ti e que é fornecido pelos testes como argumento.
Implementa a função verify/2. Deve receber um ficheiro (binário) e uma extensão (string) e devolver um tuplo :ok ou :error.
file = File.read!("application.exe")
FileSniffer.verify(file, "exe")
# => {:ok, "application/octet-stream"}
FileSniffer.verify(file, "png")
# => {:error, "Warning, file format and file extension do not match."}
Inscreve-te no Exercism para aprenderes e dominares Elixir com 58 conceitos168 exercícios, e mentoria humana real, tudo grátis.