Java导出WORD文档

简介

        Apache POI 是Java 领域中可以操作World,Excel,PPT文件的类库,可以用于生成报表,数据处理等。注意:Apache POI 从4.0.1版本开始,需要JDK 8 或更高版本支持。

依赖导入

<!--poi 类 -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>4.1.2</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>4.1.2</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml-schemas</artifactId>
    <version>4.1.2</version>
</dependency>
<!--word工具类-->
<dependency>
    <groupId>com.deepoove</groupId>
    <artifactId>poi-tl</artifactId>
    <version>1.9.1</version>
    <exclusions>
        <exclusion>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>ooxml-schemas</artifactId>
    <version>1.4</version>
</dependency>
<!--html渲染插件 注意0.4.2版本需要匹配jsoup的1.15.4版本-->
<dependency>
    <groupId>io.github.draco1023</groupId>
    <artifactId>poi-tl-ext</artifactId>
    <version>0.4.2</version>
    <exclusions>
        <exclusion>
            <groupId>com.deepoove</groupId>
            <artifactId>poi-tl</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.apache.poi</groupId>
            <artifactId>ooxml-schemas</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.15.4</version>
</dependency>
<!--样式依赖-->
<dependency>
    <groupId>org.w3c.css</groupId>
    <artifactId>sac</artifactId>
    <version>1.3</version>
</dependency>
<dependency>
    <groupId>net.sourceforge.cssparser</groupId>
    <artifactId>cssparser</artifactId>
    <version>0.9.29</version>
</dependency>

富文本导出

在导出内容中包含有html标签的富文本,或者导出的数据是动态的可以组合为html标签富文本格式导出。以下是html渲染代码。

try {
    // 富文本内容,这里读取的本地文件(文件内容可自定义,只要是html标签格式富文本都可以),文件见后面
    String filePath = "D:\\html.txt";
    FileInputStream fin = new FileInputStream(filePath);
    InputStreamReader reader = new InputStreamReader(fin);
    BufferedReader buffReader = new BufferedReader(reader);
    StringBuilder richTextHtml = new StringBuilder();
    String strTmp = "";
    while ((strTmp = buffReader.readLine()) != null) {
        richTextHtml.append(strTmp);
    }
    buffReader.close();
    // HTML内容
    String richTextHtmlString = richTextHtml.toString();
    // html渲染插件
    HtmlRenderPolicy htmlRenderPolicy = new HtmlRenderPolicy();
    Configure configure = Configure.builder()
            // 注册html解析插件,只有注册的key才会解析html富文本。
            .bind("content", htmlRenderPolicy)
            .build();
    // 映射数据Map
    Map<String, Object> data = new HashMap<>();
    data.put("topic","标题");
    data.put("content", content2Html(richTextHtmlString));
    // 读取模板文件,并渲染数据。test_template.docx模板文件 这里放在resources下面,模板内容可以自定义,与映射字段对应上即可。
    XWPFTemplate template = XWPFTemplate.compile(Objects.requireNonNull(getResourceInputStream("/test_template.docx")), configure).render(data);
    // 写入文件
    template.writeToFile("D:\\test_template_return.docx");
    template.close();
} catch (Exception e) {
    e.printStackTrace();
}

样例代码中的富文本文件html.txt

<table>
  <tr>
    <td>html富文本</td>
  </tr>
  <tr>
    <td>importance</td>
  </tr>
  
  <tr>
    <td>html富文本</td>
  </tr>
  <tr>
    <td>worry</td>
  </tr>
</table>

样例代码中的模板文档test_template.docx,注意需要替换的内容映射参数使用{{}}阔起来。模板中ha还包含两张图片、空心饼图和柱状图在下一章用。

{{topic}}
{{content}}

图表和图片导出

主要思路是替换模板中已有的图表和图片,如果图表的数量不确定可以参考富文本的方式导出,这里主要介绍替换模板内容。

/**
 * 程序入口
 */
