Велосипед для генерации Excel документов по шаблону

Как быть, если в итоговом документе должны отсутствовать некоторые страницы из шаблона?
velo_excel

Обычно для программного формирования сложных отчётов в 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 мне не нужна. А вот разобраться, как оно там, внутрях, устроено было интересно :). Рад буду, если хоть кому — то этот пост поможет.