首页 > 编程语言 >Java利用POI实现对Word的操作(包括有换行的文本和图片)

Java利用POI实现对Word的操作(包括有换行的文本和图片)

时间:2024-08-31 17:22:59浏览次数:6  
标签:Word String format Java run POI import path public

目录

一、实现效果

二、实现部分

1、导入依赖

2、工具类

3、实体类

4、测试代码


如果您也有类似的需求,可以参考这篇文章进行实现并扩展。

一、实现效果

1、重要说明:

①普通文本使用 ${字段名} 进行标注,有换行的文本使用 $${字段名} 进行标注。

②图片使用 #{字段名} 进行标注。

③${ }、$${ }、#{ } 相当于一个占位符,需要在其他地方编辑好然后一整块复制进来word文档,否则会出现不识别为一个整体的情况!!(建议在电脑版微信的聊天框里输入好后再复制到word文档,我是这样做的)

2、操作前:

3、操作后:

补充:为什么要特意加一类占位符,来表示有换行的字符串?

这是因为在Java里面字符串中的 \n ,插入word时显示的效果只是有一个空格。 

有换行的文本在word里面使用 $${字段名} 标注,在Java代码里面跟普通文本一样,Map的key值使用 ${字段名} 即可,这是为了方便用工具类生成替换文本的Map时,不需要特殊处理。

二、实现部分

1、导入依赖

		<dependency>
			<groupId>org.apache.poi</groupId>
			<artifactId>poi</artifactId>
			<version>3.16</version>
		</dependency>
		<dependency>
			<groupId>org.apache.poi</groupId>
			<artifactId>poi-ooxml</artifactId>
			<version>3.16</version>
		</dependency>

2、工具类

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.lang.reflect.Field;
import java.math.RoundingMode;
import java.text.NumberFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import org.apache.poi.util.Units;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;

import javax.imageio.ImageIO;

public class WordUtil {

