在
大数据 ,
机器学习 ,
区块链和其他时尚趋势取得成功的背景下,IT行业中的任务看起来完全没有吸引力,但是数十年来,这对于整个开发人员队伍来说都已不复存在。 创建和上载Excel文档已成为一项古老的世界性任务,它面对曾经编写过商业应用程序的每个人。

原则上存在构建Excel文件的可能性?
- VBA宏。 如今,出于安全原因,使用宏的想法通常是不合适的。
- 通过API使用带有外部程序的Excel Automation。 需要与生成Excel报告的程序在同一台计算机上的Excel。 在客户端比较庞大并以桌面Windows应用程序形式编写的时候,这种方法是合适的(尽管在速度和可靠性上没有区别),但在当前现实中,这几乎是无法实现的。
- 直接生成XML-Excel文件。 如您所知,Excel支持用于保存文档的XML格式,可以使用任何使用XML的方法来生成/修改该格式。 可以使用扩展名.xls保存该文件,尽管严格地说,它不是xls文件,但是Excel可以很好地打开它。 这种方法非常流行,但是缺点包括以下事实:基于XML-Excel格式的直接编辑的任何解决方案都是一次性的“ hack”,缺乏通用性。
- 最后,可以使用开放源代码库生成Excel文件,尤其是Apache POI 。 Apache POI开发人员已经对反向工程二进制MS Office文档格式进行了巨大的工作,并且多年来继续维护和开发该库。 例如,这种反向工程的结果在Open Office中用于以与MS Office兼容的格式实现文档的保存。
在我看来,这是现在生成MS Office兼容文档时首选的最后一种方法。 一方面,它不需要在服务器上安装任何专有软件,另一方面,它提供了丰富的API,可让您使用MS Office的所有功能。
但是直接使用Apache POI有其缺点。 首先,这是一个Java库,如果您的应用程序是用一种以上的JVM语言编写的,则几乎无法使用它。 其次,它是一个低级库,可以处理诸如“单元格”,“列”,“字体”之类的概念。 因此,“面对面”的生成文档的书面过程很快变成了难以理解的代码的“面条”,在这种情况下,没有分离成数据模型和表示形式,就很难进行更改,并且通常会感到痛苦和羞耻。 也是将任务委派给最没有经验的程序员的绝佳时机-让他选择。
但这可能完全不同。 LGPL下的
Xylophone项目建立在Apache POI的基础上,其构想具有大约15年的历史。 在我参与的项目中,它与多种平台和语言结合使用-由于在多种项目中借助多种形式做出的形式,可能已经有成千上万种。 这是一个Java项目,可以同时用作命令行实用程序和库(如果您使用JVM语言编写代码,则可以
将其作为Maven依赖项进行
连接 )。
木琴实现了将数据模型与其表示分离的原理。 在上载过程中,您需要生成XML格式的数据(无需担心单元格,字体和分隔线),并且Xylophone使用Excel模板和描述如何使用数据对XML文件进行爬网的描述符将生成结果,如下图所示:
文档模板(xls / xlsx模板)看起来像这样:
通常,此类模板的采购由客户提供。 所涉及的客户很高兴参与模板的创建:从“顾问”中选择合适的形式,或者从头开始发明一种形式,最后以分隔线的字体大小和宽度结束。 该模板的优点是,即使报表已完全开发,也可以轻松对其进行较小的编辑。
完成“设计”工作后,开发人员将保留
- 创建一个过程,以XML格式下载必要的数据。
- 创建一个描述符,该描述符描述遍历XML文件元素并将模板片段复制到结果报告的过程
- 使用XPath表达式将模板单元格绑定到XML文件的元素。
上载到XML后,一切都变得差不多了:只需为填写表单选择所需数据的适当XML表示即可。 什么是描述符?
如果我们创建的表单没有具有不同编号的重复元素(例如发票行,不同发票中的行不同),则描述符将如下所示:
<element name="root"> <output range="A1:Z100"/> </element>
这里的root是我们的XML数据文件的根元素的名称,范围A1:Z100是从要复制的模板到结果的单元格的矩形范围。 此外,从前面的插图中可以看出,其值被XML文件中的数据替换的通配符字段的格式为
~{XPath-}
(波浪号,花括号,相对于当前XML元素的XPath表达式,用大括号括起来)。
如果我们需要在报告中重复元素怎么办? 自然,它们可以表示为XML数据文件的元素,而描述符则有助于以正确的方式进行迭代。 报表中元素的重复可以具有垂直方向(例如,当我们插入发票行时)和水平方向(当我们插入分析报告的列时)。 同时,我们可以使用XML元素的嵌套来反映重复报表元素的任意深度嵌套,如下图所示:
红色正方形标记将成为报表生成器停靠的下一个矩形片段的左上角的单元格。
重复元素还有另一个可能的选择:Excel工作簿中的图纸。 还可以组织这种迭代。
考虑一个稍微复杂的例子。 假设我们需要获得如下的摘要报告:
让用户选择要卸载的年份范围,因此在此报告中动态创建行和列。 此类报告的数据的XML表示可能如下所示:
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>
我们可以根据您的喜好自由选择标签名称,其结构也可以是任意的,但要考虑到转换为报告的难易程度。 例如,我通常将工作表上显示的值写入属性,因为它简化了XPath表达式(当它们看起来像
@
时很方便)。
此类报告的模板如下所示(将XPath表达式与相应标记的属性名称进行比较):
现在最有趣的部分是:创建一个手柄。 由于这是一个几乎完全动态组装的报告,因此描述符实际上相当复杂(实际上,当我们只有文档的“标题”,行和“页脚”时),通常情况下要简单得多。 这是这种情况下所需的描述符:
描述符.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>
完整的描述符元素在
文档中进行了描述。 简而言之,描述符的基本元素含义如下:
- element-转换为XML文件元素的读取模式。 它可以是描述符的根元素,也可以位于
iteration
内部。 name
属性可用于为元素设置各种过滤器,例如name="foo"
-标签名称为foo的元素name="*"
-所有元素name="tagname[@attribute='value']"
-具有特定名称和属性值的元素name="(before)"
, name="(after)"
-迭代之前并结束迭代的“虚拟”元素。
- 迭代 -过渡到迭代模式。 它只能在
element
内部。 可以设置各种参数,例如mode="horizontal"
- mode="horizontal"
输出模式(默认为垂直)index=0
将迭代限制为遇到的第一个元素
- 输出 -切换到输出模式。 主要属性如下:
sourcesheet
—从中获取输出范围的模板手册表。 如果未指定,则应用当前(最后使用)的工作表。- range-要复制到结果文档中的模板的范围,例如“ A1:M10”或“ 5:6”或“ C:C”。 (在水平输出模式下使用类型为“ 5:6”的行范围,在垂直输出模式下使用类型为“ C:C”的列范围会导致错误)。
worksheet
-如果已定义,则会在输出文件中创建一个新工作表,并将输出位置移到该工作表的单元格A1中。 该属性的值等于常量或XPath表达式,将替换为新工作表的名称。
实际上,描述符中还有更多选项,请参见文档。
好了,现在该下载Xylophone并开始报告了。
从
bintray或
Maven Central中获取档案(注意:在阅读本文时,可能有较新的版本)。 在/ bin文件夹中有一个shell脚本,当您不带参数运行它时,将看到有关命令行参数的提示。 为了获得结果,我们需要将所有之前准备好的成分“喂”到木琴中:
xylophone -data testdata.xml -template template.xlsx -descr descriptor.xml -out report.xlsx
打开report.xlsx文件,并确保我们完全获得了所需的东西:
由于ru.curs:xylophone在LGPL许可下在Maven Central上
可用 ,因此可以在使用任何JVM语言的程序中毫无问题地使用它。 也许最紧凑,最有效的示例是在Groovy中获得的,代码不需要注释:
@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.'
XML2Spreadsheet
类具有静态
process
方法的多个重载版本,但它们都归结为传输准备报告所需的相同“成分”。
我尚未提到的一个重要选项是在使用XML数据解析文件的阶段在DOM和SAX解析器之间进行选择的能力。 如您所知,DOM解析器将整个文件加载到内存中,构建其对象表示形式,并可以以任意方式绕过其内容(包括重复返回同一元素)。 SAX解析器从不将整个数据文件放入内存;而是将其作为元素的“流”处理,以防止其再次返回到元素。
当您需要生成非常大的文件时,在Xylophone中使用SAX模式(通过
-sax
命令行
-sax
或将
useSax
方法的
useSax
参数设置为
XML2Spreadsheet.process
)非常有用。 由于SAX解析器资源的速度和获利能力,文件生成的速度提高了许多倍。 这样做的代价是对描述符(在文档中进行了描述)的一些小限制,但是在大多数情况下,报告满足这些限制,因此,我建议尽可能使用SAX模式。
希望您喜欢通过Xylophone上载到Excel的方法,并且可以节省很多时间和精力-就像我们拯救我们一样。
最后,再次链接: