XML em AdvPL – Parte 02

Introdução

No post anterior (XML em AdvPL – Parte 01) vimos o que é um XML, e vimos uma das formas de como podemos ler sua estrutura e conteúdo em AdvPL. Nesse post, vamos ver uma ferramenta interessante para avaliar conteúdo de qualquer XML.

Programa U_ZXMLKIT

Em AdvPL podemos criar uma interface de forma relativamente simples, basta conhecer um pouco sobre como a hierarquia dos componentes de interface funcionam, e com poucas linhas de código podemos criar programinhas bem eficientes. Usando um container de folders e alguns botões, junto com a classe tXmlManager, vejamos abaixo uma ferramenta útil para você informar um XML (texto ou arquivo), e ela gerar pra você uma lista de todos os dados e conteúdos disponíveis neste XML. O fonte atualizado já está disponível nesse link, no GITHUB.

#include 'protheus.ch'

/*
Funcao 		U_zXMLKit()
Autor		Julio Wittwer
Data 		14/07/1029
Descrição 	Interface para teste de Parser de XML

		Realiza o parser de uma string ou arquivo XML, e levanta todas as informações dos nodes 
		que podem ser extraídas do XML usando DOM 
*/


User Function zXMLKit()
Local oDlg
Local oGetXML
Local cXML := ''
Local oXml                    
Local cXmlFile := space(80)              
Local nOpList
Local aNodeList := {''}
Local oListResult 

oXml := tXmlManager():New()

// Cria uma caixa de diálogo com área util de 800x600  PIXELs
DEFINE DIALOG oDlg TITLE "AdvPL XML Kit" FROM 0,0 TO 600,800 PIXEL

@ 0,0 FOLDER oFolders PROMPTS "XML String","XML File","Resultado"  OF oDlg  PIXEL
oFolders:ALIGN := CONTROL_ALIGN_ALLCLIENT

// Folder 1 . Parser da String informada

@ 10,10 SAY "Informe a String XML para Parser" OF oFolders:aDialogs[1] PIXEL
@ 20,10 GET oGetXML VAR cXML MULTILINE SIZE 380, 200 of oFolders:aDialogs[1]  PIXEL

@ 230,10 BUTTON oBtmParse PROMPT "&Parser" ;
  ACTION (DoPArser(oDlg,oListResult,oXml,cXml)) ;
  SIZE 040, 013 OF oFolders:aDialogs[1]  PIXEL

@ 230,350 BUTTON oBtmParse PROMPT "Sai&r" ;
  ACTION (oDlg:End()) ;
  SIZE 040, 013 OF oFolders:aDialogs[1]  PIXEL


// Folder 2 . Parser do Arquivo XML informado

@ 10,10 SAY "Informe o arquivo XML para Parser" OF oFolders:aDialogs[2] PIXEL
@ 20,10 GET oGetFile VAR cXmlFile SIZE 380, 13 of oFolders:aDialogs[2]  PIXEL

@ 230,10 BUTTON oBtnParseF PROMPT "&Parser" ;
  ACTION (DoParserFile(oDlg,oListResult,oXml,cXmlFile)) ;
  SIZE 040, 013 of oFolders:aDialogs[2]  PIXEL

@ 230,350 BUTTON oBtmParse PROMPT "Sai&r" ;
  ACTION (oDlg:End()) ;
  SIZE 040, 013 of oFolders:aDialogs[2]  PIXEL

// Folder 3 - ListBox com o resultado 

@0,0 LISTBOX oListResult VAR nOpList;
	ITEMS aNodeList ;
	OF oFolders:aDialogs[3]  PIXEL

oListResult:ALIGN := CONTROL_ALIGN_ALLCLIENT

ACTIVATE DIALOG oDlg CENTER 

Return

// Realiza o parser do XML 
STATIC Function DoPArser(oDlg,oListResult,oXml,cXml)
Local lOk
Local nTimer 
Local cMsg

If Empty(cXml)
	MsgInfo('Infome um XML para executar o Parser')
	Return
Endif

// remove quebras de linha e tabulações
cXml := Strtran(cXml,chr(13),'')
cXml := Strtran(cXml,chr(10),'')
cXml := Strtran(cXml,chr(9),'')

nTimer := seconds()
lOk := oXml:Parse(cXml)	
nTimer := seconds()-nTimer

If !lOk
	cMsg := oXml:Error()
	oListResult:SetArray({' '})
	MsgStop(cMsg,"Parse Error")     
Else
	cMsg := "Parser executado em "+str(nTimer,12,2)+' s.'
	BuildResult(oXml,oListResult)
	MsgInfo(cMsg,"Parse OK")     
Endif
	
Return

// Faz parser de um XML lido do disco a partir do RootPAth

STATIC Function DoParserFile(oDlg,oListResult,oXml,cXmlFile)
Local lOk
Local nTimer 
Local cMsg
                                       
cXmlFile := alltrim(cXmlFile)

If Empty(cXmlFile)
	MsgStop('Infome um arquivo XML para executar o Parser')
	Return
Endif

If !file(cXmlFile)
	MsgStop('Arquivo ['+cXmlFile+'] não encontrado.')
	Return
Endif

nTimer := seconds()
lOk := oXml:ParseFile(cXmlFile)	
nTimer := seconds()-nTimer

If !lOk
	cMsg := oXml:Error()
	oListResult:SetArray({' '})
	MsgStop(cMsg,"Parse Error")     
Else
	cMsg := "Parser executado em "+str(nTimer,12,2)+' s.'
	BuildResult(oXml,oListResult)
	MsgInfo(cMsg,"Parse OK")     
Endif
	
Return
                                 
// Monta os dados para um ListBox com os resultados obtidos 

STATIC Function BuildResult(oXml,oListResult,aResult,nStack)
Local cRow          

DEFAULT aResult := {}
DEFAULT nStack := 1
 
While .T.

	// Se o nó atual tem um dado, armazena
	If !XMLempty(oXML:cText)
		cRow := 'Path ['+oXML:cPath+'] Value ['+oXML:cText+']'
		aadd(aResult,cRow)
	Endif
	
	IF oXml:DOMHASATT()
		// Se o nó atual tem atrubitos, lê todos
		aAtt := oXml:DOMGETATTARRAY()
		For nI := 1 to len(aAtt)
			cRow := 'Path ['+oXML:cPath+'] Att ['+aAtt[nI][1]+'] Value ['+aAtt[nI][2]+']'
			aadd(aResult,cRow)
		Next
	Endif
	
	If oXml:DOMHasChildNode()
		// Se o nó atual tem um filho, entra nele para avaliar o conteudo
		oxml:DOMChildNode()
		BuildResult(oXml,oListResult,aResult,nStack+1)
		oxml:DOMParentNode()
	Endif
	
	IF oXml:DOMHasNextNode()
		// Se existe um próximo nó neste nivel, 
		// vai pra ele e continua analizando 
		oxml:DOMNextNode()
		LOOP
	Endif

	// Este nó já foi analizado inteiro, sai do loop 
	EXIT
	
Enddo

If nStack == 1
	// Seta o array de resultado para o ListBox
	If len(aResult) > 0
		oListResult:SetArray(aResult)
	Else
		oListResult:SetArray({' *** NO RESULT *** '})
	Endif
Endif

Return

// Remove caracteres irrelevantes do valor de um node 
// para ver se ele é realmente vazio

Static Function XMLempty(cNodeValue)

cNodeValue := strtran(cNodeValue,chr(13),'')
cNodeValue := strtran(cNodeValue,chr(10),'')
cNodeValue := strtran(cNodeValue,chr(9),'')
cNodeValue := strtran(cNodeValue,' ','')