    /**
     * 用于直接根据对象生成需要替换的文本Map:${属性名}和值
     *
     * @param obj
     * @return
     */
    public static Map<String, String> getTextFieldMap(Object obj) {
        Map<String, String> map = new HashMap<>();
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            // 如果字段是私有的,也需要访问
            field.setAccessible(true);
            try {
                String value = (String) field.get(obj);
                map.put("${" + field.getName() + "}", Objects.nonNull(value) ? value : "     ");
            } catch (IllegalAccessException e) {
                System.out.println("字段解析失败:" + e);
            }
        }
        return map;
    }

    /**
     * 根据模板生成新word文档
     *
     * @param inputUrl  模板存放地址
     * @param outputUrl 新文档存放地址
     * @param textMap   需要替换的文本信息集合
     * @param imgMap    需要替换的图片路径集合
     */
    public static void wordToNewWord(String inputUrl, String outputUrl, Map<String, String> textMap,
                                     Map<String, List<String>> imgMap) throws Exception {
        if (Objects.isNull(textMap)) {
            textMap = new HashMap<>();
        }
        if (Objects.isNull(imgMap)) {
            imgMap = new HashMap<>();
        }
        try (FileInputStream fileInputStream = new FileInputStream(inputUrl)) {
            XWPFDocument document = new XWPFDocument(fileInputStream);
            // 解析替换文本段落对象
            WordUtil.changeParagraphs(document.getParagraphs(), textMap, imgMap);
            // 解析替换表格对象
            WordUtil.changeTables(document.getTables(), textMap, imgMap);
            // 生成新的word
            try (FileOutputStream stream = new FileOutputStream(outputUrl)) {
                document.write(stream);
            }
        }
    }

    /**
     * 解析所有段落,将占位符替换成具体的文本/图片
     *
     * @param paragraphs 所有表格集合
     * @param textMap    需要替换的文本集合
     * @param imgMap     需要替换的图片路径(本地)集合
     * @throws Exception
     */
    public static void changeParagraphs(List<XWPFParagraph> paragraphs,
                                        Map<String, String> textMap, Map<String, List<String>> imgMap) throws Exception {
        for (XWPFParagraph paragraph : paragraphs) {
            String text = paragraph.getText();
            // 检查是否需要替换
            if (!containsTextPlaceholder(text) && !containsImgPlaceholder(text)) {
                continue;
            }
            List<XWPFRun> runs = paragraph.getRuns();
            for (XWPFRun run : runs) {
                // 是否需要:占位符 $${}-->有换行的文本
                if (textMap.containsKey(run.toString().substring(1))) {
                    changeRun(run, textMap.get(run.toString().substring(1)));
                }
                // 是否需要:占位符 ${}-->文本
                if (textMap.containsKey(run.toString())) {
                    run.setText(textMap.get(run.toString()), 0);
                }
                // 是否需要:占位符 #{}-->图片
                if (imgMap.containsKey(run.toString())) {
                    setCellImg(run, imgMap.get(run.toString()));
                }
            }
        }
    }

    /**
     * 解析所有表格,将占位符替换成具体的文本/图片
     *
     * @param tables  所有表格集合
     * @param textMap 需要替换的文本集合
     * @param imgMap  需要替换的图片路径(本地)集合
     * @throws Exception
     */
    public static void changeTables(List<XWPFTable> tables,
                                    Map<String, String> textMap,
                                    Map<String, List<String>> imgMap) throws Exception {
        // 获取表格对象集合
        for (XWPFTable table : tables) {
            // 判断表格内容是否需要替换
            if (!containsTextPlaceholder(table.getText()) && !containsImgPlaceholder(table.getText())) {
                continue;
            }
            // 需要替换,则遍历表格
            List<XWPFTableRow> rows = table.getRows();
            for (XWPFTableRow row : rows) {
                List<XWPFTableCell> cells = row.getTableCells();
                for (XWPFTableCell cell : cells) {
                    // 判断单元格是否需要替换
                    if (!containsTextPlaceholder(cell.getText()) && !containsImgPlaceholder(cell.getText())) {
                        continue;
                    }
                    List<XWPFParagraph> paragraphs = cell.getParagraphs();
                    changeParagraphs(paragraphs, textMap, imgMap);
                }
            }
        }
    }

    /**
     * 处理有换行的文本
     *
     * @param run
     * @param text
     * @throws Exception
     */
    private static void changeRun(XWPFRun run, String text) throws Exception {
        run.setText("", 0);
        // 如果内容为空,则插入TAB占位
        if (text == null) {
            run.addTab();
        }
        // 处理有换行的内容
        String[] texts = text.split("\n");
        for (int i = 0; i < texts.length-1; i++){
            run.setText(texts[i]);
            run.addCarriageReturn();
        }
        // 最后一个不需要换行
        run.setText(texts[texts.length-1]);
    }

    /**
     * 批量插入多张图片,一张一行
     *
     * @param run
     * @param urls
     * @throws Exception
     */
    private static void setCellImg(XWPFRun run, List<String> urls) throws Exception {
        // 清空占位符内容
        run.setText("", 0);
        // 数字格式化器:保留0位小数、向上取整
        NumberFormat numberFormat = NumberFormat.getNumberInstance();
        numberFormat.setMaximumFractionDigits(0);
        numberFormat.setRoundingMode(RoundingMode.UP);

        if (Objects.isNull(urls) || urls.isEmpty()) {
            return;
        }
        // 循环插入图片
        for (String path : urls) {
            File imageFile = new File(path);
            //判断图片是否存在
            if (!imageFile.exists()) {
                continue;
            }
            // 获取图片格式
            int format = getImageFormat(path);
            if (format == 0) {
                continue;
            }
            // 读取图片
            try (FileInputStream is = new FileInputStream(imageFile)) {
                // 计算适合文档宽高的图片EMU数值
                BufferedImage read = ImageIO.read(imageFile);
                int width = Units.toEMU(read.getWidth());
                int height = Units.toEMU(read.getHeight());
                // 1 EMU = 1/914400英寸= 1/36000 mm,15是word文档中图片能设置的最大宽度cm
                double scaleFactor = width / 360000.0 / 14.5d;
                width = Integer.parseInt(numberFormat.format(width / scaleFactor).replace(",", ""));
                height = Integer.parseInt(numberFormat.format(height / scaleFactor).replace(",", ""));
                // 插入图片
                run.addPicture(is, format, imageFile.getName(), width, height);
            }
            // 换行
            run.addBreak();
        }
    }

    /**
     * 获取图片格式
     *
     * @param path 图片路径
     * @return 图片格式枚举数字
     */
    private static int getImageFormat(String path) {
        int format = 0;
        if (path.endsWith(".emf")) {
            format = XWPFDocument.PICTURE_TYPE_EMF;
        } else if (path.endsWith(".wmf")) {
            format = XWPFDocument.PICTURE_TYPE_WMF;
        } else if (path.endsWith(".pict")) {
            format = XWPFDocument.PICTURE_TYPE_PICT;
        } else if (path.endsWith(".jpeg") || path.endsWith(".jpg")) {
            format = XWPFDocument.PICTURE_TYPE_JPEG;
        } else if (path.endsWith(".png")) {
            format = XWPFDocument.PICTURE_TYPE_PNG;
        } else if (path.endsWith(".dib")) {
            format = XWPFDocument.PICTURE_TYPE_DIB;
        } else if (path.endsWith(".gif")) {
            format = XWPFDocument.PICTURE_TYPE_GIF;
        } else if (path.endsWith(".tiff")) {
            format = XWPFDocument.PICTURE_TYPE_TIFF;
        } else if (path.endsWith(".eps")) {
            format = XWPFDocument.PICTURE_TYPE_EPS;
        } else if (path.endsWith(".bmp")) {
            format = XWPFDocument.PICTURE_TYPE_BMP;
        } else if (path.endsWith(".wpg")) {
            format = XWPFDocument.PICTURE_TYPE_WPG;
        }
        return format;
    }

    /**
     * 判断文本中是否包含: ${
     */
    private static boolean containsTextPlaceholder(String text) {
        return text.contains("${");
    }

    /**
     * 判断文本中是否包含: #{
     */
    private static boolean containsImgPlaceholder(String text) {
        return text.contains("#{");
    }
}

