Criando Relatórios Utilizando Java e SVG

07 Jun 2011 . category: article . Comments
#java #svg #relatorios

Quem me conhece deve estar assustado com o título do post, pois bem, eu também estou bem surpreso, porém achei bem legal o resultado que obtive e estarei expondo o mesmo neste post.

A teoria pode ser aplicada a qualquer tecnologia, vou mostrar utilizando Java pois já estou com um exemplo legal pronto.

O Que é SVG?

O SVG(Scalable Vector Graphics) é uma especificação mantida pela w3c para criação de imagens vetoriais. Há diversos aplicativos gráficos que utilizam esta especificação como o Inkscape que é livre, ou o famoso Corel Draw. Além disso a imagem pode ser exibida em navegadores, como por exemplo o Firefox. Simplificando, o SVG é um arquivo xml comum, um arquivo texto que pode facilmente manipulado utilizando os diversos parses que estão por ai(ou de outra forma que você queira :)).

Outra vantagem que encontrei ao escolher esta forma para gerar os relatórios, os usuários podem editar os templates de forma fácil, já que existem diversos softwares para manipulação deste formato de forma simples, sem ter que compilar código ou chamar alguém com conhecimento mais técnico. Essa facilidade de alterações pelo usuário também foi o fator para escolher o SVG e não outros formatos comumente utilizados para este fim, como HTML por exemplo.

A Implementação

Primeiramente, para facilitar um pouco nossa vida, precisamos de um parser XML, eu sou novo no mundo Java e por funcionar como o javascript, escolhi o DOM.

O exemplo que vou mostrar aqui é para gerar uma lista de presença e o template ta bem escroto. Os arquivos podem ser obtidos em http://github.com/diegorubin/blog/tree/master/ListaChamada/test/listachamada. Não sou artista e não tenho muito tempo agora para melhorá-lo, mas para o nosso exemplo irá funcionar. O que o software irá fazer e trocar o nome do professor e colocar a lista dos alunos da turma.

A tag que descreve texto dentro do SVG é a text. O conteúdo da tag é o texto que será exibido na imagem. No nosso exemplo, a tag que exibe o texto “Professor: “ é a:

<text
       xml:space=&quot;preserve&quot;
       style=&quot;font-size:16px;font-style:arial;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans&quot;
       x=&quot;54.112129&quot;
       y=&quot;69.766144&quot;
       id=&quot;professor&quot;
       sodipodi:linespacing=&quot;125%&quot;>Professor: </text>

e foi retirado do arquivo gerado pelo Inkscape.

A classe implementada para gerar o relatório será exibida logo abaixo e explicações estarão após o código.


package listachamada;

import java.io.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
import java.util.ArrayList;


/**
 *
 * @author diego
 */
public class ListaChamada {
    private String professor;
    private ArrayList<String> alunos;
    private String template;
    private String output;
    private File fTemplate;
    private boolean bTemplate = true;
    
    Document doc;
    
    public ListaChamada(){
        bTemplate = false;
    }
    
    public ListaChamada(String template){
        importTemplate(template);
    }
    
    public void setAlunos(ArrayList<String> alunos){
        this.alunos = alunos;
    }
    
    public void setProfessor(String professor){
        this.professor = professor;
    }
    
    public boolean templateOk(){
        return bTemplate && fTemplate.exists();
    }
    
    public boolean setTemplate(String template){
        importTemplate(template);
        
        return templateOk();
    }
    
    public void setOutput(String output){
        this.output = output;
    }
    
    public boolean saveOutput(){
        generateOutput();
        
        return templateOk();
    }
    
    private void importTemplate(String template){
                
        this.template = template;
        try{
            fTemplate = new File(template);
            bTemplate = true;
        }catch(Exception e){
            bTemplate = false;
        }
    }
    
    private String createPath(Integer x, Integer y, Integer distance){
        String path;
        distance += x;
        path = "M " +
               x.toString() + " "+
               y.toString() + " L "+
               distance.toString() + " " +
               y.toString() +" z";
        
        return path;
    }
    
