Apache POI 是用Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java程式对Microsoft Office格式档案读和写的功能。POI为“Poor Obfuscation Implementation”的首字母缩写,意为“简洁版的模糊实现”。
POI生成excel是比较常用的技术之一,但是用来生成word相对来说比较少,今天演示一下利用POI生成word文档的整个流程,,当然方法有很多种,这是我感觉比较方便的一种。
需要实现的功能:
总体思路:
这种方法的本质其实是动态替换,动态替换的意思是在模板中写入很多某种特定格式的占位符(关键词)以及图的样例,如果前端需要生成这个章节的内容,那么,把这个关键词传到后端,再在模板中寻找关键词,有则替换成具体内容,无则不替换。最后把没有替换掉的占位符删除,从而达到动态替换的效果。
具体实现过程如下:
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 配置章节类和章节类路径
#文档生成相关类配置
doc:
section:
section1: com.poi.docgenerate.Section1
section2: com.poi.docgenerate.Section2
section3: com.poi.docgenerate.Section3
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;
}
}