Skip to content

Commit 75f24e1

Browse files
multiple sheets
chained format cofig
1 parent 08f6a19 commit 75f24e1

File tree

10 files changed

+181
-48
lines changed

10 files changed

+181
-48
lines changed

README.md

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,36 +8,64 @@ and latest features from
88

99
Why?
1010
-----------
11-
Some features of underlying library that I need are missing in this plugin or not implemented as I want,
11+
Some Excel features of underlying library that I need are missing in this plugin or not implemented as I want,
1212
like pages, column styling, etc.
1313
Also, I'm still working with grails 2, but latest features of 2.0.0, that works only with Grails3, are not merged back to Grails2 version...
1414
So, first commit of this branch is a pure merge of dead 1.7 and latest 2.0.0.
15-
A new branch will be spawned for every upgrade of base line (if it ever happends at all).
15+
A new branch will be spawned for every upgrade of base line (if it ever happens at all).
1616

1717
What are the new features?
1818
-----------
19-
1.7-2.0.0-2:
20-
- I see no reason why column autosizing is not turned-on by default: it perfectly resizes small columns, and doesn't resize large collumns too much. So it's on by default now. To disable it set ```Map parameters=['column.width.autoSize':false]```
21-
- supports column formating. Here is a quick example how to assign "currenty" type to a column:
22-
```groovy
23-
Map labels = ['paymentAmt':'Payment Amount']
24-
List fields = ['paymentAmt']
25-
Map parameters = ["column.formats": [paymentAmt: new WritableCellFormat(NumberFormats.ACCOUNTING_FLOAT)]]
26-
def formatters = [paymentAmt : { domain, value ->
27-
String strVal = "${value}".replace('fr.','').replaceAll(/[^\d.]/,'')
28-
return new BigDecimal(strVal?:'0')
29-
}]
19+
1.7-2.0.0-3:
20+
- Chained header and column formatting, like
21+
```
22+
def cellFormat = (new ExcelFormat()).TAHOMA().bold().noBold().struckout().VIOLET().italic().pointSize(10).wrapText().CENTRE().TOP().MINUS_45().backColor(Colour.AQUA).MIDDLE()
23+
```
24+
It is possible to set that format for all headers ```"header.format":format``` and/or individually ```"header.formats": [1:column1headerFormat,5:column5headerFormat]```
25+
- Change column size individually ```"column.widths": [null, 40]``` - here we set it only for second one, the rest will be autosized.
26+
- format can handle cell value type (currency bundles text formatter as well!):
27+
```
28+
def textFormat = new ExcelFormat()
29+
def currencyFormat = new ExcelFormat(NumberFormats.ACCOUNTING_FLOAT)
30+
def dateTimeFormat = new ExcelFormat(DateFormats.FORMAT9)
31+
```
32+
- new interface to handle multiple sheets (only Excel implemented for now):
33+
```
34+
Map sheets = ['first sheet title': [fields: fields, labels: labels, rows: rows1,
35+
"column.formats": [ someFieldName: (new ExcelFormat()).TIMES() ]]
36+
'another sheet': [rows:rows2]]
37+
38+
exportService.export(mimeType, response, sheets)
39+
// OR
40+
//setupResponse(type, response, filename, extension)
41+
//exportService.export(mimeType, response.outputStream, sheets)
42+
```
43+
- added ```import groovy.util.logging.Log4j``` annotation
3044

31-
response.contentType = Holders.config.grails.mime.types[params.exportFormat]
32-
response.setHeader("Content-disposition", "attachment; filename=test.xls")
33-
exportService.export('excel', response.outputStream, myList, fields, labels, formatters, parameters)
45+
2.1.7-2.0.0-2:
46+
- I see no reason why column autosizing is not turned-on by default: it perfectly resizes small columns, and doesn't resize large columns too much. So it's on by default now. To disable it set ```Map parameters=['column.width.autoSize':false]```
47+
- supports column formating. Here is a quick example how to assign "currency" type to a column:
48+
```groovy
49+
Map labels = ['paymentAmt':'Payment Amount']
50+
List fields = ['paymentAmt']
51+
Map parameters = ['column.formats': ['paymentAmt': new WritableCellFormat(NumberFormats.ACCOUNTING_FLOAT)]]
52+
def formatters = ['paymentAmt' : { domain, value ->
53+
String strVal = "${value}".replace('fr.','').replaceAll(/[^\d.]/,'')
54+
return new BigDecimal(strVal?:'0')
55+
}
56+
]
57+
List myRows = [['paymentAmt':'$9,876.5432'], ['paymentAmt':'$1,234.5678']]
58+
response.contentType = Holders.config.grails.mime.types[params.exportFormat]
59+
response.setHeader("Content-disposition", "attachment; filename=test.xls")
60+
exportService.export('excel', response.outputStream, myRows, fields, labels, formatters, parameters)
3461
```
3562
1.7-2.0.0-1:
3663
- initial merge of 1.7 and 2.0.0
3764
- bug: Column autosize applies on size() of rows instead of columns [#28](https://github.com/gpc/export/pull/28)
3865

3966
Installation or Upgrade:
4067
-----------
68+
Get .zip and .pom files [from latest release](https://github.com/SquareGearsLogic/export/releases/tag/1.7-2.0.0-2)
4169
Remove 'export:1.6' from your BuildConfig.groovy
4270
clean, run, watch for this notification:
4371
```"Uninstalled plugin (export)"```
@@ -73,6 +101,6 @@ and replace pom file with the one from release (If anyone knows how to integrate
73101
Add new plugin to your BuildConfig.groovy normally:
74102
```
75103
compile (':export:1.7-2.0.0-2') {
76-
excludes 'itext', 'itext-rtf' // to support birt-report:4.3 dependency hell
104+
excludes 'bcprov-jdk14', 'bcmail-jdk14' // to support birt-report:4.3 dependency hell
77105
}
78106
```

grails-app/services/de/andreasschmitt/export/ExportService.groovy

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,17 @@ class ExportService {
3232

3333
void export(String type, HttpServletResponse response, String filename, String extension, List objects, List fields, Map labels, Map formatters, Map parameters) throws ExportingException {
3434
setupResponse(type, response, filename, extension)
35+
export(type, response.outputStream, objects, fields, labels, formatters, parameters)
36+
}
3537

36-
Exporter exporter = exporterFactory.createExporter(type, fields, labels, formatters, parameters)
37-
exporter.export(response.outputStream, objects)
38+
void export(String type, HttpServletResponse response, String filename, String extension, Map sheets) throws ExportingException {
39+
setupResponse(type, response, filename, extension)
40+
export(type, response.outputStream, sheets)
41+
}
42+
43+
void export(String type, OutputStream outputStream, Map sheets) throws ExportingException {
44+
Exporter exporter = exporterFactory.createExporter(type, [''], [:], [:], [:])
45+
exporter.export(outputStream, sheets)
3846
}
3947

4048
}

src/groovy/de/andreasschmitt/export/exporter/AbstractExporter.groovy

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,19 @@ abstract class AbstractExporter implements Exporter {
1414
void export(OutputStream outputStream, List data) throws ExportingException {
1515
if (exportFields?.size() > 0) {
1616
exportData(outputStream, data, exportFields)
17-
} else {
17+
} else if(data[0]!=null) {
1818
exportData(outputStream, data, ExporterUtil.getFields(data[0]))
1919
}
2020
}
2121

22+
void export(OutputStream outputStream, Map sheets) throws ExportingException {
23+
sheets.each{title, Map config->
24+
if (!config.containsKey('fields') && config.containsKey('rows') && config.rows[0]!=null)
25+
config.fields = ExporterUtil.getFields(config.rows[0])
26+
}
27+
exportSheets(outputStream, sheets)
28+
}
29+
2230
protected String getLabel(String field) {
2331
if (labels.containsKey(field)) {
2432
return labels[field]
@@ -53,4 +61,6 @@ abstract class AbstractExporter implements Exporter {
5361

5462
abstract protected void exportData(OutputStream outputStream, List data, List fields) throws ExportingException
5563

64+
abstract protected void exportSheets(OutputStream outputStream, Map sheets) throws ExportingException
65+
5666
}

src/groovy/de/andreasschmitt/export/exporter/DefaultCSVExporter.groovy

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,8 @@ class DefaultCSVExporter extends AbstractExporter {
6161
throw new ExportingException("Error during export", e)
6262
}
6363
}
64+
65+
protected void exportSheets(OutputStream outputStream, Map sheets) throws ExportingException {
66+
// TODO
67+
}
6468
}

src/groovy/de/andreasschmitt/export/exporter/DefaultExcelExporter.groovy

Lines changed: 93 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import de.andreasschmitt.export.builder.ExcelBuilder
44
import groovy.util.logging.Log4j
55
import jxl.format.Alignment
66
import jxl.format.Colour
7-
import jxl.write.WritableCellFormat
87

98
/**
109
* @author Andreas Schmitt
@@ -13,11 +12,19 @@ import jxl.write.WritableCellFormat
1312
@Log4j
1413
class DefaultExcelExporter extends AbstractExporter {
1514

16-
private static final int MAX_PER_SHEET = 60 //000 // See https://github.com/gpc/export/pull/23
15+
private static final int MAX_PER_SHEET = 60000 // See https://github.com/gpc/export/pull/23
1716

17+
/**
18+
* Legacy interface that spills a single sheet into a workbook.
19+
* @param outputStream - Where to;
20+
* @param data - List of Objects(Maps) rows in sheet;
21+
* @param fields - List<String> header names.
22+
* @throws ExportingException
23+
* TODO: optimize this code with exportSheets
24+
*/
1825
protected void exportData(OutputStream outputStream, List data, List fields) throws ExportingException {
1926
try {
20-
def builder = new ExcelBuilder()
27+
ExcelBuilder builder = new ExcelBuilder()
2128

2229
// Enable/Disable header output
2330
boolean isHeaderEnabled = true
@@ -30,29 +37,19 @@ class DefaultExcelExporter extends AbstractExporter {
3037
useZebraStyle = getParameters().get("zebraStyle.enabled")
3138
}
3239

40+
boolean widthAutoSize = getParameters().get("column.width.autoSize")!=false
41+
3342
int maxPerSheet = MAX_PER_SHEET
3443
if (getParameters().containsKey("max.rows.persheet")) {
3544
maxPerSheet = getParameters().get("max.rows.persheet")
3645
maxPerSheet = maxPerSheet < MAX_PER_SHEET ? maxPerSheet : MAX_PER_SHEET
3746
}
3847

39-
def (sheets, limitPerSheet) = computeSheetsAndLimit(data, maxPerSheet)
40-
def startIndex = 0
41-
def endIndex = limitPerSheet
42-
48+
String title = getParameters().get("title")
4349
builder {
4450
workbook(outputStream: outputStream) {
45-
for (int j = 1; j <= sheets; j++) {
46-
def dataPerSheet = data.subList(startIndex, endIndex)
47-
48-
processSheet (getDelegate(), getParameters().get("title") + "-$j" ?: "Export-$j",
49-
dataPerSheet, fields,
50-
isHeaderEnabled, useZebraStyle,
51-
getParameters().get("column.width.autoSize")!=false)
52-
53-
startIndex = endIndex
54-
endIndex = endIndex + limitPerSheet > data.size() ? data.size() : endIndex + limitPerSheet
55-
}
51+
processSheet(getDelegate() as ExcelBuilder, title, data, fields,
52+
isHeaderEnabled, useZebraStyle, widthAutoSize, maxPerSheet, getParameters())
5653
}
5754
}
5855

@@ -63,17 +60,85 @@ class DefaultExcelExporter extends AbstractExporter {
6360
}
6461
}
6562

63+
/**
64+
* Writes multiple sheets into a workbook
65+
* @param outputStream - Where to;
66+
* @param sheets - Map of sheet title and its configuration, that is also a Map of:
67+
* ~ fields - row Object(Map) fields to export;
68+
* ~ labels - aliases for fields;
69+
* ~ header.enabled - boolean show/hide header;
70+
* ~ titles.mergeCells - merge all header cells;
71+
* ~ zebraStyle.enabled - boolean striped table;
72+
* ~ max.rows.persheet - boolean breaks down data rows into multiple enumerated sheets with same title (default is MAX_PER_SHEET);
73+
* ~ column.width.autoSize - boolean default is true;
74+
* ~ column.widths - list of column widths, Nulls will apply 'column.width.autoSize';
75+
* ~ rows - List of Objects(Maps) rows in sheet;
76+
* ~ column.formats - ExcelFormat type of cell, like date, currency, text, etc... (default is text);
77+
* ~ column.formatters - Closure that formats cell value { domain, value -> return value };
78+
* ~ header.format - ExcelFormat to format all headers same way;
79+
* ~ header.formats - Map of header index and its ExcelFormat, so it's a 'header.format' per column.
80+
* @throws ExportingException
81+
*/
82+
protected void exportSheets(OutputStream outputStream, Map sheets) throws ExportingException {
83+
ExcelBuilder builder = new ExcelBuilder()
84+
builder {
85+
workbook(outputStream: outputStream) {
86+
ExcelBuilder currentWorkbook = getDelegate()
87+
sheets.each { title, Map sheetParams ->
88+
boolean isHeaderEnabled = true
89+
if (sheetParams.containsKey("header.enabled")) {
90+
isHeaderEnabled = sheetParams.get("header.enabled")
91+
}
92+
93+
boolean useZebraStyle = false
94+
if (sheetParams.containsKey("zebraStyle.enabled")) {
95+
useZebraStyle = sheetParams.get("zebraStyle.enabled")
96+
}
97+
98+
boolean widthAutoSize = sheetParams.get("column.width.autoSize")!=false
99+
100+
int maxPerSheet = MAX_PER_SHEET
101+
if (sheetParams.containsKey("max.rows.persheet")) {
102+
maxPerSheet = sheetParams.get("max.rows.persheet")
103+
maxPerSheet = maxPerSheet < MAX_PER_SHEET ? maxPerSheet : MAX_PER_SHEET
104+
}
105+
106+
processSheet(currentWorkbook, (title?:"Export") as String, sheetParams.rows as List, sheetParams.fields as List,
107+
isHeaderEnabled, useZebraStyle, widthAutoSize, maxPerSheet, sheetParams)
108+
}
109+
}
110+
}
111+
112+
builder.write()
113+
}
114+
66115
private void processSheet(ExcelBuilder workbook, String sheetName, List data, List fields,
67-
boolean isHeaderEnabled, boolean useZebraStyle, boolean widthAutoSize){
116+
boolean isHeaderEnabled, boolean useZebraStyle, boolean widthAutoSize, int maxPerSheet, Map sheetParams){
117+
def (sheets, limitPerSheet) = computeSheetsAndLimit(data, maxPerSheet)
118+
def startIndex = 0
119+
def endIndex = limitPerSheet
120+
for (int j = 1; j <= sheets; j++) {
121+
def dataPerSheet = data.subList(startIndex, endIndex)
122+
123+
processMaxRowsPerSheet (workbook, sheetName + (sheets>1?"-$j":''),
124+
dataPerSheet, fields, isHeaderEnabled, useZebraStyle, widthAutoSize, sheetParams)
125+
126+
startIndex = endIndex
127+
endIndex = endIndex + limitPerSheet > data.size() ? data.size() : endIndex + limitPerSheet
128+
}
129+
}
130+
131+
private void processMaxRowsPerSheet(ExcelBuilder workbook, String sheetName, List data, List fields,
132+
boolean isHeaderEnabled, boolean useZebraStyle, boolean widthAutoSize, Map sheetParams){
68133
workbook.sheet (name: sheetName,
69-
widths: getParameters().get("column.widths"),
134+
widths: sheetParams.get("column.widths"),
70135
numberOfFields: fields.size(),
71136
widthAutoSize: widthAutoSize) {
72137

73138
format(name: "title") {
74139
Alignment alignment = Alignment.GENERAL
75-
if (getParameters().containsKey('titles.alignment')) {
76-
alignment = Alignment."${getParameters().get('titles.alignment')}"
140+
if (sheetParams.containsKey('titles.alignment')) {
141+
alignment = Alignment."${sheetParams.get('titles.alignment')}"
77142
}
78143
font(name: "arial", bold: true, size: 14, alignment: alignment)
79144
}
@@ -96,28 +161,28 @@ class DefaultExcelExporter extends AbstractExporter {
96161
int rowIndex = 0
97162

98163
// Option for titles on top of data table
99-
def titles = getParameters().get("titles")
164+
def titles = sheetParams.get("titles")
100165
titles.each {
101166
cell(row: rowIndex, column: 0, value: it, format: "title")
102167
rowIndex++
103168
}
104169

105170
//Create header
106171
if (isHeaderEnabled) {
107-
final def headerFormat=getParameters().get("header.format")
108-
final Map headerFormats=getParameters().get("header.formats")
172+
final def headerFormat=sheetParams.get("header.format")
173+
final Map headerFormats=sheetParams.get("header.formats")
109174
//WritableCellFormat format = headerFormats.containsKey() headerFormat
110175

111176
fields.eachWithIndex { field, index ->
112-
def format = headerFormats.get(index)?:headerFormat
177+
def format = headerFormats?.get(index)?:headerFormat
113178
String value = getLabel(field)
114179
cell(row: rowIndex, column: index, value: value, format: format?:"header")
115180
}
116181

117182
rowIndex++
118183
}
119184

120-
final Map columnFormats=getParameters().get("column.formats")
185+
final Map columnFormats=sheetParams.get("column.formats")
121186
//Rows
122187
data.eachWithIndex { object, k ->
123188

@@ -132,7 +197,7 @@ class DefaultExcelExporter extends AbstractExporter {
132197
}
133198
}
134199

135-
if (getParameters().get('titles.mergeCells')) {
200+
if (sheetParams.get('titles.mergeCells')) {
136201
//Merge title cells
137202
titles.eachWithIndex { title, index ->
138203
mergeCells(startColumn: 0, startRow: index, endColumn: fields.size(), endRow: index)

src/groovy/de/andreasschmitt/export/exporter/DefaultODSExporter.groovy

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,8 @@ class DefaultODSExporter extends AbstractExporter {
4848
throw new ExportingException("Error during export", e)
4949
}
5050
}
51+
52+
protected void exportSheets(OutputStream outputStream, Map sheets) throws ExportingException {
53+
// TODO
54+
}
5155
}

src/groovy/de/andreasschmitt/export/exporter/DefaultPDFExporter.groovy

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,10 @@ class DefaultPDFExporter extends AbstractExporter {
211211
}
212212
}
213213

214+
protected void exportSheets(OutputStream outputStream, Map sheets) throws ExportingException {
215+
// TODO
216+
}
217+
214218
// Create font from parameters
215219
private Font createFont(String type, String family, String encoding, int fontSize, int style) {
216220
// Font size

src/groovy/de/andreasschmitt/export/exporter/DefaultRTFExporter.groovy

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ class DefaultRTFExporter extends AbstractExporter {
122122
}
123123
}
124124

125+
protected void exportSheets(OutputStream outputStream, Map sheets) throws ExportingException {
126+
// TODO
127+
}
128+
125129
// Create font from parameters
126130
private Font createFont(String type, String family, String encoding, int fontSize, int style) {
127131
// Font size

src/groovy/de/andreasschmitt/export/exporter/DefaultXMLExporter.groovy

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ class DefaultXMLExporter extends AbstractExporter {
4747
}
4848
}
4949

50+
protected void exportSheets(OutputStream outputStream, Map sheets) throws ExportingException {
51+
// TODO
52+
}
53+
5054
private String properCase(String value) {
5155
if (value?.length() >= 2) {
5256
return "${value[0].toLowerCase()}${value.substring(1)}"

src/groovy/de/andreasschmitt/export/exporter/Exporter.groovy

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,6 @@ interface Exporter {
2424

2525
void export(OutputStream outputStream, List data) throws ExportingException
2626

27+
void export(OutputStream outputStream, Map data) throws ExportingException
28+
2729
}

0 commit comments

Comments
 (0)