Existem tarefas na indústria de TI que, no contexto de sucessos em
big data ,
aprendizado de máquina ,
blockchain e outras tendências da moda, parecem completamente pouco atraentes, mas há décadas não deixam de ser relevantes para todo um exército de desenvolvedores. Será sobre o antigo como a tarefa mundial de criar e enviar documentos do Excel, que enfrentou todos os que já escreveram aplicativos para negócios.

Que possibilidades de criação de arquivos do Excel existem em princípio?
- Macros VBA. Atualmente, por razões de segurança, a idéia de usar macros geralmente não é adequada.
- Automação do Excel com um programa externo através da API. Requer o Excel na mesma máquina que o programa que gera relatórios do Excel. No momento em que os clientes eram espessos e gravados na forma de aplicativos de desktop para Windows, esse método era adequado (embora não diferisse em velocidade e confiabilidade), nas realidades atuais esse dificilmente é um caso possível.
- Gere arquivo XML-Excel diretamente. Como você sabe, o Excel oferece suporte ao formato XML para salvar um documento, que pode ser potencialmente gerado / modificado usando qualquer meio de trabalhar com XML. Esse arquivo pode ser salvo com a extensão .xls e, embora estritamente não seja um arquivo xls, o Excel o abre bem. Essa abordagem é bastante popular, mas as desvantagens incluem o fato de que qualquer solução baseada na edição direta do formato XML-Excel é um "hack" único, desprovido de generalidade.
- Por fim, é possível gerar arquivos do Excel usando bibliotecas de código aberto, das quais o Apache POI é especialmente conhecido. Os desenvolvedores do Apache POI fizeram um trabalho titânico nos formatos binários de documentos de engenharia reversa do MS Office e continuam a manter e desenvolver essa biblioteca ao longo dos anos. O resultado dessa engenharia reversa, por exemplo, é usado no Open Office para implementar a preservação de documentos em formatos compatíveis com o MS Office.
Na minha opinião, é o último dos métodos que agora é preferido para gerar documentos compatíveis com o MS Office. Por um lado, não requer a instalação de nenhum software proprietário no servidor e, por outro lado, fornece uma API rica que permite usar todas as funcionalidades do MS Office.
Mas o uso direto do Apache POI tem suas desvantagens. Primeiramente, esta é uma biblioteca Java e, se seu aplicativo estiver gravado em mais de uma das linguagens da JVM, você dificilmente poderá usá-lo. Em segundo lugar, é uma biblioteca de baixo nível que trabalha com conceitos como "célula", "coluna", "fonte". Portanto, "de frente" o procedimento escrito para gerar um documento rapidamente se transforma em um "macarrão" abundante de código difícil de ler, onde não há separação em um modelo de dados e apresentação, é difícil fazer alterações e, em geral, dor e vergonha. E uma excelente ocasião para delegar a tarefa ao programador mais inexperiente - deixe-o escolher.
Mas poderia ser completamente diferente. O projeto
xilofone sob a LGPL, construído com base no Apache POI, é baseado em uma idéia que tem uma história estimada em 15 anos. Nos projetos em que participei, ele foi usado em combinação com uma grande variedade de plataformas e idiomas - e, devido à variedade de formulários criados com sua ajuda em uma grande variedade de projetos, provavelmente já existem milhares. Este é um projeto Java que pode funcionar como um utilitário de linha de comando e como uma biblioteca (se você tiver um código na linguagem JVM, poderá
conectá- lo como uma dependência do Maven).
O xilofone implementa o princípio de separar o modelo de dados da sua apresentação. No procedimento de upload, você precisa gerar dados no formato XML (sem se preocupar com células, fontes e linhas divisórias), e o Xilophone, usando um modelo do Excel e um descritor que descreve como rastrear seu arquivo XML com dados, gerará o resultado, conforme mostrado no diagrama:
O modelo de documento (modelo xls / xlsx) é mais ou menos assim:
Como regra, a compra desse modelo é fornecida pelo cliente. O cliente envolvido está feliz em participar da criação do modelo: começando por escolher o formulário certo no "Consultor" ou inventando um a partir do zero e terminando com o tamanho da fonte e a largura das linhas divisórias. A vantagem do modelo é que é fácil fazer pequenas edições, mesmo quando o relatório está totalmente desenvolvido.
Quando o trabalho de "design" é concluído, o desenvolvedor permanece
- Crie um procedimento para baixar os dados necessários no formato XML.
- Crie um descritor que descreva o procedimento para atravessar elementos de um arquivo XML e copiar fragmentos de modelo no relatório resultante
- Vincule células de modelo a elementos de um arquivo XML usando expressões XPath .
Com o upload para XML, tudo fica mais ou menos claro: basta selecionar a representação XML apropriada dos dados necessários para preencher o formulário. O que é um descritor?
Se o formulário que estamos criando não tiver elementos duplicados com números diferentes (como linhas de fatura, que são diferentes em faturas diferentes), o descritor terá a seguinte aparência:
<element name="root"> <output range="A1:Z100"/> </element>
Aqui raiz é o nome do elemento raiz do nosso arquivo de dados XML e o intervalo A1: Z100 é o intervalo retangular de células do modelo que será copiado para o resultado. Além disso, como pode ser visto na ilustração anterior, os campos curinga cujos valores são substituídos pelos dados de um arquivo XML têm o formato
~{XPath-}
(til, colchete, expressão XPath em relação ao elemento XML atual, fechando o colchete).
E se precisarmos repetir elementos no relatório? Naturalmente, eles podem ser representados como elementos de um arquivo de dados XML, e um descritor ajuda a ajudá-lo da maneira certa. A repetição de elementos no relatório pode ter uma direção vertical (quando inserimos linhas de fatura, por exemplo) e horizontal (quando inserimos colunas do relatório analítico). Ao mesmo tempo, podemos usar o aninhamento de elementos XML para refletir o aninhamento arbitrário e profundo de elementos de relatório repetidos, conforme mostrado no diagrama:
Os quadrados vermelhos marcam as células que serão o canto superior esquerdo do próximo fragmento retangular que o gerador de relatórios está encaixando.
Há outra opção possível para repetir elementos: folhas em uma pasta de trabalho do Excel. A capacidade de organizar essa iteração também está disponível.
Considere um exemplo um pouco mais complexo. Suponha que precisamos obter um relatório de resumo como o seguinte:
Deixe o usuário selecionar o intervalo de anos para descarregar, portanto, linhas e colunas são criadas dinamicamente neste relatório. A representação XML dos dados para esse relatório pode ter a seguinte aparência:
testdata.xml <?xml version="1.0" encoding="UTF-8"?> <report> <column year="2016"/> <column year="2017"/> <column year="2018"/> <item name=" 1"> <year amount="365"/> <year amount="286"/> <year amount="207"/> </item> <item name=" 2"> <year amount="95"/> <year amount="606"/> <year amount="840"/> </item> <item name=" 3"> <year amount="710"/> <year amount="437"/> <year amount="100"/> </item> <totals> <year amount="1170"/> <year amount="1329"/> <year amount="1147"/> </totals> </report>
Temos a liberdade de escolher os nomes das tags ao seu gosto, a estrutura também pode ser arbitrária, mas tendo em vista a facilidade de conversão no relatório. Por exemplo, geralmente escrevo os valores exibidos na planilha em atributos, porque simplifica as expressões XPath (é conveniente quando elas se parecem com
@
).
O modelo para esse relatório terá a seguinte aparência (compare expressões XPath com os nomes de atributo das tags correspondentes):
Agora vem a parte mais interessante: criar um identificador. Como este é um relatório quase completamente montado dinamicamente, o descritor é bastante complicado, na prática (quando só temos um "cabeçalho" do documento, suas linhas e "rodapé") tudo geralmente é muito mais simples. Aqui está o descritor necessário neste caso:
descriptor.xml <?xml version="1.0" encoding="UTF-8"?> <element name="report"> <output worksheet="" sourcesheet="1"/> <iteration mode="horizontal"> <element name="(before)"> <output range="A1"/> </element> <element name="column"> <output range="B1"/> </element> </iteration> <iteration mode="vertical"> <element name="item"> <iteration mode="horizontal"> <element name="(before)"> <output range="A2"/> </element> <element name="year"> <output range="B2"/> </element> </iteration> </element> </iteration> <iteration> <element name="totals"> <iteration mode="horizontal"> <element name="(before)"> <output range="A3"/> </element> <element name="year"> <output range="B3"/> </element> </iteration> </element> </iteration> </element>
Os elementos completos do descritor são descritos na
documentação . Em resumo, os elementos básicos de um descritor significam o seguinte:
- elemento - transição para o modo de leitura de um elemento de um arquivo XML. Pode ser o elemento raiz do descritor ou estar dentro da
iteration
. O atributo name
pode ser usado para definir vários filtros para elementos, por exemploname="foo"
- elementos com o nome de tag fooname="*"
- todos os elementosname="tagname[@attribute='value']"
- elementos com um nome e valor de atributo específicosname="(before)"
, name="(after)"
- elementos "virtuais" que precedem a iteração e fecham a iteração.
- iteração - transição para o modo de iteração. Só pode ser
element
interno. Vários parâmetros podem ser definidos, p.mode="horizontal"
- modo de saída mode="horizontal"
(vertical por padrão)index=0
- restringe a iteração ao primeiro elemento encontrado
- saída - alterna para o modo de saída. Os principais atributos são os seguintes:
sourcesheet
- A planilha do livro de modelos da qual o intervalo de saída é obtido. Se não especificado, a folha atual (última usada) é aplicada.range
- o intervalo do modelo a ser copiado para o documento resultante, por exemplo, "A1: M10", "5: 6" ou "C: C". (Usar intervalos de linhas do tipo “5: 6” no modo de saída horizontal e intervalos de colunas do tipo “C: C” no modo de saída vertical causará um erro).worksheet
- se definida, uma nova planilha é criada no arquivo de saída e a posição de saída é deslocada para a célula A1 desta planilha. O valor desse atributo, igual a uma constante ou expressão XPath, é substituído no nome da nova planilha.
Na realidade, existem muito mais opções no descritor, consulte a documentação.
Bem, agora é hora de baixar o Xilophone e começar a relatar.
Pegue o arquivo da
bandeja ou do
Maven Central (NB: no momento da leitura deste artigo, são possíveis versões mais recentes). Na pasta / bin, há um script de shell; quando você o executa sem parâmetros, você verá um prompt sobre os parâmetros da linha de comando. Para obter o resultado, precisamos "alimentar" com o xilofone todos os ingredientes previamente preparados:
xylophone -data testdata.xml -template template.xlsx -descr descriptor.xml -out report.xlsx
Abra o arquivo report.xlsx e verifique se obtemos exatamente o que precisamos:
Como a biblioteca ru.curs: xylophone
está disponível no Maven Central sob a licença LGPL, ela pode ser usada sem problemas em programas em qualquer idioma da JVM. Talvez o exemplo mais compacto e completo de trabalho seja obtido no Groovy, o código não precisa de comentários:
@Grab('ru.curs:xylophone:6.1.3') import ru.curs.xylophone.XML2Spreadsheet baseDir = '.' new File(baseDir, 'testdata.xml').withInputStream { input -> new File(baseDir, 'report.xlsx').withOutputStream { output -> XML2Spreadsheet.process(input, new File(baseDir, 'descriptor.xml'), new File(baseDir, 'template.xlsx'), false, output) } } println 'Done.'
A classe
XML2Spreadsheet
possui várias versões sobrecarregadas do método de
process
estático, mas todas se resumem a transferir os mesmos "ingredientes" necessários para a preparação do relatório.
Uma opção importante que ainda não mencionei é a capacidade de escolher entre analisadores DOM e SAX no estágio de analisar um arquivo com dados XML. Como você sabe, o analisador DOM carrega o arquivo inteiro na memória, constrói sua representação de objeto e possibilita ignorar seu conteúdo de maneira arbitrária (incluindo retornar repetidamente ao mesmo elemento). O analisador SAX nunca coloca o arquivo de dados inteiro na memória, mas processa-o como um "fluxo" de elementos, impedindo que ele retorne ao elemento novamente.
O uso do modo SAX no xilofone (por meio da
-sax
linha de comando
-sax
ou da configuração do parâmetro
useSax
método
useSax
como
XML2Spreadsheet.process
) pode ser extremamente útil quando você precisar gerar arquivos muito grandes. Devido à velocidade e lucratividade dos recursos do analisador SAX, a velocidade de geração de arquivos aumenta muitas vezes. Isso é fornecido à custa de algumas pequenas restrições no descritor (descritas na documentação), mas na maioria dos casos os relatórios atendem a essas restrições, portanto, recomendo usar o modo SAX sempre que possível.
Espero que você tenha gostado do método de upload para o Excel via xilofone e economize muito tempo e nervosismo - como você nos salvou.
E, finalmente, links novamente: