Я создаю файл .xlsx с помощью Apache POI 5.2.5, отправляю массив байтов на сторону клиента и загружаю файл локально на клиентский компьютер.
При попытке открыть файл я получаю сообщение об ошибке «Мы обнаружили проблему с некоторым содержимым в *.xlsx. Хотите, чтобы мы попытались восстановить его, насколько это возможно». При нажатии «Да» файл открывается как в Windows, так и на Mac, и все данные присутствуют, но не в виде табличного объекта. Попытка открыть файл с помощью листов Google работает без проблем, а объект таблицы имеет границу.
Проблема сохранялась и тогда, когда я пытался сохранить ее непосредственно на сервере, поэтому я не думаю, что проблема на стороне клиента.
Код, генерирующий массив байтов:
private byte[] generateExcelBytes(List<DATA_OBJECT> data) {
try (XSSFWorkbook workbook = new XSSFWorkbook()) {
XSSFSheet sheet = workbook.createSheet("Data");
CellStyle headerStyle = workbook.createCellStyle();
Font headerFont = workbook.createFont();
headerFont.setBold(true);
headerFont.setFontHeightInPoints((short) 14);
headerStyle.setFont(headerFont);
CellStyle dataStyle = workbook.createCellStyle();
Font dataFont = workbook.createFont();
dataFont.setFontHeightInPoints((short) 12);
dataStyle.setFont(dataFont);
createTable(sheet, 0, 0, headerStyle, dataStyle, data);
// Write the workbook content to a ByteArrayOutputStream
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
workbook.write(outputStream);
return outputStream.toByteArray();
} catch (IOException e) {
logger.error("Exception when creating xlsx file for account {} CDI status tables", account.getId(), e);
}
return null;
}
private void createTable(XSSFSheet sheet, int startRow, int startCol, CellStyle headerStyle, CellStyle dataStyle, List<DATA_OBJECT> data) {
// Create sample data (replace with your actual data generation logic)
Row headerRow = sheet.createRow(startRow);
// for each header
createCell(headerRow, startCol + X, HEADER_NAME, headerStyle);
// Create data rows
for (int i = 0; i < data.size(); i++) {
DATA_OBJECT data_object = data.get(i);
Row dataRow = sheet.createRow(startRow + i + 1);
// for each data object
createCell(dataRow, startCol, data_object.get_relevant_value, dataStyle);
}
int endRow = startRow + baseHoldings.size();
int endCol = startCol + NUM_OF_COLUMNS;
XSSFAutoFilter autoFilter = sheet.setAutoFilter(new CellRangeAddress(startRow, endRow, startCol, endCol));
for (int i = startCol; i <= startCol + NUM_OF_COLUMNS; i++) {
sheet.autoSizeColumn(i);
sheet.setColumnWidth(i, sheet.getColumnWidth(i) + 1000);
}
// Create Excel table
XSSFTable table = sheet.createTable(null);
CTTable cttable = table.getCTTable();
cttable.setDisplayName("Data Table");
cttable.setId(1);
cttable.setName("Data_Table");
cttable.setRef(new CellRangeAddress(startRow, endRow, startCol, endCol).formatAsString());
}
private void createCell(Row row, int columnIndex, String value, CellStyle style) {
Cell cell = row.createCell(columnIndex);
cell.setCellValue(value);
cell.setCellStyle(style);
}
Открыв файл на Mac, мне удалось увидеть настоящую ошибку:
<?xml version = "1.0" encoding = "UTF-8" standalone = "yes"?>
<recoveryLog xmlns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main">
<logFileName>Repair Result to <FILE_NAME>0.xml</logFileName>
<summary>Errors were detected in file '<FILE_PATH>.xlsx'</summary>
<removedParts summary = "Following is a list of removed parts:">
<removedPart>Removed Part: /xl/tables/table1.xml part with XML error. (Table) Xml parsing error Line 2, column 70.</removedPart>
</removedParts>
</recoveryLog>
Содержимое файла /xl/tables/table1.xml:
<?xml version = "1.0" encoding = "UTF-8"?>
<table id = "1" displayName = "Data Table" name = "Data_Table" ref = "A1:H3" xmlns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"/>
Я не знаю, что вызывает ошибку синтаксического анализа XML или как ее избежать при создании файла на стороне сервера. Любая помощь будет оценена по достоинству.
Здесь несколько проблем.
Не используйте areaReference
== null
в конструкторе XSSFTable
. Вместо этого определите диапазон данных для таблицы как AreaReference
.
Избегайте использования CTTable
. Apache POI 5.2.5 предоставляет больше всего необходимого в XSSFTable
. В вашем случае вы забыли создать столбцы таблицы, которые должны соответствовать ячейкам листа. Это делается XSSFTable table = sheet.createTable(areaReference)
правильно, когда areaReference
не null
, а правильный AreaReference
.
Отображаемое имя не должно содержать пробелов.
И для таблицы не может быть фильтра листов. Таблицы используют собственные фильтры. К сожалению, XSSFTable
до сих пор не предоставлено. Итак, для этого необходимо использовать CTTable
: table.getCTTable().addNewAutoFilter().setRef(areaReference.formatAsString());
.
Не совсем уверен насчет вашего List<DATA_OBJECT> data
, поэтому я не совсем уверен, правильно ли вы рассчитываете endRow
и endCol
. Конечно, таблица не может начинаться с startRow
, так как есть заголовок, который находится за пределами таблицы, не так ли?
Полный пример, использующий большую часть вашего подхода, но для простоты Object[][] data
и файл результатов:
import java.io.FileOutputStream;
import java.io.IOException;
import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.util.AreaReference;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFTable;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFAutoFilter;
public class CreateTableExample {
static void createTable(XSSFSheet sheet, int startRow, int startCol, CellStyle headerStyle, CellStyle dataStyle, Object[][] data) {
Row headerRow = sheet.createRow(startRow);
// for each header
createCell(headerRow, startCol, "HEADER_NAME", headerStyle);
int headerRows = 1; // how many rows outside table?
int endRow = startRow + headerRows + data.length - 1;
int endCol = startCol + data[0].length - 1;
// Merge cells for Header
sheet.addMergedRegion(new CellRangeAddress(startRow, startRow, startCol, endCol));
// Create data rows
for (int i = 0; i < data.length; i++) {
Object[] data_object = data[i];
Row dataRow = sheet.createRow(startRow + headerRows + i);
int c = 0;
for (Object value : data_object) {
createCell(dataRow, startCol + c++, value, dataStyle);
}
}
//XSSFAutoFilter autoFilter = sheet.setAutoFilter(new CellRangeAddress(startRow + headerRows, endRow, startCol, endCol)); // No sheet filter for Table!
for (int i = startCol; i <= endCol; i++) {
sheet.autoSizeColumn(i);
sheet.setColumnWidth(i, sheet.getColumnWidth(i) + 1000);
}
// Define the data range for the table
AreaReference areaReference = new AreaReference(new CellReference(startRow + headerRows, startCol), new CellReference(endRow, endCol), SpreadsheetVersion.EXCEL2007);
// Create Excel table
//XSSFTable table = sheet.createTable(null); // do not use areaReference == null!
XSSFTable table = sheet.createTable(areaReference);
// Set the table style
table.getCTTable().addNewTableStyleInfo();
table.getCTTable().getTableStyleInfo().setName("TableStyleLight12");
table.setName("Data Table");
table.setDisplayName("Data_Table"); // display name must not contain spaces!
table.getCTTable().addNewAutoFilter().setRef(areaReference.formatAsString()); // set AutoFilter in table
}
static void createCell(Row row, int columnIndex, Object value, CellStyle style) {
Cell cell = row.createCell(columnIndex);
if (value instanceof String) {
cell.setCellValue((String)value);
} else if (value instanceof Number) {
cell.setCellValue(((Number)value).doubleValue());
}
cell.setCellStyle(style);
}
public static void main(String[] args) {
// Create sample data (replace with your actual data generation logic)
Object[][] data = new Object[][] {
new Object[] {"Name", "Value", "Class", "Amount"},
new Object[] {"Name 1", 123, "Class 1", 123.45},
new Object[] {"Name 2", 456, "Class 2", 456.78},
new Object[] {"Name 3", 789, "Class 3", 789.01},
new Object[] {"Name 4", 123, "Class 4", 123.45},
new Object[] {"Name 5", 456, "Class 5", 456.78},
//...
};
try (Workbook workbook = new XSSFWorkbook()) {
Sheet sheet = workbook.createSheet("Sheet1");
CellStyle headerStyle = workbook.createCellStyle();
Font headerFont = workbook.createFont();
headerFont.setBold(true);
headerFont.setFontHeightInPoints((short) 14);
headerStyle.setFont(headerFont);
CellStyle dataStyle = workbook.createCellStyle();
Font dataFont = workbook.createFont();
dataFont.setFontHeightInPoints((short) 12);
dataStyle.setFont(dataFont);
createTable((XSSFSheet)sheet, 0, 0, headerStyle, dataStyle, data);
// Save the workbook
try (FileOutputStream fileOut = new FileOutputStream("./workbook_with_table.xlsx")) {
workbook.write(fileOut);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Спасибо. Как выяснилось. У меня возникла проблема с определенной областью таблицы. Я также столкнулся с проблемой с фильтром листов для таблицы, когда добавлял на страницу еще одну таблицу. Спасибо за пример!