文件处理(四):word文本替换、添加水印等

一、概述

在这篇博客中,整理了一些word的处理方法:替换普通段落文本、替换表格中的文本、添加文字水印等。

二、依赖

依赖和上一篇博客相同,在这里不再赘述。

三、相关工具类

DocxUtil
可直接食用,也可以根据自己的需求做一些修改。

public class DocxUtil {
    /**
     * 水印参数
     */
    private static final String fontColor = "#D3D3D3"; // 字体颜色

    /**
     * 艺术字水印参数
     */
    private static final String fontName = "华文行楷"; // word字体
    private static final String fontSize = "0.5pt"; // 字体大小
    private static final int widthPerWord = 10; // 一个字平均长度,单位pt,用于:计算文本占用的长度(文本总个数*单字长度)
    private static final String styleRotation = "-45"; // 文本旋转角度

    /**
     * word文字水印(调用poi封装的createWatermark方法)
     * @param doc XWPFDocument对象
     * @param markStr 水印文字
     */
    public static void setWordWaterMark(XWPFDocument doc, String markStr) {
        XWPFParagraph paragraph = doc.createParagraph();
        XWPFHeaderFooterPolicy headerFooterPolicy = doc.getHeaderFooterPolicy();
        if (headerFooterPolicy == null) {
            headerFooterPolicy = doc.createHeaderFooterPolicy();
        }
        // create default Watermark - fill color black and not rotated
        headerFooterPolicy.createWatermark(markStr);
        // get the default header
        // Note: createWatermark also sets FIRST and EVEN headers
        // but this code does not updating those other headers
        XWPFHeader header = headerFooterPolicy.getHeader(XWPFHeaderFooterPolicy.DEFAULT);
        paragraph = header.getParagraphArray(0);
//            // get com.microsoft.schemas.vml.CTShape where fill color and rotation is set
        paragraph.getCTP().newCursor();
        org.apache.xmlbeans.XmlObject[] xmlobjects = paragraph.getCTP().getRArray(0).getPictArray(0).selectChildren(
                new javax.xml.namespace.QName("urn:schemas-microsoft-com:vml", "shape"));
        if (xmlobjects.length > 0) {
            com.microsoft.schemas.vml.CTShape ctshape = (com.microsoft.schemas.vml.CTShape) xmlobjects[0];
            ctshape.setFillcolor(fontColor);
            ctshape.setStyle(ctshape.getStyle() + ";rotation:315");
        }
    }

    /**
     * 以艺术字方式加上水印(平铺)
     * @param docx XWPFDocument对象
     * @param customText 水印文字
     */
    public static void makeFullWaterMarkByWordArt(XWPFDocument docx, String customText) {
        customText = customText + repeatString(" ", 8); // 水印文字之间使用8个空格分隔
        customText = repeatString(customText, 10); // 一行水印重复水印文字次数
        String styleTop = "0pt";  // 与顶部的间距

        if (docx == null) {
            return;
        }
        // 遍历文档,添加水印
        for (int lineIndex = -10; lineIndex < 20; lineIndex++) {
            styleTop = 100 * lineIndex + "pt";
            waterMarkDocXDocument(docx, customText, styleTop, 1);
        }
    }

    /**
     * 以艺术字方式加上水印(单个)
     * @param docx XWPFDocument对象
     * @param customText 水印文字
     */
    public static void makeWaterMarkByWordArt(XWPFDocument docx, String customText) {
        String styleTop = "0pt";  // 与顶部的间距

        if (docx == null) {
            return;
        }
        // 添加水印
        waterMarkDocXDocument(docx, customText, styleTop, 2);
    }

    /**
     * 将指定的字符串重复repeats次.
     * @param pattern 字符串
     * @param repeats 重复次数
     * @return 生成的字符串
     */
    private static String repeatString(String pattern, int repeats) {
        StringBuilder buffer = new StringBuilder(pattern.length() * repeats);
        Stream.generate(() -> pattern).limit(repeats).forEach(buffer::append);
        return new String(buffer);
    }

