Documentação do Código

As classes do projeto são distribuídas em 5 pacotes:

whatsappwebfix
Contém as classes WhatsAppEditor, NormalizeFilenames e DownloadPngs que fazem o trabalho principal em seus respectivos executáveis.
whatsappwebfix.apps
Classes executáveis do projeto
whatsappwebfix.global
Classes para constantes e definições gerais que possam ser acessadas por qualquer outra classe do projeto
whatsappwebfix.gui
Classes para as interfaces gráficas dos programas
whatsappwebfix.util
Classes utilitárias com função de preparação

Pacote whatsappwebfix

Classe WhatsAppEditor

Esta classe deve ser a encarregada de ler o arquivo de uma página WhatsApp salva e produzir uma cópia desse arquivo onde os emojis inseridos pelo usuário apareçam renderizados.

utf8EmojiToFilename()

Converte um emoji codificado como String UTF-8 no nome do arquivo PNG que contém a figura deste emoji.

A linha 107 obtém a string de codepoints para este emoji. Enquanto a linha 109 acrescenta a esta String o caminho relativo e a extensão produzindo o nome do arquivo.

Isto é, o nome do arquivo tem caminho relativo ao diretório onde estão os arquivos HTML processados pelo programa.

A linha 112 cria um objeto File com o caminho absoluto para representar este arquivo e a linha 114 testa se o arquivo existe. (Aqui precisamos do caminho absoluto para o programa verificar se existe um arquivo no disco com este nome que foi obtido. Já o caminho relativo será utilizado no documento HTML para que este possa renderizar o emoji com este arquivo PNG.)

Mas caso não exista o arquivo, o método então verifica se existe um arquivo com a figura do emoji raiz deste emoji. E se existir retorna este nome de arquivo para que o programa possa pelo menos renderizar uma figura que se aproxime do emoji correto. No entanto a informação de que se trata de um arquivo com uma figura apenas semelhante é passada acrescentando uma exclamação (!) antes do nome do arquivo retornado.

Mas se não existir nem mesmo um arquivo com a figura do emoji raiz o método retorna null.

Em suma: este método faz a associação entre a tag no arquivo HTML original que insere a figura do emoji, com o nome de um arquivo PNG na pasta emoji-images contendo justamente uma figura para o emoji especificado na tag.

getNewTag()

O método recebe uma tag que insere emoji no arquivo original e retorna uma nova tag que deve substitui-la no arquivo cópia.

O dado que define qual emoji deve ser renderizado é o valor do atributo alt na tag do arquivo original.

Portanto na linha 158 um objeto Matcher é criado para localizar o atributo alt na tag.

A String com o valor do atributo alt é atribuído ao objeto utf8Emoji na linha 164. E o nome do arquivo que contém a figura deste emoji é obtido na linha 169.

A nova tag que este método vai retornar dependerá do nome atribuído a utf8Emoji.

getMap()

Constrói um HashMap associando cada tag que insere emojis no arquivo original à tag que irá substitui-la no arquivo destino.

getMap() varre o conteúdo do arquivo original procurando por tags que inserem emojis. Para cada nova tag localizada o método cria uma entrada na estrutura HashMap que tem esta tag como chave. E como o valor para esta chave, a tag pela qual será substituída no arquivo destino (cópia do original mas com tags deste tipo substituídas).

Numa próxima etapa, o método createNewFile() irá varrer o arquivo original para cada chave (tag original) neste HashMap, substituindo-a no arquivo destino pelo seu respetivo valor (tag gerada pelo programa) neste HashMap.

createNewFile()

Cria o arquivo HTML capaz de renderizar os emojis.

Na linha 221, tagMap recebe um HashMap mapeando todas as tags que inserem emojis no arquivo original, em tags que irão substitui-las no arquivo destino.

