Apache POI 是用Java編寫的免費開源的跨平台的 Java API,Apache POI提供API給Java程式對Microsoft Office格式檔案讀和寫的功能。POI為“Poor Obfuscation Implementation”的首字母縮寫,意為“簡潔版的模糊實作”。
POI生成excel是比較常用的技術之一,但是用來生成word相對來說比較少,今天示範一下利用POI生成word文檔的整個流程,,當然方法有很多種,這是我感覺比較友善的一種。
需要實作的功能:
- 在文檔中動态生成章節标題、文本段落、表格标題、表格、統計圖等等。
- 給每個章節編号
- 删除沒有替換的占位符
總體思路:
這種方法的本質其實是動态替換,動态替換的意思是在模闆中寫入很多某種特定格式的占位符(關鍵詞)以及圖的樣例,如果前端需要生成這個章節的内容,那麼,把這個關鍵詞傳到後端,再在模闆中尋找關鍵詞,有則替換成具體内容,無則不替換。最後把沒有替換掉的占位符删除,進而達到動态替換的效果。
具體實作過程如下:
- 建立word模闆.文檔中包含若幹個章節,章節中包含章節标題、文本段落、表格标題、表格、統計圖的占位符。占位符的内容依據自己的實際情況而定。
- 加載模闆,判斷該章節是否存在,如果存在就替換具體資料。本示例資料都是模拟的,實際情況應該從資料庫中查出。
- 再次周遊替換後的word文檔,把沒有替換掉的占位符删除。
示例
1. 建立模闆
如下圖所示:
2.替換資料
2.1 測試類
import org.apache.poi.xwpf.usermodel.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.ResourceUtils;
import java.io.*;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;
@RunWith(SpringRunner.class)
@SpringBootTest
public class PoiChartsTest {
@Autowired
private PoiPropsConfig config;
@Autowired
private PoiUtils poiUtils;
//預編譯正規表達式,加快正則比對速度
private Pattern pattern = Pattern.compile("\\$\\{(.+?)\\}", Pattern.CASE_INSENSITIVE);
@Test
public void test() throws Exception {
XWPFDocument document = null;
InputStream inputStream=null;
try {
File file = ResourceUtils.getFile("D://poi.docx");
inputStream = new FileInputStream(file);
document = new XWPFDocument(inputStream);
} catch (Exception e) {
e.printStackTrace();
}
Iterator<XWPFParagraph> itPara = document.getParagraphsIterator();
//處理文字
while (itPara.hasNext()) {
XWPFParagraph paragraph = itPara.next();
String paraText = paragraph.getText();
//如果沒有比對到指定格式的關鍵詞占位符(如${title}格式的)則不進行後續處理
if (!pattern.matcher(paraText).find()) {
continue;
}
//提取出文檔模闆占位符中的章節标題
String keyInParaText = paraText.split("\\$\\{")[1].split("\\}")[0];
//如果占位符是大标題
if ("title".equalsIgnoreCase(keyInParaText)) {
insertTitle(paragraph);
continue;
}
//如果占位符代表文本總描述
if ("totalDesc".equalsIgnoreCase(keyInParaText)) {
insertText(paragraph);
continue;
}
//如果占位符代表章節标題
if (keyInParaText.contains("section") && keyInParaText.contains("Title")) {
//擷取章節類名
String name = keyInParaText.substring(0, 8);
//擷取章節類的路徑
String classPath = config.getSection().get(name);
//通過類路徑擷取類對象
BaseSection base = (BaseSection) Class.forName(classPath).newInstance();
base.replaceSectionTitle(document, paragraph);
continue;
}
//如果占位符代表章節文本描述
if (keyInParaText.contains("body")) {
String name = keyInParaText.substring(0, 8);
BaseSection base = (BaseSection) Class.forName(config.getSection().get(name)).newInstance();
base.replaceBody(paragraph);
continue;
}
//如果占位符代表表名
if (keyInParaText.contains("tableName")) {
String name = keyInParaText.substring(0, 8);
BaseSection base = (BaseSection) Class.forName(config.getSection().get(name)).newInstance();
base.replaceTableName(paragraph);
continue;
}
//如果占位符代表表
if (keyInParaText.endsWith("table")) {
String name = keyInParaText.substring(0, 8);
BaseSection base = (BaseSection) Class.forName(config.getSection().get(name)).newInstance();
base.insertTable(document, paragraph);
continue;
}
//如果占位符代表統計圖
if (keyInParaText.endsWith("chart")) {
String name = keyInParaText.substring(0, 8);
paragraph.removeRun(0);
BaseSection base = (BaseSection) Class.forName(config.getSection().get(name)).newInstance();
base.replaceChart(document, keyInParaText);
continue;
}
//如果占位符代表圖名
if (keyInParaText.contains("chartName")) {
String name = keyInParaText.substring(0, 8);
BaseSection base = (BaseSection) Class.forName(config.getSection().get(name)).newInstance();
base.replaceChartName(paragraph);
continue;
}
}
//再周遊一次文檔,把沒有替換的占位符段落删除
List<IBodyElement> elements = document.getBodyElements();
int indexTable = 0;
for (int k = 0; k < elements.size(); k++) {
IBodyElement bodyElement = elements.get(k);
//所有段落,如果有${}格式的段落便删除該段落
if (bodyElement.getElementType().equals(BodyElementType.PARAGRAPH)) {
XWPFParagraph p = (XWPFParagraph) bodyElement;
String paraText = p.getText();
boolean flag = false;
if (pattern.matcher(paraText).find()) {
flag = document.removeBodyElement(k);
if (flag) {
k--;
}
}
}
//如果是表格,那麼給表格的前一個段落(即表名加上編号,如表1)
if (bodyElement.getElementType().equals(BodyElementType.TABLE)) {
indexTable++;
XWPFParagraph tableTitleParagraph = (XWPFParagraph) elements.get(k - 1);
StringBuilder tableTitleText = new StringBuilder(tableTitleParagraph.getParagraphText());
tableTitleText.insert(0, "表" + indexTable + " ");
poiUtils.setTableOrChartTitle(tableTitleParagraph, tableTitleText.toString());
}
}
//給章節與小節添加序号
poiUtils.init(document);
//導出word文檔
FileOutputStream docxFos = new FileOutputStream("D://test1.docx");
document.write(docxFos);
docxFos.flush();
docxFos.close();
inputStream.close();
}
//插入大标題
public void insertTitle(XWPFParagraph paragraph) {
String title = "步步升超市報告";
List<XWPFRun> runs = paragraph.getRuns();
int runSize = runs.size();
/**Paragrap中每删除一個run,其所有的run對象就會動态變化,即不能同時周遊和删除*/
int haveRemoved = 0;
for (int runIndex = 0; runIndex < runSize; runIndex++) {
paragraph.removeRun(runIndex - haveRemoved);
haveRemoved++;
}
/**3.插入新的Run即将新的文本插入段落*/
XWPFRun createRun = paragraph.insertNewRun(0);
createRun.setText(title);
XWPFRun separtor = paragraph.insertNewRun(1);
/**兩段之間添加換行*/
separtor.setText("\r");
//設定字型大小
createRun.setFontSize(22);
//是否加粗
createRun.setBold(true);
//設定字型
createRun.setFontFamily("宋體");
//設定居中
paragraph.setAlignment(ParagraphAlignment.CENTER);
}
//插入文本描述
private void insertText(XWPFParagraph paragraph) {
String text = "步步升超市作為零售業的典型代表,它已經在全國迅速發展起來。在2018年上半年取得了不菲的成績," +
"創造銷售額230億,本着友善于民,服務于民的宗旨,我們會繼續努力。以下是詳細資訊報告:"
poiUtils.setTextPro(paragraph, text);
}
}
2.2 章節基類
因為要實作動态生成,是以要用到反射和多态。
import org.apache.poi.ooxml.POIXMLDocumentPart;
import org.apache.poi.xwpf.usermodel.*;
import org.apache.xmlbeans.XmlCursor;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTChart;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTTitle;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTTx;
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextBody;
import java.util.ArrayList;
import java.util.List;
public abstract class BaseSection {
PoiUtils poiUtils = new PoiUtils();
//替換章節标題
public abstract void replaceSectionTitle(XWPFDocument document, XWPFParagraph paragraph);
//替換章節内容
public abstract void replaceBody(XWPFParagraph paragraph);
//替換章節表名
public void replaceTableName(XWPFParagraph paragraph){
String tableName="水果銷售資料";
poiUtils.setTableOrChartTitle(paragraph,tableName);
}
//生成表
public void insertTable(XWPFDocument document, XWPFParagraph paragraph) {
/*1.将段落原有文本(原有所有的Run)全部删除*/
poiUtils.deleteRun(paragraph);
/*生成表格插入提示遊标*/
XmlCursor cursor = paragraph.getCTP().newCursor();
// 在指定遊标位置插入表格
XWPFTable table = document.insertNewTbl(cursor);
//設定表格居中
table.setTableAlignment(TableRowAlign.CENTER);
//模拟表格資料
List<String[]> list = new ArrayList<>();
list.add(new String[]{"蘋果", "100", "6", "600", "10.7"});
list.add(new String[]{"香蕉", "200", "5", "1000", "17.9"});
list.add(new String[]{"桃子", "300", "4", "1200", "21.4"});
list.add(new String[]{"葡萄", "400", "3", "1200", "21.4"});
list.add(new String[]{"西瓜", "500", "2", "1000", "17.9"});
list.add(new String[]{"車厘子", "600", "1", "600", "10.7"});
//根據資料生成表格
inserInfo(table, list);
//設定表格中所有單元格水準居中對齊
poiUtils.setTableCenter(table);
}
//替換統計圖資料
public void replaceChart(XWPFDocument document,String key){
//模拟統計圖資料
//系列
String[] series={"銷售量(kg)","銷售額(元)","淨盈利額(元)"};
//x軸
String[] categories={"星期一","星期二","星期三","星期四","星期五","星期六","星期日"};
List<Number[]> values = new ArrayList<>();
//一周的銷售量
Number[] value1 = {60,80,74,52,66,88,90};
//一周的銷售額
Number[] value2 = {450.2,652.1,554,384.6,486.5,688.9,711.1};
//一周的淨盈利額
Number[] value3 = {200.2,326.4,266,159.5,222.2,355.5,369.5};
values.add(value1);
values.add(value2);
values.add(value3);
try {
XWPFChart xChart = null;
CTChart ctChart = null;
for (POIXMLDocumentPart part : document.getCharts()) {
if (part instanceof XWPFChart) {
xChart = (XWPFChart) part;
ctChart = xChart.getCTChart();
String chartTitle=getTitle(ctChart);
if (key.equalsIgnoreCase(chartTitle) ) {
generateChart(xChart, series,categories,values);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 擷取模闆中表格的标題
* @param chart
* @return
*/
public String getTitle(CTChart chart) {
CTTitle title = chart.getTitle();
if (title != null) {
CTTx tx = title.getTx();
CTTextBody tb = tx.getRich();
return tb.getPArray(0).getRArray(0).getT();
}
return "";
}
/**
*
* @param chart 模闆中統計圖對象
* @param series 系列
* @param categories x軸
* @param values 具體的值
*/
public abstract void generateChart(XWPFChart chart,String[]series,String[]categories,List<Number[]> values);
// 替換圖名稱
public abstract void replaceChartName(XWPFParagraph paragraph);
/**
* 把資訊插入表格,因為每個章節的表格生成都一樣,避免麻煩,是以統一用這個資料
* @param table
* @param list
*/
private void inserInfo(XWPFTable table, List<String[]> list) {
//設定表格寬度
table.setWidth(10000);
XWPFTableRow row = table.getRow(0);
row.getCell(0).setText("名額");
row.addNewTableCell().setText("銷售數量");
row.addNewTableCell().setText("單價");
row.addNewTableCell().setText("銷售總額");
row.addNewTableCell().setText("銷售額占比");
for (int i = 0; i < list.size(); i++) {
row = table.createRow();
String[] obj = list.get(i);
for (int j = 0; j < obj.length; j++) {
row.getCell(j).setText(obj[j]);
}
}
}
}
2.3 章節1
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xddf.usermodel.chart.*;
import org.apache.poi.xwpf.usermodel.XWPFChart;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import java.util.List;
public class Section1 extends BaseSection{
@Override
public void replaceSectionTitle(XWPFDocument document, XWPFParagraph paragraph) {
String sectionTitle ="水果一";
poiUtils.setLevelTitle1(document,paragraph,sectionTitle);
}
@Override
public void replaceBody(XWPFParagraph paragraph) {
String body ="我藏不住秘密,也藏不住憂傷,正如我藏不住愛你的喜悅,藏不住分離時的彷徨。" +
"我就是這樣坦然,你舍得傷,就傷。如果有一天,你要離開我,我不會留你,我知道你有" +
"你的理由;如果有一天,你說還愛我,我會告訴你,其實我一直在等你;如果有一天,我們" +
"擦肩而過,我會停住腳步,凝視你遠去的背影,告訴自己那個人我曾經愛過。或許人一生可" +
"以愛很多次,然而總有一個人,可以讓我們笑得最燦爛,哭得最透徹,想得最深切。炊煙起" +
"了,我在門口等你。夕陽下了,我在山邊等你。葉子黃了,我在樹下等你。月兒彎了,我在" +
"十五等你。細雨來了,我在傘下等你。流水凍了,我在河畔等你。生命累了,我在天堂等你" +
"。我們老了,我在來生等你。";
poiUtils.setTextPro(paragraph,body);
}
@Override
public void generateChart(XWPFChart chart, String[] series, String[] categories, List<Number[]> values) {
String chartTitle="香蕉銷售資料周統計圖";
final List<XDDFChartData> data = chart.getChartSeries();
final XDDFBarChartData bar = (XDDFBarChartData) data.get(0);
final int numOfPoints = categories.length;
final String categoryDataRange = chart.formatRange(new CellRangeAddress(1, numOfPoints, 0, 0));
final XDDFDataSource<?> categoriesData = XDDFDataSourcesFactory.fromArray(categories, categoryDataRange, 0);
for (int i = 0; i < values.size(); i++) {
final String valuesDataRange = chart.formatRange(new CellRangeAddress(1, numOfPoints, i + 1, i + 1));
Number[] value = values.get(i);
final XDDFNumericalDataSource<? extends Number> valuesData = XDDFDataSourcesFactory.fromArray(value, valuesDataRange, i + 1);
XDDFChartData.Series ser;
if (i < 3) {
ser = bar.getSeries().get(i);
ser.replaceData(categoriesData, valuesData);
} else {
ser = bar.addSeries(categoriesData, valuesData);
}
CellReference cellReference = chart.setSheetTitle(series[i], 1);
ser.setTitle(series[i], cellReference);
}
chart.plot(bar);
chart.setTitleText(chartTitle);
chart.setTitleOverlay(false);
}
@Override
public void replaceChartName(XWPFParagraph paragraph) {
String chartName="香蕉銷售統計圖(柱狀圖)";
poiUtils.setTableOrChartTitle(paragraph,chartName);
}
}
2.4 章節2
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xddf.usermodel.chart.*;
import org.apache.poi.xwpf.usermodel.XWPFChart;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import java.util.List;
public class Section2 extends BaseSection{
@Override
public void replaceSectionTitle(XWPFDocument document, XWPFParagraph paragraph) {
String sectionTitle ="水果二";
poiUtils.setLevelTitle1(document,paragraph,sectionTitle);
}
@Override
public void replaceBody(XWPFParagraph paragraph) {
String body ="無論是太陽撥開雲霧,還是雲霧遮擋太陽,都是彼此間的争鬥。不要在晴空萬裡時," +
"才說天空“蔚藍如海”。我的前身本是大海中的一滴水,當我們的雲層在天空中翻騰湧動時," +
"這才是波濤洶湧的大海。即使卑微,也有自己的理想,也想留下瞬間的精彩。即便陽光把我由" +
"一滴晶瑩剔透的水轉成一縷遊絲般的水汽,也要在天空中展現出鮮豔曼妙的身影,留下瞬間的精彩" +
",也要和其他兄弟姐妹組成波濤萬裡的雲海,凝成雨珠,滴落大海,在輪回中重生!";
poiUtils.setTextPro(paragraph,body);
}
@Override
public void insertTable(XWPFDocument document, XWPFParagraph paragraph) {
super.insertTable(document, paragraph);
}
@Override
public void generateChart(XWPFChart chart, String[] series, String[] categories, List<Number[]> values) {
String chartTitle="香蕉銷售資料周統計圖";
final List<XDDFChartData> data = chart.getChartSeries();
XDDFPieChartData pie = (XDDFPieChartData) data.get(0);
Number[]values1 = values.get(2);
final int numOfPoints = categories.length;
final String categoryDataRange = chart.formatRange(new CellRangeAddress(1, numOfPoints, 0, 0));
final String valuesDataRange = chart.formatRange(new CellRangeAddress(1, numOfPoints, 1, 1));
final XDDFDataSource<?> categoriesData = XDDFDataSourcesFactory.fromArray(categories, categoryDataRange, 0);
final XDDFNumericalDataSource<?> valuesData = XDDFDataSourcesFactory.fromArray(values1, valuesDataRange, 1);
XDDFChartData.Series series1 = pie.getSeries().get(0);
series1.replaceData(categoriesData, valuesData);
series1.setTitle(series[0], chart.setSheetTitle(series[0], 0));
chart.plot(pie);
chart.setTitleText(chartTitle);
chart.setTitleOverlay(false);
}
@Override
public void replaceChartName(XWPFParagraph paragraph) {
String chartName="香蕉銷售統計圖(餅圖)";
poiUtils.setTableOrChartTitle(paragraph,chartName);
}
}
2.5 章節3
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xddf.usermodel.chart.*;
import org.apache.poi.xwpf.usermodel.XWPFChart;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import java.util.List;
public class Section3 extends BaseSection{
@Override
public void replaceSectionTitle(XWPFDocument document, XWPFParagraph paragraph) {
String sectionTitle ="水果三";
poiUtils.setLevelTitle1(document,paragraph,sectionTitle);
}
@Override
public void replaceBody(XWPFParagraph paragraph) {
String body="在一次次水與雲的交替變化中,蓦然發現無論自己是天邊的一片雲,還是大海中的一滴水," +
"都是那毫不起眼的卑微之物,誰會去解讀一片雲的心緒,聆聽一滴水的聲音?經曆了風起風落," +
"也就習慣了雲卷雲舒,漸漸的我懂得了順其自然,心境歸于平和,不在向往風起雲湧的氣象," +
"雲淡風輕才是自己的本色。";
poiUtils.setTextPro(paragraph,body);
}
@Override
public void generateChart(XWPFChart chart, String[] series, String[] categories, List<Number[]> values) {
String chartTitle="香蕉銷售資料周統計圖";
final List<XDDFChartData> data = chart.getChartSeries();
final XDDFLineChartData line = (XDDFLineChartData) data.get(0);
final int numOfPoints = categories.length;
final String categoryDataRange = chart.formatRange(new CellRangeAddress(1, numOfPoints, 0, 0));
final XDDFDataSource<?> categoriesData = XDDFDataSourcesFactory.fromArray(categories, categoryDataRange, 0);
for (int i = 0; i < values.size(); i++) {
final String valuesDataRange = chart.formatRange(new CellRangeAddress(1, numOfPoints, i + 1, i + 1));
Number[] value = values.get(i);
final XDDFNumericalDataSource<? extends Number> valuesData = XDDFDataSourcesFactory.fromArray(value, valuesDataRange, i + 1);
XDDFChartData.Series ser;
if (i < 3) {
ser = line.getSeries().get(i);
ser.replaceData(categoriesData, valuesData);
} else {
ser = line.addSeries(categoriesData, valuesData);
}
CellReference cellReference = chart.setSheetTitle(series[i], 1);
ser.setTitle(series[i], cellReference);
}
chart.plot(line);
chart.setTitleText(chartTitle);
chart.setTitleOverlay(false);
}
@Override
public void replaceChartName(XWPFParagraph paragraph) {
String chartName="香蕉銷售統計圖(折線圖)";
poiUtils.setTableOrChartTitle(paragraph,chartName);
}
}
2.6 配置章節類和章節類路徑
- application.yml
#文檔生成相關類配置
doc:
section:
section1: com.poi.docgenerate.Section1
section2: com.poi.docgenerate.Section2
section3: com.poi.docgenerate.Section3
- PoiPropConfig.java
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Map;
@ConfigurationProperties(prefix = "doc")
@Component
public class PoiPropsConfig {
//把章節名作為key,章節類的全路徑名作為value
private Map<String,String> section;
public void setSection(Map<String, String> section) {
this.section = section;
}
public Map<String, String> getSection() {
return section;
}
@Override
public String toString() {
for (Map.Entry<String,String> entry:section.entrySet()){
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key+":"+value);
}
return null;
}
}
2.7 工具類
import com.google.common.base.Strings;
import com.police.hunan.judge.entity.vo.DocGenerateVO;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import org.springframework.stereotype.Component;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Component
public class PoiUtils {
private Map<String, Map<String, Object>> orderMap = new HashMap<String, Map<String, Object>>();
/*把*/
public static void copy(Object obj, Object dest) {
// 擷取屬性
BeanInfo sourceBean = null;
try {
sourceBean = Introspector.getBeanInfo(obj.getClass(), Object.class);
} catch (IntrospectionException e) {
e.printStackTrace();
}
PropertyDescriptor[] sourceProperty = sourceBean.getPropertyDescriptors();
BeanInfo destBean = null;
try {
destBean = Introspector.getBeanInfo(dest.getClass(), Object.class);
} catch (IntrospectionException e) {
e.printStackTrace();
}
PropertyDescriptor[] destProperty = destBean.getPropertyDescriptors();
try {
for (int i = 0; i < sourceProperty.length; i++) {
Object value = sourceProperty[i].getReadMethod().invoke(obj);
if (value != null) {
for (int j = 0; j < destProperty.length; j++) {
if (sourceProperty[i].getName().equals(destProperty[j].getName()) && sourceProperty[i].getPropertyType() == destProperty[j].getPropertyType()) {
// 調用source的getter方法和dest的setter方法
destProperty[j].getWriteMethod().invoke(dest, value);
break;
}
}
} else {
continue;
}
}
} catch (Exception e) {
try {
throw new Exception("屬性複制失敗:" + e.getMessage());
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
/**
* 設定一級标題内容及樣式
*
* @param paragraph
* @param text
*/
public void setLevelTitle1(XWPFDocument document, XWPFParagraph paragraph, String text) {
/**1.将段落原有文本(原有所有的Run)全部删除*/
deleteRun(paragraph);
/**3.插入新的Run即将新的文本插入段落*/
XWPFRun createRun = paragraph.insertNewRun(0);
createRun.setText(text);
XWPFRun separtor = paragraph.insertNewRun(1);
/**兩段之間添加換行*/
separtor.setText("\r");
createRun.setFontSize(16);
createRun.setFontFamily("黑體");
paragraph.setIndentationFirstLine(600);
paragraph.setSpacingAfter(20);
paragraph.setSpacingBefore(20);
addCustomHeadingStyle(document, "标題1", 1);
paragraph.setStyle("标題1");
}
/**
* 設定二級标題内容及樣式
*
* @param paragraph
* @param text
*/
public void setLevelTitle2(XWPFDocument document, XWPFParagraph paragraph, String text) {
deleteRun(paragraph);
/**3.插入新的Run即将新的文本插入段落*/
XWPFRun createRun = paragraph.insertNewRun(0);
createRun.setText(text);
XWPFRun separtor = paragraph.insertNewRun(1);
/**兩段之間添加換行*/
separtor.setText("\r");
createRun.setFontSize(16);
createRun.setBold(true);
createRun.setFontFamily("楷體_GB2312");
paragraph.setIndentationFirstLine(600);
paragraph.setSpacingAfter(10);
paragraph.setSpacingBefore(10);
addCustomHeadingStyle(document, "标題2", 2);
paragraph.setStyle("标題2");
}
/**
* 設定正文文本内容及樣式
*
* @param paragraph
* @param text
*/
public void setTextPro(XWPFParagraph paragraph, String text) {
deleteRun(paragraph);
/**3.插入新的Run即将新的文本插入段落*/
XWPFRun createRun = paragraph.insertNewRun(0);
createRun.setText(text);
XWPFRun separtor = paragraph.insertNewRun(1);
/**兩段之間添加換行*/
separtor.addBreak();
createRun.addTab();
createRun.setFontFamily("仿宋_GB2312");
createRun.setFontSize(16);
paragraph.setFirstLineIndent(20);
paragraph.setAlignment(ParagraphAlignment.BOTH);
paragraph.setIndentationFirstLine(600);
paragraph.setSpacingAfter(10);
paragraph.setSpacingBefore(10);
}
/**
* 向段落添加文本
*
* @param paragraph
* @param text
*/
public void addTextPro(XWPFParagraph paragraph, String text) {
/**3.插入新的Run即将新的文本插入段落*/
XWPFRun createRun = paragraph.createRun();
createRun.setText(text);
XWPFRun separtor = paragraph.createRun();
/**兩段之間添加換行*/
separtor.addBreak();
createRun.addTab();
createRun.setFontFamily("仿宋_GB2312");
createRun.setFontSize(16);
paragraph.setFirstLineIndent(20);
paragraph.setAlignment(ParagraphAlignment.BOTH);
paragraph.setIndentationFirstLine(600);
paragraph.setSpacingAfter(10);
paragraph.setSpacingBefore(10);
paragraph.addRun(createRun);
paragraph.addRun(separtor);
}
/**
* 設定表格标題内容及樣式
*
* @param paragraph
* @param text
*/
public void setTableOrChartTitle(XWPFParagraph paragraph, String text) {
/**1.将段落原有文本(原有所有的Run)全部删除*/
deleteRun(paragraph);
XWPFRun createRun = paragraph.insertNewRun(0);
createRun.setText(text);
XWPFRun separtor = paragraph.insertNewRun(1);
/**兩段之間添加換行*/
separtor.setText("\r");
createRun.setFontFamily("楷體");
createRun.setFontSize(16);
createRun.setBold(true);
paragraph.setSpacingAfter(10);
paragraph.setSpacingBefore(10);
paragraph.setAlignment(ParagraphAlignment.CENTER);
}
public void deleteRun(XWPFParagraph paragraph) {
/*1.将段落原有文本(原有所有的Run)全部删除*/
List<XWPFRun> runs = paragraph.getRuns();
int runSize = runs.size();
/*Paragrap中每删除一個run,其所有的run對象就會動态變化,即不能同時周遊和删除*/
int haveRemoved = 0;
for (int runIndex = 0; runIndex < runSize; runIndex++) {
paragraph.removeRun(runIndex - haveRemoved);
haveRemoved++;
}
}
/**
* 合并行
*
* @param table
* @param col 需要合并的列
* @param fromRow 開始行
* @param toRow 結束行
*/
public static void mergeCellVertically(XWPFTable table, int col, int fromRow, int toRow) {
for (int rowIndex = fromRow; rowIndex <= toRow; rowIndex++) {
CTVMerge vmerge = CTVMerge.Factory.newInstance();
if (rowIndex == fromRow) {
vmerge.setVal(STMerge.RESTART);
} else {
vmerge.setVal(STMerge.CONTINUE);
}
XWPFTableCell cell = table.getRow(rowIndex).getCell(col);
CTTcPr tcPr = cell.getCTTc().getTcPr();
if (tcPr != null) {
tcPr.setVMerge(vmerge);
} else {
tcPr = CTTcPr.Factory.newInstance();
tcPr.setVMerge(vmerge);
cell.getCTTc().setTcPr(tcPr);
}
}
}
/**
* 設定表格内容居中
*
* @param table
*/
public void setTableCenter(XWPFTable table) {
List<XWPFTableRow> rows = table.getRows();
for (XWPFTableRow row : rows) {
row.setHeight(400);
List<XWPFTableCell> cells = row.getTableCells();
for (XWPFTableCell cell : cells) {
CTTc cttc = cell.getCTTc();
CTTcPr ctPr = cttc.addNewTcPr();
ctPr.addNewVAlign().setVal(STVerticalJc.CENTER);
cttc.getPList().get(0).addNewPPr().addNewJc().setVal(STJc.CENTER);
}
}
}
public void init(XWPFDocument document) {
//擷取段落
List<XWPFParagraph> paras = document.getParagraphs();
for (int i = 0; i < paras.size(); i++) {
XWPFParagraph para = paras.get(i);
// System.out.println(para.getCTP());//得到xml格式
// System.out.println(para.getStyleID());//段落級别
// System.out.println(para.getParagraphText());//段落内容
String titleLvl = getTitleLvl(document, para);//擷取段落級别
if ("a5".equals(titleLvl) || "HTML".equals(titleLvl) || "".equals(titleLvl) || null == titleLvl) {
titleLvl = "8";
}
if (null != titleLvl && !"".equals(titleLvl) && !"8".equals(titleLvl)) {
String t = titleLvl;
String orderCode = getOrderCode(titleLvl);//擷取編号
String text = para.getParagraphText();
text = orderCode + " " + text;
List<XWPFRun> runs = para.getRuns();
int runSize = runs.size();
/**Paragrap中每删除一個run,其所有的run對象就會動态變化,即不能同時周遊和删除*/
int haveRemoved = 0;
for (int runIndex = 0; runIndex < runSize; runIndex++) {
para.removeRun(runIndex - haveRemoved);
haveRemoved++;
}
if ("1".equals(titleLvl)) {
setLevelTitle1(document, para, text);
}
if ("2".equals(titleLvl)) {
setLevelTitle2(document, para, text);
}
}
}
}
/**
* Word中的大綱級别,可以通過getPPr().getOutlineLvl()直接提取,但需要注意,Word中段落級别,通過如下三種方式定義:
* 1、直接對段落進行定義;
* 2、對段落的樣式進行定義;
* 3、對段落樣式的基礎樣式進行定義。
* 是以,在通過“getPPr().getOutlineLvl()”提取時,需要依次在如上三處讀取。
*
* @param doc
* @param para
* @return
*/
private String getTitleLvl(XWPFDocument doc, XWPFParagraph para) {
String titleLvl = "";
//判斷該段落是否設定了大綱級别
CTP ctp = para.getCTP();
if (ctp != null) {
CTPPr pPr = ctp.getPPr();
if (pPr != null) {
if (pPr.getOutlineLvl() != null) {
return String.valueOf(pPr.getOutlineLvl().getVal());
}
}
}
//判斷該段落的樣式是否設定了大綱級别
if (para.getStyle() != null) {
if (doc.getStyles().getStyle(para.getStyle()).getCTStyle().getPPr().getOutlineLvl() != null) {
return String.valueOf(doc.getStyles().getStyle(para.getStyle()).getCTStyle().getPPr().getOutlineLvl().getVal());
}
//判斷該段落的樣式的基礎樣式是否設定了大綱級别
if (doc.getStyles().getStyle(doc.getStyles().getStyle(para.getStyle()).getCTStyle().getBasedOn().getVal())
.getCTStyle().getPPr().getOutlineLvl() != null) {
String styleName = doc.getStyles().getStyle(para.getStyle()).getCTStyle().getBasedOn().getVal();
return String.valueOf(doc.getStyles().getStyle(styleName).getCTStyle().getPPr().getOutlineLvl().getVal());
}
}
if (para.getStyleID() != null) {
return para.getStyleID();
}
return titleLvl;
}
/**
* 增加自定義标題樣式。這裡用的是stackoverflow的源碼
*
* @param docxDocument 目标文檔
* @param strStyleId 樣式名稱
* @param headingLevel 樣式級别
*/
private static void addCustomHeadingStyle(XWPFDocument docxDocument, String strStyleId, int headingLevel) {
CTStyle ctStyle = CTStyle.Factory.newInstance();
ctStyle.setStyleId(strStyleId);
CTString styleName = CTString.Factory.newInstance();
styleName.setVal(strStyleId);
ctStyle.setName(styleName);
CTDecimalNumber indentNumber = CTDecimalNumber.Factory.newInstance();
indentNumber.setVal(BigInteger.valueOf(headingLevel));
// lower number > style is more prominent in the formats bar
ctStyle.setUiPriority(indentNumber);
CTOnOff onoffnull = CTOnOff.Factory.newInstance();
ctStyle.setUnhideWhenUsed(onoffnull);
// style shows up in the formats bar
ctStyle.setQFormat(onoffnull);
// style defines a heading of the given level
CTPPr ppr = CTPPr.Factory.newInstance();
ppr.setOutlineLvl(indentNumber);
ctStyle.setPPr(ppr);
XWPFStyle style = new XWPFStyle(ctStyle);
// is a null op if already defined
XWPFStyles styles = docxDocument.createStyles();
style.setType(STStyleType.PARAGRAPH);
styles.addStyle(style);
}
/**
* 擷取标題編号
*
* @param titleLvl
* @return
*/
private String getOrderCode(String titleLvl) {
String order = "";
if ("0".equals(titleLvl) || Integer.parseInt(titleLvl) == 8) {//文檔标題||正文
return "";
} else if (Integer.parseInt(titleLvl) > 0 && Integer.parseInt(titleLvl) < 8) {//段落标題
//設定最進階别标題
Map<String, Object> maxTitleMap = orderMap.get("maxTitleLvlMap");
if (null == maxTitleMap) {//沒有,表示第一次進來
//最進階别标題指派
maxTitleMap = new HashMap<String, Object>();
maxTitleMap.put("lvl", titleLvl);
orderMap.put("maxTitleLvlMap", maxTitleMap);
} else {
String maxTitleLvl = maxTitleMap.get("lvl") + "";//最上層标題級别(0,1,2,3)
if (Integer.parseInt(titleLvl) < Integer.parseInt(maxTitleLvl)) {//目前标題級别更高
maxTitleMap.put("lvl", titleLvl);//設定最進階别标題
orderMap.put("maxTitleLvlMap", maxTitleMap);
}
}
//查父節點标題
int parentTitleLvl = Integer.parseInt(titleLvl) - 1;//父節點标題級别
Map<String, Object> cMap = orderMap.get(titleLvl);//目前節點資訊
Map<String, Object> pMap = orderMap.get(parentTitleLvl + "");//父節點資訊
if (0 == parentTitleLvl) {//父節點為文檔标題,表明目前節點為1級标題
int count = 0;
//最上層标題,沒有父節點資訊
if (null == cMap) {//沒有目前節點資訊
cMap = new HashMap<String, Object>();
} else {
count = Integer.parseInt(String.valueOf(cMap.get("cCount")));//目前序個數
}
count++;
order = count + "";
cMap.put("cOrder", order);//目前序
cMap.put("cCount", count);//目前序個數
orderMap.put(titleLvl, cMap);
} else {//父節點為非文檔标題
int count = 0;
//如果沒有相鄰的父節點資訊,目前标題級别自動更新
if (null == pMap) {
return getOrderCode(String.valueOf(parentTitleLvl));
} else {
String pOrder = String.valueOf(pMap.get("cOrder"));//父節點序
if (null == cMap) {//沒有目前節點資訊
cMap = new HashMap<String, Object>();
} else {
count = Integer.parseInt(String.valueOf(cMap.get("cCount")));//目前序個數
}
count++;
order = pOrder + "." + count;//目前序編号
cMap.put("cOrder", order);//目前序
cMap.put("cCount", count);//目前序個數
orderMap.put(titleLvl, cMap);
}
}
//位元組點标題計數清零
int childTitleLvl = Integer.parseInt(titleLvl) + 1;//子節點标題級别
Map<String, Object> cdMap = orderMap.get(childTitleLvl + "");//
if (null != cdMap) {
cdMap.put("cCount", 0);//子節點序個數
orderMap.get(childTitleLvl + "").put("cCount", 0);
}
}
return order;
}
}