    /**
     * 为文档添加水印
     * 实现参考了{@link org.apache.poi.xwpf.model.XWPFHeaderFooterPolicy#getWatermarkParagraph(String, int)}
     * @param doc 需要被处理的docx文档对象
     * @param customText 水印文本
     * @param type 类型:1.平铺;2.单个
     */
    private static void waterMarkDocXDocument(XWPFDocument doc, String customText, String styleTop, int type) {
        XWPFHeader header = doc.createHeader(HeaderFooterType.DEFAULT); // 如果之前已经创建过 DEFAULT 的Header,将会复用之
        int size = header.getParagraphs().size();
        if (size == 0) {
            header.createParagraph();
        }
        CTP ctp = header.getParagraphArray(0).getCTP();
        byte[] rsidr = doc.getDocument().getBody().getPArray(0).getRsidR();
        byte[] rsidrdefault = doc.getDocument().getBody().getPArray(0).getRsidRDefault();
        ctp.setRsidP(rsidr);
        ctp.setRsidRDefault(rsidrdefault);
        CTPPr ppr = ctp.addNewPPr();
        ppr.addNewPStyle().setVal("Header");
        // 开始加水印
        CTR ctr = ctp.addNewR();
        CTRPr ctrpr = ctr.addNewRPr();
        ctrpr.addNewNoProof();
        CTGroup group = CTGroup.Factory.newInstance();
        CTShapetype shapetype = group.addNewShapetype();
        CTTextPath shapeTypeTextPath = shapetype.addNewTextpath();
        shapeTypeTextPath.setOn(STTrueFalse.T);
        shapeTypeTextPath.setFitshape(STTrueFalse.T);
        CTLock lock = shapetype.addNewLock();
        lock.setExt(STExt.VIEW);
        CTShape shape = group.addNewShape();
        shape.setId("PowerPlusWaterMarkObject");
        shape.setSpid("_x0000_s102");
        shape.setType("#_x0000_t136");
        if(type != 2){
            shape.setStyle(getShapeStyle(customText, styleTop)); // 设置形状样式(旋转,位置,相对路径等参数)
        }else{
            shape.setStyle(getShapeStyle()); // 设置形状样式(旋转,位置,相对路径等参数)
        }
        shape.setFillcolor(fontColor);
        shape.setStroked(STTrueFalse.FALSE); // 字体设置为实心
        CTTextPath shapeTextPath = shape.addNewTextpath(); // 绘制文本的路径
        shapeTextPath.setStyle("font-family:" + fontName + ";font-size:" + fontSize); // 设置文本字体与大小
        shapeTextPath.setString(customText);
        CTPicture pict = ctr.addNewPict();
        pict.set(group);
    }

    /**
     * 加载docx格式的word文档
     * @param inputStream
     * @return
     */
    private static XWPFDocument loadDocXDocument(InputStream inputStream) {
        XWPFDocument doc;
        try {
            doc = new XWPFDocument(inputStream);
        } catch (Exception e) {
            throw new RuntimeException("文档加载失败!!");
        }
        return doc;
    }

    /**
     * 构建Shape的样式参数
     * @param customText 水印文本
     * @return
     */
    private static String getShapeStyle(String customText, String styleTop) {
        StringBuilder sb = new StringBuilder();
        sb.append("position: ").append("absolute"); // 文本path绘制的定位方式
        sb.append(";width: ").append(customText.length() * widthPerWord).append("pt"); // 计算文本占用的长度(文本总个数*单字长度)
        sb.append(";height: ").append("20pt"); // 字体高度
        sb.append(";z-index: ").append("-251654144");
        sb.append(";mso-wrap-edited: ").append("f");
        sb.append(";margin-top: ").append(styleTop);
        sb.append(";mso-position-horizontal-relative: ").append("margin");
        sb.append(";mso-position-vertical-relative: ").append("margin");
        sb.append(";mso-position-vertical: ").append("left");
        sb.append(";mso-position-horizontal: ").append("center");
        sb.append(";rotation: ").append(styleRotation);
        return sb.toString();
    }

    /**
     * 构建Shape的样式参数
     * @return
     */
    private static String getShapeStyle() {
        StringBuilder sb = new StringBuilder();
        sb.append("position: ").append("absolute"); // 文本path绘制的定位方式
        sb.append(";left: ").append("opt");
        sb.append(";width: ").append("500pt"); // 计算文本占用的长度(文本总个数*单字长度)
        sb.append(";height: ").append("150pt"); // 字体高度
        sb.append(";z-index: ").append("-251654144");
        sb.append(";mso-wrap-edited: ").append("f");
        sb.append(";margin-left: ").append("-50pt");
        sb.append(";margin-top: ").append("270pt");
        sb.append(";mso-position-horizontal-relative: ").append("margin");
        sb.append(";mso-position-vertical-relative: ").append("margin");
        sb.append(";mso-width-relative: ").append("page");
        sb.append(";mso-height-relative: ").append("page");
        sb.append(";rotation: ").append("-2949120f");
        return sb.toString();
    }

    /**
     * 替换word段落文本
     * @param docx
     * @param datamap
     */
    public static void replaceTextData(XWPFDocument docx, Map<String, Object> datamap){
        // 遍历所有的段落对象,将标记好的文本替换成我们想要的数据(这里无法处理表格,表格要另外处理)
        // 获取所有的段落
        List<XWPFParagraph> paragraphs = docx.getParagraphs();
        // 遍历所有的段落
        for (int i = 0; i < paragraphs.size(); i++) {
            // 获取该段所有的文本对象
            List<XWPFRun> runs = paragraphs.get(i).getRuns();
            for (int j = 0; j < runs.size(); j++) {
                XWPFRun run = runs.get(j);
                // 匹配内容,进行替换
                if(run != null && StrUtil.isNotEmpty(run.toString())){
                    for(String key : datamap.keySet()){
                        if(run.toString().contains(key)){
                            run.setText(run.toString().replace(key, datamap.get(key).toString()), 0);
                        }
                    }
                }
            }
        }
    }