O loop na linha 223 obtém cada uma das chaves neste mapa (as tags originais) e varre o conteúdo do arquivo original substituindo as ocorrências desta tag. As novas tags irão renderizar o emoji com os arquivos PNG baixados pela aplicação GetPngs.jar deste projeto.

A linha 226 grava este conteúdo editado no arquivo destino.

O método agora se ocupa de gravar um arquivo de relatório listando todos os emojis que a página tentava inserir, informando juntamente seus respectivos codepoints e a informação se o emoji foi renderizado corretamente, se o programa teve que usar o arquivo com a figura deste emoji raiz, ou se, na falta de um arquivo PNG para este emoji e até para seu raiz, este foi inserido na página simplesmente como um caractere UNICODE por meio de uma tag span.

A estrutura de dados com esta informação é também um HashMap cujas entradas foram inseridas nas execuções do método utf8EmojiToFilename(). Para cada String UTF-8 que utf8EmojiToFilename() converteu para nome de arquivo, o método verificou se este respectivo arquivo existia, se o que existia era apenas o arquivo com a imagem do emoji raiz, ou se nenhum arquivo PNG disponível poderia ser adequado para a renderização do emoji. E esse dado foi então inserido como uma entrada no HashMap emojisReport.

Classe DownloadPngs

Esta classe usa um arquivo com o código fonte da URL https://emojipedia.org/whatsapp/ para localizar e baixar os arquivos PNG com figuras de emojis estilo WhatsApp.

A classe declara dois campos privados:

downloadAll()

O método recebe um objeto Matcher que empacota uma regex juntamente com o texto do arquivo HTML e localiza todas as ocorrências que casem com a regex neste texto. A regex deve localizar URLs que serão baixadas para o diretório TARGET_DIR.

No loop da linha 42, o método find() localiza uma URL que case com a regex e retorna true. Esta URL é recuperada pelo método group() do objeto m. A linha 46 pega essa String a partir de "https:" até o final. (Portanto a regex não deve localizar as aspas no final de um link, caso contrário daria erro ao tentar baixar o arquivo).

Na linha 48 o método estático da classe FileTools é chamado para baixar o arquivo da URL para o diretório TARGET_DIR.

Na linha 50 uma saída formatada na janela de progresso da interface informa a URL que foi baixada.

DownloadPngs()

Este método recebe o arquivo que é o código fonte da página https://emojipedia.org/whatsapp e baixa todos os arquivos PNG com imagens de emojis que estiverem referenciados nesta página. (O código fonte desta página foi obtido com a opção "Exibir código fonte" do Chrome, seguido de Copy/Paste em um editor de textos).

A linha 71 cria o diretório TARGET_DIR para onde serão baixados os arquivos se este diretório não existir. Se não houver este diretório e o método falhar ao criar um, o programa é abortado.

A linha 80 copia todo o conteúdo do código fonte da página https://emojipedia.org/whatsapp para o objeto String contentFile.

A linha 84 abre na tela a janela que irá exibir o progresso na execução do método.

Nas linhas 89 a 94, todas as URLs de arquivos PNG que forem o valor de um atributo srcset serão baixadas. (Note que a regex na linha 89 começa com um espaço em branco para evitar que URLs de um atributo como, por exemplo, data-srcset sejam indevidamente baixadas).

Nas linhas 99 a 104, todas as URLs de arquivos PNG que forem o valor de um atributo data-src serão baixadas.

A linha 106 apaga o título da janela de progresso do método. A linha 108 informa que todos as URLs localizadas foram baixadas e quantas foram baixadas. A linha 110 emite um sinal sonoro de aviso de término.

Classe NormalizeFilenames

Faz o trabalho de normalizar os nomes dos arquivos baixados pela aplicação GetPngs.jar

A execução do método normalize() desta classe gera um arquivo de log em HTML que tabela todos os emojis catalogados. Os campos estáticos HEAD e FOOTER são respectivamente a parte inicial e final desse arquivo.

normalize()

