Skip to content

Commit 20fd2a0

Browse files
committed
Use generated field names for dynamic tables
Keep dynamic table labels as display text while using stable internal field keys. Add a regression test to verify generated field access with custom column names.
1 parent 30a2216 commit 20fd2a0

4 files changed

Lines changed: 111 additions & 8 deletions

File tree

core/src/main/java/org/mapfish/print/processor/jasper/TableProcessor.java

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import net.sf.jasperreports.engine.type.ScaleImageEnum;
3939
import net.sf.jasperreports.engine.type.StretchTypeEnum;
4040
import net.sf.jasperreports.engine.type.TextAdjustEnum;
41+
import net.sf.jasperreports.engine.util.JRStringUtil;
4142
import net.sf.jasperreports.engine.xml.JRXmlLoader;
4243
import net.sf.jasperreports.engine.xml.JRXmlWriter;
4344
import org.apache.commons.io.IOUtils;
@@ -254,15 +255,18 @@ public Output execute(final Input values, final ExecutionContext context) throws
254255
final Collection<Map<String, ?>> table = new ArrayList<>();
255256

256257
final String[] columnNames = jsonTable.columns;
258+
final String[] fieldNames = toFieldNames(columnNames);
257259

258260
// this map needs to be linked so it keeps order
259261
Map<String, Class<?>> columns = new LinkedHashMap<>();
262+
Map<String, String> columnLabels = new LinkedHashMap<>();
260263
final PArray[] jsonData = jsonTable.data;
261264
for (final PArray jsonRow : jsonData) {
262265
context.stopIfCanceled();
263266
final Map<String, Object> row = new HashMap<>();
264267
for (int j = 0; j < jsonRow.size(); j++) {
265268
final String columnName = columnNames[j];
269+
final String fieldName = fieldNames[j];
266270
Object rowValue = jsonRow.get(j);
267271
if (rowValue == JSONObject.NULL) {
268272
rowValue = null;
@@ -275,16 +279,17 @@ public Output execute(final Input values, final ExecutionContext context) throws
275279
rowValue = tryConvert(values.clientHttpRequestFactoryProvider.get(), rowValue);
276280
}
277281
if (columns.size() < this.maxColumns && !this.excludeColumns.contains(columnName)) {
278-
Class<?> columnDef = columns.get(columnName);
282+
Class<?> columnDef = columns.get(fieldName);
279283
if (columnDef == null) {
280284
Class<?> rowValueClass = null;
281285
if (rowValue != null) {
282286
rowValueClass = rowValue.getClass();
283287
}
284-
columns.put(columnName, rowValueClass);
288+
columns.put(fieldName, rowValueClass);
289+
columnLabels.put(fieldName, columnName);
285290
}
286291
}
287-
row.put(columnName, rowValue);
292+
row.put(fieldName, rowValue);
288293
}
289294
table.add(row);
290295
}
@@ -313,7 +318,7 @@ public Output execute(final Input values, final ExecutionContext context) throws
313318

