java 操作word(docx4j)

1.情景展示

  用java编辑word文档

2.准备工作

  通过docx4j实现 

  所需jar包

<!-- https://mvnrepository.com/artifact/org.docx4j/docx4j-JAXB-Internal -->
<dependency>
    <groupId>org.docx4j</groupId>
    <artifactId>docx4j-JAXB-Internal</artifactId>
    <version>8.2.9</version>
</dependency>

3.功能实现

  生成word文件

import org.docx4j.openpackaging.packages.WordprocessingMLPackage;  
/*
 * 创建docx文档
 * @attention:
 * 1.原文件如果已经存在的情况下,原文件中的内容会被清空
 * 2.在windows中无论后缀名的大小写问题,只要是相同后缀名且前缀也相同就是同一文件,
 * 当是同一后缀名且大小写不一致时,已存在的文件后缀名大小写不会改动
 * 3.如果原文件已存在,则该word文件不能处于打开状态(没有处在使用中)
 * @date: 2021-05-12 14:57
 * @param: fileSavePath
 * 文件保存所在路径
 * @param: fileSaveName
 * @return: boolean
 * Word创建成功、失败
 */
private static boolean createDocx(String fileSavePath, String fileSaveName) {
    try {
        // 确保文件的后缀名为".docx"
        if (!fileSaveName.substring(fileSaveName.indexOf(".")).equalsIgnoreCase(".docx")) fileSaveName += ".docx";

        // 确保文件的绝对路径
        if (fileSavePath.endsWith("/") || fileSavePath.endsWith("\\"))
            fileSavePath += fileSaveName;
        else
            fileSavePath += File.separator + fileSaveName;

        // Create the package 建包
        WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.createPackage();
        // 另存为新的文件 保存
        // 如果原文件已存在的话,里面的内容会被清空
        wordMLPackage.save(new File(fileSavePath));

        log.info("docx创建成功");
        return true;
    } catch (Exception e) {
        e.printStackTrace();
        log.error("docx创建失败:", e.getMessage());
        return false;
    }
}

  追加内容

import org.docx4j.openpackaging.exceptions.Docx4JException;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import org.jetbrains.annotations.NotNull;

import java.io.*;
import java.util.List;
/*
 * 增加单个段落
 * @attention:
 * 1.原文件不能在的话,会报错
 * 2.原文件不能处于打开状态(没有处在使用中)
 * @date: 2021-05-12 15:28
 * @param: docxAbsolutePath
 * 文件的绝对(完整)路径,比如:C:\Users\Marydon\Desktop\aaaaa.docx
 * @param: paragraphText
 * 不能为空,要添加的段落集合
 * @return: boolean
 * 添加成功、失败
 */
public static boolean insertOneParagraph(String docxAbsolutePath, @NotNull String paragraphText) {
    // 段落内容为空相当于word文档内容没有发生变化,不再继续执行
    if (StringUtils.isEmpty(paragraphText)) return false;

    ArrayList<String> simpleTextList = new ArrayList<>(1);
    simpleTextList.add(paragraphText);

    // 批量增加段落
    return insertParagraphs(docxAbsolutePath, simpleTextList);
}

/*
 * 批量增加段落
 * @attention:
 * 1.原文件不能在的话,会报错
 * 2.原文件不能处于打开状态(没有处在使用中)
 * @date: 2021-05-12 15:28
 * @param: docxAbsolutePath
 * 文件的绝对(完整)路径,比如:C:\Users\Marydon\Desktop\aaaaa.docx
 * @param: textList
 * 不能为空,要添加的段落集合
 * @return: boolean
 * 添加成功、失败
 */
public static boolean insertParagraphs(String docxAbsolutePath, @NotNull List<String> textList) {
    // 段落内容为空相当于word文档内容没有发生变化,不再继续执行
    if (null == textList || textList.isEmpty() || (textList.size() == 1 && StringUtils.isEmpty(textList.get(0)))) return false;

    // 批量添加段落
    return insertParagraphs(docxAbsolutePath, docxAbsolutePath, textList);
}

