Note
最新更新了github,有問題歡迎評論。感謝評論+討論的同在奮鬥的人
需求:(項目又一反人類需求)不同的樣式模板,替換數據生成新的PPT
花了我4天時間,轉載請註明作者感謝~
Github
地址:https://github.com/ithuhui/hui-core-autoreport
分支:master
位置:com.hui.core.report.ReportApp
Core
效果圖:
- 讀取模板,樣式不變只替換其中數據
- POI操作
- 數據封裝
Code
-
接口設計
流程: 讀取模板 -》 構建每一頁的PPT(圖表,表格,文本框) -》把傳入的數據參數進行替換 -》保存生成新的PPT
-
接口實現
/** * <b><code>PowerPointGenerator</code></b> * <p/> * Description: * <p/> * <b>Creation Time:</b> 2018/10/23 21:15. * * @author Hu Weihui */ public class PowerPointGenerator { private PowerPointGenerator() { } /** * PPT構造方法 * * @param templateFilePath * @param destFilePath * @param slideDataMap * @throws IOException */ public static void generatorPowerPoint(String templateFilePath, String destFilePath, Map<Integer, SlideData> slideDataMap) throws IOException, NoSuchChartTypeException { XMLSlideShow ppt = readPowerPoint(templateFilePath); List<XSLFSlide> slideList = ppt.getSlides(); for (XSLFSlide slide : slideList) { int slidePage = slide.getSlideNumber(); SlideData slideData = slideDataMap.get(slidePage); generatorSlide(slide, slideData); } savePowerPoint(ppt, destFilePath); } /** * 保存ppt到指定路徑 * * @param ppt * @param outputFilePath * @throws IOException */ private static void savePowerPoint(XMLSlideShow ppt, String outputFilePath) throws IOException { try ( FileOutputStream fileOutputStream = new FileOutputStream(outputFilePath); ) { ppt.write(fileOutputStream); ppt.close(); } } /** * 讀取模板庫PPT * * @param inputFilePath * @return * @throws IOException */ private static XMLSlideShow readPowerPoint(String inputFilePath) throws IOException { FileInputStream fileInputStream = new FileInputStream(inputFilePath); XMLSlideShow ppt = new XMLSlideShow(fileInputStream); return ppt; } /** * 替換每一頁數據 * * @param slide * @param slideData * @throws IOException */ private static void generatorSlide(XSLFSlide slide, SlideData slideData) throws IOException, NoSuchChartTypeException { List<POIXMLDocumentPart> partList = slide.getRelations(); int chartNum = 0; for (POIXMLDocumentPart part : partList) { if (part instanceof XSLFChart) { List<SeriesData> seriesDataList = slideData.getChartDataList().get(chartNum).getSeriesDataList(); generatorChart((XSLFChart) part, seriesDataList); chartNum++; } } List<XSLFShape> shapeList = slide.getShapes(); for (XSLFShape shape : shapeList) { Map<String, String> textMap = slideData.getTextMap(); List<TableData> tableDataList = slideData.getTableDataList(); int tableNum = 0; //判斷文本框 if (shape instanceof XSLFTextShape) { generatorTextBox((XSLFTextShape) shape, textMap); } //判斷表格 if (shape instanceof XSLFTable) { List<TableRowData> tableRowDataList = tableDataList.get(tableNum).getTableRowDataList(); generatorTable((XSLFTable) shape, tableRowDataList); } } } /** * 構造圖表 * * @param chart * @param seriesDataList * @throws IOException */ private static void generatorChart(XSLFChart chart, List<SeriesData> seriesDataList) throws IOException, NoSuchChartTypeException { if (seriesDataList.size() < 1) { return; } GraphUtils.refreshGraph(chart, seriesDataList); } /** * 構造文本 * * @param textShape * @param textMap */ private static void generatorTextBox(XSLFTextShape textShape, Map<String, String> textMap) { List<XSLFTextParagraph> textParagraphList = textShape.getTextParagraphs(); for (XSLFTextParagraph textParagraph : textParagraphList) { String text = textParagraph.getText(); String regex = "\\$\\{.*?\\}"; Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(text); List<String> keys = new ArrayList<>(); while (matcher.find()) { keys.add(matcher.group()); } for (String key : keys) { String textKey = key.substring(2, key.length() - 1); text = text.replace(key, textMap.get(textKey) == null ? " " : textMap.get(textKey)); } List<XSLFTextRun> textRuns = textParagraph.getTextRuns(); for (XSLFTextRun textRun : textRuns) { String textStr = textRun.getRawText() == null ? "" : textRun.getRawText(); textRun.setText(""); } if (textRuns.size() > 0) { textRuns.get(0).setText(text); } } } /** * 構造表格 * * @param table * @param tableDataList */ private static void generatorTable(XSLFTable table, List<TableRowData> tableDataList) { List<XSLFTableRow> rows = table.getRows(); int rowSize = rows.size() - 1; for (int i = 0; i < tableDataList.size(); i++) { if (i < rowSize) { List<XSLFTableCell> cells = rows.get(i + 1).getCells(); for (int j = 0; j < tableDataList.get(i).getDataList().size(); j++) { String s = tableDataList.get(i).getDataList().get(j); cells.get(j).setText(s); } } else { table.addRow(); XSLFTableRow row = rows.get(i + 1); for (int j = 0; j < tableDataList.get(i).getDataList().size(); j++) { String s = tableDataList.get(i).getDataList().get(j); row.addCell().setText(s); } } } table.getCell(0, 0).setText(""); }
-
圖表的構造工具
圖表構造工具沒時間重構,只有個demo,這裏只挑core代碼進行記錄。
- 判斷圖表類型
參考別人,判斷圖表的類型真的是反人類,但是暫時還沒找到其他的方法。
String chartType = ""; CTPlotArea plotArea = part.getCTChart().getPlotArea(); if (plotArea.getLineChartList().size() != 0) { chartType = "lie"; } if (plotArea.getBarChartList().size() != 0) { chartType = "bar"; } if (plotArea.getLineChartList().size() != 0 && plotArea.getBarChartList().size() != 0) { chartType = "barAndlie"; } if (plotArea.getPieChartList().size() != 0) { chartType = "pie"; } if (chartType == null) { throw new NoSuchChartTypeException("no Such Chart Type be found"); } else { return chartType; }
- 刷新圖表
// 創建一個excel Workbook wb = new XSSFWorkbook(); Sheet sheet = wb.createSheet(); // 先生成excel表格,在刷新函數中往excel表格中添加數據 for (int i = 0; i <= seriesDataList.get(0).getCategoryDataList() .size(); i++) { sheet.createRow(i); for (int j = 0; j <= seriesDataList.size(); j++) { sheet.getRow(i).createCell(j); } } CTChart ctChart = chart.getCTChart(); // 獲取圖表區域 CTPlotArea plotArea = ctChart.getPlotArea(); // 獲取柱狀圖表 CTBarChart barChart = plotArea.getBarChartArray(0); // 獲取圖表的系列 for (int i = 0; i < barChart.getSerList().size(); i++) { CTSerTx barTx = barChart.getSerArray(i).getTx(); barTx.getStrRef().getStrCache().getPtArray(0) .setV(seriesDataList.get(i).getSeriesName()); sheet.getRow(0).getCell(i + 1) .setCellValue(seriesDataList.get(0).getSeriesName()); String barTitleRef = new CellReference(sheet.getSheetName(), 0, i + 1, true, true).formatAsString(); barTx.getStrRef().setF(barTitleRef); CTAxDataSource barCat = barChart.getSerArray(i).getCat(); CTNumDataSource barVal = barChart.getSerArray(i).getVal(); refreshGraphContent(sheet, barCat, barVal, seriesDataList.get(i), i + 1); } } // 更新嵌入的workbook POIXMLDocumentPart xlsPart = chart.getRelations().get(0); OutputStream xlsOut = xlsPart.getPackagePart().getOutputStream(); wb.write(xlsOut);刷新圖表數據
- 刷新圖表Excel內容
/** * 刷新圖表內容. * * @param sheet * @param cat * @param val * @param seriesData * @param cellNum * @author Hu Weihui */ private static void refreshGraphContent(final Sheet sheet, final CTAxDataSource cat, final CTNumDataSource val, final SeriesData seriesData, final int cellNum) { // 獲取類別 CTStrData strData = cat.getStrRef().getStrCache(); // 獲取系列對應的值 CTNumData numData = val.getNumRef().getNumCache(); // strData.set strData.setPtArray((CTStrVal[]) null); // unset old axis text numData.setPtArray((CTNumVal[]) null); // unset old values // set model long idx = 0; int rownum = 1; for (CategoryData categoryData : seriesData.getCategoryDataList()) { CTNumVal numVal = numData.addNewPt(); numVal.setIdx(idx); numVal.setV(categoryData.getVal() + ""); CTStrVal sVal = strData.addNewPt(); sVal.setIdx(idx); sVal.setV(categoryData.getCategoryName()); idx++; rownum++; } // 設置excel的值 sheet.getRow(0).getCell(cellNum).setCellValue(seriesData.getSeriesName()); for (int i = 1; i < sheet.getLastRowNum() + 1; i++) { sheet.getRow(i).getCell(cellNum).setCellValue( seriesData.getCategoryDataList().get(i - 1).getVal()); } // 設置excel的標題 for (int i = 0; i < seriesData.getCategoryDataList().size(); i++) { String serName = seriesData.getCategoryDataList().get(i).getCategoryName(); sheet.getRow(i + 1).getCell(0).setCellValue(serName); } numData.getPtCount().setVal(idx); strData.getPtCount().setVal(idx); String numDataRange = new CellRangeAddress(1, rownum - 1, cellNum, cellNum).formatAsString(sheet.getSheetName(), true); val.getNumRef().setF(numDataRange); String axisDataRange = new CellRangeAddress(1, rownum - 1, 0, 0) .formatAsString(sheet.getSheetName(), true); cat.getStrRef().setF(axisDataRange); }
- 封裝能提供一頁PPT的數據實體
/** * <b><code>SlideData</code></b> * <p/> * Description:一頁PPT的內容 * <p/> * <b>Creation Time:</b> 2018/10/23 19:34. * * @author huweihui */ public class SlideData implements Serializable { private static final long serialVersionUID = -69655131782023929L; private Integer slidePage;//頁碼 private List<TableData> tableDataList;//表格數據(有可能有多個表格) private List<ChartData> chartDataList;//圖表數據(有可能有多個圖表) private Map<String,String> textMap;//文本框裏面文本數據 } /** * <b><code>SeriesData</code></b> * <p/> * Description:圖表的系列(不能想象的話可以PPT新建一個柱狀圖就看到了) * <p/> * <b>Creation Time:</b> 2018/10/19 11:37. * * @author huweihui */ public class SeriesData { // 系列名稱 private String seriesName; // 所有類別的值 private List<CategoryData> categoryDataList = new ArrayList<>(); } /** * <b><code>SeriesData</code></b> * <p/> * Description:圖表的類別 * <p/> * <b>Creation Time:</b> 2018/10/19 11:37. * * @author huweihui */ public class CategoryData { // 類別名稱 private String categoryName; // 類別值 private double Val; } /** * <b><code>ChartData</code></b> * <p/> * Description:圖表數據 * <p/> * <b>Creation Time:</b> 2018/10/24 11:46. * * @author huweihui */ public class ChartData { private List<SeriesData> seriesDataList; } /** * <b><code>TableData</code></b> * <p/> * Description:表格一行的數據 * <p/> * <b>Creation Time:</b> 2018/10/23 11:34. * * @author huweihui */ public class TableRowData { private List<String> dataList = new ArrayList<>(); } /** * <b><code>TableData</code></b> * <p/> * Description:表格數據 * <p/> * <b>Creation Time:</b> 2018/10/24 14:19. * * @author huweihui */ public class TableData { private List<TableRowData> tableRowDataList; }
總結
1.代碼並不全,但核心思想和核心代碼都出來了,以後整理放到github再更新鏈接。
2.這裏是PPT的模板構造工具,業務上傳過來的數據解析成SlideData這個類。就可以根據模板生成PPT
3.數據獲取的配置也是個考驗。我們是把數據和查找的SQL放到PPT模板讀取並執行SQL後替換數據到PPT
4.讀取Config 更爲複雜,但項目的業務不一樣。往後更新一個DEMO可跑單元測試