314319
String subreport = null;
315320
if (this.dynamic) {
316-
subreport = generateSubReport(values, columns);
321+
subreport = generateSubReport(values, columns, columnLabels);
317322
}
318323
final JRMapCollectionDataSource dataSource = new JRMapCollectionDataSource(table);
319324
return new Output(dataSource, table.size(), subreport);
@@ -341,6 +346,14 @@ private Object tryConvert(
341346

342347
private String generateSubReport(final Input input, final Map<String, Class<?>> columns)
343348
throws JRException, IOException {
349+
return generateSubReport(input, columns, new HashMap<>());
350+
}
351+
352+
private String generateSubReport(
353+
final Input input,
354+
final Map<String, Class<?>> columns,
355+
final Map<String, String> columnLabels)
356+
throws JRException, IOException {
344357
byte[] bytes = loadJasperTemplate(input.template.getConfiguration());
345358
final JasperDesign templateDesign = JRXmlLoader.load(new ByteArrayInputStream(bytes));
346359

@@ -352,7 +365,8 @@ private String generateSubReport(final Input input, final Map<String, Class<?>>
352365
final JRDesignSection detailSection = (JRDesignSection) templateDesign.getDetailSection();
353366
int detailHeight = detailSection.getBands()[0].getHeight();
354367

355-
populateTemplateDesign(columns, templateDesign, detailSection, headerHeight, detailHeight);
368+
populateTemplateDesign(
369+
columns, columnLabels, templateDesign, detailSection, headerHeight, detailHeight);
356370

357371
final File jrxmlFile =
358372
File.createTempFile("table-", JASPER_REPORT_XML_FILE_EXT, input.tempTaskDirectory);
@@ -369,6 +383,7 @@ private String generateSubReport(final Input input, final Map<String, Class<?>>
369383

370384
private void populateTemplateDesign(
371385
final Map<String, Class<?>> columns,
386+
final Map<String, String> columnLabels,
372387
final JasperDesign templateDesign,
373388
final JRDesignSection detailSection,
374389
final int headerHeight,
@@ -416,6 +431,7 @@ private void populateTemplateDesign(
416431
columnHeaderStyle = templateDesign.getStylesMap().get(this.headerStyle);
417432
}
418433
String columnName = entry.getKey();
434+
String columnLabel = columnLabels.getOrDefault(columnName, columnName);
419435
Class<?> valueClass = String.class;
420436
if (entry.getValue() != null) {
421437
valueClass = entry.getValue();
@@ -439,7 +455,7 @@ private void populateTemplateDesign(
439455
columnWidth,
440456
headerHeight,
441457
columnHeaderStyle,
442-
columnName,
458+
columnLabel,
443459
headerBand);
444460

445461
// Add fields to the detailBand
@@ -518,7 +534,7 @@ private static void addHeaderFieldToHeaderBand(
518534
final int columnWidth,
519535
final int headerHeight,
520536
final JRStyle columnHeaderStyle,
521-
final String columnName,
537+
final String columnLabel,
522538
final JRDesignBand headerBand) {
523539
JRDesignTextField colHeaderField = new JRDesignTextField();
524540
colHeaderField.setX(headerPosX);
@@ -531,11 +547,23 @@ private static void addHeaderFieldToHeaderBand(
531547
colHeaderField.setStretchType(StretchTypeEnum.ELEMENT_GROUP_HEIGHT);
532548

533549
JRDesignExpression headerExpression = new JRDesignExpression();
534-
headerExpression.setText('"' + columnName + '"');
550+
headerExpression.setText('"' + JRStringUtil.escapeJavaStringLiteral(columnLabel) + '"');
535551
colHeaderField.setExpression(headerExpression);
536552
headerBand.addElement(colHeaderField);
537553
}
538554

555+
private String[] toFieldNames(final String[] columnNames) {
556+
if (!this.dynamic) {
557+
return columnNames;
558+
}
559+
560+
final String[] result = new String[columnNames.length];
561+
for (int i = 0; i < columnNames.length; i++) {
562+
result[i] = "col_" + i;
563+
}
564+
return result;
565+
}
566+
539567
private void addTextField(
540568
final String columnName,
541569
final JRDesignBand detailBand,

core/src/test/java/org/mapfish/print/processor/jasper/TableProcessorTest.java

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package org.mapfish.print.processor.jasper;
22

33
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertNull;
5+
import static org.junit.jupiter.api.Assertions.assertTrue;
46

57
import java.io.File;
68
import java.io.IOException;
@@ -10,6 +12,7 @@
1012
import java.util.concurrent.atomic.AtomicBoolean;
1113
import net.sf.jasperreports.engine.JasperPrint;
1214
import net.sf.jasperreports.engine.data.JRMapCollectionDataSource;
15+
import net.sf.jasperreports.engine.design.JRDesignField;
1316
import org.junit.jupiter.api.Test;
1417
import org.mapfish.print.AbstractMapfishSpringTest;
1518
import org.mapfish.print.TestHttpClientFactory;
@@ -20,6 +23,7 @@
2023
import org.mapfish.print.output.OutputFormat;
2124
import org.mapfish.print.output.Values;
2225
import org.mapfish.print.test.util.ImageSimilarity;
26+
import org.mapfish.print.wrapper.json.PJsonArray;
2327
import org.mapfish.print.wrapper.json.PJsonObject;
2428
import org.springframework.beans.factory.annotation.Autowired;
2529
import org.springframework.test.annotation.DirtiesContext;
@@ -196,4 +200,61 @@ public void testTableConvertersDynamic() throws Exception {
196200
// otherwise small differences are not detected!
197201
new ImageSimilarity(getFile(baseDir + "expectedImage.png")).assertSimilarity(print, 0, 0);
198202
}
203+
204+
@Test
205+
public void testDynamicTableUsesGeneratedFieldNames() throws Exception {
206+
final String baseDir = DYNAMIC_BASE_DIR;
207+
final Configuration config = configurationFactory.getConfig(getFile(baseDir + "config.yaml"));
208+
final Template template = config.getTemplate("main");
209+
PJsonObject requestData = loadJsonRequestData(baseDir);
210+
211+
final PJsonObject table = requestData.getJSONObject("attributes").getJSONObject("table");
212+
final PJsonArray columns = table.getJSONArray("columns");
213+
final String customColumnName = "id \"value\" {v2}";
214+
columns.getInternalArray().put(1, customColumnName);
215+
216+
Values values =
217+
new Values(
218+
new HashMap<>(),
219+
requestData,
220+
template,
221+
getTaskDirectory(),
222+
this.httpRequestFactory,
223+
new File("."),
224+
HTTP_REQUEST_MAX_NUMBER_FETCH_RETRY,
225+
HTTP_REQUEST_FETCH_RETRY_INTERVAL_MILLIS,
226+
new AtomicBoolean(false));
227+
forkJoinPool.invoke(template.getProcessorGraph().createTask(values));
228+
229+
final JRMapCollectionDataSource tableDataSource =
230+
values.getObject("tableDataSource", JRMapCollectionDataSource.class);
231+
assertTrue(tableDataSource.next());
232+
233+
final JRDesignField safeField = new JRDesignField();
234+
safeField.setName("col_1");
235+
assertEquals(1, tableDataSource.getFieldValue(safeField));
236+
237+
final JRDesignField injectedField = new JRDesignField();
238+
injectedField.setName(customColumnName);
239+
assertNull(tableDataSource.getFieldValue(injectedField));
240+
}
241+
242+
@Test
243+
public void testDefaultDynamicTablePrintWithQuotedHeader() throws Exception {
244+
final String baseDir = DEFAULT_DYNAMIC_BASE_DIR;
245+
final Configuration config = configurationFactory.getConfig(getFile(baseDir + "config.yaml"));
246+
PJsonObject requestData =
247+
parseJSONObjectFromFile(TableProcessorTest.class, baseDir + "requestData-quoted.json");
248+
249+
final AbstractJasperReportOutputFormat format =
250+
(AbstractJasperReportOutputFormat) this.outputFormat.get("pngOutputFormat");
251+
final File file = getFile(TableProcessorTest.class, baseDir);
252+
JasperPrint print =
253+
format
254+
.getJasperPrint(new HashMap<>(), requestData, config, file, getTaskDirectory())
255+
.print();
256+
257+
new ImageSimilarity(getFile(baseDir + "expectedImage-quoted.png"))
258+
.assertSimilarity(print, 0, 0);
259+
}
199260
}
22 KB
Loading
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"layout": "main",
3+
"attributes": {
4+
"table": {
5+
"columns": ["exclude", "id", "icon", "name", "last ' \""],
6+
"data": [
7+
["excluded", 1, "file://logo.png", "name1", "last1 ' \""],
8+
["excluded", 2, "file://logo.png", "name2", "last2 ' \""],
9+
["excluded", 3, "file://logo.png", "name3", "last3 ' \""],
10+
["excluded", 4, "file://logo.png", "name4", "last4 ' \""]
11+
]
12+
}
13+
}
14+
}

0 commit comments

Comments
 (0)