public static void main(String[] args) throws Exception {
    WordContentParserVO param = new WordContentParserVO();
    // word模板路径
    param.setPath("D:/test_template.docx");
    // 饼图
    List<WordChartParser.ChartReplace> chartReplaces = new ArrayList<>();
    WordChartParser.ChartReplace chartReplace1 = new WordChartParser.ChartReplace();
    chartReplace1.setIndex(1);
    // 饼图-项
    String[] categoryNames1 = new String[2];
    categoryNames1[0]="通过率";
    categoryNames1[1]="未通过率";
    chartReplace1.setCategoryNames(categoryNames1);
    // 饼图-值
    List<WordChartParser.LineValue> lineValues1 = new ArrayList<>();
    WordChartParser.LineValue lineValue1 = new WordChartParser.LineValue();
    lineValue1.setLineName("饼图");
    Number[] lineValueNumbers1 = new Number[2];
    lineValueNumbers1[0]=85.5;
    lineValueNumbers1[1]=14.5;
    lineValue1.setLineValue(lineValueNumbers1);
    lineValues1.add(lineValue1);
    chartReplace1.setLineValues(lineValues1);
    chartReplaces.add(chartReplace1);
    // 柱状图
    WordChartParser.ChartReplace chartReplace2 = new WordChartParser.ChartReplace();
    chartReplace2.setIndex(2);
    // 柱状图-项
    String[] categoryNames2 = new String[2];
    categoryNames2[0]="核心测评项";
    categoryNames2[1]="一般测评项";
    chartReplace2.setCategoryNames(categoryNames2);
    // 柱状图-值
    List<WordChartParser.LineValue> lineValues2 = new ArrayList<>();
    WordChartParser.LineValue lineValue2 = new WordChartParser.LineValue();
    lineValue2.setLineName("通过");
    Number[] lineValueNumbers2 = new Number[2];
    lineValueNumbers2[0]=60;
    lineValueNumbers2[1]=61;
    lineValue2.setLineValue(lineValueNumbers2);
    lineValues2.add(lineValue2);
    WordChartParser.LineValue lineValue3 = new WordChartParser.LineValue();
    lineValue3.setLineName("未通过");
    Number[] lineValueNumbers3 = new Number[2];
    lineValueNumbers3[0]=20;
    lineValueNumbers3[1]=21;
    lineValue3.setLineValue(lineValueNumbers3);
    lineValues2.add(lineValue3);
    chartReplace2.setLineValues(lineValues2);
    chartReplaces.add(chartReplace2);
    param.setChartReplaces(chartReplaces);
    // 模板图片替换
    List<WordPictureParser.PictureReplace> pictureReplaceList = new ArrayList<>();
    WordPictureParser.PictureReplace pictureReplace1=new WordPictureParser.PictureReplace();
    pictureReplace1.setIndex(0);
    pictureReplace1.setType(2);
    // 需要替换成哪张图片 自己下载到本地的图片
    pictureReplace1.setPictureData(getResourceInputStream("D:/img1.png"));
    pictureReplaceList.add(pictureReplace1);
    WordPictureParser.PictureReplace pictureReplace2=new WordPictureParser.PictureReplace();
    pictureReplace2.setIndex(1);
    pictureReplace2.setType(2);
    // 需要替换成哪张图片 自己下载到本地的图片
    pictureReplace2.setPictureData(getResourceInputStream("D:/img2.png"));
    pictureReplaceList.add(pictureReplace2);
    param.setPictureReplaces(pictureReplaceList);
    // 模板渲染
    WordContentParser parser = PoiUtil.replaceWordContent(param);
    File file = parser.outputFile("D:/test_result.docx");
    System.out.println("word文件替换成功,保存路径:"+file.getAbsolutePath());
}

/**
 * 获取资源文件的文件流
 *
 * @return InputStream
 */
public static InputStream getResourceInputStream(String filePath) {
    File file = new File(filePath);
    FileInputStream fileInputStream = null;
    try {
        fileInputStream = new FileInputStream(file);
    } catch (FileNotFoundException e) {
        throw new RuntimeException(e);
    }
    return fileInputStream;
}
/**
* word模板图片、图表内容替换工具类
*
*/
public class PoiUtil {
    public static WordContentParser replaceWordContent(WordContentParserVO param) {
        XWPFDocument doc = WordContentParser.loadXWPFDocument(param.getPath());
        //图表替换
        WordChartParser chartParser = new WordChartParser(doc, param.getChartReplaces());
        chartParser.replace();
        //图片替换
        WordPictureParser pictureParser = new WordPictureParser(doc, param.getPictureReplaces());
        pictureParser.replace();
        return chartParser;
    }
}
@Data
public class WordContentParserVO {
    private String path;
    private List<WordChartParser.ChartReplace> chartReplaces;
    private List<WordPictureParser.PictureReplace> pictureReplaces;
}
/**
 * Word图表解析器
 */
@Setter
@Getter
public class WordChartParser extends WordContentParser {

    private List<ChartReplace> chartReplaces;