    private void generateOutput(){
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        try{
            /*open template*/
            DocumentBuilder db = dbf.newDocumentBuilder();
            doc = db.parse(fTemplate);
            Element root = doc.getDocumentElement();
            
            /* setar variaveis */
            NodeList texts = root.getElementsByTagName("text");
            Node text;
            for(int i = 0; i <= texts.getLength()-1; i++){
                text = texts.item(i);
                if(text.getAttributes().getNamedItem("id").getTextContent().equals("professor")){
                    text.setTextContent(text.getTextContent() + professor);
                }
            }
            
            //inicio do retangulo x:40 y:164
            /* Criação de um novo layer para a listagem de alunos */
            Integer x = 45;
            Integer y = 185;
            
            Element layer = doc.createElement("g");
            layer.setAttribute("id", "layer_listagem");
            root.appendChild(layer);
            
            for(String aluno: alunos ){
                Element txtAluno = doc.createElement("text");
                txtAluno.setTextContent(aluno);
                txtAluno.setAttribute("style","font-size:16px;font-style:arial;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans");
                txtAluno.setAttribute("x", x.toString());
                txtAluno.setAttribute("y", y.toString());
                layer.appendChild(txtAluno);
                
                y += 10;
                
                Element pthAluno = doc.createElement("path");
                pthAluno.setAttribute("d",createPath(x-5, y, 669));
                pthAluno.setAttribute("stroke", "black");
                pthAluno.setAttribute("stroke-width","1");
                layer.appendChild(pthAluno);
                
                y += 20;
            }           
            
            /* gerando xml de saida */
            TransformerFactory transfac = TransformerFactory.newInstance();
            Transformer trans = transfac.newTransformer();
            trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
            trans.setOutputProperty(OutputKeys.INDENT, "yes");

            /* criação da string */
            StringWriter sw = new StringWriter();
            StreamResult result = new StreamResult(sw);
            DOMSource source = new DOMSource(doc);
            trans.transform(source, result);
            String xmlString = sw.toString();

            /* arquivo de saida */
            File fOutput = new File(output);
            FileOutputStream saida = new FileOutputStream(fOutput);
            saida.write(xmlString.getBytes());
            
            saida.close();
        }catch(Exception e){
            e.printStackTrace();
            bTemplate = false;
        }
    }
}

Toda lógica da inserção de dados no template está dentro do método generateOutput. Primeiramente o arquivo é aberto e parseado, a partir daí utilizaremos o objeto doc para consulta ou alteração da nossa estrutura xml. Para quem está acostumado o funcionamento é identico a manipulação do objecto document(DOM) feito em javascript. Para facilitar nosso trabalho referenciaremos o nó raiz, svg, no objeto root. A partir dai, vamos começar a manipular nosso template.

Primeiramente iremos adicionar o nome do professor, para isso iremos buscar dentro da tag svg todas as tags text. Dentre os encontrados iremos alterar a tag que tenha o atributo id igual a “professor”. Tentei utilizar o método root.getElementsById(“professor”), porém o nó não era encontrado, se alguém souber o motivo disso, por favor, comente este post. Encontrado o nó com id “professor” iremos adicionar seu nome usando o seguinte código text.setTextContent(text.getTextContent() + professor.getNome()).

A segunda coisa que faremos nesse exemplo é criação da listagem dos alunos, para isso criaremos um novo nó com do tipo “g”. Esta tag descreve uma camada(layer). Colocaremos a nossa lista dentro dessa nova camada. Após sua criação, iremos coloca-la dentro da tag raiz “svg” utilizando o código root.appendChild(layer). O próximo passo é iterar sobre nossa lista de alunos e para cada um iremos criar um nó do tipo text, da mesma forma que criamos a nova camada utilizando o código doc.createElement(“text”). Após isso setamos alguns atributos da tag. “x” e “y” são os atributos responsáveis em posicionar o elemento na tela, enquanto o atributo “style” é responsável por formatar o texto. Também setamos o conteúdo da tag colocando assim o nome do aluno na lista utilizando o comando txtAluno.setTextContent(aluno.getNome()), da mesma forma que fizemos com o professor.

Para nossa lista parecer com as tradicionais lista de presença, iremos colocar debaixo do nome de cada aluno um traço, este será criado utilizando a tag “path”. Essa tag é um pouco mais complicadas que as outras utilizadas neste exemplo, por isso recomendo uma leitura sobre ela na documentação encontrada no site da w3c. Os atributos que setaremos nela são: “stroke” e “stroke-width”, que são responsaveis pela cor da linha e pela largura respectivamente e o atributo “d” que irá de fato desenhar a linha. Foi criado um médoto(String createPath(Integer x, Integer y, Integer distance)) para facilitar a criação deste atributo. Após a criação de cada novo né iremos adicioná-los dentro da nossa nova camada por meio do objeto layer utilizando o código layer.appendChild(txtAluno) e layer.appendChild(pthAluno).

Considerações Finais

O exemplo dado é bem simples, porém o padrão SVG é muito útil e podemos utiliza-lo de forma simples. Os arquivos de código, que são um projeto do NetBeans, estão no repositório do blog em http://github.com/diegorubin/blog/ListaChamada. Junto com ele estão alguns testes unitário que facilitarão a visualização do código funcionando, para executá-los é só pressionar ctrl+F6 no NetBeans, a saída estará no diretório de testes e chama lista.svg.


Me

Tenho estudado esse mundo mágico da programação desde 2005. Já consegui sustentar minha família usando Ruby, Java, Python, C++ e Javascript. O resto tenho usado para diversão ou aprendizado.