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

作者:GreenHand2333 时间:2021-08-19 21:00:25 

docx4j变量替换的问题

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

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

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

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

上面是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);
       }
   }
}

来源:https://blog.csdn.net/qq_35598240/article/details/84439929

标签:docx4j,变量,替换
0
投稿

猜你喜欢

  • Java倒计时三种实现方式代码实例

    2021-09-22 00:20:59
  • Java输出链表倒数第k个节点

    2023-03-22 01:22:34
  • java跟踪执行的sql语句示例分享

    2022-07-30 20:13:18
  • mybatis-plus3.0.1枚举返回为null解决办法

    2023-11-07 16:59:50
  • WCF和Remoting之间的消息传输

    2023-04-15 01:01:20
  • Java单例模式下的MongoDB数据库操作工具类

    2023-11-20 12:55:01
  • Android中SeekBar和RatingBar用法实例分析

    2023-07-28 00:13:59
  • Java获取时间年、月、日的方法

    2022-02-10 16:58:36
  • 详解IntelliJ IDEA 自带的 HTTP Client 接口调用插件吊打 Postman

    2023-08-06 07:56:08
  • 使用JPA自定义VO类型转换(EntityUtils工具类)

    2023-08-26 14:56:17
  • 通过实例了解Java 8创建Stream流的5种方法

    2023-04-13 22:41:02
  • Monaco Editor实现sql和java代码提示实现示例

    2023-01-31 16:59:48
  • 分析java中全面的单例模式多种实现方式

    2021-12-28 05:40:29
  • SpringBoot执行定时任务@Scheduled的方法

    2022-08-13 03:43:31
  • Java详解实现多线程的四种方式总结

    2023-04-04 19:43:34
  • java + dom4j.jar提取xml文档内容

    2023-11-29 03:55:10
  • Java Stream流零基础教程

    2023-08-15 19:33:20
  • java 文件上传到读取文件内容的实例

    2023-11-09 22:00:27
  • java实战之猜字小游戏

    2022-03-31 14:48:01
  • java LRU(Least Recently Used )详解及实例代码

    2022-10-08 10:42:43
  • asp之家 软件编程 m.aspxhome.com