    public WordChartParser(XWPFDocument doc, List<ChartReplace> chartReplaces) {
        super(doc);
        this.chartReplaces = chartReplaces;
    }

    @Setter
    @Getter
    public static class ChartReplace {
        //图表标题
        private String chartTitle;
        //x分类轴标题
        private String categoryAxisTitle;
        //y值轴标题
        private String valueAxesTitle;
        //图表在模板中的下标,从1开始
        private int index;
        //图表分类名称
        private String[] categoryNames;
        //图表数据行值
        private List<LineValue> lineValues;
    }

    @Setter
    @Getter
    public static class LineValue {
        //图表数据名称
        private String lineName;
        //图表数据行值
        private Number[] lineValue;
        //设置标记样式
        private MarkerStyle markerStyle;

        public LineValue() {
            // 设置标记样式
            markerStyle = MarkerStyle.NONE;
        }

    }

    @Override
    public XWPFDocument replace() {
        if (Objects.isNull(getDoc())) {
            throw new RuntimeException(ERR_MSG_DOC_NOT_LOADED);
        }
        if (CollectionUtils.isEmpty(chartReplaces)) {
            throw new RuntimeException(ERR_MSG_PARAM_NOT_INITIALIZED);
        }

        List<XWPFChart> charts = getDoc().getCharts();
        if (CollectionUtils.isEmpty(charts)) {
            throw new RuntimeException(ERR_MSG_TEMPLATE_CHART_NOT_EXIST.replace("${name}", ""));
        }

        Map<Integer, XWPFChart> templateChartMap = new HashMap<>();
        for (int i = 1, size = charts.size(); i <= size; i++) {
            String key = charts.get(i - 1).toString().replaceAll("Name: ", "")
                    .replaceAll(" - Content Type: application/vnd\\.openxmlformats-officedocument\\.drawingml\\.chart\\+xml", "").trim();
            key = key.replace("/word/charts/chart", "").replace(".xml", "");
            templateChartMap.put(Integer.valueOf(key), charts.get(i - 1));
        }

        chartReplaces.forEach(lineChartReplace -> {
            if (CollectionUtils.isEmpty(lineChartReplace.getLineValues())) {
                throw new RuntimeException(ERR_MSG_PARAM_NOT_INITIALIZED);
            }
            //校验元素个数是否一致
            int dataLength = lineChartReplace.getCategoryNames().length;
            for (int i = 0, size = lineChartReplace.getLineValues().size(); i < size; i++) {
                if (lineChartReplace.getLineValues().get(i).getLineValue().length != dataLength) {
                    throw new RuntimeException(ERR_MSG_ROW_ELEMENT_MISMATCH.replace("${index}", String.valueOf(i)));
                }
            }
            //校验图表是否存在
            XWPFChart templateChart = templateChartMap.get(lineChartReplace.getIndex());
            if (Objects.isNull(templateChart)) {
                throw new RuntimeException(ERR_MSG_TEMPLATE_CHART_NOT_EXIST.replace("${index}", String.valueOf(lineChartReplace.getIndex())));
            }
        });

        //逐个图表进行内容替换
        chartReplaces.forEach(lineChartReplace -> {
            XWPFChart templateChart = templateChartMap.get(lineChartReplace.getIndex());
            templateChart.setTitleText(lineChartReplace.getChartTitle());//图表标题
            XDDFCategoryDataSource categoryDataSource = XDDFDataSourcesFactory.fromArray(lineChartReplace.getCategoryNames());//分类名称
            templateChart.getChartSeries().forEach(chartData -> {
                if (Objects.nonNull(chartData.getCategoryAxis())) {
                    chartData.getCategoryAxis().setTitle(lineChartReplace.getCategoryAxisTitle());//x轴标题
                }
                if (Objects.nonNull(chartData.getValueAxes())) {
                    chartData.getValueAxes().forEach(axis -> {
                        axis.setTitle(lineChartReplace.getValueAxesTitle());//y轴标题
                    });
                }
                //替换模板自带的图表数据
                for (int i = 0, count = chartData.getSeriesCount(); i < count; i++) {
                    XDDFNumericalDataSource<Number> values = XDDFDataSourcesFactory.fromArray(lineChartReplace.getLineValues().get(i).getLineValue());
                    XDDFChartData.Series series = chartData.getSeries(i);
                    series.replaceData(categoryDataSource, values);
                }
                // 绘制
                templateChart.plot(chartData);

            });
        });

        return getDoc();
    }

}
/**
 * Word内容解析器
 */
@Setter
@Getter
@Slf4j
public abstract class WordContentParser {

