一、準備模闆
1、建立模闆檔案
建立一個word檔案,輸入如下圖所示的内容:
模闆檔案
二、代碼實踐
1、引入依賴
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.0</version>
</dependency>
2、自定義XWPFDocument
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlToken;
import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps;
import org.openxmlformats.schemas.drawingml.x2006.main.CTPositiveSize2D;
import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.CTInline;
import java.io.IOException;
import java.io.InputStream;
public class CustomXWPFDocument extends XWPFDocument {
public CustomXWPFDocument(InputStream in) throws IOException {
super(in);
}
public CustomXWPFDocument() {
super();
}
public CustomXWPFDocument(OPCPackage pkg) throws IOException {
super(pkg);
}
/**
* @param id
* @param width 寬
* @param height 高
* @param paragraph 段落
*/
public void createPicture(int id, int width, int height,
XWPFParagraph paragraph) {
final int EMU = 9525;
width *= EMU;
height *= EMU;
String blipId = super.getRelationId(super.getAllPictures().get(id));
CTInline inline = paragraph.createRun().getCTR().addNewDrawing()
.addNewInline();
String picXml = ""
+ "<a:graphic xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\">"
+ " <a:graphicData uri=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">"
+ " <pic:pic xmlns:pic=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">"
+ " <pic:nvPicPr>" + " <pic:cNvPr id=\""
+ id
+ "\" name=\"Generated\"/>"
+ " <pic:cNvPicPr/>"
+ " </pic:nvPicPr>"
+ " <pic:blipFill>"
+ " <a:blip r:embed=\""
+ blipId
+ "\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\"/>"
+ " <a:stretch>"
+ " <a:fillRect/>"
+ " </a:stretch>"
+ " </pic:blipFill>"
+ " <pic:spPr>"
+ " <a:xfrm>"
+ " <a:off x=\"0\" y=\"0\"/>"
+ " <a:ext cx=\""
+ width
+ "\" cy=\""
+ height
+ "\"/>"
+ " </a:xfrm>"
+ " <a:prstGeom prst=\"rect\">"
+ " <a:avLst/>"
+ " </a:prstGeom>"
+ " </pic:spPr>"
+ " </pic:pic>"
+ " </a:graphicData>" + "</a:graphic>";
inline.addNewGraphic().addNewGraphicData();
XmlToken xmlToken = null;
try {
xmlToken = XmlToken.Factory.parse(picXml);
} catch (XmlException xe) {
xe.printStackTrace();
}
inline.set(xmlToken);
inline.setDistT(0);
inline.setDistB(0);
inline.setDistL(0);
inline.setDistR(0);
CTPositiveSize2D extent = inline.addNewExtent();
extent.setCx(width);
extent.setCy(height);
CTNonVisualDrawingProps docPr = inline.addNewDocPr();
docPr.setId(id);
docPr.setName("圖檔名稱");
docPr.setDescr("描述資訊");
}
}
2、公用的方法和變量
private final String REGEX = "\\$\\{(.+?)\\}";
private CustomXWPFDocument document;
public PoICreateWordFactory(String templatePath) throws IOException {
loadTemplate(templatePath);
}
/**
* 加載模闆
*
* @param templatePath 模闆路徑
* @return 包含傳回true, 不包含傳回false
*/
private void loadTemplate(String templatePath) throws IOException {
try (InputStream in = Files.newInputStream(Paths.get(templatePath))) {
//轉成word
this.document = new CustomXWPFDocument(in);
}
}
/**
* 生成word
*
* @param targetFile word生成路徑
* @return 包含傳回true, 不包含傳回false
*/
public void createWordFile(String targetFile) throws IOException {
try (FileOutputStream out = new FileOutputStream(targetFile)){
document.write(out);
}
}
/**
* 判斷文本中是否包含$
*
* @param text 文本
* @return 包含傳回true, 不包含傳回false
*/
public boolean checkText(String text) {
boolean check = false;
if (text.indexOf("#34;) != -1) {
check = true;
}
return check;
}
3、工具類引用的包名
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTc;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STJc;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STVerticalJc;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
4、段落文本替換
/**
* 替換段落文本
*
* @param textMap(資料源)
*/
public void replaceText(Map<String, Object> textMap) {
//擷取段落集合
List<XWPFParagraph> paragraphs = document.getParagraphs();
for (XWPFParagraph paragraph : paragraphs) {
//擷取到段落中的所有文本内容
String text = paragraph.getText();
//判斷此段落中是否有需要進行替換的文本
if (checkText(text)) {
List<XWPFRun> runs = paragraph.getRuns();
for (XWPFRun run : runs) {
//替換模闆原來位置
Pattern pattern = Pattern.compile(REGEX);
Matcher matcher = pattern.matcher(run.toString());
if (matcher.find()) {
String key = matcher.group(1);
if(textMap.containsKey(key)){
run.setText(String.valueOf(textMap.get(key)), 0);
}
}
}
}
}
}
5、圖檔替換
/**
* 替換圖檔
*
* @param imageMap(資料源)
*/
public void replaceImage(Map<String, byte[]> imageMap) throws org.apache.poi.openxml4j.exceptions.InvalidFormatException {
//段落集合
List<XWPFParagraph> paragraphs = document.getParagraphs();
Set<Map.Entry<String, byte[]>> imageSets = imageMap.entrySet();
for (XWPFParagraph paragraph : paragraphs) {
//擷取到段落中的所有文本内容
String text = paragraph.getText();
//判斷此段落中是否有需要進行替換的文本
if (checkText(text)) {
List<XWPFRun> runs = paragraph.getRuns();
for (XWPFRun run : runs) {
//替換模闆原來位置
Pattern pattern = Pattern.compile(REGEX);
String runText = run.toString();
Matcher matcher = pattern.matcher(runText);
if (matcher.find()) {
String key = matcher.group(1);
if(imageMap.containsKey(key)){
//清空原有内容
run.setText("", 0);
//設定圖檔
document.addPictureData(imageMap.get(key), XWPFDocument.PICTURE_TYPE_PNG);
//建立一個word圖檔,并插入到文檔中-->像素可改
document.createPicture(document.getAllPictures().size() - 1, 240, 240,paragraph);
break;
}
}
}
}
}
}
6、表格替換
/**
* 替換表格内容
*
* @param index(表格索引:第幾個表格)
* @param tableList(資料源)
* @Return void
* @Exception
*/
public void replaceTable(int index, List<List<String>> tableList) {
XWPFTable table = document.getTables().get(index);
//建立行,根據需要插入的資料添加新行,不處理表頭
for (int i = 1; i <= tableList.size(); i++) {
table.createRow();
}
//周遊表格插入資料
List<XWPFTableRow> rows = table.getRows();
for (int i = 1; i < tableList.size()+1; i++) {
XWPFTableRow newRow = table.getRow(i);
List<XWPFTableCell> cells = newRow.getTableCells();
List<String> rowData = tableList.get(i - 1);
for (int j = 0; j < rowData.size(); j++) {
XWPFTableCell cell = cells.get(j);
String text = rowData.get(j);
cell.setText(text);
//表格樣式一緻-->沒有此段表格會預設左對齊
//有此段會使表格格式一緻
CTTc cttc = cell.getCTTc();
CTTcPr ctPr = cttc.addNewTcPr();
ctPr.addNewVAlign().setVal(STVerticalJc.CENTER);
cttc.getPList().get(0).addNewPPr().addNewJc().setVal(STJc.CENTER);
}
}
}
7、完整的工具類代碼
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTc;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STJc;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STVerticalJc;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class PoICreateWordFactory {
/**
* 生成ord
*
*/
public void crateWord(Map<String, Object> dataMap, String templatePath, String targetFile) throws IOException, InvalidFormatException {
//加載模闆檔案
loadTemplate(templatePath);
//将dataMap拆分成 textMap、imageMap、tableMap TODO 爛尾中
//段落替換變量
Map<String, Object> textMap = new HashMap<>();
//替換模闆資料
replaceText(textMap);
//圖檔替換變量
Map<String, byte[]> imageMap = new HashMap<>();
replaceImage(imageMap);
//寫入表格
List<List<String>> arrearsList = new ArrayList<>();
replaceTable(0, arrearsList);
//生成新的word
createWordFile(targetFile);
}
private final String REGEX = "\\$\\{(.+?)\\}";
private CustomXWPFDocument document;
public PoICreateWordFactory() {}
public PoICreateWordFactory(String templatePath) throws IOException {
loadTemplate(templatePath);
}
/**
* 加載模闆
*
* @param templatePath 模闆路徑
* @return 包含傳回true, 不包含傳回false
*/
public void loadTemplate(String templatePath) throws IOException {
try (InputStream in = Files.newInputStream(Paths.get(templatePath))) {
//轉成word
this.document = new CustomXWPFDocument(in);
}
}
/**
* 生成word
*
* @param targetFile word生成路徑
* @return 包含傳回true, 不包含傳回false
*/
public void createWordFile(String targetFile) throws IOException {
try (FileOutputStream out = new FileOutputStream(targetFile)){
document.write(out);
}
}
/**
* 判斷文本中是否包含$
*
* @param text 文本
* @return 包含傳回true, 不包含傳回false
*/
public boolean checkText(String text) {
boolean check = false;
if (text.indexOf("#34;) != -1) {
check = true;
}
return check;
}
/**
* 替換段落文本
*
* @param textMap(資料源)
*/
public void replaceText(Map<String, Object> textMap) {
//擷取段落集合
List<XWPFParagraph> paragraphs = document.getParagraphs();
for (XWPFParagraph paragraph : paragraphs) {
//擷取到段落中的所有文本内容
String text = paragraph.getText();
//判斷此段落中是否有需要進行替換的文本
if (checkText(text)) {
List<XWPFRun> runs = paragraph.getRuns();
for (XWPFRun run : runs) {
//替換模闆原來位置
Pattern pattern = Pattern.compile(REGEX);
Matcher matcher = pattern.matcher(run.toString());
if (matcher.find()) {
String key = matcher.group(1);
if(textMap.containsKey(key)){
run.setText(String.valueOf(textMap.get(key)), 0);
}
}
}
}
}
}
/**
* 替換圖檔
*
* @param imageMap(資料源)
*/
public void replaceImage(Map<String, byte[]> imageMap) throws org.apache.poi.openxml4j.exceptions.InvalidFormatException {
//段落集合
List<XWPFParagraph> paragraphs = document.getParagraphs();
Set<Map.Entry<String, byte[]>> imageSets = imageMap.entrySet();
for (XWPFParagraph paragraph : paragraphs) {
//擷取到段落中的所有文本内容
String text = paragraph.getText();
//判斷此段落中是否有需要進行替換的文本
if (checkText(text)) {
List<XWPFRun> runs = paragraph.getRuns();
for (XWPFRun run : runs) {
//替換模闆原來位置
Pattern pattern = Pattern.compile(REGEX);
String runText = run.toString();
Matcher matcher = pattern.matcher(runText);
if (matcher.find()) {
String key = matcher.group(1);
if(imageMap.containsKey(key)){
//清空原有内容
run.setText("", 0);
//設定圖檔
document.addPictureData(imageMap.get(key), XWPFDocument.PICTURE_TYPE_PNG);
//建立一個word圖檔,并插入到文檔中-->像素可改
document.createPicture(document.getAllPictures().size() - 1, 240, 240,paragraph);
break;
}
}
}
}
}
}
/**
* 替換表格内容
*
* @param index(表格索引:第幾個表格)
* @param tableList(資料源)
* @Return void
* @Exception
*/
public void replaceTable(int index, List<List<String>> tableList) {
XWPFTable table = document.getTables().get(index);
//建立行,根據需要插入的資料添加新行,不處理表頭
for (int i = 1; i <= tableList.size(); i++) {
table.createRow();
}
//周遊表格插入資料
List<XWPFTableRow> rows = table.getRows();
for (int i = 1; i < tableList.size()+1; i++) {
XWPFTableRow newRow = table.getRow(i);
List<XWPFTableCell> cells = newRow.getTableCells();
List<String> rowData = tableList.get(i - 1);
for (int j = 0; j < rowData.size(); j++) {
XWPFTableCell cell = cells.get(j);
String text = rowData.get(j);
cell.setText(text);
//表格樣式一緻-->沒有此段表格會預設左對齊
//有此段會使表格格式一緻
CTTc cttc = cell.getCTTc();
CTTcPr ctPr = cttc.addNewTcPr();
ctPr.addNewVAlign().setVal(STVerticalJc.CENTER);
cttc.getPList().get(0).addNewPPr().addNewJc().setVal(STJc.CENTER);
}
}
}
}
三、驗證模闆生成
1、測試代碼
public static void main(String[] args) throws IOException, InvalidFormatException {
String templatePath = "D:\\文章\\word生成\\poi\\qiantiao.docx";
String targetFile = "D:\\test\\qiantiao.docx";
//初始化,并加載模闆檔案
PoICreateWordFactory poICreateWordFactory = new PoICreateWordFactory(templatePath);
//段落替換變量
LocalDate currentDate = LocalDate.now();
LocalDate endDate = currentDate.plusYears(1L);
Map<String, Object> textMap = new HashMap<>();
textMap.put("debtor", "陳有楚");
textMap.put("nowYear", String.valueOf(currentDate.getYear()));
textMap.put("nowMonth", String.valueOf(currentDate.getMonthValue()));
textMap.put("nowDay", String.valueOf(currentDate.getDayOfMonth()));
textMap.put("arrears", "一頓老魏、貴州大黃牛、v我50");
textMap.put("endYear", String.valueOf(endDate.getYear()));
textMap.put("endMonth", String.valueOf(endDate.getMonthValue()));
textMap.put("endDay", String.valueOf(endDate.getDayOfMonth()));
textMap.put("creditor", "知北遊");
//替換模闆資料
poICreateWordFactory.replaceText(textMap);
//圖檔替換變量
FileInputStream imageInput = new FileInputStream("D:\\picture\\其他\\24-05-23-142418.png");
byte[] bytes = new byte[imageInput.available()];
imageInput.read(bytes);
imageInput.close();
Map<String, byte[]> imageMap = new HashMap<>();
imageMap.put("image1", bytes);
poICreateWordFactory.replaceImage(imageMap);
//寫入表格
List<List<String>> arrearsList = new ArrayList<>();
arrearsList.add(Arrays.asList("一頓老魏", "1", "三月内"));
arrearsList.add(Arrays.asList("貴州大黃牛", "1", "一年内"));
arrearsList.add(Arrays.asList("v我50", "1", "一月内"));
//擷取表格位置 0代表第一個表格,寫死第一個,模闆裡也隻有一個模闆
poICreateWordFactory.replaceTable(0, arrearsList);
//生成新的word
poICreateWordFactory.createWordFile(targetFile);
}
2、生成效果
word生成效果
四、總結
其實從測試代碼裡就可以發現這其實隻是一個半成品代碼,文本替換、圖檔替換、表格替換甚至需要分别傳遞不同的資料map。本來是打算合并成一個dataMap,然後根據參數類來區分是文本、圖檔、表格的。然後拆分成多個資料map。但是在寫這些代碼時發現了也是基于poi開發的開源項目poi-tl。功能很全,我想實作的功能他都有,頓時我寫的上面這些代碼就失去了意義,然後就爛尾了。。。後面有時間介紹一下poi-tl這個開源項目使用方式吧,經過試驗這個确實功能完善,非常推薦。