    /**
     * 替换word表格内容
     * @param table XWPFTable对象
     * @param datamap 数据
     */
    public static void replaceTableData(XWPFTable table, Map<String, Object> datamap){
        // 获取这个表格所有的行
        List<XWPFTableRow> rows = table.getRows();
        // 遍历每一行
        for (XWPFTableRow xwpfTableRow : rows) {
            // 获取当前行所有的单元格
            List<XWPFTableCell> cells = xwpfTableRow.getTableCells();
            for (XWPFTableCell xwpfTableCell : cells) {
                // 获取单元格中的文本段落
                List<XWPFParagraph> para = xwpfTableCell.getParagraphs();
                for (XWPFParagraph xwpfParagraph : para) {
                    List<XWPFRun> runs = xwpfParagraph.getRuns();
                    // 遍历文本段落,替换成我们想要的数据
                    for (int i = 0; i < runs.size(); i++) {
                        XWPFRun run = runs.get(i);
                        if(run != null && StrUtil.isNotEmpty(run.toString())){
                            // 匹配内容,进行替换
                            for(String key : datamap.keySet()){
                                if(run.toString().contains(key)){
                                    run.setText(run.toString().replace(key, datamap.get(key).toString()), 0);
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

四、示例

1. 替换word段落文本(即非表格中的文本)

效果

替换前

替换后

示例代码

    // 1.获取word需要的数据(这里简化操作,手动造数据)
    Order order = new Order("XF-001", "xxxx", "xxxx", "xxxx",
            "xxxx", "xxxx", "xxxx", "xx", "987654321",
            "xx", "xx");
    // 2.将数据转成map
    HashMap<String, Object> datamap = JSONObject.parseObject(JSONObject.toJSONString(order), HashMap.class);
    // 3.获取docx模板,创建XWPFDocument对象,操作word
    InputStream is = new FileInputStream(xfConfig.rsrootpath + xfConfig.filepath + "/order_document.docx");
    XWPFDocument docx = new XWPFDocument(is);
    is.close();
    // 4.替换文本
    DocxUtil.replaceTextData(docx, datamap);

关键方法
DocxUtil.replaceTextData(XWPFDocument docx, Map<String, Object> datamap);
用map的方式,找到文本进行替换。

2.替换word表格内容

效果

替换前

替换后

示例代码

// 1.获取word需要的数据(这里简化操作,手动造数据)
Order order = new Order("XF-001", "xxxx", "xxxx", "xxxx",
            "xxxx", "xxxx", "xxxx", "xx", "987654321",
            "xx", "xx");
// 2.将数据转成map
HashMap<String, Object> datamap = JSONObject.parseObject(JSONObject.toJSONString(order), HashMap.class);
// 3.获取docx模板,创建XWPFDocument对象,操作word
InputStream is = new FileInputStream(xfConfig.rsrootpath + xfConfig.filepath + "/order_document.docx");
XWPFDocument docx = new XWPFDocument(is);
is.close();
// 4.处理表格数据
// 获取word的所有表格
List<XWPFTable> tables = docx.getTables();
// 获取我们想要操作的表格(表格从上到下,序号依次为0、1、2...)
XWPFTable infotable = tables.get(0);
// 替换数据
DocxUtil.replaceTableData(infotable, datamap);

关键方法
DocxUtil.replaceTableData(XWPFTable table, Map<String, Object> datamap);

3.添加水印

A.单个文字水印(调用poi方法)

效果


示例代码

// 1.获取docx模板,创建XWPFDocument对象,操作word
InputStream is = new FileInputStream(xfConfig.rsrootpath + xfConfig.filepath + "/order_document.docx");
XWPFDocument docx = new XWPFDocument(is);
is.close();
// 2.添加水印
DocxUtil.setWordWaterMark(docx, "内部资料");

关键方法
DocxUtil.setWordWaterMark(XWPFDocument doc, String markStr);

缺陷
仔细看这个方法的实现,可以发现调用了poi封装的createWatermark方法;这里存在一些问题,在poi4.1的版本中,水印无法平铺;以及遇到带页眉的word添加不上去。(具体记不清了。。。这是很久以前写的)

B.单个文字水印(重写poi方法)

效果

image.png

示例代码

// 1.获取源文件
InputStream is = new FileInputStream(xfConfig.rsrootpath + xfConfig.filepath + "/order_document.docx");
XWPFDocument temp = new XWPFDocument(is);
// 2.添加水印
DocxUtil.makeWaterMarkByWordArt(temp, "内部资料");
C.平铺文字水印(重写poi方法)

效果

image.png

示例代码

// 1.获取源文件
InputStream is = new FileInputStream(xfConfig.rsrootpath + xfConfig.filepath + "/order_document.docx");
XWPFDocument temp = new XWPFDocument(is);
// 2.添加水印
DocxUtil.makeFullWaterMarkByWordArt(temp, "内部资料");
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容