    public static final String ERR_MSG_DOC_LOAD_FAIL = "文档加载失败";
    public static final String ERR_MSG_DOC_OUTPUT_FAIL = "文档导出失败";
    public static final String ERR_MSG_DOC_NOT_LOADED = "文档未加载";
    public static final String ERR_MSG_PARAM_NOT_INITIALIZED = "替换参数未初始化";
    public static final String ERR_MSG_ROW_ELEMENT_MISMATCH = "下标${index}的行值元素不匹配";
    public static final String ERR_MSG_TABLE_NOT_EXIST = "不存在下标${index}的表格";
    public static final String ERR_MSG_CELLS_NOT_UNANIMOUS = "下标${index}的表格值和单元格个数不一致";
    public static final String ERR_MSG_TEMPLATE_CHART_NOT_EXIST = "不存在下标${index}的图表";
    public static final String ERR_MSG_PIC_NOT_ADD = "下标${index}的图片替换失败";
    public static final String ERR_MSG_PIC_NOT_EXIST = "模板中${type}下标${index}的图片不存在替换值";

    //文档对象
    private XWPFDocument doc;

    public WordContentParser(InputStream in) {
        try {
            this.doc = new XWPFDocument(in);
        } catch (IOException e) {
            log.error("{}:{}", ERR_MSG_DOC_LOAD_FAIL, e.getMessage());
            throw new RuntimeException(ERR_MSG_DOC_LOAD_FAIL);
        }
    }

    public WordContentParser(XWPFDocument doc) {
        this.doc = doc;
    }

    public WordContentParser(String path) {
        this.doc = loadXWPFDocument(path);
    }

    public static XWPFDocument loadXWPFDocument(String path) {
        InputStream in = null;
        XWPFDocument doc = null;
        try {
            in = new FileInputStream(path);
            //in = new ClassPathResource(path).getInputStream(); spring-boot项目打成jar部署到Linux需要改成这个加载方式,path用相对路径
            doc = new XWPFDocument(in);
        } catch (Exception e) {
            log.error("{}:{}", ERR_MSG_DOC_LOAD_FAIL, e.getMessage());
            throw new RuntimeException(ERR_MSG_DOC_LOAD_FAIL);
        } finally {
            if (Objects.nonNull(in)) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return doc;
        }
    }

    public File outputFile(String path) {
        if (Objects.isNull(doc)) {
            throw new RuntimeException(ERR_MSG_DOC_LOAD_FAIL);
        }

        File file = null;
        FileOutputStream fos = null;
        try {
            file = new File(path);
            if (file.exists()) {
                file.delete();
            }
            fos = new FileOutputStream(path);
            doc.write(fos);
        } catch (Exception e) {
            log.error("{}:{}", ERR_MSG_DOC_OUTPUT_FAIL, e.getMessage());
            throw new RuntimeException(ERR_MSG_DOC_OUTPUT_FAIL);
        } finally {
            if (Objects.nonNull(fos)) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return file;
        }
    }

    public abstract XWPFDocument replace();
}
/**
 * Word图片解析器
 *
 */
@Setter
@Getter
@Slf4j
public class WordPictureParser extends WordContentParser {

    //模板中${type}下标${index}的图片不存在替换值---是否抛异常
    private boolean throwException;

    private List<PictureReplace> pictureReplaces;

    private int picIndexOfParagraph;

    private void setPicIndexOfParagraph(int picIndexOfParagraph) {
        this.picIndexOfParagraph = picIndexOfParagraph;
    }

    public WordPictureParser(InputStream in, List<PictureReplace> pictureReplaces) {
        super(in);
        this.pictureReplaces = pictureReplaces;
        this.throwException = true;
        this.picIndexOfParagraph = 0;
    }

    public WordPictureParser(XWPFDocument doc, List<PictureReplace> pictureReplaces) {
        super(doc);
        this.pictureReplaces = pictureReplaces;
        this.throwException = true;
        this.picIndexOfParagraph = 0;
    }

    public WordPictureParser(InputStream in) {
        super(in);
        this.throwException = true;
        this.picIndexOfParagraph = 0;
    }

    public WordPictureParser(XWPFDocument doc) {
        super(doc);
        this.throwException = true;
        this.picIndexOfParagraph = 0;
    }

    public WordPictureParser(String path) {
        super(path);
        this.throwException = true;
        this.picIndexOfParagraph = 0;
    }

