Обычно для программного формирования сложных отчётов в xls предлагается использовать «вручную» сформированный документ — шаблон, в нужных местах которого вместо реальных данных подставлены некоторые «теги» н.п. ${userName}, ${userAge}, и в процессе заполнения шаблона находить теги в тексте документа и заменять их соответствующими значениями.
Но как быть, если в итоговом документе должны отсутствовать некоторые страницы из шаблона, и наоборот, другие страницы могут быть несколько раз «клонированы» и заполнены разными данными? И как избавиться от нудного заполнения маппинга тег->значение в коде?
Опишем модель листа(Sheet) документа.
public class SheetModel {
private String sheetToClone; //Лист,клонируя который получим листы итогового документа
private String sheetName; //Имя листа итогового документа
private Map<String, Object> mappings;
//getters and setters
}
Далее используя Apache POI пробежимся по книге, найдём лист с именем sheetToClone и создадим его копию. Таким образом, можно создать сколько угодно объектов класса SheetModel, пробежаться по ним в цикле и в итоге получить документ, содержащий в себе листы исходного документа и их копии. Далее «исходные» листы удаляются.
private void createNewSheets(List<SheetModel> sheetModelList){
for (SheetModel sheetModel: sheetModelList){
String sheetName=sheetModel.getSheetName();
String sheetToClone=sheetModel.getSheetToClone();
cloneSheet(sheetName, sheetToClone);
}
}
private void cloneSheet(String sheetName,String sheetToClone ){
int sheetToCloneIdx=getSheetIndex(sheetToClone);
cloneSheet(sheetToCloneIdx, sheetName);
}
private int getSheetIndex(String sheetName) throws SheetNotFoundException{
for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
if(workbook.getSheetAt(i).getSheetName().equals(sheetName) ) {
return i;
}
}
throw new SheetNotFoundException("Sheet '" + sheetName +"' not found" );
}
public void cloneSheet(int index, String newSheetName) {
HSSFSheet newSheet = workbook.cloneSheet(index);
for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
if(newSheet.equals(workbook.getSheetAt(i))) {
workbook.setSheetName(i, newSheetName);
break;
}
}
}
Но самое интересное — это Apache Commons JEXL library.
JEXL implements an Expression Language based on some extensions to the JSTL Expression Language supporting most of the constructs seen in shell-script or ECMAScript
Вот немного переделанный пример с сайта:
// Create or retrieve a JexlEngine
JexlEngine jexl = new JexlEngine();
// Create an expression object
String jexlExp = "user.name";
Expression e = jexl.createExpression( jexlExp );
// Create a context and add data
JexlContext jc = new MapContext();
jc.set("user", new User("Вася") );
// Now evaluate the expression, getting the result
Object o = e.evaluate(jc);
o.toString(); //Вернёт "Вася"
Таким образом отпадает необходимость перечислять в java коде все теги, задавая им соответствие с реальными данными.
Достаточно задать соответствие только для объекта, после этого задача замены «тега» на его значение сводится к поиску «тегов», скармливания их Commons JEXL и запись в ячейку с тегом результата работы библиотеки. Как то сумбурно получилось, попробую объяснить на примере.
Пусть у нас в шаблоне «теги» будут выглядеть например так: ${user.name}, ${user.age}. А в java коде достаточно будет просто поместить в карту объект класса User.
После этого пробежимся в цикле по всем ячейкам документа, найдём строки, ограниченные '${' и '}' и заменим значения в них на результат работы Commons JEXL library
private void fillSheet() {
User user=new User("Вася",25); //Имя и возраст
Map<String,Object> mappings=new HashMap<String,Object>();
mappings.put("user",user);
JexlEngine engine=new JexlEngine();
JexlContext context=new MapContext(mappings);
for(Row row : sheet) {
for(Cell cell : row) {
if(cell.getCellType()==Cell.CELL_TYPE_STRING){
String exp=findExpression(cell);
if(exp!=null){
Expression e=engine.createExpression(exp);
Object o=e.evaluate(context);
if(o!=null){
String result=o.toString();
cell.setCellValue(result);
}
}
}
}
}
}
Метод findExpression() ищет в строке, содержащейся в ячейке подстроку, заключённую между между '${' и '}'
О Apache Commons JEXL library я узнал, встретившись с проектом JETT. Не хотелось добавлять кучу библиотек в проект (JETT зависит ещё от нескольких библиотек), да и вся функциональность JETT мне не нужна. А вот разобраться, как оно там, внутрях, устроено было интересно :). Рад буду, если хоть кому — то этот пост поможет.
Автор: eof404