Return empty(cNodeValue)

Agora, vamos ver o resultado prático deste fonte. Vou usar como exemplo o XML de “pessoas” do post anterior.

<pessoas>
<pessoa tipo="F">
<nome>Julio</nome>
<cpf>123.456.789/09</cpf>
</pessoa>
<pessoa tipo="J">
<nome>Empresa 123 SA</nome>
<cnpj>12.345.678-0001/99</cnpj>
</pessoa>
</pessoas>

Ao executar o programa, entramos com o XML no campo de texto.

zXmkKit 1

Após pressionar o botão “Parser”, o XML será avaliado e os resultados dos dados identificados estão no Folder “Resultado”. Caso você queira informar um arquivo, já para o Folder “XML File”, entre com o caminho do arquivo a partir do RootPath do ambiente atual e pressione o botão “Parser”. Segue o resultado obtido no folder “Resultado”:

zXmkKit 2

Reparem que para cada node do XML avaliado, é mostrado o caminho até o node/dado (formato XPATH) e o valor correspondente, e no caso de atributos, é informado o caminho XPATH, seguido do atributo e do valor do tributo encontrado.

Avaliando o fonte

A função BuildResult() varre recursivamente a estrutura do XML usando os métodos DOM da classe tXmlManager, levantando todas as informações de todos os nós de forma sequencial, e armazenando o resultado em um array de strings, que é atribuído a um tListBox no último Folder. Tão simples quanto isso.

O importante é que a forma de avaliação verifica se o nó atual tem nodes “filho”, e entra neles para fazer a varredura, retornando ao node anterior após a análise, e verificando se existe próximo node no mesmo nível, assim todos os nós do XML são percorridos.

Conclusão

Ainda existem mais truques e recursos para estes componentes, que serão abordados nos próximos posts desta sequência.

Novamente agradeço a audiência, e desejo a todos TERABYTES DE SUCESSO !!!

 

O que é CODEPAGE e ENCODING – Parte 03

Introdução

No post anterior (O que é CODEPAGE e ENCODING – Parte 02) vimos como o AdvPL trata strings e conversões entre diferentes encodings. Agora, vamos ver … MAIS ENCODINGS 😀

BASE64, o que é e como funciona ?

Imagine que você precisa transportar ou armazenar, por exemplo, uma imagem — pode ser um arquivo no formato BMP, JPG, PNG, GIF, etc — e o arquivo que contém a imagem contém sequencias de bytes de 0 a 255 … mas o meio de transporte que você vai usar — por exemplo um XML — não suporta você colocar isso “direto” dentro do XML como um conteúdo de um node.

Então, existe uma forma de você codificar um buffer binário, com bytes de 0 a 255, usando um alfabeto restrito de 64 bytes da tabela ASCII, chamado de “Base 64”. Na prática, a conversão de uma sequencia de bytes para Base64 consiste em converter cada byte da string para uma sequência binária (aquela sequencia de 0s e 1s), e converter esta sequencia para o alfabeto do Base64 de 6 em 6 bits.

JULIO em base64 é representado por "SlVMSU8="

Em Advpl, existem as funções Encode64() e Decode64(), criadas para converter qualquer string — mesmo em formato binário — para Base64, e vice-versa. Quem utiliza WebServices e precisa trafegar o conteúdo binário de uma imagem normalmente utiliza esta codificação para os dados deste node. Existe um tipo de dado descrito na especificação do SOAP XML para isso.

Este tipo de codificação também pode ser usado em cabeçalhos de requisições HTTP, quando o servidor exige um tipo de autenticação simples, conhecido por “basic access authentication”. O cliente que faz a requisição precisa inserir no HEADER da requisição HTTP uma tag no formato “Authorization: Basic <credentials>”, onde <credentials> é a string contendo o nome do usuário e senha, separados por  “:”, e representado em Base64.

Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l

Hexadecimal e Octal

Finalmente, chegamos nele! A representação hexadecimal consiste em usar um alfabeto restrito de 16 símbolos (daí o seu nome , hexadecimal, ou base 16), para representar 16 valores diferentes — ou 4 bits. Já o octal usa apenas 8 símbolos ( os números de 0 a 7, ou 3 bits) para representar um valor.

Usando a calculadora do Windows (8 ou superior), no modo “Programmer”, podemos entrar com um número nos formatos decimal, hexadecimal, octal ou binário, e ela automaticamente mostra todas as representações deste número. Por exemplo, o número decimal 255 pode ser representado como:

Calculadora - Prog

Em Advpl existem as funções __HEXTODEC() e __DECTOHEX() para fazer as conversões de hexadecimal para decimal e vice-versa.

UTF-16

Devido a diferenças de arquitetura entre equipamentos e plataformas, uma dupla de bytes na memória pode ser representada internamente dentro da máquina com o byte mais significativo do lado direito ou do lado esquerdo. Essa característica chama-se “endianess“, onde algumas plataformas armazenam números inteiros na memória em ordem crescente de peso numérico (little-endian) ou decrescente (big-endian). Por exemplo, as plataformas Intel x86 e x86_64 são little-endian, PowerPC e SPARC são big-endian. Como o UTF-16 trabalha com duplas de bytes ( 16 bits ), é necessário informar qual a ordenação interna a ser usada. Para isso, as funções EncodeUtf16 e DecodeUTF16 do AdvPL possuem um parâmetro adicional para informar se a conversão deve ser de/para Big-endian (default) ou Little-endian.

AdvPL e as funções CHR() e ASC()

Estas funções servem para, respectivamente, retornar um byte em formato de string AdvPL (tipo ‘C’ Caractere), passando o valor do byte, e recuperar o valor do primeiro byte de uma string AdvPL, respectivamente. Por exemplo:

conout(chr(65)+chr(66)+chr(67)) // resultado "ABC"
conout(asc("A")) // resultado 65

CP-1252 e caracteres não suportados

Quando você verifica em detalhes uma tabela de CodePage, como por exemplo a CP-850, existe uma representação gráfica para todos os bytes da faixa superior ( 128 a 255 ). POREM, o CP-1252 possui 5 códigos VAZIOS. Isso mesmo, não usados, sem representação gráfica, e naturalmente inválidos nesta tabela. São eles : 129, 141, 143, 144 e 157

Logo, se você montar uma string em AdvPL com estes caracteres, e enviar para uma classe de interface, no lugar destes caracteres, ou será mostrado um espaço em branco, ou um caractere diferenciado para indicar que não existe representação deste byte. Se você tentar converter um destes caracteres para UTF-8 usando  por exemplo a função EncodeUTF8() do AdvPL, ela vai retornar uma string em branco, indicando uma situação de erro, pois o parâmetro informado contém um byte que não pertence ao CP-1252.

Da mesma forma, como a função DecodeUTF8() pode receber qualquer um dos 107 mil caracteres da tabela Unicode, ela somente vai conseguir converter de UTF-8 para CP-1252 os caracteres que possuam representação no CP-1252.

Eu tenho como descobrir como uma string está codificada ?!

Bem, se você não tiver esta informação… você pode “chutar”, mas isso está muito sujeito a erro. Ao olhar para uma string, se nenhum byte dela é maior que 127, ela contém apenas caracteres da tabela ASCII, mas ela pode ser UTF-8 — afinal se não foi usado nenhum caractere acentuado na string, sua representação será igual. Se a string tiver algum caractere a partir do 128, ela pode ser de algum CodePage, ou mesmo UTF-8. A diferença é que, em UTF-8, a sequência de bytes após o primeiro byte acima de 128 precisa formar uma combinação válida, e quando for usado um codepage — single byte ecoding — cada um desses bytes possui uma representação de um símbolo de acordo com o Codepage esperado que seja usado.

