19 de agosto de 2010

Design Patterns com Delphi : Interpreter - parte I

Apesar de não ser muito comum, pode aparecer entre as manutenções de um ERP a situação em que algo similar a uma linguagem tenha que ser desenvolvida. Por "linguagem" eu quero dizer uma sequência de comandos que sigam uma sintaxe bem definida, isto é, um conjunto de regras que devem ser respeitas quando escrevemos uma expressão nessa linguagem. É o mesmo conceito por trás de linguagens como o Pascal, C#, VB ou SQL em que cada comando dado deve seguir estritamente as regras estipuladas para a respectiva linguagem; caso contrário, a expressão não poderá ser avaliada já que ela não teria um significado associado.

Um exemplo seria o que ocorre com o cálculo de saldo disponível dos itens em estoque. Cada empresa tem um jeito próprio de verificar esse saldo, dependendo da necessidade. Alguns incluem a quantidade que está em produção e abatem a carteira de pedidos; há ainda tratamentos diferentes em relação à mercadoria que esteja "fora de estoque" (em beneficiamento ou em poder de terceiros, por exemplo), variando as combinações possíveis entre os tipos. Com isso, é possível construir uma sintaxe com operações simples - adição e subtração - e os nomes de cada tipo de variável (carteira de pedidos, em produção, etc.) como termos inerentes à linguagem. Se quiser, o usuário informa uma expressão para cada item (ou família de itens) respeitando a sintaxe e obterá o saldo conforme suas necessidades.

Este tipo de situação que envolve a construção de uma sintaxe é abordado pelo Design Pattern comportamental Interpreter, cujo foco está na representação das possíveis operações estabelecidas pela sintaxe. Isso significa que este padrão não se preocupa com a análise sintática de uma expressão mas sim com sua avaliação num determinado contexto, ficando a cargo da aplicação fazer a análise necessária para montar a lista de instâncias de classes que representam a expressão.

Segue um diagrama UML modelando o cenário que envolve cálculo de saldo através do padrão Interpreter, conforme descrito antes:
Diagrama UML para o padrão Interpreter

No quadro abaixo está a nomenclatura normalmente utilizada para as classes participantes da solução:
O Context contem informações globais que delimitam o cenário no qual uma expressão será computada. Cada parte da expressão pode buscar no contexto informações que a ajudem a determinar seu próprio valor. Ou seja, o valor retornado pela avaliação de uma expressão depende do contexto onde ela foi avaliada. A classe TWItem do diagrama tem essa responsabilidade.

AbstractExpression é uma interface que introduz os meios para avaliação de um termo qualquer de uma expressão da linguagem. No exemplo, a interface TWExpressaoAbstrata detem esse papel. Ela introduz a operação Calcular, que recebe um Contexto como parâmetro de modo que a mesma expressão pode ser avaliada em diversos contextos distintos.

TerminalExpression é o nome dado à um termo atômico da expressão, isto é, qualquer termo que não dependa da avaliação de outros termos para ter seu próprio valor avaliado. É o caso de variáveis e constantes numa linguagem. No exemplo desse post, representam expressões terminais as classes TWExpSaldo, TWExpPedidos e TWExpEmFabricacao pois seus valores são calculados com base apenas no contexto.

São classificados como NonterminalExpression os termos de expressões cujo valor depende do cálculo de outros termos. Se encaixam nessa definição as operações de adição e subtração previstas na sintaxe do exemplo; elas não são autocontidas e dependem da avaliação de outros dois termos. No exemplo, as operações citadas são implementadas respectivamente pelas classes TWExpSoma e TWExpSubtracao.

A classe Client é responsável por juntar todos os outros participantes da solução, construindo a árvore de instâncias que representam uma expressão e fornecendo um contexto apropriado para avaliar essa expressão quantas vezes forem necessárias. Este é o papel da classe TWEditaItem no diagrama.

Nos próximos posts eu apresento uma implementação desse diagrama em Delphi e também um exemplo de como analisar uma expressão na sintaxe estabelecida, transformando essa expressão em instâncias de classes que possibilitem avaliá-la em vários contextos.

16 de agosto de 2010

Traçando rotas com a versão 3 da API do Google Maps

Usar a versão 3 da API do Google Maps para traçar a rota entre dois endereços envolve basicamente os mesmos procedimentos descritos no post "Usando a versão 3 da API do Google Maps" aos quais se acrescenta o uso de um objeto específico da API para traçar a rota - o DirectionsService.