A linha 78 inicializa a janela que exibe o progresso da execução do método.

A linha 81 cria um array de arquivos que deve conter todos os arquivos PNG com imagens de emojis que foram baixados da Emojipedia. O objeto da classe EmojiFileFilter passado ao método listFiles() garante que apenas estes tipos de arquivos serão inseridos no array.

A linha 85 verifica se pelo menos algum arquivo foi encontrado no diretório indicado e caso contrário aborta a execução do método.

A linha 97 inicializa a barra de progresso passando o número de arquivos que serão normalizados.

O código entre as linhas 103 e 107 cria um novo diretório dentro do diretório onde estão os arquivos PNG. Arquivos com nomes normalizados serão movidos para este diretório de nome emoji-images.

Note-se que para criar este diretório a linha 103 obtém o caminho absoluto (caminho completo desde a raiz) do diretório onde estão os arquivos PNG baixados. Isso porque o diretório corrente pode ser diferente deste no momento da execução do programa.

A linha 113 cria um TreeMap onde cada entrada contém - como chave - o nome do arquivo antes de ser normalizado associado ao valor que é o nome que ele recebe depois de normalizado. Mostrando a alteração realizada no nome de cada arquivo baixado. As entradas deste TreeMap é que serão gravadas no arquivo HTML de log e por isso a escolha de uma estrutura TreeMap. Para que a listagem seja exibida ordenada alfabeticamente pelos nomes originais dos arquivos.

O loop da linha 119 até 150 renomeia e move todos os arquivos.

Por fim o método cria um arquivo HTML com a listagem dos arquivos renomeados. Onde para cada arquivo na lista é exibida a figura do seu emoji. A este arquivo é atribuído nome de _emoji-list.html

Pacote apps

Classe Fix

Gera cópias de páginas salvas onde os emojis são renderizados.

Classe Normalize

Classe que normaliza os nomes dos arquivos PNG baixados da Emojipedia.

Classe GetPngs

O executável que obtém os arquivos PNGs no servidor da Emojipedia.

Pacote global

Classe Global

Métodos e campos com acesso por todas as classes do projeto.

Pacote gui

Classe ProgressFrame

Janela de interface que mostra o progresso da execução dos programas.

Pacote util

Classe FileTools

Metodos estáticos para leitura, gravação e download de arquivos.

readTextFile()

Recebe um objeto File indicando um arquivo texto e lê todo o conteúdo deste arquivo para dentro de um objeto String que é retornado pelo método.

É importante ressaltar que o método espera que o arquivo esteja codificado com o padrão UTF-8

readTextFile()

Recebe o nome de um arquivo texto e lê todo o conteúdo deste arquivo para dentro de um objeto String que é retornado pelo método.

É importante ressaltar que o método espera que o arquivo esteja codificado com o padrão UTF-8

writeTextFile()

Recebe um objeto File e uma String e grava esta String no arquivo. Se o arquivo não existir será criado; se já existir seu conteúdo atual será substituído.

O método espera que a String passada como parâmetro esteja codificada em UTF-8.

writeTextFile()

Recebe o nome de um arquivo e uma String e grava esta String no arquivo. Se o arquivo não existir será criado; se já existir seu conteúdo atual será substituído.

O método espera que a String passada como parâmetro esteja codificada em UTF-8.

downloadUrl()

Recebe a URL de um arquivo a ser baixado e o caminho do diretório onde baixar este arquivo. O caminho pode ser relativo ao diretório corrente ou absoluto e não deve terminar com o caractere /. O método baixa o arquivo para o diretório indicado mantendo seu nome original.

Deve-se notar que nomes de arquivos inválidos no sistema em que este programa estiver rodando não permitirão baixar o arquivo e uma exceção IOException será lançada.

Classe Normalizer