/*
 * 批量添加段落
 * @explain:
 * 当入参oldDocxCompleteRoute和newDocxCompleteRoute一致时,段落内容将会被添加到原文档中
 * 当入参oldDocxCompleteRoute和newDocxCompleteRoute不一致时,原文件内容+段落内容会被插入到新文档中(新文档不存在时会被自动创建)
 * @attention:
 * 1.原文件不能在的话,会报错
 * 2.原文件不能处于打开状态(没有处在使用中)
 * 3.新文件不存在会自动创建,新文件内容=旧文件内容+插入段落内容
 * @date: 2021-05-12 15:28
 * @param: oldDocxCompleteRoute
 * 原文件的绝对(完整)路径,比如:C:\Users\Marydon\Desktop\aaaaa.docx
 * @param: oldDocxCompleteRoute
 * 新文件的绝对(完整)路径,比如:C:/Users/Marydon/Desktop/ccccc.docx
 * @param: textList
 * 要添加的段落集合
 * 当原文件的完整路径与新文件完整路径一致且要添加的段落内容为空时,其实现的效果是:
 * 完成了原文件的拷贝工作(原文件内容会被复制到新文件当中)
 * @return: boolean
 * 添加成功、失败
 */
public static boolean insertParagraphs(String oldDocxCompleteRoute, String newDocxCompleteRoute, List<String> textList) {
    try {
        // 当原文件的完整路径与新文件完整路径一致,且要添加的段落内容为空时,不往下执行
        if (newDocxCompleteRoute.equals(oldDocxCompleteRoute)
                && (null == textList || textList.isEmpty() || (textList.size() == 1 && StringUtils.isEmpty(textList.get(0))))) return false;

        // 加载word文件
        WordprocessingMLPackage wPackage = WordprocessingMLPackage.load(new File(oldDocxCompleteRoute));
        MainDocumentPart mdt = wPackage.getMainDocumentPart();
        // 批量添加段落内容
        textList.forEach(simpleText -> {
            mdt.addParagraphOfText(simpleText);
            // mdt.addStyledParagraphOfText()可以指定段落的级别,如:标题使用Title,副标题使用Subtitle等
        });
        // 如果仅仅是想要实现换行的话,除了增加段落外,还可以使用换行符来实现
        // mdt.addParagraphOfText("张三\r\n李四");
        // 保存
        wPackage.save(new File(newDocxCompleteRoute));
        log.info("段落批量添加成功");

        return true;
    } catch (Docx4JException e) {
        e.printStackTrace();
        log.error("段落添加失败:", e.getMessage());
        return false;
    }
} 

  插入图片

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.docx4j.dml.wordprocessingDrawing.Inline;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.BinaryPartAbstractImage;
import org.docx4j.wml.Drawing;
import org.docx4j.wml.ObjectFactory;
import org.docx4j.wml.P;
import org.docx4j.wml.R;
import org.jetbrains.annotations.NotNull;

import java.io.File;
/*
 * 插入单个图片
 * @attention:
 * 1.需要确保文件的有效性(真实存在、路径完整)
 * 2.原文件不能处于打开状态(没有处在使用中)
 * @date: 2021-05-12 17:45
 * @param: docxCompleteRoute
 * word文档的绝对(完整)路径
 * @param: imgCompleteRoute
 * 图片的绝对(完整)路径
 * @return: boolean
 * 插入成功、失败
 */
public static boolean insertOneImg(String docxCompleteRoute, String imgCompleteRoute) {
    try {
        // 图片转byte[]
        byte[] bytes = FileUtils.readFileToByteArray(new File(imgCompleteRoute));
        // 加载word文档
        // 创建了一个包(package)来容纳文档
        WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(new File(docxCompleteRoute));
        // 插入图片(将图片添加到包中)
        addImageToPackage(wordMLPackage, bytes);
        // 保存这个包
        wordMLPackage.save(new File(docxCompleteRoute));
        log.info("图片插入成功");
        return true;
    } catch (Exception e) {
        e.printStackTrace();
        log.error("图片插入失败:" + e.getMessage());
        return false;
    }

}

