[JAVA] word 透過 template 產生報表 (poi.xwpf.usermodel.*)

摘要:[JAVA] word 透過 template 產生報表 (poi.xwpf.usermodel.*)

主要是利用POI 抓取特定關鍵字並加以取代

快速產生報表,適用于報表格式較為複雜的word,

先製作好準備產生報表的樣式,有個很重要的地方在於

看似相同的${title} 字串,實際word中可能拆成多個段落(Paragraph)

所以建議事先透過xml分析器(ex: XMLspy)將取代字元檢查一下,是否符合于預期的排列方式.

先取得template 


String tempPath = new StringBuilder().append("META-INF/report-template/").append(templateName).append(".docx").toString();
InputStream b03TempInput = ReportUtil.class.getClassLoader().getResourceAsStream(tempPath);

並產生一個新的XWPFDocument,帶入此template.


XWPFDocument doc = null;
try {
    doc = new XWPFDocument(b03TempInput);
...

將資料已map方式填入


// 將欲填入資料放進map
Map map = new HashMap();
map.put("heading",“heading”); 
map.put("recorder","xxx"); // recorder
...

定義pattern 此範例為${key值}


// 建立取代的pattern (正則表示式)
String pattern = "";
int first = 0;
for (String str : map.keySet()) {
    if (first > 0) {
	pattern += "|";
    }
    pattern = pattern + "(?<=\\$\\{)" + str + "(?=\\})";
    first++;
}
Pattern pat = Pattern.compile(pattern);

開始針對xml格式進行搜尋並取代key


// 針對段落模板取代
Iterator itrPs = doc.getParagraphsIterator();
while (itrPs.hasNext()) {
	XWPFParagraph paragraph = itrPs.next();
	for (XWPFRun run : paragraph.getRuns()) {
		for (CTText text : run.getCTR().getTList()) {
			matcher = pat.matcher(text.getStringValue());
			if (matcher.find()) {
				key = matcher.group();
				text.setStringValue(text.getStringValue().
                                        replaceAll("\\$\\{"+ key + "\\}", map.get(key)));
			 }
			
		}
	}
 }

// 針對表單模板取代 (若無表單 可直接針對段落search)
Iterator itrTable = doc.getTablesIterator();
while (itrTable.hasNext()) {
    XWPFTable table = itrTable.next();
    for (XWPFTableRow row : table.getRows()) {
	for (XWPFTableCell cell : row.getTableCells()) {
	    this.fillUpParagraph(cell, pat, map);
	}
    }
}


private void fullUpParagraph(XWPFTableCell cell, Pattern pat, Map map) {
	XWPFDocument tempDoc = new XWPFDocument();
	Matcher matcher;
	String key;
	List tempParagraphList = new ArrayList();
	// 取得每個段落
	for (XWPFParagraph paragraph : cell.getParagraphs()) {
	    // 取得每個Run
	    for (XWPFRun run : paragraph.getRuns()) {
		// 分析每個run中的 text
		for (CTText text : run.getCTR().getTList()) {
		    matcher = pat.matcher(text.getStringValue());
		    if (matcher.find()) {
			key = matcher.group();
			if (map.get(key) != null) {
			    String[] paragraphRows = map.get(key).split("\n");
			    for (int i = 0; i < paragraphRows.length; i++) {
				if (i == 0) {
				    text.setStringValue(text.getStringValue().replaceAll("\\$\\{" + key + "\\}",
					    paragraphRows[i]));
				} else {
				    XWPFParagraph newParagraph = tempDoc.createParagraph();
				    XWPFRun newRun = newParagraph.createRun();
				    newRun.setText(paragraphRows[i]);
				    tempParagraphList.add(newParagraph);
				}
			    }
			}
		    }
		}
	    }
	}

備註 : 有時會因為套用模板而 多一頁空白頁 可以加一個Break


//將多個doc中的表單組合成一個最後的報表
for (XWPFDocument doc : docList) {
    for (XWPFTable table : doc.getTables()) {

	finalDoc.createTable();
	finalDoc.setTable(count, table);
	XWPFParagraph paragraph = finalDoc.createParagraph();
	XWPFRun run = paragraph.createRun();
	// 避免最後一個doc多一頁空白頁
	if (count != docList.size() - 1) {
	    run.addBreak(BreakType.PAGE);
	}
	count++;
    }
}