mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 13:10:44 +00:00
Initial basic working version of POI pure-streaming excel generation, with pivot tables.
- moved fastexcel to dedicated sub-package
This commit is contained in:
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* QQQ - Low-code Application Framework for Engineers.
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
* contact@kingsrook.com
|
* contact@kingsrook.com
|
||||||
* https://github.com/Kingsrook/
|
* https://github.com/Kingsrook/
|
||||||
@ -19,7 +19,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.kingsrook.qqq.backend.core.actions.reporting.excelformatting;
|
package com.kingsrook.qqq.backend.core.actions.reporting.excel.fastexcel;
|
||||||
|
|
||||||
|
|
||||||
import org.dhatim.fastexcel.BorderSide;
|
import org.dhatim.fastexcel.BorderSide;
|
||||||
@ -30,7 +30,7 @@ import org.dhatim.fastexcel.StyleSetter;
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Version of excel styler that does bold headers and footers, with basic borders.
|
** Version of excel styler that does bold headers and footers, with basic borders.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class BoldHeaderAndFooterExcelStyler implements ExcelStylerInterface
|
public class BoldHeaderAndFooterFastExcelStyler implements FastExcelStylerInterface
|
||||||
{
|
{
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -60,6 +60,9 @@ public class BoldHeaderAndFooterExcelStyler implements ExcelStylerInterface
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
@Override
|
@Override
|
||||||
public void styleTotalsRow(StyleSetter totalsRowStyle)
|
public void styleTotalsRow(StyleSetter totalsRowStyle)
|
||||||
{
|
{
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* QQQ - Low-code Application Framework for Engineers.
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
* contact@kingsrook.com
|
* contact@kingsrook.com
|
||||||
* https://github.com/Kingsrook/
|
* https://github.com/Kingsrook/
|
||||||
@ -19,7 +19,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.kingsrook.qqq.backend.core.actions.reporting;
|
package com.kingsrook.qqq.backend.core.actions.reporting.excel.fastexcel;
|
||||||
|
|
||||||
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
@ -34,8 +34,8 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import com.kingsrook.qqq.backend.core.actions.reporting.excelformatting.ExcelStylerInterface;
|
import com.kingsrook.qqq.backend.core.actions.reporting.ExportStreamerInterface;
|
||||||
import com.kingsrook.qqq.backend.core.actions.reporting.excelformatting.PlainExcelStyler;
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QReportingException;
|
import com.kingsrook.qqq.backend.core.exceptions.QReportingException;
|
||||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ExportInput;
|
import com.kingsrook.qqq.backend.core.model.actions.reporting.ExportInput;
|
||||||
@ -43,7 +43,9 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
|||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.DisplayFormat;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.DisplayFormat;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.ObjectUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||||
import org.dhatim.fastexcel.StyleSetter;
|
import org.dhatim.fastexcel.StyleSetter;
|
||||||
@ -52,19 +54,19 @@ import org.dhatim.fastexcel.Worksheet;
|
|||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Excel export format implementation
|
** Excel export format implementation - built using fastexcel library
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class ExcelExportStreamer implements ExportStreamerInterface
|
public class ExcelFastexcelExportStreamer implements ExportStreamerInterface
|
||||||
{
|
{
|
||||||
private static final QLogger LOG = QLogger.getLogger(ExcelExportStreamer.class);
|
private static final QLogger LOG = QLogger.getLogger(ExcelFastexcelExportStreamer.class);
|
||||||
|
|
||||||
private ExportInput exportInput;
|
private ExportInput exportInput;
|
||||||
private QTableMetaData table;
|
private QTableMetaData table;
|
||||||
private List<QFieldMetaData> fields;
|
private List<QFieldMetaData> fields;
|
||||||
private OutputStream outputStream;
|
private OutputStream outputStream;
|
||||||
|
|
||||||
private ExcelStylerInterface excelStylerInterface = new PlainExcelStyler();
|
private FastExcelStylerInterface fastExcelStylerInterface = new PlainFastExcelStyler();
|
||||||
private Map<String, String> excelCellFormats;
|
private Map<String, String> excelCellFormats;
|
||||||
|
|
||||||
private Workbook workbook;
|
private Workbook workbook;
|
||||||
private Worksheet worksheet;
|
private Worksheet worksheet;
|
||||||
@ -76,7 +78,7 @@ public class ExcelExportStreamer implements ExportStreamerInterface
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public ExcelExportStreamer()
|
public ExcelFastexcelExportStreamer()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,14 +107,14 @@ public class ExcelExportStreamer implements ExportStreamerInterface
|
|||||||
** Starts a new worksheet in the current workbook. Can be called multiple times.
|
** Starts a new worksheet in the current workbook. Can be called multiple times.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@Override
|
@Override
|
||||||
public void start(ExportInput exportInput, List<QFieldMetaData> fields, String label) throws QReportingException
|
public void start(ExportInput exportInput, List<QFieldMetaData> fields, String label, QReportView view) throws QReportingException
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.exportInput = exportInput;
|
this.exportInput = exportInput;
|
||||||
this.fields = fields;
|
this.fields = fields;
|
||||||
table = exportInput.getTable();
|
table = exportInput.getTable();
|
||||||
outputStream = this.exportInput.getReportOutputStream();
|
outputStream = this.exportInput.getReportDestination().getReportOutputStream();
|
||||||
this.row = 0;
|
this.row = 0;
|
||||||
this.sheetCount++;
|
this.sheetCount++;
|
||||||
|
|
||||||
@ -121,7 +123,7 @@ public class ExcelExportStreamer implements ExportStreamerInterface
|
|||||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
if(workbook == null)
|
if(workbook == null)
|
||||||
{
|
{
|
||||||
String appName = "QQQ";
|
String appName = ObjectUtils.tryAndRequireNonNullElse(() -> QContext.getQInstance().getBranding().getAppName(), "QQQ");
|
||||||
QInstance instance = exportInput.getInstance();
|
QInstance instance = exportInput.getInstance();
|
||||||
if(instance != null && instance.getBranding() != null && instance.getBranding().getCompanyName() != null)
|
if(instance != null && instance.getBranding() != null && instance.getBranding().getCompanyName() != null)
|
||||||
{
|
{
|
||||||
@ -167,7 +169,7 @@ public class ExcelExportStreamer implements ExportStreamerInterface
|
|||||||
worksheet.range(row, 0, row, fields.size() - 1).merge();
|
worksheet.range(row, 0, row, fields.size() - 1).merge();
|
||||||
|
|
||||||
StyleSetter titleStyle = worksheet.range(row, 0, row, fields.size() - 1).style();
|
StyleSetter titleStyle = worksheet.range(row, 0, row, fields.size() - 1).style();
|
||||||
excelStylerInterface.styleTitleRow(titleStyle);
|
fastExcelStylerInterface.styleTitleRow(titleStyle);
|
||||||
titleStyle.set();
|
titleStyle.set();
|
||||||
|
|
||||||
row++;
|
row++;
|
||||||
@ -187,7 +189,7 @@ public class ExcelExportStreamer implements ExportStreamerInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
StyleSetter headerStyle = worksheet.range(row, 0, row, fields.size() - 1).style();
|
StyleSetter headerStyle = worksheet.range(row, 0, row, fields.size() - 1).style();
|
||||||
excelStylerInterface.styleHeaderRow(headerStyle);
|
fastExcelStylerInterface.styleHeaderRow(headerStyle);
|
||||||
headerStyle.set();
|
headerStyle.set();
|
||||||
|
|
||||||
row++;
|
row++;
|
||||||
@ -315,7 +317,7 @@ public class ExcelExportStreamer implements ExportStreamerInterface
|
|||||||
writeRecord(record);
|
writeRecord(record);
|
||||||
|
|
||||||
StyleSetter totalsRowStyle = worksheet.range(row, 0, row, fields.size() - 1).style();
|
StyleSetter totalsRowStyle = worksheet.range(row, 0, row, fields.size() - 1).style();
|
||||||
excelStylerInterface.styleTotalsRow(totalsRowStyle);
|
fastExcelStylerInterface.styleTotalsRow(totalsRowStyle);
|
||||||
totalsRowStyle.set();
|
totalsRowStyle.set();
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* QQQ - Low-code Application Framework for Engineers.
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
* contact@kingsrook.com
|
* contact@kingsrook.com
|
||||||
* https://github.com/Kingsrook/
|
* https://github.com/Kingsrook/
|
||||||
@ -19,7 +19,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.kingsrook.qqq.backend.core.actions.reporting.excelformatting;
|
package com.kingsrook.qqq.backend.core.actions.reporting.excel.fastexcel;
|
||||||
|
|
||||||
|
|
||||||
import org.dhatim.fastexcel.StyleSetter;
|
import org.dhatim.fastexcel.StyleSetter;
|
||||||
@ -29,7 +29,7 @@ import org.dhatim.fastexcel.StyleSetter;
|
|||||||
** Interface for classes that know how to apply styles to an Excel stream being
|
** Interface for classes that know how to apply styles to an Excel stream being
|
||||||
** built by fastexcel.
|
** built by fastexcel.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public interface ExcelStylerInterface
|
public interface FastExcelStylerInterface
|
||||||
{
|
{
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.actions.reporting.excel.fastexcel;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Excel styler that does nothing - just takes defaults (which are all no-op) from the interface.
|
||||||
|
*******************************************************************************/
|
||||||
|
public class PlainFastExcelStyler implements FastExcelStylerInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.actions.reporting.excel.poi;
|
||||||
|
|
||||||
|
|
||||||
|
import org.apache.poi.ss.usermodel.BorderStyle;
|
||||||
|
import org.apache.poi.ss.usermodel.CreationHelper;
|
||||||
|
import org.apache.poi.ss.usermodel.Font;
|
||||||
|
import org.apache.poi.ss.usermodel.HorizontalAlignment;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Version of POI excel styler that does bold headers and footers, with basic borders.
|
||||||
|
*******************************************************************************/
|
||||||
|
public class BoldHeaderAndFooterPoiExcelStyler implements PoiExcelStylerInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public XSSFCellStyle createStyleForTitle(XSSFWorkbook workbook, CreationHelper createHelper)
|
||||||
|
{
|
||||||
|
Font font = workbook.createFont();
|
||||||
|
font.setFontHeightInPoints((short) 14);
|
||||||
|
font.setBold(true);
|
||||||
|
|
||||||
|
XSSFCellStyle cellStyle = workbook.createCellStyle();
|
||||||
|
cellStyle.setFont(font);
|
||||||
|
cellStyle.setAlignment(HorizontalAlignment.CENTER);
|
||||||
|
|
||||||
|
return (cellStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public XSSFCellStyle createStyleForHeader(XSSFWorkbook workbook, CreationHelper createHelper)
|
||||||
|
{
|
||||||
|
Font font = workbook.createFont();
|
||||||
|
font.setBold(true);
|
||||||
|
|
||||||
|
XSSFCellStyle cellStyle = workbook.createCellStyle();
|
||||||
|
cellStyle.setFont(font);
|
||||||
|
cellStyle.setBorderBottom(BorderStyle.THIN);
|
||||||
|
|
||||||
|
return (cellStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public XSSFCellStyle createStyleForFooter(XSSFWorkbook workbook, CreationHelper createHelper)
|
||||||
|
{
|
||||||
|
Font font = workbook.createFont();
|
||||||
|
font.setBold(true);
|
||||||
|
|
||||||
|
XSSFCellStyle cellStyle = workbook.createCellStyle();
|
||||||
|
cellStyle.setFont(font);
|
||||||
|
cellStyle.setBorderTop(BorderStyle.THIN);
|
||||||
|
cellStyle.setBorderBottom(BorderStyle.DOUBLE);
|
||||||
|
|
||||||
|
return (cellStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,751 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.actions.reporting.excel.poi;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipInputStream;
|
||||||
|
import java.util.zip.ZipOutputStream;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.reporting.ExportStreamerInterface;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.reporting.ReportUtils;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.reporting.pivottable.PivotTableGroupBy;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.reporting.pivottable.PivotTableValue;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QReportingException;
|
||||||
|
import com.kingsrook.qqq.backend.core.instances.QInstanceEnricher;
|
||||||
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.reporting.ExportInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportDestination;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.DisplayFormat;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportField;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.reporting.ReportType;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||||
|
import org.apache.poi.ss.SpreadsheetVersion;
|
||||||
|
import org.apache.poi.ss.usermodel.CreationHelper;
|
||||||
|
import org.apache.poi.ss.usermodel.DataConsolidateFunction;
|
||||||
|
import org.apache.poi.ss.usermodel.DateUtil;
|
||||||
|
import org.apache.poi.ss.util.AreaReference;
|
||||||
|
import org.apache.poi.ss.util.CellReference;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFCell;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFPivotTable;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFRow;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFSheet;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Excel export format implementation using POI library, but with modifications
|
||||||
|
** to actually stream output rather than use any temp files.
|
||||||
|
*******************************************************************************/
|
||||||
|
public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInterface
|
||||||
|
{
|
||||||
|
private static final QLogger LOG = QLogger.getLogger(ExcelPoiBasedStreamingExportStreamer.class);
|
||||||
|
|
||||||
|
private List<QReportView> views;
|
||||||
|
private ExportInput exportInput;
|
||||||
|
private List<QFieldMetaData> fields;
|
||||||
|
private OutputStream outputStream;
|
||||||
|
private ZipOutputStream zipOutputStream;
|
||||||
|
|
||||||
|
private PoiExcelStylerInterface poiExcelStylerInterface = new PlainPoiExcelStyler();
|
||||||
|
private Map<String, String> excelCellFormats;
|
||||||
|
|
||||||
|
private int rowNo = 0;
|
||||||
|
private int sheetIndex = 1;
|
||||||
|
|
||||||
|
private Map<String, String> pivotViewToCacheDefinitionReferenceMap = new HashMap<>();
|
||||||
|
private Map<String, XSSFCellStyle> styles = new HashMap<>();
|
||||||
|
|
||||||
|
private Writer activeSheetWriter = null;
|
||||||
|
private StreamedPoiSheetWriter sheetWriter = null;
|
||||||
|
|
||||||
|
private QReportView currentView = null;
|
||||||
|
private Map<String, List<QFieldMetaData>> fieldsPerView = new HashMap<>();
|
||||||
|
private Map<String, Integer> rowsPerView = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public ExcelPoiBasedStreamingExportStreamer()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public void preRun(ReportDestination reportDestination, List<QReportView> views) throws QReportingException
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.outputStream = reportDestination.getReportOutputStream();
|
||||||
|
this.views = views;
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// create 'template' workbook through poi - with sheets corresponding to our //
|
||||||
|
// actual file this will be a zip file (stream), with entries for all of the //
|
||||||
|
// files in the final xlsx but without any data, so it'll be small //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
XSSFWorkbook workbook = new XSSFWorkbook();
|
||||||
|
createStyles(workbook);
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// for each of the sheets, create it in the workbook, and put a reference to it in the sheetMap //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
Map<String, XSSFSheet> sheetMapByExcelReference = new HashMap<>();
|
||||||
|
Map<String, XSSFSheet> sheetMapByViewName = new HashMap<>();
|
||||||
|
|
||||||
|
int sheetCounter = 1;
|
||||||
|
for(QReportView view : views)
|
||||||
|
{
|
||||||
|
String label = Objects.requireNonNullElse(view.getLabel(), "Sheet " + sheetCounter);
|
||||||
|
XSSFSheet sheet = workbook.createSheet(label);
|
||||||
|
String sheetReference = sheet.getPackagePart().getPartName().getName().substring(1);
|
||||||
|
sheetMapByExcelReference.put(sheetReference, sheet);
|
||||||
|
sheetMapByViewName.put(view.getName(), sheet);
|
||||||
|
sheetCounter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
// if any views are pivot tables, create them now //
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
List<String> pivotViewNames = new ArrayList<>();
|
||||||
|
for(QReportView view : views)
|
||||||
|
{
|
||||||
|
if(ReportType.PIVOT.equals(view.getType()))
|
||||||
|
{
|
||||||
|
pivotViewNames.add(view.getName());
|
||||||
|
|
||||||
|
XSSFSheet pivotTableSheet = Objects.requireNonNull(sheetMapByViewName.get(view.getName()), "Could not get pivot table sheet view by name: " + view.getName());
|
||||||
|
XSSFSheet dataSheet = Objects.requireNonNull(sheetMapByViewName.get(view.getPivotTableSourceViewName()), "Could not get pivot table source sheet by view name: " + view.getPivotTableSourceViewName());
|
||||||
|
QReportView dataView = ReportUtils.getSourceViewForPivotTableView(views, view);
|
||||||
|
createPivotTableTemplate(pivotTableSheet, view, dataSheet, dataView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Iterator<String> pivotViewNameIterator = pivotViewNames.iterator();
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////
|
||||||
|
// write that template worksheet zip out to byte array //
|
||||||
|
/////////////////////////////////////////////////////////
|
||||||
|
ByteArrayOutputStream templateBAOS = new ByteArrayOutputStream();
|
||||||
|
workbook.write(templateBAOS);
|
||||||
|
templateBAOS.close();
|
||||||
|
byte[] templateBytes = templateBAOS.toByteArray();
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// open up a zipOutputStream around the output stream that the report is to be written to. //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
this.zipOutputStream = new ZipOutputStream(this.outputStream);
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// copy over all the entries in the template zip that aren't the sheets into the output stream //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
ZipInputStream zipInputStream = new ZipInputStream(new ByteArrayInputStream(templateBytes));
|
||||||
|
ZipEntry zipTemplateEntry = null;
|
||||||
|
byte[] buffer = new byte[2048];
|
||||||
|
while((zipTemplateEntry = zipInputStream.getNextEntry()) != null)
|
||||||
|
{
|
||||||
|
if(zipTemplateEntry.getName().matches(".*/pivotCacheDefinition.*.xml"))
|
||||||
|
{
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// if this zip entry is a pivotCacheDefinition, then don't write it to the output stream right now. //
|
||||||
|
// instead, just map the pivot view's name to the zipTemplateEntry name //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
if(!pivotViewNameIterator.hasNext())
|
||||||
|
{
|
||||||
|
throw new QReportingException("Found a pivot cache definition [" + zipTemplateEntry.getName() + "] in the template ZIP, but no (more) corresponding pivot view names");
|
||||||
|
}
|
||||||
|
|
||||||
|
String pivotViewName = pivotViewNameIterator.next();
|
||||||
|
LOG.info("Holding on a pivot cache definition zip template entry [" + pivotViewName + "] [" + zipTemplateEntry.getName() + "]...");
|
||||||
|
pivotViewToCacheDefinitionReferenceMap.put(pivotViewName, zipTemplateEntry.getName());
|
||||||
|
}
|
||||||
|
else if(!sheetMapByExcelReference.containsKey(zipTemplateEntry.getName()))
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// else, if we don't have this zipTemplateEntry name in our map of sheets, then this is a kinda "meta" //
|
||||||
|
// file that we don't really care about (e.g., not our sheet data), so just copy it to the output stream. //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
LOG.info("Copying zip template entry [" + zipTemplateEntry.getName() + "] to output stream");
|
||||||
|
zipOutputStream.putNextEntry(new ZipEntry(zipTemplateEntry.getName()));
|
||||||
|
|
||||||
|
int length;
|
||||||
|
while((length = zipInputStream.read(buffer)) > 0)
|
||||||
|
{
|
||||||
|
zipOutputStream.write(buffer, 0, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
zipInputStream.closeEntry();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// else - this is a sheet - so again, don't write it yet - stream its data below. //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
LOG.info("Skipping presumed sheet zip template entry [" + zipTemplateEntry.getName() + "] to output stream");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
zipInputStream.close();
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
throw (new QReportingException("Error preparing to generate spreadsheet", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private void createPivotTableTemplate(XSSFSheet pivotTableSheet, QReportView pivotView, XSSFSheet dataSheet, QReportView dataView) throws QReportingException
|
||||||
|
{
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// write just enough data to the dataView's sheet so that we can refer to it for creating the pivot table. //
|
||||||
|
// we need to do this, because POI will try to create the pivotCache referring to the data sheet, and if //
|
||||||
|
// there isn't any data there, it'll crash. //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
XSSFRow headerRow = dataSheet.createRow(0);
|
||||||
|
int columnNo = 0;
|
||||||
|
for(QReportField column : dataView.getColumns())
|
||||||
|
{
|
||||||
|
XSSFCell cell = headerRow.createCell(columnNo++);
|
||||||
|
// todo ... not like this
|
||||||
|
cell.setCellValue(QInstanceEnricher.nameToLabel(column.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
XSSFRow valuesRow = dataSheet.createRow(1);
|
||||||
|
columnNo = 0;
|
||||||
|
for(QReportField column : dataView.getColumns())
|
||||||
|
{
|
||||||
|
XSSFCell cell = valuesRow.createCell(columnNo++);
|
||||||
|
cell.setCellValue("Value " + columnNo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// for this template version of the pivot table, tell it there are only 2 rows in the source sheet //
|
||||||
|
// as that's all that we wrote above (a header and 1 fake value row) //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
int rows = 2;
|
||||||
|
String colsLetter = CellReference.convertNumToColString(dataView.getColumns().size() - 1);
|
||||||
|
AreaReference source = new AreaReference("A1:" + colsLetter + rows, SpreadsheetVersion.EXCEL2007);
|
||||||
|
CellReference position = new CellReference("A1");
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////
|
||||||
|
// tell poi all about our pivot table - rows, cols, and columns //
|
||||||
|
//////////////////////////////////////////////////////////////////
|
||||||
|
XSSFPivotTable pivotTable = pivotTableSheet.createPivotTable(source, position, dataSheet);
|
||||||
|
|
||||||
|
for(PivotTableGroupBy row : CollectionUtils.nonNullList(pivotView.getPivotTableDefinition().getRows()))
|
||||||
|
{
|
||||||
|
int rowLabelColumnIndex = getColumnIndex(dataView.getColumns(), row.getFieldName());
|
||||||
|
pivotTable.addRowLabel(rowLabelColumnIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(PivotTableGroupBy column : CollectionUtils.nonNullList(pivotView.getPivotTableDefinition().getColumns()))
|
||||||
|
{
|
||||||
|
int colLabelColumnIndex = getColumnIndex(dataView.getColumns(), column.getFieldName());
|
||||||
|
pivotTable.addColLabel(colLabelColumnIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(PivotTableValue value : CollectionUtils.nonNullList(pivotView.getPivotTableDefinition().getValues()))
|
||||||
|
{
|
||||||
|
int columnLabelColumnIndex = getColumnIndex(dataView.getColumns(), value.getFieldName());
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// todo - some bug where, if use a group-by field here, then ... it doesn't get used for the grouping. //
|
||||||
|
// g-sheets does let me do this, so, maybe, download their file and see how it's different? //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
pivotTable.addColumnLabel(DataConsolidateFunction.valueOf(value.getFunction().name()), columnLabelColumnIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private int getColumnIndex(List<QReportField> columns, String fieldName) throws QReportingException
|
||||||
|
{
|
||||||
|
for(int i = 0; i < columns.size(); i++)
|
||||||
|
{
|
||||||
|
if(columns.get(i).getName().equals(fieldName))
|
||||||
|
{
|
||||||
|
return (i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw (new QReportingException("Could not find column by name [" + fieldName + "]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private void createStyles(XSSFWorkbook workbook)
|
||||||
|
{
|
||||||
|
CreationHelper createHelper = workbook.getCreationHelper();
|
||||||
|
|
||||||
|
XSSFCellStyle dateStyle = workbook.createCellStyle();
|
||||||
|
dateStyle.setDataFormat(createHelper.createDataFormat().getFormat("yyyy-MM-dd"));
|
||||||
|
styles.put("date", dateStyle);
|
||||||
|
|
||||||
|
XSSFCellStyle dateTimeStyle = workbook.createCellStyle();
|
||||||
|
dateTimeStyle.setDataFormat(createHelper.createDataFormat().getFormat("yyyy-MM-dd H:mm:ss"));
|
||||||
|
styles.put("datetime", dateTimeStyle);
|
||||||
|
|
||||||
|
styles.put("title", poiExcelStylerInterface.createStyleForTitle(workbook, createHelper));
|
||||||
|
styles.put("header", poiExcelStylerInterface.createStyleForHeader(workbook, createHelper));
|
||||||
|
styles.put("footer", poiExcelStylerInterface.createStyleForFooter(workbook, createHelper));
|
||||||
|
|
||||||
|
XSSFCellStyle footerDateStyle = poiExcelStylerInterface.createStyleForFooter(workbook, createHelper);
|
||||||
|
footerDateStyle.setDataFormat(createHelper.createDataFormat().getFormat("yyyy-MM-dd"));
|
||||||
|
styles.put("footer-date", footerDateStyle);
|
||||||
|
|
||||||
|
XSSFCellStyle footerDateTimeStyle = poiExcelStylerInterface.createStyleForFooter(workbook, createHelper);
|
||||||
|
footerDateTimeStyle.setDataFormat(createHelper.createDataFormat().getFormat("yyyy-MM-dd H:mm:ss"));
|
||||||
|
styles.put("footer-datetime", footerDateTimeStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Starts a new worksheet in the current workbook. Can be called multiple times.
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public void start(ExportInput exportInput, List<QFieldMetaData> fields, String label, QReportView view) throws QReportingException
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
/////////////////////////////////////////
|
||||||
|
// close previous sheet if one is open //
|
||||||
|
/////////////////////////////////////////
|
||||||
|
closeLastSheetIfOpen();
|
||||||
|
|
||||||
|
if(currentView != null)
|
||||||
|
{
|
||||||
|
this.rowsPerView.put(currentView.getName(), rowNo);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentView = view;
|
||||||
|
this.exportInput = exportInput;
|
||||||
|
this.fields = fields;
|
||||||
|
this.rowNo = 0;
|
||||||
|
|
||||||
|
this.fieldsPerView.put(view.getName(), fields);
|
||||||
|
|
||||||
|
//////////////////////////////////////////
|
||||||
|
// start the new sheet as: //
|
||||||
|
// - a new entry in the zipOutputStream //
|
||||||
|
// - with a new output stream writer //
|
||||||
|
// - and with a SpreadsheetWriter //
|
||||||
|
//////////////////////////////////////////
|
||||||
|
zipOutputStream.putNextEntry(new ZipEntry("xl/worksheets/sheet" + this.sheetIndex++ + ".xml"));
|
||||||
|
activeSheetWriter = new OutputStreamWriter(zipOutputStream);
|
||||||
|
sheetWriter = new StreamedPoiSheetWriter(activeSheetWriter);
|
||||||
|
|
||||||
|
if(ReportType.PIVOT.equals(view.getType()))
|
||||||
|
{
|
||||||
|
writePivotTable(view, ReportUtils.getSourceViewForPivotTableView(views, view));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sheetWriter.beginSheet();
|
||||||
|
|
||||||
|
////////////////////////////////////////////////
|
||||||
|
// put the title and header rows in the sheet //
|
||||||
|
////////////////////////////////////////////////
|
||||||
|
writeTitleAndHeader();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
throw (new QReportingException("Error starting worksheet", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private void writeTitleAndHeader() throws QReportingException
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
///////////////
|
||||||
|
// title row //
|
||||||
|
///////////////
|
||||||
|
if(StringUtils.hasContent(exportInput.getTitleRow()))
|
||||||
|
{
|
||||||
|
sheetWriter.insertRow(rowNo++);
|
||||||
|
sheetWriter.createCell(0, exportInput.getTitleRow(), styles.get("title").getIndex());
|
||||||
|
sheetWriter.endRow();
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////
|
||||||
|
// header row //
|
||||||
|
////////////////
|
||||||
|
if(exportInput.getIncludeHeaderRow())
|
||||||
|
{
|
||||||
|
sheetWriter.insertRow(rowNo++);
|
||||||
|
XSSFCellStyle headerStyle = styles.get("header");
|
||||||
|
|
||||||
|
int col = 0;
|
||||||
|
for(QFieldMetaData column : fields)
|
||||||
|
{
|
||||||
|
sheetWriter.createCell(col, column.getLabel(), headerStyle.getIndex());
|
||||||
|
col++;
|
||||||
|
}
|
||||||
|
|
||||||
|
sheetWriter.endRow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
throw (new QReportingException("Error starting Excel report"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public void addRecords(List<QRecord> qRecords) throws QReportingException
|
||||||
|
{
|
||||||
|
LOG.info("Consuming [" + qRecords.size() + "] records from the pipe");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for(QRecord qRecord : qRecords)
|
||||||
|
{
|
||||||
|
writeRecord(qRecord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
LOG.error("Exception generating excel file", e);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
outputStream.close();
|
||||||
|
}
|
||||||
|
catch(IOException ex)
|
||||||
|
{
|
||||||
|
LOG.warn("Secondary error closing excel output stream", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw (new QReportingException("Error generating Excel report", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private void writeRecord(QRecord qRecord) throws IOException
|
||||||
|
{
|
||||||
|
writeRecord(qRecord, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private void writeRecord(QRecord qRecord, boolean isFooter) throws IOException
|
||||||
|
{
|
||||||
|
sheetWriter.insertRow(rowNo++);
|
||||||
|
|
||||||
|
int styleIndex = -1;
|
||||||
|
int dateStyleIndex = styles.get("date").getIndex();
|
||||||
|
int dateTimeStyleIndex = styles.get("datetime").getIndex();
|
||||||
|
if(isFooter)
|
||||||
|
{
|
||||||
|
styleIndex = styles.get("footer").getIndex();
|
||||||
|
dateStyleIndex = styles.get("footer-date").getIndex();
|
||||||
|
dateTimeStyleIndex = styles.get("footer-datetime").getIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
int col = 0;
|
||||||
|
for(QFieldMetaData field : fields)
|
||||||
|
{
|
||||||
|
Serializable value = qRecord.getValue(field.getName());
|
||||||
|
|
||||||
|
if(value != null)
|
||||||
|
{
|
||||||
|
if(value instanceof String s)
|
||||||
|
{
|
||||||
|
sheetWriter.createCell(col, s, styleIndex);
|
||||||
|
}
|
||||||
|
else if(value instanceof Number n)
|
||||||
|
{
|
||||||
|
sheetWriter.createCell(col, n.doubleValue(), styleIndex);
|
||||||
|
|
||||||
|
if(excelCellFormats != null)
|
||||||
|
{
|
||||||
|
String format = excelCellFormats.get(field.getName());
|
||||||
|
if(format != null)
|
||||||
|
{
|
||||||
|
// todo - formats...
|
||||||
|
// worksheet.style(rowNo, col).format(format).set();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(value instanceof Boolean b)
|
||||||
|
{
|
||||||
|
sheetWriter.createCell(col, b, styleIndex);
|
||||||
|
}
|
||||||
|
else if(value instanceof Date d)
|
||||||
|
{
|
||||||
|
sheetWriter.createCell(col, DateUtil.getExcelDate(d), dateStyleIndex);
|
||||||
|
}
|
||||||
|
else if(value instanceof LocalDate d)
|
||||||
|
{
|
||||||
|
sheetWriter.createCell(col, DateUtil.getExcelDate(d), dateStyleIndex);
|
||||||
|
}
|
||||||
|
else if(value instanceof LocalDateTime d)
|
||||||
|
{
|
||||||
|
sheetWriter.createCell(col, DateUtil.getExcelDate(d), dateStyleIndex);
|
||||||
|
}
|
||||||
|
else if(value instanceof ZonedDateTime d)
|
||||||
|
{
|
||||||
|
sheetWriter.createCell(col, DateUtil.getExcelDate(d.toLocalDateTime()), dateTimeStyleIndex);
|
||||||
|
}
|
||||||
|
else if(value instanceof Instant i)
|
||||||
|
{
|
||||||
|
// todo - what would be a better zone to use here?
|
||||||
|
sheetWriter.createCell(col, DateUtil.getExcelDate(i.atZone(ZoneId.systemDefault()).toLocalDateTime()), dateTimeStyleIndex);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sheetWriter.createCell(col, ValueUtils.getValueAsString(value), styleIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
col++;
|
||||||
|
}
|
||||||
|
|
||||||
|
sheetWriter.endRow();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void addTotalsRow(QRecord record) throws QReportingException
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
writeRecord(record, true);
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
throw (new QReportingException("Error adding totals row", e));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* todo
|
||||||
|
CellStyle totalsStyle = workbook.createCellStyle();
|
||||||
|
Font font = workbook.createFont();
|
||||||
|
font.setBold(true);
|
||||||
|
totalsStyle.setFont(font);
|
||||||
|
totalsStyle.setBorderTop(BorderStyle.THIN);
|
||||||
|
totalsStyle.setBorderTop(BorderStyle.THIN);
|
||||||
|
totalsStyle.setBorderBottom(BorderStyle.DOUBLE);
|
||||||
|
|
||||||
|
row.cellIterator().forEachRemaining(cell -> cell.setCellStyle(totalsStyle));
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public void finish() throws QReportingException
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//////////////////////////////////////////////
|
||||||
|
// close the last open sheet if one is open //
|
||||||
|
//////////////////////////////////////////////
|
||||||
|
closeLastSheetIfOpen();
|
||||||
|
|
||||||
|
/////////////////////////////
|
||||||
|
// close the output stream //
|
||||||
|
/////////////////////////////
|
||||||
|
zipOutputStream.close();
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
throw (new QReportingException("Error finishing Excel report", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private void closeLastSheetIfOpen() throws IOException
|
||||||
|
{
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// if we have an active sheet writer: //
|
||||||
|
// - end the current sheet in the spreadsheet writer (write some closing xml, unless it's a pivot!) //
|
||||||
|
// - flush the contents through the activeSheetWriter //
|
||||||
|
// - close the zip entry in the output stream. //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
if(activeSheetWriter != null)
|
||||||
|
{
|
||||||
|
if(!ReportType.PIVOT.equals(currentView.getType()))
|
||||||
|
{
|
||||||
|
sheetWriter.endSheet();
|
||||||
|
}
|
||||||
|
|
||||||
|
activeSheetWriter.flush();
|
||||||
|
zipOutputStream.closeEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** display formats is a map of field name to Excel format strings (e.g., $#,##0.00)
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public void setDisplayFormats(Map<String, String> displayFormats)
|
||||||
|
{
|
||||||
|
this.excelCellFormats = new HashMap<>();
|
||||||
|
for(Map.Entry<String, String> entry : displayFormats.entrySet())
|
||||||
|
{
|
||||||
|
String excelFormat = DisplayFormat.getExcelFormat(entry.getValue());
|
||||||
|
if(excelFormat != null)
|
||||||
|
{
|
||||||
|
excelCellFormats.put(entry.getKey(), excelFormat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private void writePivotTable(QReportView pivotTableView, QReportView dataView) throws QReportingException
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// write the xml file that is the pivot table sheet. //
|
||||||
|
// note that the ZipEntry here will have been started above in the start method //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////
|
||||||
|
activeSheetWriter.write("""
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:mx="http://schemas.microsoft.com/office/mac/excel/2008/main" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mv="urn:schemas-microsoft-com:mac:vml" xmlns:x14="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main" xmlns:x15="http://schemas.microsoft.com/office/spreadsheetml/2010/11/main" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac" xmlns:xm="http://schemas.microsoft.com/office/excel/2006/main">
|
||||||
|
<sheetPr>
|
||||||
|
<outlinePr summaryBelow="0" summaryRight="0"/>
|
||||||
|
</sheetPr>
|
||||||
|
<sheetViews>
|
||||||
|
<sheetView workbookViewId="0"/>
|
||||||
|
</sheetViews>
|
||||||
|
<sheetFormatPr customHeight="1" defaultColWidth="14.43" defaultRowHeight="15.0"/>
|
||||||
|
<sheetData>
|
||||||
|
<row r="1"/>
|
||||||
|
</sheetData>
|
||||||
|
</worksheet>
|
||||||
|
""");
|
||||||
|
activeSheetWriter.flush();
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// start a new zip entry, for this pivot view's cacheDefinition reference //
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
zipOutputStream.putNextEntry(new ZipEntry(pivotViewToCacheDefinitionReferenceMap.get(pivotTableView.getName())));
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////
|
||||||
|
// prepare the xml for each field (e.g., w/ its labelO //
|
||||||
|
/////////////////////////////////////////////////////////
|
||||||
|
List<String> cachedFieldElements = new ArrayList<>();
|
||||||
|
for(QFieldMetaData column : this.fieldsPerView.get(dataView.getName()))
|
||||||
|
{
|
||||||
|
cachedFieldElements.add(String.format("""
|
||||||
|
<cacheField numFmtId="0" name="%s">
|
||||||
|
<sharedItems/>
|
||||||
|
</cacheField>
|
||||||
|
""", column.getLabel()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// write the xml file that is the pivot cache definition (structure only, no data) //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
activeSheetWriter = new OutputStreamWriter(zipOutputStream);
|
||||||
|
activeSheetWriter.write(String.format("""
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<pivotCacheDefinition xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" createdVersion="3" minRefreshableVersion="3" refreshedVersion="3" refreshedBy="Apache POI" refreshedDate="1.7113> 95767702E12" refreshOnLoad="true" r:id="rId1">
|
||||||
|
<cacheSource type="worksheet">
|
||||||
|
<worksheetSource sheet="table1" ref="A1:%s%d"/>
|
||||||
|
</cacheSource>
|
||||||
|
<cacheFields count="%d">
|
||||||
|
%s
|
||||||
|
</cacheFields>
|
||||||
|
</pivotCacheDefinition>
|
||||||
|
""", CellReference.convertNumToColString(dataView.getColumns().size() - 1), rowsPerView.get(dataView.getName()), dataView.getColumns().size(), StringUtils.join("\n", cachedFieldElements)));
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
throw (new QReportingException("Error writing pivot table", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* QQQ - Low-code Application Framework for Engineers.
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
* contact@kingsrook.com
|
* contact@kingsrook.com
|
||||||
* https://github.com/Kingsrook/
|
* https://github.com/Kingsrook/
|
||||||
@ -19,13 +19,13 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.kingsrook.qqq.backend.core.actions.reporting.excelformatting;
|
package com.kingsrook.qqq.backend.core.actions.reporting.excel.poi;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Excel styler that does nothing - just takes defaults (which are all no-op) from the interface.
|
** Excel styler that does nothing - just takes defaults (which are all no-op) from the interface.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class PlainExcelStyler implements ExcelStylerInterface
|
public class PlainPoiExcelStyler implements PoiExcelStylerInterface
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.actions.reporting.excel.poi;
|
||||||
|
|
||||||
|
|
||||||
|
import org.apache.poi.ss.usermodel.CreationHelper;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Interface for classes that know how to apply styles to an Excel stream being
|
||||||
|
** built by POI.
|
||||||
|
*******************************************************************************/
|
||||||
|
public interface PoiExcelStylerInterface
|
||||||
|
{
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
default XSSFCellStyle createStyleForTitle(XSSFWorkbook workbook, CreationHelper createHelper)
|
||||||
|
{
|
||||||
|
return (workbook.createCellStyle());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
default XSSFCellStyle createStyleForHeader(XSSFWorkbook workbook, CreationHelper createHelper)
|
||||||
|
{
|
||||||
|
return (workbook.createCellStyle());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
default XSSFCellStyle createStyleForFooter(XSSFWorkbook workbook, CreationHelper createHelper)
|
||||||
|
{
|
||||||
|
return (workbook.createCellStyle());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,205 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.actions.reporting.excel.poi;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import org.apache.poi.ss.util.CellReference;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Write excel formatted XML to a Writer.
|
||||||
|
** Originally from https://coderanch.com/t/548897/java/Generate-large-excel-POI
|
||||||
|
*******************************************************************************/
|
||||||
|
public class StreamedPoiSheetWriter
|
||||||
|
{
|
||||||
|
private static Pattern xmlSpecialChars = Pattern.compile(".*[&<>'\"].*");
|
||||||
|
|
||||||
|
private final Writer writer;
|
||||||
|
private int rowNo;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public StreamedPoiSheetWriter(Writer writer)
|
||||||
|
{
|
||||||
|
this.writer = writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void beginSheet() throws IOException
|
||||||
|
{
|
||||||
|
writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
|
||||||
|
"<worksheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\">");
|
||||||
|
writer.write("<sheetData>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void endSheet() throws IOException
|
||||||
|
{
|
||||||
|
writer.write("</sheetData>");
|
||||||
|
writer.write("</worksheet>");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Insert a new row
|
||||||
|
**
|
||||||
|
** @param rowNo 0-based row number
|
||||||
|
*******************************************************************************/
|
||||||
|
public void insertRow(int rowNo) throws IOException
|
||||||
|
{
|
||||||
|
writer.write("<row r=\"" + (rowNo + 1) + "\">\n");
|
||||||
|
this.rowNo = rowNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Insert row end marker
|
||||||
|
*******************************************************************************/
|
||||||
|
public void endRow() throws IOException
|
||||||
|
{
|
||||||
|
writer.write("</row>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void createCell(int columnIndex, String value, int styleIndex) throws IOException
|
||||||
|
{
|
||||||
|
String ref = new CellReference(rowNo, columnIndex).formatAsString();
|
||||||
|
writer.write("<c r=\"" + ref + "\" t=\"inlineStr\"");
|
||||||
|
if(styleIndex != -1)
|
||||||
|
{
|
||||||
|
writer.write(" s=\"" + styleIndex + "\"");
|
||||||
|
}
|
||||||
|
writer.write(">");
|
||||||
|
writer.write("<is><t>" + cleanseValue(value) + "</t></is>");
|
||||||
|
writer.write("</c>");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private String cleanseValue(String value)
|
||||||
|
{
|
||||||
|
// todo - profile...
|
||||||
|
if(xmlSpecialChars.matcher(value).find())
|
||||||
|
{
|
||||||
|
value = value.replace("&", "&");
|
||||||
|
value = value.replace("<", "<");
|
||||||
|
value = value.replace(">", ">");
|
||||||
|
value = value.replace("'", "'");
|
||||||
|
value = value.replace("\"", """);
|
||||||
|
}
|
||||||
|
return (value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void createCell(int columnIndex, String value) throws IOException
|
||||||
|
{
|
||||||
|
createCell(columnIndex, value, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void createCell(int columnIndex, double value, int styleIndex) throws IOException
|
||||||
|
{
|
||||||
|
String ref = new CellReference(rowNo, columnIndex).formatAsString();
|
||||||
|
writer.write("<c r=\"" + ref + "\" t=\"n\"");
|
||||||
|
if(styleIndex != -1)
|
||||||
|
{
|
||||||
|
writer.write(" s=\"" + styleIndex + "\"");
|
||||||
|
}
|
||||||
|
writer.write(">");
|
||||||
|
writer.write("<v>" + value + "</v>");
|
||||||
|
writer.write("</c>");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void createCell(int columnIndex, double value) throws IOException
|
||||||
|
{
|
||||||
|
createCell(columnIndex, value, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void createCell(int columnIndex, Boolean value) throws IOException
|
||||||
|
{
|
||||||
|
createCell(columnIndex, value, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void createCell(int columnIndex, Boolean value, int styleIndex) throws IOException
|
||||||
|
{
|
||||||
|
String ref = new CellReference(rowNo, columnIndex).formatAsString();
|
||||||
|
writer.write("<c r=\"" + ref + "\" t=\"n\"");
|
||||||
|
if(styleIndex != -1)
|
||||||
|
{
|
||||||
|
writer.write(" s=\"" + styleIndex + "\"");
|
||||||
|
}
|
||||||
|
writer.write(">");
|
||||||
|
writer.write("<v>" + value + "</v>");
|
||||||
|
writer.write("</c>");
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo? public void createCell(int columnIndex, Calendar value, int styleIndex) throws IOException
|
||||||
|
// todo? {
|
||||||
|
// todo? createCell(columnIndex, DateUtil.getExcelDate(value, false), styleIndex);
|
||||||
|
// todo? }
|
||||||
|
}
|
@ -0,0 +1,176 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.actions.reporting.pivottable;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Full definition of a pivot table - its rows, columns, and values.
|
||||||
|
*******************************************************************************/
|
||||||
|
public class PivotTableDefinition
|
||||||
|
{
|
||||||
|
private List<PivotTableGroupBy> rows;
|
||||||
|
private List<PivotTableGroupBy> columns;
|
||||||
|
private List<PivotTableValue> values;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public PivotTableDefinition withRow(PivotTableGroupBy row)
|
||||||
|
{
|
||||||
|
if(this.rows == null)
|
||||||
|
{
|
||||||
|
this.rows = new ArrayList<>();
|
||||||
|
}
|
||||||
|
this.rows.add(row);
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public PivotTableDefinition withColumn(PivotTableGroupBy column)
|
||||||
|
{
|
||||||
|
if(this.columns == null)
|
||||||
|
{
|
||||||
|
this.columns = new ArrayList<>();
|
||||||
|
}
|
||||||
|
this.columns.add(column);
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public PivotTableDefinition withValue(PivotTableValue value)
|
||||||
|
{
|
||||||
|
if(this.values == null)
|
||||||
|
{
|
||||||
|
this.values = new ArrayList<>();
|
||||||
|
}
|
||||||
|
this.values.add(value);
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for rows
|
||||||
|
*******************************************************************************/
|
||||||
|
public List<PivotTableGroupBy> getRows()
|
||||||
|
{
|
||||||
|
return (this.rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for rows
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setRows(List<PivotTableGroupBy> rows)
|
||||||
|
{
|
||||||
|
this.rows = rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for rows
|
||||||
|
*******************************************************************************/
|
||||||
|
public PivotTableDefinition withRows(List<PivotTableGroupBy> rows)
|
||||||
|
{
|
||||||
|
this.rows = rows;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for columns
|
||||||
|
*******************************************************************************/
|
||||||
|
public List<PivotTableGroupBy> getColumns()
|
||||||
|
{
|
||||||
|
return (this.columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for columns
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setColumns(List<PivotTableGroupBy> columns)
|
||||||
|
{
|
||||||
|
this.columns = columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for columns
|
||||||
|
*******************************************************************************/
|
||||||
|
public PivotTableDefinition withColumns(List<PivotTableGroupBy> columns)
|
||||||
|
{
|
||||||
|
this.columns = columns;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for values
|
||||||
|
*******************************************************************************/
|
||||||
|
public List<PivotTableValue> getValues()
|
||||||
|
{
|
||||||
|
return (this.values);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for values
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setValues(List<PivotTableValue> values)
|
||||||
|
{
|
||||||
|
this.values = values;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for values
|
||||||
|
*******************************************************************************/
|
||||||
|
public PivotTableDefinition withValues(List<PivotTableValue> values)
|
||||||
|
{
|
||||||
|
this.values = values;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.actions.reporting.pivottable;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Functions that can be applied to Values in a pivot table.
|
||||||
|
*******************************************************************************/
|
||||||
|
public enum PivotTableFunction
|
||||||
|
{
|
||||||
|
AVERAGE("Average"),
|
||||||
|
COUNT("Count Numbers (COUNTA)"),
|
||||||
|
COUNT_NUMS("Count Values (COUNT)"),
|
||||||
|
MAX("Max"),
|
||||||
|
MIN("Min"),
|
||||||
|
PRODUCT("Product"),
|
||||||
|
STD_DEV("StdDev"),
|
||||||
|
STD_DEVP("StdDevp"),
|
||||||
|
SUM("Sum"),
|
||||||
|
VAR("Var"),
|
||||||
|
VARP("Varp");
|
||||||
|
|
||||||
|
|
||||||
|
private final String label;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
PivotTableFunction(String label)
|
||||||
|
{
|
||||||
|
this.label = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for label
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getLabel()
|
||||||
|
{
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,128 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.actions.reporting.pivottable;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Either a row or column grouping in a pivot table. e.g., a field plus
|
||||||
|
** sorting details, plus showTotals boolean.
|
||||||
|
*******************************************************************************/
|
||||||
|
public class PivotTableGroupBy
|
||||||
|
{
|
||||||
|
private String fieldName;
|
||||||
|
private PivotTableOrderBy orderBy;
|
||||||
|
private boolean showTotals;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for fieldName
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getFieldName()
|
||||||
|
{
|
||||||
|
return (this.fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for fieldName
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setFieldName(String fieldName)
|
||||||
|
{
|
||||||
|
this.fieldName = fieldName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for fieldName
|
||||||
|
*******************************************************************************/
|
||||||
|
public PivotTableGroupBy withFieldName(String fieldName)
|
||||||
|
{
|
||||||
|
this.fieldName = fieldName;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for orderBy
|
||||||
|
*******************************************************************************/
|
||||||
|
public PivotTableOrderBy getOrderBy()
|
||||||
|
{
|
||||||
|
return (this.orderBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for orderBy
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setOrderBy(PivotTableOrderBy orderBy)
|
||||||
|
{
|
||||||
|
this.orderBy = orderBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for orderBy
|
||||||
|
*******************************************************************************/
|
||||||
|
public PivotTableGroupBy withOrderBy(PivotTableOrderBy orderBy)
|
||||||
|
{
|
||||||
|
this.orderBy = orderBy;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for showTotals
|
||||||
|
*******************************************************************************/
|
||||||
|
public boolean getShowTotals()
|
||||||
|
{
|
||||||
|
return (this.showTotals);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for showTotals
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setShowTotals(boolean showTotals)
|
||||||
|
{
|
||||||
|
this.showTotals = showTotals;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for showTotals
|
||||||
|
*******************************************************************************/
|
||||||
|
public PivotTableGroupBy withShowTotals(boolean showTotals)
|
||||||
|
{
|
||||||
|
this.showTotals = showTotals;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.actions.reporting.pivottable;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** How a group-by (rows or columns) should be sorted.
|
||||||
|
*******************************************************************************/
|
||||||
|
public class PivotTableOrderBy
|
||||||
|
{
|
||||||
|
// todo - implement, but only if POI supports (or we build our own support...)
|
||||||
|
}
|
@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.actions.reporting.pivottable;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** a value (e.g., field name + function) used in a pivot table
|
||||||
|
*******************************************************************************/
|
||||||
|
public class PivotTableValue
|
||||||
|
{
|
||||||
|
private String fieldName;
|
||||||
|
private PivotTableFunction function;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for fieldName
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getFieldName()
|
||||||
|
{
|
||||||
|
return (this.fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for fieldName
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setFieldName(String fieldName)
|
||||||
|
{
|
||||||
|
this.fieldName = fieldName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for fieldName
|
||||||
|
*******************************************************************************/
|
||||||
|
public PivotTableValue withFieldName(String fieldName)
|
||||||
|
{
|
||||||
|
this.fieldName = fieldName;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for function
|
||||||
|
*******************************************************************************/
|
||||||
|
public PivotTableFunction getFunction()
|
||||||
|
{
|
||||||
|
return (this.function);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for function
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setFunction(PivotTableFunction function)
|
||||||
|
{
|
||||||
|
this.function = function;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for function
|
||||||
|
*******************************************************************************/
|
||||||
|
public PivotTableValue withFunction(PivotTableFunction function)
|
||||||
|
{
|
||||||
|
this.function = function;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user