Mas, vamos por partes. Como já disse naquele post, a API é disponibilizada como um conjunto de classes e outros códigos JavaScript. Então, o local deste script precisa ser indicado no HTML:

<script type="text/javascript" src="http://maps.google.com/maps/api/js?v=3.1&sensor=false&language=pt-BR"></script>

Também precisaremos de instâncias dos dois objetos principais da API - o que representa o mapa em si e o Geocoder, responsável pela conversão entre endereços reais e suas respectivas coordenadas (latitude/longitude).
var map, geocoder;
function initialize() {
var lOptions = {zoom: 15,
mapTypeId: google.maps.MapTypeId.ROADMAP};
var lMapNode;
lMapNode = document.getElementById('map_canvas');
map = new google.maps.Map(lMapNode, lOptions);
geocoder = new google.maps.Geocoder();
}

function trataLocais (results, status) {
if (status == google.maps.GeocoderStatus.OK) {
map.setCenter(results[0].geometry.location);
var marker = new google.maps.Marker({map: map, position: results[0].geometry.location });
}
}

Apenas para lembrar, o nome 'map_canvas' identifica uma tag HTML do tipo DIV. Com isso, mostrar um endereço no mapa se restringe ao código javascript abaixo.
var lEndereco;
var lRequest;

lEndereco = 'ALAMEDA SANTOS, 1000, SÃO PAULO - SP, 01418-9028';
lRequest = { address: lEndereco, region: 'BR'};

geocoder.geocode( lRequest, trataLocais);

Até aqui, o processo foi o mesmo descrito no outro post. No entanto, traçar a rota não exige muito mais que isso. Para esse recurso funcionar, temos que instanciar o serviço de cálculo de rotas do Google Maps e o serviço que exibe uma rota calculada. Depois, basta solicitar ao primeiro que trace a rota entre dois endereços fornecidos:
var directionsService;
var directionsRenderer;

directionsService = new google.maps.DirectionsService();

directionsRenderer = new google.maps.DirectionsRenderer();
directionsRenderer.setMap(map);

var enderDe = 'ALAMEDA SANTOS, 1000, SÃO PAULO - SP, 01418-9028';
var enderAte = 'AVENIDA NAÇÕES UNIDAS, 17-17, BAURU - SP, 17013-035';

var request = {
origin:endDe,
destination:endPara,
travelMode: google.maps.DirectionsTravelMode.DRIVING
};

directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
directionsRenderer.setDirections(response);
}
});)

Veja que é o serviço de exibição quem é conectado ao mapa. Como o serviço que traça a rota apenas realiza os cálculos, este não precisa conhecer o mapa que está instanciado. Veja também que foi necessário construir uma requisição com as informações sobre a rota que se deseja traçar. Esta requisição é um objeto do tipo DirectionsRequest, que oferece opções para refinar a rota. Além do endereço de origem para a rota e o seu destino, é exigido da requisição que se estipule o meio de transporte a ser utilizado para ir de um endereço a outro. Nesta versão da API, as possibilidades são ir de carro (como no exemplo), ir de bicicleta ou andando. Outros ajustes disponíveis na requisição permitem configurar a rota para evitar estradas pedagiadas e fornecer rotas alternativas.

Outro ponto a destacar é o segundo parâmetro da função route, responsável pelo cálculo da rota. Este parâmetro é uma função que devemos usar para receber os dados da rota calculada e um status desse cálculo. Caso o status seja OK, basta passar os dados calculados ao serviço de exibição, fechando o ciclo.

Esse procedimento resulta no seguinte mapa:


O HTML com este exemplo pode ser acessado neste link.

Um outro recurso útil a ser adicionado é o que descreve como seguir a rota, da mesma maneira que um aparelho de GPS narra as direções a tomar e distâncias a percorrer. Para isso, crie uma nova tag DIV no seu HTML - digamos que você a identifique como panel. Basta, então, configurar o serviço de exibição, indicando-lhe qual a tag onde descrever a rota. Apenas uma linha, como segue:
directionsRenderer.setPanel(document.getElementById("panel"));


13 de agosto de 2010

Manipulando conteúdo de arquivos PDF

