完美解决docx4j变量替换的问题

 更新时间:2022年07月04日 14:32:09   作者:GreenHand2333  
这篇文章主要介绍了完美解决docx4j变量替换的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

docx4j变量替换的问题

最近工作上需要自己完成word文档变量替换的问题

把里面的变量给替换成数据库里的值,但是由于在word文档渲染成xml的时候,会通过某些原因把字段放在不同层次的xml标签

上面是docx4j文档说的原因,大概是字体格式不同(我的问题是用了粗体 ${ 和 正常中文是不同格式的),拼写语法问题,编辑顺序。

在StackOverflow 找了很久解决方案,Variableprepare.prepare方法确实测试后能解决部分替换问题,但还是不能满足我的需求。

阅读源码后重新清扫了一下字符串。

测试代码

  public static void main(String[] args) throws Exception {
        File file = new File("C:\\Users\\scoli\\Desktop\\t.docx");
        WordprocessingMLPackage doc = WordprocessingMLPackage.load(new FileInputStream(file));
        Docx4jUtils.cleanDocumentPart(doc.getMainDocumentPart());
        Map map = new HashMap();
        map.put("订单编号", "1");
        OutputStream outputStream = new FileOutputStream(new File("C:\\Users\\scoli\\Desktop\\test1.docx"));
        MainDocumentPart mainDocumentPart = doc.getMainDocumentPart();
        if (!map.isEmpty()) {
            // 替换文本内容
            mainDocumentPart.variableReplace(map);
        }
        // 输出word文件
        doc.save(outputStream);
        outputStream.flush();
    }

docx4j版本

 <!--docx4j支持 start-->
        <dependency>
            <groupId>org.docx4j</groupId>
            <artifactId>docx4j</artifactId>
            <version>3.3.6</version>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.docx4j</groupId>
            <artifactId>docx4j-export-fo</artifactId>
            <version>3.3.6</version>
        </dependency>
        <!--docx4j支持 end-->

下面是工具类

package zwy.saas.common.util;  
import org.docx4j.XmlUtils;
import org.docx4j.jaxb.Context;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import org.docx4j.wml.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.Map;
import java.util.regex.Pattern;
 
/**
 * 关于文件操作的工具类
 *
 * @author kaizen
 * @date 2018-10-23 17:21:36
 */
public final class Docx4jUtils {
 
    private static final Logger logger = LoggerFactory.getLogger(Docx4jUtils.class);
 
    /**
     * 替换变量并下载word文档
     *
     * @param inputStream
     * @param map
     * @param response
     * @param fileName
     */
    public static void downloadDocUseDoc4j(InputStream inputStream, Map<String, String> map,
                                          HttpServletResponse response, String fileName) {
 
        try {
            // 设置响应头
            fileName = URLEncoder.encode(fileName, "UTF-8");
            response.setContentType("application/octet-stream;charset=UTF-8");
            response.setCharacterEncoding("utf-8");
            response.setHeader("Content-Disposition", "attachment; filename=" + fileName + ".docx");
            response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
 
            OutputStream outs  = response.getOutputStream();
            Docx4jUtils.replaceDocUseDoc4j(inputStream,map,outs);
 
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    }
 
    /**
     * 替换变量并输出word文档
     * @param inputStream
     * @param map
     * @param outputStream
     */
    public static void replaceDocUseDoc4j(InputStream inputStream, Map<String, String> map,
                                          OutputStream outputStream) {
        try {
            WordprocessingMLPackage doc = WordprocessingMLPackage.load(inputStream);
            MainDocumentPart mainDocumentPart = doc.getMainDocumentPart();
            if (null != map && !map.isEmpty()) {
                // 将${}里的内容结构层次替换为一层
                Docx4jUtils .cleanDocumentPart(mainDocumentPart);
                // 替换文本内容
                mainDocumentPart.variableReplace(map);
            }
 
            // 输出word文件
            doc.save(outputStream);
            outputStream.flush();
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    }
 
 
    /**
     * cleanDocumentPart
     *
     * @param documentPart
     */
    public static boolean cleanDocumentPart(MainDocumentPart documentPart) throws Exception {
        if (documentPart == null) {
            return false;
        }
        Document document = documentPart.getContents();
        String wmlTemplate =
                XmlUtils.marshaltoString(document, true, false, Context.jc);
        document = (Document) XmlUtils.unwrap(DocxVariableClearUtils.doCleanDocumentPart(wmlTemplate, Context.jc));
        documentPart.setContents(document);
        return true;
    }
 
    /**
     * 清扫 docx4j 模板变量字符,通常以${variable}形式
     * <p>
     * XXX: 主要在上传模板时处理一下, 后续
     *
     * @author liliang
     * @since 2018-11-07
     */
    private static class DocxVariableClearUtils {
 
 
        /**
         * 去任意XML标签
         */
        private static final Pattern XML_PATTERN = Pattern.compile("<[^>]*>");
 
        private DocxVariableClearUtils() {
        }
 
        /**
         * start符号
         */
        private static final char PREFIX = '$';
 
        /**
         * 中包含
         */
        private static final char LEFT_BRACE = '{';
 
        /**
         * 结尾
         */
        private static final char RIGHT_BRACE = '}';
 
        /**
         * 未开始
         */
        private static final int NONE_START = -1;
 
        /**
         * 未开始
         */
        private static final int NONE_START_INDEX = -1;
 
        /**
         * 开始
         */
        private static final int PREFIX_STATUS = 1;
 
        /**
         * 左括号
         */
        private static final int LEFT_BRACE_STATUS = 2;
 
        /**
         * 右括号
         */
        private static final int RIGHT_BRACE_STATUS = 3;
 
 
        /**
         * doCleanDocumentPart
         *
         * @param wmlTemplate
         * @param jc
         * @return
         * @throws JAXBException
         */
        private static Object doCleanDocumentPart(String wmlTemplate, JAXBContext jc) throws JAXBException {
            // 进入变量块位置
            int curStatus = NONE_START;
            // 开始位置
            int keyStartIndex = NONE_START_INDEX;
            // 当前位置
            int curIndex = 0;
            char[] textCharacters = wmlTemplate.toCharArray();
            StringBuilder documentBuilder = new StringBuilder(textCharacters.length);
            documentBuilder.append(textCharacters);
            // 新文档
            StringBuilder newDocumentBuilder = new StringBuilder(textCharacters.length);
            // 最后一次写位置
            int lastWriteIndex = 0;
            for (char c : textCharacters) {
                switch (c) {
                    case PREFIX:
                        // TODO 不管其何状态直接修改指针,这也意味着变量名称里面不能有PREFIX
                        keyStartIndex = curIndex;
                        curStatus = PREFIX_STATUS;
                        break;
                    case LEFT_BRACE:
                        if (curStatus == PREFIX_STATUS) {
                            curStatus = LEFT_BRACE_STATUS;
                        }
                        break;
                    case RIGHT_BRACE:
                        if (curStatus == LEFT_BRACE_STATUS) {
                            // 接上之前的字符
                            newDocumentBuilder.append(documentBuilder.substring(lastWriteIndex, keyStartIndex));
                            // 结束位置
                            int keyEndIndex = curIndex + 1;
                            // 替换
                            String rawKey = documentBuilder.substring(keyStartIndex, keyEndIndex);
                            // 干掉多余标签
                            String mappingKey = XML_PATTERN.matcher(rawKey).replaceAll("");
                            if (!mappingKey.equals(rawKey)) {
                                char[] rawKeyChars = rawKey.toCharArray();
                                // 保留原格式
                                StringBuilder rawStringBuilder = new StringBuilder(rawKey.length());
                                // 去掉变量引用字符
                                for (char rawChar : rawKeyChars) {
                                    if (rawChar == PREFIX || rawChar == LEFT_BRACE || rawChar == RIGHT_BRACE) {
                                        continue;
                                    }
                                    rawStringBuilder.append(rawChar);
                                }
                                // FIXME 要求变量连在一起
                                String variable = mappingKey.substring(2, mappingKey.length() - 1);
                                int variableStart = rawStringBuilder.indexOf(variable);
                                if (variableStart > 0) {
                                    rawStringBuilder = rawStringBuilder.replace(variableStart, variableStart + variable.length(), mappingKey);
                                }
                                newDocumentBuilder.append(rawStringBuilder.toString());
                            } else {
                                newDocumentBuilder.append(mappingKey);
                            }
                            lastWriteIndex = keyEndIndex;
 
                            curStatus = NONE_START;
                            keyStartIndex = NONE_START_INDEX;
                        }
                    default:
                        break;
                }
                curIndex++;
            }
            // 余部
            if (lastWriteIndex < documentBuilder.length()) {
                newDocumentBuilder.append(documentBuilder.substring(lastWriteIndex));
            }
            return XmlUtils.unmarshalString(newDocumentBuilder.toString(), jc);
        } 
    }
}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • spring-boot-starter-web更换默认Tomcat容器的方法

    spring-boot-starter-web更换默认Tomcat容器的方法

    Spring Boot支持容器的自动配置,默认是Tomcat,当然我们也是可以进行修改的。下面小编给大家带来了spring-boot-starter-web更换默认Tomcat容器的方法,感兴趣的朋友跟随小编一起看看吧
    2019-04-04
  • 利用Java读取Word表格中文本和图片的方法实例

    利用Java读取Word表格中文本和图片的方法实例

    这篇文章主要给大家介绍了关于如何利用Java读取Word表格中文本和图片的相关资料,主要利用的是free spire.doc.jar 包,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2021-07-07
  • 使用Java将字符串在ISO-8859-1和UTF-8之间相互转换

    使用Java将字符串在ISO-8859-1和UTF-8之间相互转换

    大家都知道在一些情况下,我们需要特殊的编码格式,如:UTF-8,但是系统默认的编码为ISO-8859-1,遇到这个问题,该如何对字符串进行两个编码的转换呢,下面小编给大家分享下java中如何在ISO-8859-1和UTF-8之间相互转换,感兴趣的朋友一起看看吧
    2021-12-12
  • @JsonSerialize序列化注解的使用

    @JsonSerialize序列化注解的使用

    这篇文章主要介绍了@JsonSerialize序列化注解的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-08-08
  • Java并发 synchronized锁住的内容解析

    Java并发 synchronized锁住的内容解析

    这篇文章主要介绍了Java并发 synchronized锁住的内容解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-09-09
  • Spring中@Async用法详解及简单实例

    Spring中@Async用法详解及简单实例

    这篇文章主要介绍了Spring中@Async用法详解及简单实例的相关资料,需要的朋友可以参考下
    2017-02-02
  • 通过一个命令轻松切换Java的版本

    通过一个命令轻松切换Java的版本

    这篇文章主要给大家介绍了如何通过一个命令轻松实现切换Java的版本,通过本文介绍的方法,大家就可以将jdk版本之间轻松切换,需要的朋友可以参考学习,下面跟着小编一起来看看吧。
    2017-05-05
  • MapStruct对象映射转换解决Bean属性拷贝性能问题

    MapStruct对象映射转换解决Bean属性拷贝性能问题

    无意间看到项目中有小伙伴用到了 MapStruct 来做对象映射转换当时我就很好奇,这个是什么框架,能够解决什么问题,带着这两个疑问就有了下面的文章
    2022-02-02
  • SpringBoot多种生产打包方式详解

    SpringBoot多种生产打包方式详解

    生产上发布 Spring Boot 项目时,流程颇为繁琐且低效,但凡代码有一丁点改动,就得把整个项目重新打包部署,耗时费力不说,生成的 JAR 包还特别臃肿,体积庞大,本文给大家介绍了SpringBoot多种生产打包方式,需要的朋友可以参考下
    2025-01-01
  • dom4j从jar包中读取xml文件的方法

    dom4j从jar包中读取xml文件的方法

    这篇文章主要介绍了dom4j从jar包中读取xml文件的方法,需要的朋友可以参考下
    2014-02-02

最新评论