Conclusão

Tudo é bem simples quando usamos apenas letras e números,  mas mesmo assim eles podem ter diferentes significados dependendo do contexto. É a diferença básica entre DADOS e INFORMAÇÕES. Todos os arquivos em um sistema informatizado contém dados, eles apenas são considerados como informações quando você entende o que os dados representam.

Espero que estas informações sejam úteis e esclarecedoras, e desejo a todos TERABYTES DE SUCESSO !!! 

Referências

 

 

 

O que é CODEPAGE e ENCODING – Parte 02

Introdução

No post anterior (O que é CODEPAGE e ENCODING – Parte 01) vimos que por baixo do capô que tudo são bytes, e a interpretação deles depende da aplicação que o manipula e como o texto está codificado (ASCII, CP-nnnn ,UTF-8, etc) . Agora, vamos ver o impacto disso em interface, processamento e armazenamento de dados, relacionado ao ADVPL !!!

CP-1252 e ANSI

O Windows em Inglês, Português, e demais idiomas que usam o alfabeto Romano/Latino utiliza o codepage CP-1252 — também conhecido por WIN-1252. Veja a tabela completa nas referências, no final do post. Nas primeiras versões do Windows, a expressão “ANSI Code Page” foi utilizada para designar os codepages novos, não usados no DOS. Oficialmente o CodePage 1252 não virou um “ANSI Standard” (padrão oficial documentado pela A.N.S.I — American National Standard Institute),  por isso muitos programas se referem ao codepage do Windows como “ANSI”.

Microsiga, AdvPL e Protheus

Os códigos em AdvPL antes do Protheus foram escritos usando codepage CP-437 , e posteriormente o codepage CP-850 — que permitia caracteres acentuados em português — usados no DOS na época. Como existiam duas versões do ERP Siga Advanced — um executável para DOS e outro para Windows, e os componentes de interface do Windows usam o codepage CP-1252, existem duas funções para realizar a conversão de-para o CP-850 e o CP-1252. Chamam-se OemtoAnsi() e AnsiToOem() — e existem até hoje no Protheus. Embora o nome destas funções seja um tanto genérico, entenda-se por “OEM” o codepage do DOS CP-850, e por “ANSI” o codepage CP-1252 do Windows.

Quando o ERP Siga Advanced era configurado para permitir caracteres acentuados, estes eram gravados nas tabelas de dados do ERP — DBF na época — com o codepage em uso (CP-850). Porém, para mostrar este nome em um componente visual na versão Windows, o dado precisava ser convertido usando OemToAnsi(). A mesma conversão também era necessária ser feita com as mensagens em string presentes no código-fonte para serem visualizadas corretamente na interface.

Protheus, DBAccess e CP-1252

Com o nascimento do Protheus como plataforma de desenvolvimento client-server, com porte para Windows e Linux, foi adotado oficialmente o codepage CP-1252 para strings, fontes, includes e dados. Por isso, quando usamos o TDS (Totvs Development Studio) e/ou o VSCode para AdvPL, devemos configurar que os arquivos fonte devem ser tratados como CP-1252, e os bancos de dados relacionais (SQL) homologados para o Protheus devem também usar especificamente este codepage ou um equivalente — como o MSSQL por exemplo, o charset (definição de caratere) usado chama-se “Latin1”, o que corresponde diretamente ao CP-1252. No Banco de Dados Oracle, por exemplo, o charset chama-se “WE8MSWIN1252”

Pela mesma razão, as ODBCs utilizadas para conexão do DBAccess com os bancos de dados homologados, quando existem duas versões (ANSI ou UNICODE), deve ser usada a versão ANSI.

Protheus e UTF-8

Quando criamos páginas WEB (AdvPL-ASP) ou vamos integrar sistemas usando XML / Web Services, podemos receber conteúdos e dados codificados em UTF-8 (Unicode), mas para mostrar estes dados na interface, manipulá-los e gravá-los na base de dados, precisamos converter entre UTF-8 e CP-1252. Para isso, existem os seguintes recursos:

  • Funções AdvPL EncodeUTF8() e DecodeUTF8().

A primeira recebe uma string em CP-1252 e converte para UTF-8, enquanto a segunda faz a conversão de UTF-8 para CP1252.

  • Parsers XML ( XmlParser e tXMLManager)

Como os parsers XML podem receber dados em UTF-8 (Unicode), eles podem receber qualquer caractere de qualquer alfabeto. Porém, a linguagem AdvPL trabalha com o CP-1252. Logo, os parsers XML do AdvPL lêem os conteúdos do CML em UTF-8, e já convertem internamente para CP-1252, assim se um XML vir com uma string acentuada no alfabeto latino, a consulta ao valor da propriedade já vai retornar uma string no codepage da linguagem.

  • Web Services AdvPL ( Client e Server – SOAP ) 

Da mesma forma, uma string alimentada pelo AdvPL em uma propriedade de WebServices com o CP-1252, vai ser convertida internamente para UTF-8 na geração do XML SOAP para envio, e na recepção de dados.

Strings em AdvPL

Uma String — variável do tipo ‘c’ caractere em AdvPL — pode conter bytes de valor 0 a 255. Logo, você pode abrir um arquivo no disco — usando as funções de acesso direto a disco e arquivos FOpen() e FRead() — e ler qualquer coisa, desde texto em qualquer formato, imagens, etc. Porém, os componentes da interface visual do AdvPL — como tSay, tGet e afins — vão tratar este conteúdo como CP-1252.

Comparação de Strings – operadores > e <

O AdvPL compara strings pelo valor de seus bytes. Isso chama-se comparação BINÁRIA. Logo, ao comparar se uma string de mesmo tamanho é maior ou menor que outra, a sequência de bytes da string é percorrida desde o início e sendo comparada um a um, enquanto os valores forem iguais, até que o primeiro valor diferente entre as strings seja encontrado.

DBACCESS e SGDBs — O que é  “collation” ?

Até agora falamos de codificações (encoding) e formatos de representação de strings. Um banco de dados relacional possui tipos de dados criados para trabalhar com um codepage, e tipos de dados para trabalhar com Unicode. Normalmente os tipos CHAR e VARCHAR trabalham com um codepage, e os tipos NCHAR e NVARCHAR trabalhar com Unicode. Essa nomenclatura de tipos é praticamente universal.

Porém, o Banco de Dados precisa de mais informações para definir COMO ele deve tratar estes dados, em um caso de comparação e ordenação. As informações de como este dado será codificado e tratado é definida por um configuração de COLLATION. Normalmente eu posso escolher um collation para os campos que trabalham com um codepage, que define regras como :

  • Letras maiúsculas e minúsculas devem ser consideradas iguais
  • Letras acentuadas e não-acentuadas também devem ser tratadas iguais
  • Ao solicitar uma Query com uma determinada ordem, a ordem pode ser linguística (ordem fonética do idioma) ou binária (comparação dos valores dos bytes).

O collation do banco de dados que é compatível com o tratamento de strings do AdvLP é chamado de “collation binário”, onde a ordenação de strings é feita pelo valor dos bytes de seu conteúdo, e letras maiúsculas, minúsculas ,acentuadas e não-acentuadas são diferentes.

Então é por isso que os bancos de dados para o ERP Microsiga devem ser criados com um Collation de ordenação binaria ? R : SIM ! Vamos ver mais abaixo o porque.

O Problema de JOSÉ e JOÃO

Vamos pegar o CP-1252 do Windows, e ver quais são os bytes usados para representar estes dois nomes, com os respectivos acentos, em letras maiúsculas:

J = 74
O = 79
S = 83
à = 195
É = 201

JOÃO = 74,79,195,79
JOSÉ = 74,79,83,201

Logo, se eu colocar estes dois nomes em String no AdvPL, e perguntar se “JOÃO” é MENOR QUE “JOSÉ”, a resposta será .F. (falso). Pois embora alfabeticamente JOÃO vêm ANTES de JOSÉ, como o AdvPL trabalha com comparação binária de strings, a partir do 3o byte, a letra “S” de JOSÉ tem um valor menor que a letra “Ô de JOÃO.

Então, o que acontece se eu  usar um collation linguístico ?

Bem, primeiramente um collation “linguístico” é qualquer collation não-binário. E, um collation linguístico vai trazer os dados ordenados alfabeticamente de acordo com o idioma utilizado. Vamos pegar um exemplo de collation do MSSQL: Latin1_CI_AI ( CP1252, Idioma latino, case-insensitive ( CI = minúsculas e maiúsculas são consideradas iguais para comparação), e accent-insensitive ( AI = letras acentuadas ou não são consideradas iguais para efeito de comparação).

Usando esse collation no banco de dados, o comportamento das Queries em comparação e ordenação de resultado vai mudar. Por exemplo:

Na TABELA existem quatro registros : JOAO, JOSE, JOÃO e JOSÉ 

-- Collation Latin1_CI_AI
SELECT NOME FROM TABELA WHERE NOME = 'JOSE'
-- Serão retornados "JOSE" e "JOSÉ"

-- Collation Latin1_CI_AS
-- Será retornado apenas "JOSE", sem acento

O banco de dados está correto, este é o comportamento para  o qual ele foi configurado. Agora, se em AdvpL eu receber estes valores, e compará-los com a string “JOSE” sem acento, elas serão diferentes.

Se eu tiver na base de dados os nomes JOSE e JOAO, sem e com acento, e pedir a lista de nomes ordenada por nome, usando um collation linguístico — não-binário — a ordem de retorno será:

JOAO
JOÃO
JOSE
JOSÉ

Agora, se em AdvPL , cada registro retornado for testado com a comparação abaixo, eis o que acontece:

IF cNome >= "JOAO" .and. cNome <= "JOSE"
   conout("Nome .... " + cNome )
Endif

-- Como a comparação é binária, apenas os 
-- nomes sem acento serão considerados

Na prática, um collation linguístico somente pode ser usado em um banco de dados se a aplicação estiver preparada para lidar com estas diferenças. Vou agora dar um exemplo um pouco pior. Se eu usar em AdvPL um Collation CI_AI ( maiúsculas, minúsculas, letras acentuadas ou não são consideradas iguais), o que pode acontecer ?

R: Se você possui algum tipo de código em alguma tabela, onde você pode usar letras maiúsculas e minúsculas, mas elas devem ser tratadas como diferentes, uma Query feita selecionando o código “a1” pode trazer os códigos “a1” e “A1”.

R: Se existe algum campo na base de dados que grava um conteúdo codificado (como o ERP faz com os campos USERLGI e USERLGA), uma busca com uma Query vai considerar caracteres acentuados iguais aos não acentuados, minúsculas iguais a maiúsculas, e retornar dados a mais.

R: Mesmo que a ordenação retornada pelo banco de dados esteja linguisticamente correta, se eu REVALIDAR a ordem comparando a String retornada pelo banco de dados usando a comparação (binária) de strings no AdvPL, como por exemplo nome >= “JOAO” ( sem acento) .AND. nome <= ‘JOSÉ’ ( com acento), somente serão impressos os nomes JOAO (sem acento), JOSE (sem acento) e JOSÉ (com acento). JOÃO com acento não será considerado, pois é maior que “JOSÉ” na comparação binária. Se houver qualquer um destes nomes com letras minúsculas na base de dados, nenhum passa nessa comparação.

E, só pra ficar mais divertido , exitem diferenças de ordenação do mesmo collation entre bancos de dados diferentes. Independentemente do collation ser case sensitive ou não, accent sensitive ou não, a ordem de retorno pode mudar entre bancos de dados. Alguns bancos agrupam o retorno com as letras maiúsculas primeiro e depois as minúsculas, outros fazem o contrário — minúsculas primeiro –, outros agrupam as palavras acentuadas depois das não acentuadas, outros trazem as acentuadas próximas das não acentuadas. 

Conclusão

Para o AdvPL e o DBAccess, tanto faz o Collation usado no banco. Porém, a aplicação escrita na linguagem AdvPL que conta com um comportamento de comparação e ordenação binária teria que ser adequada e refatorada, e algumas funcionalidades da emulação ISAM — como o DBSEEK com SOFTSEEK ( posicione no primeiro registro imediatamente maior do que a chave informada caso esta não seja encontrada ) podem ter comportamento diferentes do esperado e diferentes entre bancos de dados.

Espero que tenham gostado, e desejo a todos TERABYTES DE SUCESSO !! 

Referências

 

 

O que é CODEPAGE e ENCODING – Parte 01

Introdução

Sempre existem algumas dúvidas que dão nó na cabeça de muita gente … acho que a mais famosa é sobre a representação de caracteres em um computador, e como o sistema operacional e as aplicações lidam com isso. Vou tentar clarear um pouco disso nesse post, e explicar por quê saber disso é muito, muito importante. Depois de ler isso, você vai entender o que é CodePage, Character Encoding, ASCII, CP-1252, UNICODE, UTF-8, UTF-16 e afins!

O Byte

Vamos começar do princípio da criação … o BYTE ! Não interessa o nome ou a extensão de um arquivo em um computador, todos eles têm uma coisa em comum: Todos são compostos por uma sequência de BYTES. Um byte representa 256 valores numéricos, de 0 a 255. Dentro do computador, na memória, tudo são BYTES, sejam dados ou mesmo programas, tudo são bytes. Simples assim.

Porém, para que eu possa representar informações, como textos e imagens, usando BYTES — números de 0 a 255 — eu preciso agrupá-los ou definir um FORMATO para que eles sejam interpretados como tal. Tendo isso em mente, existe uma tabela binária chamada ASCII (American Standard Code for Information Interchange), que codifica um conjunto de 128 sinais: 95 sinais gráficos (letras do alfabeto latino, sinais de pontuação e sinais matemáticos) e 33 sinais de controle, utilizando apenas 7 bits para representar todos os seus símbolos — do 0 ao 127.

Segundo a tabela ASCII, a letra “A” maiúscula equivale ao número 65. O caractere correspondente ao sinal de ‘+’ soma é o número 43, e assim por diante. As 26 letras maiúsculas do alfabeto romano / latino são representadas em ordem alfabética do número 65 ao 90, as minúsculas do número 97 ao 122.

Embora esta tabela seja muito utilizada, existe uma outra tabela chamada EBCDIC, criada pela IBM na década de 60, usada em seus mainframes até hoje. Como a tabela é diferente, os números representam outros caracteres ou símbolos. A tabela EBCDIC usa 8 bits, e por exemplo, a letra “A” — que na tabela ASCII é o número 65 — equivale ao número 193 em EBCDIC.

Se o BYTE tem 8 bits, por que a tabela ASCII usa 7 ?

Essa é a pergunta certa !!! A sacada da tabela ASCII foi deixar o primeiro BIT reservado para representar outros caracteres de outros alfabetos. Na prática , um CODEPAGE ou um CHARACTER ENCODING são formas de representar outras letras e símbolos de outros alfabetos, usando a outra metade da tabela ASCII ( do numero 128 ao 255 ) — onde representamos os demais caracteres de um outro alfabeto informando ao programa qual é a página de códigos (ou CODEPAGE) que deve ser utilizada para interpretar os números dessa faixa.

Por exemplo, no antigo MS-DOS, a versão em inglês vinha pré-configurada com o CodePage (ou simplesmente CP) 437. Nessa definição, por exemplo, a faixa de bytes do 180 ao 218 representavam vários símbolos gráficos para desenhar linhas e colunas horizontais e verticais simples ou duplas, e todas as possibilidades de cantos, junções e cruzamentos. Quando você instalava o DOS para o Idioma português-brasileiro, a página de código em uso era a CP-850, onde uma parte destes símbolos gráficos foi mantida, mas os caracteres usados para representar as junções entre os caracteres gráficos de linha simples ou dupla horizontais e verticais foi trocada para representar os caracteres acentuados do idioma latino. Isso gerava resultados engraçados, pois um programa feito para desenhar uma tela de texto usando por exemplo uma janela com uma barra horizontal simples, e uma barra vertical dupla, onde em cada canto dessa janela deveria ter um caractere de junção, feito para rodar no DOS configurado para CP-437, quando era executado no DOS configurado com o CP-850, no lugar desses cantos apareciam letras acentuadas.

Conclusões iniciais

O programa originalmente mandou desenhar o caractere de código 182. Se o sistema operacional está configurado para o CP-437, deve aparecer na tela o caractere ‘‘  (barra vertical dupla com junção horizontal simples à esquerda), mas se o CodePage em uso for o CP-850, vai aparecer na tela um ‘Á‘ (A maiúsculo acentuado).

Logo, se você têm um arquivo de texto simples — identificado normalmente pela extensão .TXT — e dentro desse arquivo existe um byte 182, ele será mostrado de forma diferente de acordo com a página de códigos em uso pelo programa que está abrindo e mostrando o arquivo na tela.

Vantagens e desvantagens do CODEPAGE

O uso de páginas de código permitiu de forma simples aproveitar o primeiro bit de cada byte para representar símbolos de outros alfabetos, usando apenas um byte. Esse tipo de recurso é conhecido como single byte encoding, ou codificação de byte único. Por outro lado, se você precisa representar dois símbolos de alfabetos diferentes na mesma tela ou no mesmo arquivo, isso não era possível. Afinal, o CODEPAGE é uma definição aplicada ao arquivo inteiro. E, se você está lendo um arquivo de texto ou dados, você somente vai saber o que um determinado byte significa (da faixa superior de 128 a 255) se você souber o CODEPAGE no qual ele foi criado.

UNICODE

Unicode é o nome de um padrão internacional para representar qualquer escrita existente. Na prática, trata-se de uma tabela imensa, com mais de 107 mil caracteres. Agora, eu preciso representar esses caracteres usando BYTES, certo ? Então, existem algumas regras para codificar (character encoding) estes caracteres usando bytes. Estas regras são descritas pelos esquemas de transformação Unicode (ou Unicode Transformation Format, UTF).

A representação dos caracteres da tabela Unicode pode ser feita em bytes usando três esquemas de transformação: UTF-8 , UTF-16 e UTF-32. Todos estes esquemas são caracterizados por ser um multibyte encoding. Isto é, pode ser necessário ou obrigatório usar mais de um byte para representar uma letra ou símbolo da tabela Unicode. Vamos ver agora a diferença entre cada um deles.

UTF-8

Trata-se de um método de transformação para representação de um caractere da tabela Unicode usando de 1 a 4 bytes. Quando usamos UTF-8, todos os 128 primeiros caracteres da tabela ASCII são representados exatamente da mesma forma. Isto é, a letra “A” é representada pelo byte 65, tanto na tabela ASCII como em UTF-8. Caso um byte tenha o valor entre 128 e 255 — primeiro bit ligado — isso significa que os demais 7 bits desse byte contém apenas uma parte das informações para representar o caractere, e que os próximos 1, 2 ou 3 bytes seguintes serão usados para completar esta informação.

Prós e Contras do UTF-8

Acredito que este seja um dos mais difundidos métodos de representação Unicode utilizados mundialmente, entre plataformas e aplicativos. A vantagem é a economia relativa de espaço, pois vários caracteres acentuados do idioma latino por exemplo usam de dois a três bytes em sequencia para serem representados, e todos os demais símbolos comuns da tabela ASCII ainda usam um byte.

Porém, uma vez que você têm a unidade de armazenamento destes caracteres como sendo o BYTE, o nome ‘Julio’ sem acento ocupa 5 bytes na memória, enquanto ‘Júlio’ com acento, codificado em UTF-8 ocupa 6 bytes — a letra minúscula ‘ú’ é representada em UTF-8 usando dois bytes : 195 e 186

Logo, imagine que você precise guardar o nome de uma pessoa em uma tabela, porém você não tem a possibilidade de trabalhar com um campo com conteúdo de tamanho variável, e você deve especificar este tamanho em BYTES, e originalmente você separou 60 bytes pra gravar o nome do cidadão. Originalmente, qualquer nome escrito com o idioma latino sem acentuação usando UTF-8 vai usar apenas um byte. Porém, se a sua base de dados é global, e você precisa garantir que mesmo um nome grande usando o alfabeto cirílico ou árabe “caiba” dentro desse campo, você precisa alocar quase 4 vezes o número de bytes para não estourar o tamanho de armazenamento do campo, ou suprimir algum nome do meio …

UTF-16 e UTF-32

Trata-se de outra forma de codificar o mesmo caractere da tabela Unicode usando mais ou menos bytes. Enquanto UTF-8 usa de 1 a 4 bytes para representar qualquer caractere, o UTF-16 usa sempre 2 ou 4 bytes para fazer isso, e a UTF-32 usa sempre 4 bytes para representar qualquer caractere da tabela Unicode.

Exemplo prático

Eu abri um prompt de comando no meu computador. Meu prompt de comando está configurado para usar CP-437. Eu vou criar um arquivo de texto com o meu nome dentro, usando a letra ‘ú’ — representada pelo byte 163 ( ou ASCII 163 )

C:\Temp>echo Júlio >nome.txt

C:\Temp>type nome.txt
Júlio

C:\Temp>mode

Status for device CON:
----------------------
    Lines:          9001
    Columns:        120
    Keyboard rate:  31
    Keyboard delay: 1
    Code page:      437

C:\Temp>

O arquivo nome.txt deve ter 8 bytes de tamanho. Ele contém 5 bytes correspondentes aos caracteres do meu nome, mais um espaço em branco ( ASCII 32 ) e a quebra de linha de texto do padrão do DOS/Windows ( ASCII 13 + ASCII 10 ). A letra ‘ú’ minúscula e com acento agudo existe no CodePage 437 e no CodePage 850, como o código ASCII 163, que está gravado no arquivo.

Agora, eu vou abrir este arquivo usando o NOTEPAD, e veja o que acontece:

Julio - Notepad

Então, eu acabei de criar este arquivo no prompt de comando, e quanto eu mostrei o conteúdo do arquivo, apareceu certinho na tela. Agora eu abri o Notepad e me aparece o símbolo da Libra Esterlina ao invés do ‘ú’ ?! Sim, isso mesmo.

POR QUE ? COMO ?

Então, o Windows utiliza por padrão OUTRO CODEPAGE — O CP-1252 (também conhecido por WIN-1252) é usado nas versões do Windows em Inglês e contempla os caracteres do idioma latino. Porém, por ser uma tabela diferente, o byte 163 nessa tabela é usado para a libra esterlina, enquanto a letra ‘ú’ é representada pelo byte 250 !

Agora, se eu abrir o arquivo nome.txt usando um editor de textos um pouco mais avançado, como o Notepad++, logo de cara ele também vai mostrar a libra esterlina no ligar do ‘ú’. Porém, eu posso configurar no editor qual é a codificação de origem do arquivo de forma manual — Menu Encoding -> Character Sets -> Western European ->OEM-850 ( o Codepage 850 também é conhecido por OEM-850). Pronto, agora aparece certinho !

Julio - OEM-850

Da mesma forma, se eu criar o arquivo usando o NOTEPAD, e entrar com o nome ‘Júlio’, ele será criado no disco com apenas 5 bytes, e a letra ‘ú’ com o byte 250. Quando eu abrir o prompt de comandos do Windows ( CMD ), e mostrar o conteúdo do arquivo, acontece isso:

C:\Users>type nome2.txt
J·lio

Agora, se dentro do prompt de comando, eu trocar o codepage em uso de CP-437 para CP-1252, aí ele mostra o que eu queria ver:

C:\Users>mode con cp select=1252

Status for device CON:
----------------------
Lines: 9001
Columns: 120
Keyboard rate: 31
Keyboard delay: 1
Code page: 1252


C:\Users>type nome2.txt
Júlio

E com UTF-8 ? Como fica ?

Usando o NOTEPAD do Windows, vou criar um arquivo chamado nome3.txt, com o conteúdo ‘Júlio’, mas ao SALVAR o arquivo, eu vou informar que eu quero esse arquivo salvo no formato UTF-8.

Julio - UTF-8

Quando eu abro o prompt de comando (CMD) e tento mostrar o arquivo, aparece isso aqui:

C:\Users\siga0>type nome3.txt
Júlio

Apareceu três letras estranhas antes do “J” ?! E no lugar do acento outras duas letras ?! Que bruxaria !!! 

Quando eu peço para o NOTEPAD gravar meu texto como Unicode em UTF-8, ele acrescenta três bytes no inicio do arquivo. Estes bytes são conhecidos como BYTE ORDER MARK, e são usados para identificar se um arquivo de texto Unicode está em formato UTF-8, UTF-16 ou UTF-32. O indicador de UTF-8 são os bytes 239, 187 e 191, nesta ordem. Já a letra ‘ú’ virar dois bytes, esse era o comportamento esperado, como vimos anteriormente ela é representada em UTF-8 pelos bytes 195 e 186.

Abracadabra

Agora vamos fazer o conteúdo ser mostrado de forma correta no prompt de comando do Windows:

C:\Users>mode con cp select=65001

Status for device CON:
----------------------
Lines: 9001
Columns: 120
Keyboard rate: 31
Keyboard delay: 1
Code page: 65001


C:\Users>type nome3.txt
Júlio

Sim, eu consigo dizer pro terminal do Windows suportar UTF-8 de forma nativa, setando o Codepage para 65001 ! Com isso, ao mostrar o conteúdo do arquivo nome3.txt, ele é interpretado de forma correta. Agora, usando o terminal em UTF-8 , olha o que acontece quando eu tento mostrar o conteúdo dos outros arquivos anteriores:

Julio - Wrong

Repare que, no lugar do caractere acentuado, foi mostrada uma interrogação dentro de um retângulo ! Sim, pois o terminal está configurado para decodificar UTF-8 … e os arquivos anteriores não foram criados com UTF-8, os bytes isolados da letra ‘ú’ não representam uma sequência válida de conversão, logo não há o que ser mostrado, e a interface do Widows mostra apenas uma “?” ….

Conclusão

Acho que por hoje já tem muita informação … No próximo post vamos ver mais de perto a relação dessas codificações com o AdvPL !!!!

E desejo a todos TERABYTES DE SUCESSO !!!!

Referências

 

 

QR Code em AdvPL – Parte 03

Introdução

No post anterior vimos uma breve introdução a classe ZQRCODE. Agora, vamos ver um fonte usando essa classe ! A classe faz parte do projeto ZLIB, fontes disponíveis no GITHUB.

QR Code Print

Geração de QR Code

A geração da imagem de um determinado QR Code envolve duas etapas. A primeira é criar a matriz de bits ou Grid do QR Code, e a segunda é a criação da imagem — por hora um Bitmap monocromático. O fonte abaixo dá conta do recado usando a classe ZQRCODE e a ZBITMAP.

#include 'protheus.ch'
#include 'zlib.ch'

#define MODULE_PIXEL_SIZE     12   //  Tamanho em PIXELs do Modulo

/* ------------------------------------------------
Primeira interface para a criação de um QR Code 
------------------------------------------------ */

USER Function NewQRCode()
Local oDlg
Local cQRData := space(240)
Local cErrC , aErrorC := {}
Local nMode := 2  // Alfanumerico
Local aMasks := {}                
Local cMask 
Local nI
Local oQR

// Cria uma caixa de diálogo com área util de 800x600  PIXELs
DEFINE DIALOG oDlg TITLE "QR Code em AdvPL" FROM 0,0 TO 600,800 PIXEL

// Monta um GET para entrada de dados em variável string
// @ 18,15 SAY oSay1 PROMPT "Get String" SIZE 30,12 OF oDlg  PIXEL
@ 18,15 SAY "QR Data" PIXEL
@ 15,50 GET oGet1 VAR cQRData MULTILINE SIZE 260,36 OF oDlg  PIXEL

// Lista de níveis de correções de erro disponíveis 
aadd(aErrorC,"L.Low")
aadd(aErrorC,"M.Medium")
aadd(aErrorC,"Q.Quartile")
aadd(aErrorC,"H.High")

// Monta um objeto Combo, para escolha de string entre elementos de um array
@ 58,15 SAY "Error C" PIXEL
@ 55,50 COMBOBOX oComboBox1 VAR cErrC ITEMS aErrorC SIZE 80,12 OF oDlg  PIXEL

// Lista de Tipos de Mascaramento de dados 
// TODO - Escolha automatica de mascaramento 
// aadd(aMasks,"A.Auto")
For nI := 0 to 7
	aadd(aMasks,cValToChar(nI)+".Mask "+cValToChar(nI))
Next

// Monta um objeto Combo, para escolher o mascaramento de dados
// ou deixar no automatico ( TODO ) 
@ 73,15 SAY "Mask" PIXEL
@ 70,50 COMBOBOX oCombo2 VAR cMask ITEMS aMasks SIZE 80,12 OF oDlg  PIXEL

// Area reservada a Imagem 
@ 55,135 BITMAP oBmpQRCode OF oDlg PIXEL 
  
// Reserva a imagem em branco com 
// o Tamanho da versao 1 com margem de 4 modulos ) 
oBmpQRCode:nWidth := 29*MODULE_PIXEL_SIZE  
oBmpQRCode:nHeight := 29*MODULE_PIXEL_SIZE
oBmpQRCode:lStretch := .T.

// Botao para a geracão do QR Code
@ 85,50 BUTTON oButton1 PROMPT "&Gerar QR Code" ;
  ACTION (NewQRCode(@oQR,cQRData,cErrC,nMode,cMask,oBMPQRCode)) ;
  SIZE 080, 013 of oDlg  PIXEL

@ 100,50 BUTTON oButton2 PROMPT "&Exportar Imagem" ;
  ACTION (ExportImg(oQR)) WHEN oQR != NIL ;
  SIZE 080, 013 of oDlg  PIXEL

ACTIVATE DIALOG oDlg CENTER 

Return

// ------------------------------------------------
// Geração do QR Code na interface

STATIC Function NewQRCode(oQR,cQRData,cErrC,nMode,cMask,oBMPQRCode)

IF oQR != NIL
	FreeObj(oQR)
Endif

// Cria QRCode vazio 
oQR := ZQRCODE():New()

// Trata os dados informados 
cQRData := alltrim(cQRData)
cErrC := left(cErrC,1)
cMask := left(cMask,1)
                             
If empty(cQRData)
	MsgStop("Dados para o QR Code não informados.")
Endif

// Inicializa o QRCode com Error Correction "L", sem versao definida
// Informa o Error Correction
oQR:SetEC(cErrC)

// Seta o dado a ser colocado no QRCode
// e seta o modo de codificacao ( 1 = Numerico, 2 = Alfanumerico, 3 = Bytes , 4 = Kanji )
oQR:SetData(cQRData,nMode)

// Monta a matriz de dados 
// Se a versao nao foi especificada, determina de acordo 
// com o tamanho dos dados a serem plotados
// oQR:oLogger:SETECHO(.T.)

oQR:BuildData()

// oQR:oLogger:SETECHO(.F.)

// Monta a matriz final do QRCode 
oQR:DeployBits()

/*
conout("After Deploy Bits ")
oQR:PrintBits()
*/

// Seleciona a mascara de dados 
// Caso seja automatico, nao informa
// para a engine de emissao decidir
// O método SelectMask já aplica a máscara
If cMask <> 'A'
	oQR:SelectMask(val(cMask))
Else
	oQR:SelectMask()
Endif

/*
conout("After Mask Data ")
oQR:PrintBits()
*/

// Exporta para um BITMAP monocromatico
// Margem minima = 4 modulos ( de cada lado ) 
oBMP := ZBITMAP():NEW(oQR:nSize+8,oQR:nSize+8,1)

// Plota os módulos setados com 1 
// com a cor preta no Bitmap

For nL := 1 to oQR:nSize
	For nC := 1 to oQR:nSize
		If oQR:aGrid[nL][nC] == 1 .or. oQR:aGrid[nL][nC] == 3
			oBmp:SetPixel(nL+3,nC+3,0)
		Endif
	NExt
Next	

// Gera a imagem em disco 
// CRia um arquivo temporario com nome unico 
// para driblar o cache de imagem da interface
cFile := '\qrtmp_'+cvaltochar(seconds())+'.bmp'
oBmp:SaveToBmp(cFile)

// Redimensiona a imagem na tela
// com o tamanho da versao escolhida +margens
oBmpQRCode:nWidth := (oQR:nSize+8)*MODULE_PIXEL_SIZE 
oBmpQRCode:nHeight := (oQR:nSize+8)*MODULE_PIXEL_SIZE
oBmpQRCode:lStretch := .T.

// Carrega a imagem gerada na interface
oBMPQRCode:Load(,cFile)

// apaga a imagem temporária do disco 
// e limpa a classe bitmap da memoria 
ferase(cFile)
FreeObj(oBmp)

Return


// ------------------------------------------
// Exporta o QR Code para imagem BITMAP 

STATIC Function ExportImg(oQR)
Local nImgSize
Local oBmp
Local nL
Local nC
Local nL1,nL2,nC1,nC2
Local cFile

nImgSize := ( oQR:nSize+8 ) * MODULE_PIXEL_SIZE

oBMP := ZBITMAP():NEW( nImgSize,nImgSize, 1 )
oBMP:nPenSize := 1

// Plota os módulos setados com 1 
// com quadrados vazados no Bitmap
// e pinta o interior dos quadrados

For nL := 1 to oQR:nSize
	For nC := 1 to oQR:nSize
		If oQR:aGrid[nL][nC] == 1 .or. oQR:aGrid[nL][nC] == 3
		    nL1 := (nL+2)*MODULE_PIXEL_SIZE
		    nC1 := (nC+2)*MODULE_PIXEL_SIZE
		    nL2 := nL1+MODULE_PIXEL_SIZE
		    nC2 := nC1+MODULE_PIXEL_SIZE
			oBmp:Rectangle(nL1,nC1,nL2,nC2,0)
			oBmp:Paint(nL1+1,nC1+1)
		Endif
	Next
Next	

// Salva o arquivo em disc 
cFile := '\QRCode.bmp'
oBmp:SaveToBmp(cFile)
FreeObj(oBmp)

MsgInfo("Imagem do QR Code exportada para o arquivo ["+cFile+"]")

Return

Por hora a aplicação gera um QR Code com capacidade aproximada de até 200 caracteres, usando uma codificação Alfanumérica restrita de 44 caracteres. É suficiente para textos simples e URLs. O fonte da implementação será destrinchado em próximos posts da sequência, afinal ele está com quase 3000 linhas de código — e ainda não está completo.

Exportação da Imagem

A imagem exportada é criada como um BITMAP monocromático, com cada módulo desenhado com um quadrado de 12 pixels. Podemos diminuir no fonte este tamanho, tomando apenas o cuidado de gerar um QR Code com tamanho suficiente para o scanner ou aplicativo usado para leitura seja capaz de lê-lo. No objeto BITMAP criado é possível aumentar o número de cores da imagem, trocar a cor de plotagem, etc.

Mascaramento de Dados

Existem oito formas de mascaramento de dados, para gerar um QR Code que seja mais fácil ou menos suscetível de erro de leitura pelo Scanner. Atualmente a implementação ainda requer que você informe qual o mascaramento desejado, a parte que faz a avaliação dos mascaramentos para escolha automática ainda está em desenvolvimento.

Próximas etapas

O objetivo da implementação é suportar até o nível 40 com correção de erro “H” (High), mas para isso ainda é necessário codificar a separação dos dados em grupos para a geração do Grid do QR Code — também em desenvolvimento.

Conclusão

Não sabendo que era impossível, ele foi lá e fez. — Jean Cocteau

Agradeço a todos pela audiência, e lhes desejo TERABYTES DE SUCESSO !!!

 

 

Bitmaps em AdvPL – Parte 08

Introdução

No post anterior, vimos com mais detalhes o suporte a corres de uma imagem Bitmap. Agora, vamos ver mais um método interessante da classe ZBITMAP — o FlipV() — ou inversão vertical — usado para colocar a imagem ou uma parte dela literalmente “de ponta cabeça”. Quer ver esta sequência desde o início? Acesse clicando aqui o primeiro post.

Método FlipV()

Este método pode receber opcionalmente duas coordenadas, para permitir inverter verticalmente uma área da imagem. Caso nenhum parâmetro seja informado, ele inverte a imagem inteira. Ele é praticamente uma implementação “prima” da FlipH(), porém atua trocando as cores entre as linhas da área informada, enquanto a FlipH() troca as cores das colunas.

Como os dados do Bitmap estão em um array bidimensional, existem várias formas de se realizar a inversão. Em ambas as implementações, eu optei por usar a alternativa mais “econômica” de memória, criando uma instrução na forma de #translate para fazer uma troca de conteúdo entre duas variáveis, no caso a cor do ponto no array, sem alocar arrays intermediários ou temporários. Apenas tomei o cuidado de declarar uma variável local para ser usada como container temporário para fazer a troca sem perder a informação original. Vamos ao método:

// Inverte verticalmente uma área da imagem
// Ou a imagem inteira caso a área nao seja especificada
METHOD FlipV(L1,C1,L2,C2) CLASS ZBITMAP
Local nL  , nC            
Local nRow, nSwap
IF pCount() == 0
	// Faz flip vertical da imagem inteira
	L1 := 0
	C1 := 0
	L2 := ::nHeight-1
	C2 := ::nWidth-1
Else
	// Valida coordenadas informados
	IF L1 < 0 .or. L1 >= ::nHeight
		::cError := "Invalid 1o Line -- Out Of Image Area"
		Return .F.
	ElseIF L2 < 0 .or. L2 >= ::nHeight
		::cError := "Invalid 2o Line -- Out Of Image Area"
		Return .F.
	ElseIf C1 < 0 .or. C1 >= ::nWidth
		::cError := "Invalid 1o Column -- Out Of Image Area"
		Return .F.
	ElseIf C2 < 0 .or. C2 >= ::nWidth
		::cError := "Invalid 2o Column -- Out Of Image Area"
		Return .F.
	ElseIf L1 > L2
		::cError := "Invalid Lines -- Order mismatch"
		Return .F.
	ElseIf C1 > C2
		::cError := "Invalid Columns -- Order mismatch"
		Return .F.
	Endif
Endif

// Troca os pontos da primeira linha com a ultima
// depois da segunda com a penultima 
// até chegar na linha central da área a ser invertida      
nRow := L2+1
For nL := L1+1 to L1 + INT( ( L2-L1 ) / 2 ) + 1
	For nC := C1+1 to C2+1
		ZSWAP( ::aMatrix[nL][nC] , ::aMatrix[nRow][nC] , nSwap )
	Next
	nRow--
Next

Return .T.

Instrução ZSWAP

Se eu quero trocar conteúdos entre duas variáveis, para que uma assuma o conteúdo da outra, (ainda) não existe função da linguagem que permita fazer isso de forma direta. A alternativa mais eficiente é armazenar o conteúdo da primeira variável em uma variável de uso temporário, depois atribuir o conteúdo da segunda variável sobre a primeira, e então atribuir o conteúdo da primeira variável — salvo e armazenado na variável temporária — na segunda variável. Para isso, eu uso a instrução abaixo:

#TRANSLATE ZSWAP( <X> , <Y> , <S> ) => ( <S> := <X> , <X> := <Y> , <Y> := <S> )

Usando a diretiva de pré-compilação #TRANSLATE, é possível criar uma pseudo-função em tempo de compilação — algo equivalente a um método “inline”, disponível por exemplo no C++. A diretiva identifica as ocorrências de chamada da pseudo-função ZSWAP, recebendo três parâmetros: <X> é a primeira variável ou expressão, <Y> é a segunda variável ou expressão, e <S> é a variável de armazenamento temporário para fazer a troca. Veja abaixo um exemplo de uso e como ela será traduzida na pré-copilação deste código:

// Chamada original
ZSWAP ( nVar1 , nVar2, nTmp ) 

// Após a pré-compilação 
( nTmp := nVar1 , nVar1 := nVar2 , nVar2 := nTmp )

A utilização de diretivas de #TRANSLATE e similares no AdvPL (como a #COMMAND e #XCOMMAND) permite implementações desta ordem, e também aglutinação e tradução de chamadas, até a implementação de comandos — que serão traduzidos para chamadas de funções ou métodos, tornando a sintaxe para diversas operações bem mais leve e intuitiva.

Conclusão

Por hora, concluo apenas que falta pouco para a classe ZBITMAP ser declarada como “pronta”.

Agradeço a todos pela audiência, e desejo a todos TERABYTES DE SUCESSO !!! 

 

Bitmaps em AdvPL – Parte 07

Introdução

No post anterior, vimos os métodos Cut(), Copy(), Paste() e FlipH(). Agora, vamos ver o suporte a cores, desde o monocromático até 24 bits (ou True Color).

Mapa de Cores

Nas versões de cores implementadas até agora — monocromático, 16 cores e 256 cores — exite uma informação entre o Header do Bitmap e a área de dados, onde é armazenada a tabela de cores em uso. São 4 bytes para cada cor armazenada na tabela, no  formato BGRA ( Blue, Green, Red e Alpha). No Bitmap monocromático, são apenas duas cores ( 0,0,0,0 e 255,255,255,0 ou Preto e Branco), tando que a representação de pontos da imagem no Bitmap Array é feita por BITS — cada Byte representa o estado de 8 pixels ( 0=preto, 1=branco).

No Bitmap de 16 cores, existem 16 cores na tabela padrão após o Header, que podem ser trocadas no mapa. Cada byte da área de dados usa 4 bits (ou um nibble) para representar a posição do mapa de cores para aquele ponto. Por isso o Bitmap de 16 cores utiliza 4 bits por pixel (4 BPP).

No Bitmap de 256 cores, como é de se esperar, o mapa de cores têm 256 cores pré-definidas, e cada pixel no mapa de dados da imagem é representado por um byte inteiro, contendo o valor da cor no índice, de 0 a 255.

Já no Bitmap de 24 bits (também conhecido por True Color), não há mapa de cores. Cada pixel no mapa de dados da imagem é representado por uma sequência de 4 bytes, n formato BGRA, que corresponde a uma cor distinta. Como cada uma das três cores possui intensidades de 0 a 255, isso permite representar mais cores do que o olho humano é capaz de distinguir — total de 16.777.216 cores. Naturalmente um Bitmap com esta definição de cores ocupa aproximadamente 4 vezes mais de tamanho do que o mesmo Bitmap em 256 cores.

Representação da cor em cada ponto

A propriedade ::aMatrix da classe ZBITMAP contém apenas o número de uma cor, quando usado qualquer um dos formatos inferior a 24 bits. Quando usamos esta capacidade de cores (True Color), ainda assim cada ponto apenas possui um número que representa aquela cor. Ele é formado pela conversão para decimal da representação binária das cores Vermelha, Verde e Azul (RGB) em sequência. Por exemplo, existe um tom de vermelho chamado “Tomate”, sua representação em RGB é 255, 99, 71 (decimal).

RGB ( 255 , 99 , 71 ) em 24 bits, nesta ordem, é :
255 = 11111111
 99 = 01100011
 71 = 01000111

Representação final : "111111110110001101000111"
Convertendo esta sequencia para decimal, temos o número 16737095

Logo, um ponto com a cor 16737095 corresponde ao tom de vermelho “tomate”. Quando o Bitmap de 24 bits é carregado, as cores são convertidas nesta representação numérica decimal. No momento de gravar a imagem após uma alteração, os números são reconvertidos para a sequência binária de 24 bits, e cada grupo de 8 bits gera um byte com o valor da respectiva cor.

Mudança no método NEGATIVE

O efeito de foto negativa, anteriormente feito alterando a tabela de cores, deve ser realizado ponto a ponto no Bitmap de 24 bits, aplicando a mesma regra do complemento para as cores RGB — 255 menos a cor. O método fica assim:

METHOD Negative()  CLASS ZBITMAP
Local nI
Local nL , nC
Local nRed := 0
Local nGreen := 0
Local nBlue := 0

If ::nBPP < 24
	// Formatos menores que True Color, altera a tabela de cores
	For nI := 1 to len(::aColors)
		::aColors[nI][1] := 255-::aColors[nI][1]
		::aColors[nI][2] := 255-::aColors[nI][2]
		::aColors[nI][3] := 255-::aColors[nI][3]
	Next
Else
	// Imagem True Color, inverte as cores ponto a ponto                      
	For nL := 1 to ::nHeight
		For nC := 1 to ::nWidth
			RGB2Dec(::aMatrix[nL][nC],@nRed,@nGreen,@nBlue)
			nRed := 255 - nRed
			nGreen := 255 - nGreen
			nBlue := 255 - nBlue
			::aMatrix[nL][nC] := RGB(nRed,nGreen,nBlue)
		Next
	Next
Endif
Return

Em memória, as cores são armazenadas e calculadas em RGB, apenas na leitura e gravação a ordem é invertida para o formato de armazenamento (BGR).

Conclusão

Fonte atualizado no GITHUB, divirta-se 😀

E desejo a todos TERABYTES DE SUCESSO !!!

Referências