O PDF - Portable Document Format - é um dos formatos de arquivo mais populares para distribuição de documentos. Uma das razões para isso é o fato de que - como o próprio nome diz - esse ser um formato criado para ser "portável". Isso quer dizer que ele poderá ser aberto e lido em praticamente qualquer sistema operacional - Windows, Linux, Mac OS, etc. Ajuda também na popularização a existência de plugins para lê-lo diretamente nos principais navegadores da Internet.

Mas o HTML também é um formato portável, com recursos para criar documentos tão ricos quando o PDF. Por que, então, criar um PDF para publicar um documento na Internet? Acontece que o PDF permitir configurar propriedades extras para, por exemplo, evitar que alguém sem permissão possa alterar o conteúdo ou mesmo copiá-lo. Esse recurso é imprescindível para proteger direitos autorais. Um PDF também pode ser assinado digitalmente, fornecendo uma dupla proteção para previnir alterações não autorizadas.

É corrente a ideia de que criar PDFs profissionais exige a compra de softwares caros, em especial o Acrobat Pro da Adobe, empresa que especificou o formato e desenvolveu as primeiras ferramentas. Mas, de acordo com o Wikipedia, a especificação do PDF foi feita como um padrão aberto e qualquer um pode criar programas para leitura e/ou criação de arquivos nesse padrão. Tal especificação está publicada no site da ISO, podendo ser adquirida neste link.

Por isso, há bons programas gratuitos para criação desses arquivos, tais como o CutePDF Writer que funciona como uma impressora. Isto é, qualquer documento que você mandar nessa impressora gerará um PDF com o conteúdo correspondente.

Já o NitroPDF Reader é mais flexível pois tem uma interface gráfica inspirada no Office 2007 que permite realizar manutenções diretamente no PDF:
NitroPDF Reader

De acordo com o fabricante, o programa é capaz de ler cerca de 300 extensões diferentes de arquivos e convertê-los em PDF, prontos para edição no próprio ambiente da ferramenta. Entre os formatos com os quais ele trabalha estão os do Microsoft Office (Word, Excel), páginas HTML e diversos tipos de imagens.

Esta ferramenta permite que se adicione comentários e anotações no arquivo, recurso útil para trabalhos colaborativos nos quais mais de uma pessoa faz sugestões e correções ao documento. Nesta mesma direção, a ferramenta também tem recursos para destacar trechos do documento e acrescentar textos em qualquer ponto.

É possível ainda a criação de "carimbos" para identificar o autor de uma manutenção ou garantir a procedência do arquivo, numa espécie de assinatura eletrônica embutida no documento final. De acordo com a sensibilidade do conteúdo, esse carimbo pode ser protegido por senha para evitar uso indevido.

Pra quem trabalha com formulários em PDF, o NitroReader permite salvar uma cópia com as informações lançadas nos campos, garantindo o registro de como o formulário foi preenchido.

Um outro recurso bastante interessante é o que permite extrair todo o texto do documento de um modo que a formatação - parágrafos e espaços - não se perca, ao contrário do que ocorre com o Copiar do Acrobat Reader. Isso levanta, no entanto, uma preocupação com seus próprios textos inseridos num PDF: se não quer que seu trabalho seja tão facilmente copiado, a sugestão é não inserí-lo como texto mas como uma imagem.

O NitroPDF Reader está em versão beta e é gratuito. Há uma outra versão chamada NitroPDF Reader Pro mais completa, com outros recursos e facilidades mas esta é paga.

3 de agosto de 2010

Usando a versão 3 da API do Google Maps

A API do Google Maps é uma coleção de classes, estruturas e outros tipos de dados construídos em JavaScript para facilitar o trabalho de manipulação de mapas e rotas em uma página HTML, tanto para exibição nos navegadores tradicionais disponíveis em desktops (Internet Explorer, Firefox, etc.) quanto para dispositivos móveis, cujos recursos são mais limitados.

De acordo com o site do Goggle Maps, a versão 3 foi projetada para ser mais rápida que a anterior, além de dar um suporte melhor à criação de sites com mapas para uso específico em dispositivos móveis (celulares, smartphones, tablets, etc).

Recentemente, a ABC71 passou a usar esse serviço gratuito para exibir no cadastro de Clientes e Fornecedores de seu ERP Omega um mapa com a localização do Cliente ou Fornecedor. A tela também permite que se trace a rota entre a empresa usuária do ERP e seu Cliente ou Fornecedor. Isso foi possível em parte devido à facilidade com que a API permite trabalhar diretamente com endereços reais - a alternativa é o sistema onde temos que fornecer a latitude e longitude do ponto a ser exibido.