3、实体类

public class User {
    private String name;
    private String gender;
    private String phone;
    private String address;
    private String province;
    private String city;

    public User(String name, String gender, String phone, String address, String province, String city) {
        this.name = name;
        this.gender = gender;
        this.phone = phone;
        this.address = address;
        this.province = province;
        this.city = city;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getProvince() {
        return province;
    }

    public void setProvince(String province) {
        this.province = province;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }
}

4、测试代码

import org.junit.jupiter.api.Test;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class TestWordUtil {
    @Test
    public void test() {
        String inputUrl = "D:\\test-poi-word.docx";
        String outputUrl = "D:\\new-poi-word.docx";
        User user = new User("张三", "男", "12345678901", "广东省广州市\n这是具体地址", "广东省", "广州市");
        // 使用工具类生成字段与值的映射,也可以自己手动写,对于字段多的这样比较方便
        Map<String, String> textMap = WordUtil.getTextFieldMap(user);
        // 需要插入的图片
        Map<String, List<String>> imgMap = new HashMap<>();
        imgMap.put("#{image}", List.of("D:\\test-poi-word1.jpeg","D:\\test-poi-word2.jpg"));
        try {
            WordUtil.wordToNewWord(inputUrl, outputUrl, textMap, imgMap);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

标签:Word,String,format,Java,run,POI,import,path,public
From: https://blog.csdn.net/m0_74413652/article/details/141755198

相关文章

  • Java设计模式之单例模式(Singleton)
    单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。这样的模式有几个好处:某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。省去了new操作符,降低了系统内存的使用频率,减轻GC压力。有些类如交易所的核心交易引擎......
  • JAVA基础之三-接口和抽象类
    java提供了抽象类和接口,总体是好事。有的OOP语言并没有接口的概念,但相当一部分其实用其它方式实现了JAVA中接口类似的功能。如果不太清楚二者的区别,难免在面临具体业务的时候,在二者之间摇摆。---实际上,关于抽象类和接口的共同点和不同点没有什么可以写的。设计原则原则让我们......
  • 入门篇-其之七-Java运算符
    入门篇-其之七-Java运算符(下)合集-iCode504的Java学习空间(13)1.入门篇-其之一-第一个Java程序2023-07-232.入门篇-其之二-Java基础知识2023-09-043.入门篇-其之三-基本数据类型及其转换2023-09-064.入门篇-其之四-字符串String的简单使用2023-09-255.入门篇-其之......
  • 【华为OD机试真题E卷】31、最大社交距离 | 机试真题+思路参考+代码分析(E卷复用)(C语言、
    文章目录一、题目......
  • (免费源码)计算机毕业设计必看必学 SSM大学生实习就业推荐系统68986 原创定制程序 java
    SSM大学生实习就业推荐系统 摘 要信息化社会内需要与之针对性的信息获取途径,但是途径的扩展基本上为人们所努力的方向,由于角度存在偏差,人们经常能够获取不同类型的信息,这也是技术最为难以攻克的课题。针对大学生实习就业推荐系统等问题,对大学生实习就业推荐系统进行研究......
  • A-计算机毕业设计定制:10508民大校园美食推荐系统的设计与实现(免费领源码)可做计算机毕
    摘要 随着数字化时代的到来,校园美食推荐系统的设计与实现具有重要意义。针对民大校园中商家、普通用户和管理员之间的信息交互和服务需求,开发这样一个系统能够有效促进校园内美食资源的共享和利用,提供美食介绍和美食推荐的渠道,提高校园内美食行业的服务水平,增强校园内外用户......
  • A-计算机毕业设计定制:18099居家养老服务系统(免费领源码)可做计算机毕业设计JAVA、PHP
    摘  要1绪论1.1研究背景1.2研究意义1.3主要研究内容1.4论文章节安排2 相关技术介绍2.1Node.JS编程语言2.2MySQL数据库3 系统分析3.1可行性分析3.1.1技术可行性分析3.1.2经济可行性分析3.1.3操作可行性分析3.2系统流程分析3.2.1 ......
  • 一些JAVA面试题
    前言由于这段时间经常性的找工作找工作找工作,然后面试题问的也比较多,我就想着对这个进行一些整合,基本都是我面试的时候问过的一些问题,三年经验的JAVA开发,可能答案有些不太准确,请多多包涵和见谅;线上面试题基础类型判断publicstaticvoidmain(String[]args){//......
  • Java中抽象类的学习
    抽象类目录抽象类抽象类的概念抽象类的好处1.代码复用与简化2.强制实现特定方法3.隐藏实现细节4.支持扩展性和灵活性5.公共服务功能6.稳定的抽象层依赖抽象类的概念在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个......
  • JAVA多线程异步与线程池------JAVA
    初始化线程的四种方式继承Thread实现Runnable接口实现Callable接口+FutureTask(可以拿到返回结果,可以处理异常)线程池继承Thread和实现Runnable接口的方式,主进程无法获取线程的运算结果,不适合业务开发实现Callable接口+FutureTask可以获取线程内的返回结果,但是不利......