    @Setter
    @Getter
    public static class PictureReplace {
        //内容类型,1 段落图片 、 2 表格图片
        private int type;
        public static final int TYPE_PARAGRAPH = 1;
        public static final int TYPE_TABLE = 2;
        //图片在模板的下标,Word文档段落和表格从左到右算起,段落和表格单独分开计数,0开始
        private int index;
        //图片流数据
        private InputStream pictureData;
        //图片类型
        private int pictureType;

        public PictureReplace() {
            this.type = TYPE_PARAGRAPH;
            this.pictureType = XWPFDocument.PICTURE_TYPE_PNG;
        }
    }

    public static InputStream loadPictureData(String path) {
        try {
            return new FileInputStream(path);
        } catch (FileNotFoundException e) {
            log.error("FileNotFoundException->{}", path);
            return null;
        }
    }

    @Override
    public XWPFDocument replace() {
        if (Objects.isNull(getDoc())) {
            throw new RuntimeException(ERR_MSG_DOC_NOT_LOADED);
        }
        if (CollectionUtils.isEmpty(pictureReplaces)) {
            throw new RuntimeException(ERR_MSG_PARAM_NOT_INITIALIZED);
        }

        picIndexOfParagraph = 0;
        pictureReplace(PictureReplace.TYPE_PARAGRAPH, getDoc().getParagraphs());

        picIndexOfParagraph = 0;
        getDoc().getTables().forEach(xwpfTable -> {
            xwpfTable.getRows().forEach(row -> {
                row.getTableCells().forEach(xwpfTableCell -> {
                    pictureReplace(PictureReplace.TYPE_TABLE, xwpfTableCell.getParagraphs());
                });
            });
        });

        pictureReplaces.forEach(pictureReplace -> {
            if (Objects.nonNull(pictureReplace.getPictureData())) {
                try {
                    pictureReplace.getPictureData().close();
                } catch (IOException e) {
                    log.error("{} {}", pictureReplace, e.getMessage());
                }
            }
        });

        return getDoc();
    }

    private void pictureReplace(int type, List<XWPFParagraph> xwpfParagraphs) {
        int runIndex = 0;
        for (int i = 0, size = xwpfParagraphs.size(); i < size; i++) {
            XWPFParagraph xwpfParagraph = xwpfParagraphs.get(i);
            List<XWPFRun> runs = xwpfParagraph.getRuns();
            for (int j = 0, size2 = runs.size(); j < size2; j++) {
                List<XWPFPicture> xwpfPictures = runs.get(j).getEmbeddedPictures();
                if (CollectionUtils.isNotEmpty(xwpfPictures)) {
                    PictureReplace pictureReplace = null;
                    runIndex = j;
                    XWPFRun newXWPFRun = xwpfParagraph.createRun();
                    for (int k = 0, size3 = xwpfPictures.size(); k < size3; k++) {
                        int width = (int) xwpfPictures.get(k).getCTPicture().getSpPr().getXfrm().getExt().getCx();
                        int height = (int) xwpfPictures.get(k).getCTPicture().getSpPr().getXfrm().getExt().getCy();
                        String filename = xwpfPictures.get(k).getPictureData().getFileName();
                        int finalPicIndexOfParagraph = picIndexOfParagraph;
                        pictureReplace = pictureReplaces.stream().filter(o -> type == o.getType()).filter(o -> o.getIndex() == finalPicIndexOfParagraph).findFirst().orElse(null);
                        if (Objects.nonNull(pictureReplace)) {
                            try {
                                newXWPFRun.addPicture(pictureReplace.getPictureData(), pictureReplace.getPictureType(), filename, width, height);
                            } catch (Exception e) {
                                log.error("{};{}", ERR_MSG_PIC_NOT_ADD.replace("${index}", String.valueOf(finalPicIndexOfParagraph)), e.getMessage());
                                throw new RuntimeException(ERR_MSG_PIC_NOT_ADD.replace("${index}", String.valueOf(finalPicIndexOfParagraph)));
                            }
                        } else {
                            if (throwException) {
                                throw new RuntimeException(ERR_MSG_PIC_NOT_EXIST.replace("${type}", type == PictureReplace.TYPE_PARAGRAPH ? "段落图片" : "表格图片").replace("${index}", String.valueOf(finalPicIndexOfParagraph)));
                            }
                        }
                        picIndexOfParagraph++;
                    }
                    if (Objects.nonNull(pictureReplace)) {
                        xwpfParagraph.removeRun(runIndex);
                        xwpfParagraph.addRun(newXWPFRun);
                    }
                }
            }
        }
    }
}

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值