/**
 * 将图片放到文档的包中
 *
 * Docx4j拥有一个由字节数组创建图片部件的工具方法, 随后将其添加到给定的包中. 为了能将图片添加 到一个段落中,
 * 我们需要将图片转换成内联对象. 这也有一个方法, 方法需要文件名提示, 替换文本, 两个id标识符和一个是嵌入还是链接到的指示作为参数.
 * 一个id用于文档中绘图对象不可见的属性, 另一个id用于图片本身不可见的绘制属性. 最后我们将内联 对象添加到段落中并将段落添加到包的主文档部件.
 *
 * @param wordMLPackage
 *            要添加图片的包
 * @param bytes
 *            图片对应的字节数组
 * @throws Exception
 *             不幸的createImageInline方法抛出一个异常(没有更多具体的异常类型)
 */
private static void addImageToPackage(WordprocessingMLPackage wordMLPackage, byte[] bytes) throws Exception {
    // 创建图片部件
    BinaryPartAbstractImage imagePart = BinaryPartAbstractImage.createImagePart(wordMLPackage, bytes);
    // 创建内联对象
    Inline inline = imagePart.createImageInline("Filename hint","Alternative text", 1, 2, false);
    // 将内联对象添加到段落中
    // 实现方法在下方
    P paragraph = addInlineImageToParagraph(inline);
    // 将段落添加到文档中
    wordMLPackage.getMainDocumentPart().addObject(paragraph);
}

/**
 * 将内联对象添加到段落中
 *
 * 创建一个对象工厂并用它创建一个段落和一个可运行块R. 然后将可运行块添加到段落中. 接下来创建一个图画并将其添加到可运行块R中. 最后我们将内联
 * 对象添加到图画中并返回段落对象.
 *
 * @param inline
 *            包含图片的内联对象.
 * @return 包含图片的段落
 */
@NotNull
private static P addInlineImageToParagraph(Inline inline) {
    // 采用工厂类增加段落的方法
    ObjectFactory factory = new ObjectFactory();
    // 创建段落
    P paragraph = factory.createP();
    // R是一个运行块,负责便于将多个属性相同的Object对象统一操作,通过其内部的content成员变量可以添加内容
    R run = factory.createR();
    paragraph.getContent().add(run);

    Drawing drawing = factory.createDrawing();
    run.getContent().add(drawing);
    drawing.getAnchorOrInline().add(inline);
    return paragraph;
}  

4.调用

public static void main(String[] args) {
    // 创建docx文档
    createDocx("C:\\Users\\Marydon\\Desktop\\", "aaaaa.docx");
    String text = "龙傲天,骨傲天,光傲天";
    insertParagraphs("C:\\Users\\Marydon\\Desktop\\aaaaa.docx", Arrays.asList(text.split(",")));

    // insertOneImg("C:\\Users\\Marydon\\Desktop\\aaaaa.docx","C:\\Users\\Marydon\\Desktop\\开光.jpg");
}  

5.关于docx

  .docx文件的本质是xml文件,要想操作docx,其实操作的就是xml。我们一起来看下:

  将word文档的后缀名.docx改成.zip

  打开之后就会发现,里面全是xml文件

  word的正文内容在上图word文件夹中的document.xml中   

  在word中段落标记符号和使用\r\n生成的换行符是不一样的,区别如下:

 

写在最后

  哪位大佬如若发现文章存在纰漏之处或需要补充更多内容,欢迎留言!!!

 相关推荐:

 

posted @ 2021-05-12 18:55  Marydon  阅读(3209)  评论(4编辑  收藏  举报