O que o Omega faz é preparar um HTML em tempo de execução usando as informações de endereço do Cliente e apresentar esse HTML num navegador embutido na aplicação, segundo a técnica descrita no post Usando "streams" para navegação no TWebBrowser. No presente post eu mostro como construir esse HTML que exibe um endereço.

Todo o conteúdo da API está disponível num único local que precisa ser indicado no HTML de modo que você possa usar as classes e outros símbolos aí definidos. Para isso, basta incluir uma tag script simples na área de cabeçalho do HTML:

<script type="text/javascript" src="http://maps.google.com/maps/api/js?v=3.1&sensor=false&language=pt-BR"></script>

Repare que há alguns parâmetros na referência ao endereço do script, permitindo um ajuste mais fino do uso da API. No exemplo, instruo os servidores do Google a me fornecer a versão 3.1 do script e a usar textos traduzidos para o português brasileiro - veja outras configurações possíveis aqui.

Há dois objetos principais na API que devem ser criados para mostrar um mapa. O primeiro é o mapa em si e o segundo chama-se Geocoder, responsável por converter de forma transparente um endereço real em suas respectivas coordenadas (latitude/longitude), usadas internamente pelo mapa.

var map, geocoder;
function initialize() {
var lOptions = {zoom: 15,mapTypeId: google.maps.MapTypeId.ROADMAP};
var lMapNode;
lMapNode = document.getElementById('map_canvas');
map = new google.maps.Map(lMapNode, lOptions);

geocoder = new google.maps.Geocoder();

mostraEndereco ('AVENIDA NAÇÕES UNIDAS, 17-17, BAURU - SP, 17013-035');
}

function mostraEndereco (pEndereco) {
var lRequest = { address: pEndereco, region: 'BR'};
geocoder.geocode( lRequest, trataLocais);
}

O construtor da classe de mapa aceita 2 parâmetros: o nó HTML onde o mapa será desenhado (no exemplo, uma tag DIV cujo nome é map_canvas) e uma estrutura com a configuração inicial do mapa. No HTML acima eu estabeleço explicitamente as opções de nível de zoom e o tipo de visão desejado. O zoom pode variar de 0 (mais afastado) até 20 (maior proximidade), mas nem todos os níveis de zoom estão disponíveis para todos os endereços. O tipo de visão configurada é o mapa de ruas tradicional - outras opções incluem o uso de imagens de satélite ou uma mistura dos dois tipos, por exemplo. A documentação das opções disponíveis pode ser encontrada aqui.

O construtor do Geocoder em si não possui parâmetros mas sua principal função - geocode - sim. Também são 2: o primeiro é uma estrutura do tipo GeocoderRequest, onde passamos informações sobre o local a ser tratado no mapa. Repare na forma com que passei o endereço, separando logradouro, número, cidade, estado e CEP. O segundo parâmetro é uma função que será chamada automaticamente pelo script com a lista dos endereços encontrados pela função geocode. Isso significa que se os dados do GeocoderRequest forem insuficientes para determinar um lugar único - endereço errado ou não mapeado, por exemplo - pode ser retornado mais de um endereço aproximado. Para esses casos, pode-se optar por fornecer o par latitude/longitude diretamente, garantindo a exatidão e unicidade do local que desejamos apresentar. No entanto, você precisará ter essa informação de antemão para poder repassá-la ao mapa - talvez até mesmo cadastrando-a num banco de dados.

De qualquer forma, a função pode simplesmente exibir o primeiro endereço retornado. Veja um exemplo, onde o primeiro local encontrado é centralizado no mapa e identificado por um marcador:

function trataLocais (results, status) {
if (status == google.maps.GeocoderStatus.OK) {
map.setCenter(results[0].geometry.location);
var marker = new google.maps.Marker({map: map, position: results[0].geometry.location });
}
}

A partir deste momento, temos criado o vinculo entre o mapa e o Geocoder, resultando no mapa interativo abaixo:

O HTML com o exemplo completo pode ser acessado neste link; ele inclui código para tratar a situação em que mais de um endereço atende os parâmetros informados. Para um exemplo traçando rotas, veja o post Traçando Rotas com a API do Google Maps