Обычно для программного формирования сложных отчётов в xls предлагается использовать «вручную» сформированный документ — шаблон, в нужных местах которого вместо реальных данных подставлены некоторые «теги» н.п. ${userName}, ${userAge}, и в процессе заполнения шаблона находить теги в тексте документа и заменять их соответствующими значениями.
Но как быть, если в итоговом документе должны отсутствовать некоторые страницы из шаблона, и наоборот, другие страницы могут быть несколько раз «клонированы» и заполнены разными данными? И как избавиться от нудного заполнения маппинга тег->значение в коде?
Опишем модель листа (Sheet) документа.
Далее используя Apache POI пробежимся по книге, найдём лист с именем sheetToClone и создадим его копию. Таким образом, можно создать сколько угодно объектов класса SheetModel, пробежаться по ним в цикле и в итоге получить документ, содержащий в себе листы исходного документа и их копии. Далее «исходные» листы удаляются.
Но самое интересное — это Apache Commons JEXL library.
Вот немного переделанный пример с сайта:
Таким образом отпадает необходимость перечислять в java коде все теги, задавая им соответствие с реальными данными.
Достаточно задать соответствие только для объекта, после этого задача замены «тега» на его значение сводится к поиску «тегов», скармливания их Commons JEXL и запись в ячейку с тегом результата работы библиотеки. Как то сумбурно получилось, попробую объяснить на примере.
Пусть у нас в шаблоне «теги» будут выглядеть например так: ${user.name}, ${user.age}. А в java коде достаточно будет просто поместить в карту объект класса User.
После этого пробежимся в цикле по всем ячейкам документа, найдём строки, ограниченные '${' и '}' и заменим значения в них на результат работы Commons JEXL library
Метод findExpression() ищет в строке, содержащейся в ячейке подстроку, заключённую между между '${' и '}'
О Apache Commons JEXL library я узнал, встретившись с проектом JETT. Не хотелось добавлять кучу библиотек в проект (JETT зависит ещё от нескольких библиотек), да и вся функциональность JETT мне не нужна. А вот разобраться, как оно там, внутрях, устроено было интересно :). Рад буду, если хоть кому — то этот пост поможет.
Но как быть, если в итоговом документе должны отсутствовать некоторые страницы из шаблона, и наоборот, другие страницы могут быть несколько раз «клонированы» и заполнены разными данными? И как избавиться от нудного заполнения маппинга тег->значение в коде?
Опишем модель листа (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 мне не нужна. А вот разобраться, как оно там, внутрях, устроено было интересно :). Рад буду, если хоть кому — то этот пост поможет.
