ExcelContext.java
package pro.verron.officestamper;
import org.docx4j.openpackaging.exceptions.Docx4JException;
import org.docx4j.openpackaging.packages.SpreadsheetMLPackage;
import org.docx4j.openpackaging.parts.SpreadsheetML.WorkbookPart;
import org.docx4j.openpackaging.parts.SpreadsheetML.WorksheetPart;
import org.docx4j.openpackaging.parts.relationships.RelationshipsPart;
import org.jspecify.annotations.NonNull;
import org.xlsx4j.org.apache.poi.ss.usermodel.DataFormatter;
import org.xlsx4j.sml.Row;
import org.xlsx4j.sml.Sheet;
import org.xlsx4j.sml.Workbook;
import org.xlsx4j.sml.Worksheet;
import pro.verron.officestamper.api.OfficeStamperException;
import java.io.InputStream;
import java.util.*;
import static java.util.Collections.emptyList;
/// Context for Excel-based data.
public class ExcelContext
extends AbstractMap<String, List<Map<String, String>>> {
/// Data formatter for Excel cells.
public static final DataFormatter formatter = new DataFormatter();
private final Map<String, List<Map<String, String>>> source;
/// Constructs an ExcelContext from the specified spreadsheet package.
///
/// @param spreadsheetPackage the spreadsheet package
public ExcelContext(SpreadsheetMLPackage spreadsheetPackage) {
var workbookPart = spreadsheetPackage.getWorkbookPart();
var workbook = getWorkbook(workbookPart);
var allSheets = workbook.getSheets();
var sheets = allSheets.getSheet();
var relationshipsPart = workbookPart.getRelationshipsPart();
var excel = new TreeMap<String, List<Map<String, String>>>();
for (var sheet : sheets) {
var worksheetPart = extractWorksheetPart(sheet, relationshipsPart);
var worksheet = extractWorksheet(worksheetPart);
var sheetDate = worksheet.getSheetData();
var rows = sheetDate.getRow();
excel.put(sheet.getName(), extractRecords(rows));
}
source = Collections.unmodifiableMap(excel);
}
private static Workbook getWorkbook(WorkbookPart workbookPart) {
try {
return workbookPart.getContents();
} catch (Docx4JException e) {
throw new OfficeStamperException(e);
}
}
private WorksheetPart extractWorksheetPart(Sheet sheet, RelationshipsPart relationshipsPart) {
return (WorksheetPart) relationshipsPart.getPart(sheet.getId());
}
private Worksheet extractWorksheet(WorksheetPart worksheetPart) {
try {
return worksheetPart.getContents();
} catch (Docx4JException e) {
throw new OfficeStamperException(e);
}
}
private List<Map<String, String>> extractRecords(List<Row> rows) {
if (rows.isEmpty()) return emptyList();
var headers = extractHeaders(rows.getFirst());
return extractRecords(headers, rows.subList(1, rows.size()));
}
private List<String> extractHeaders(Row row) {
return row.getC()
.stream()
.map(formatter::formatCellValue)
.toList();
}
private List<Map<String, String>> extractRecords(List<String> headers, List<Row> rows) {
List<Map<String, String>> list = new ArrayList<>();
for (var row : rows) {
Map<String, String> rec = new TreeMap<>();
for (var i = 0; i < headers.size(); i++) {
rec.put(headers.get(i), formatCellValueAt(row, i));
}
list.add(rec);
}
return list;
}
private static String formatCellValueAt(Row row, int i) {
var cells = row.getC();
if (i >= cells.size()) return "";
return formatter.formatCellValue(cells.get(i));
}
/// Creates an ExcelContext from the specified input stream.
///
/// @param inputStream the input stream
///
/// @return the ExcelContext
public static Object from(InputStream inputStream) {
try {
return from(SpreadsheetMLPackage.load(inputStream));
} catch (Docx4JException e) {
throw new OfficeStamperException(e);
}
}
private static Object from(SpreadsheetMLPackage spreadsheetPackage) {
return new ExcelContext(spreadsheetPackage);
}
@Override
public @NonNull Set<Entry<String, List<Map<String, String>>>> entrySet() {
return source.entrySet();
}
@Override
public boolean equals(Object o) {
return super.equals(o);
}
@Override
public int hashCode() {
return super.hashCode();
}
}