Como um exemplo, tomemos o emoji 🇧🇷 que é codificado pelos seguintes UNICODES: U+1F1E7 U+1F1F7. Para renderizar este emoji uma página do WhatsApp Web o faria por intermédio de uma tag como esta abaixo:

<img src="d5fceb6532643d0d84ffe09c40c481ecdf59e15a.gif" alt="🇧🇷" draggable="false" style="background-position: -60px -40px;" class="b92 emoji wa _3Whw5">

Mas como este código não funciona offline, a ideia é gerar um novo arquivo - cópia do original - onde esta tag seria trocada por:

<img src="1f1e7-1f1f7.png" alt="🇧🇷" width="20px" height="20px">

Assim esta última tag renderizará o emoji desde que o arquivo 1f1e7-1f1f7.png exista e contenha a figura do emoji da bandeira do Brasil.

O programa teria que fazer esse tipo de conversão para qualquer tag como esta exemplificada acima.

Portanto é necessário que o programa seja capaz de converter 🇧🇷 na string 1f1e7-1f1f7. Converter qualquer emoji codificado em UTF-8 em seus respectivos codepoints escritos em hexadecimal e separados por um tracinho (-).

Mas também é preciso que possa converter o nome do arquivo flag-brazil_1f1e7-1f1f7.png em 1f1e7-1f1f7.png. E fazer o mesmo com qualquer nome de arquivo PNG baixado da Emojipedia: extrair os codepoints que aparecem como sufixos nestes nomes de arquivos e normalizar para o mesmo padrão usado na conversão de emojis UTF-8 para sequências de codepoints em hexa.

Ou seja, o algoritmo que vai converter UTF-8 emojis em strings de codepoints deve produzir exatamente o mesmo resultado que o algoritmo que vai normalizar os nomes dos arquivos obtidos na Emojipedia.

Para tanto é necessário observar em detalhe que padrão é utilizado para codificar emojis em UTF-8. E também observar cuidadosamente o padrão utilizado pela Emojipedia para nomear os arquivos com figuras de emojis.

No caso dos emojis codificados como caracteres há duas particularidades que precisam ser levadas em consideração:

  1. Podem fazer parte da string os chamados LOW SURROGATE BYTES, que servem apenas para decodificar os caracteres mas não fazem parte dos caracteres de definição do emoji
  2. O caractere UNICODE U+FE0F pode ou não fazer parte da definição do emoji aparecendo como o último caractere da string. Mas esse caractere pode ser omitido e mesmo assim o emoji é corretamente interpretado por um editor de textos.

A consequência do item 1 para os nossos propósitos neste projeto é que LOW SURROGATE BYTES nunca constam nas strings de codepoints que são os sufixos dos nomes dos arquivos que serão baixados da Emojipedia.

Portanto o algoritmo de conversão de string UTF-8 para string codepoint deve simplesmente desprezar qualquer LOW SURROGATE BYTE.

Já a consequência do item 2 é que para um determinado emoji, o caractere U+FE0F pode estar presente na string UTF-8 mas não no nome do arquivo. Ou pode estar presente no nome do arquivo mas não na string UTF-8. Ou pode estar ausente em ambos ou presente em ambos. E isto é imprevisível.

Portanto os algoritmos de normalização do programa irão eliminar códigos U+FE0F sempre que ocorrerem. Seja em strings UTF-8, seja como parte de nomes de arquivos.

Também há uma particularidade em como o Emojipedia nomeia estes arquivos... A parte do sufixo com os codepoints inicia sempre com um underscore e os diferentes codepoints são separados entre si por um tracinho. No entanto, por algum motivo, às vezes um codepoint está repetido nesta string. Mas quando esta repetição ocorre ele é separado do anterior não por um tracinho, mas por um underscore.

Este padrão também deve ser detectado pelo algoritmo para poder eliminar estes codepoints supérfluos.

E esta é fundamentalmente a ideia do processo de normalização aplicada nos dois métodos da classe Normalizer do pacote util.