Spring处理Word文档: 保留文档原格式,标记文中关键词,以及word绘制表格替换

508 阅读3分钟

缘起

起因任职公司为了所谓需要内网部署,不依赖外网的文本敏感词检测。这波啊,内网是假,不花钱是真,百度人家现成的文本检测API,成熟的,完善的API多香,非要手写一个。

考虑过蓝冰的word处理也不错,但是他的免费版会出现红字提醒,但是目的是不花钱。

查阅网上资料包括掘金,CSDN,百度等等。发现网上的比较少,大部分都是替换的功能,但是不能对敏感词高亮。或者对word处理的比较少。

所以没有办法,只能开发一个简单的文本探测的功能。可能代码不太优雅,或者存在一定缺陷,只提供各位参考,也希望技术大牛,可以提供一些更好的思路或者解法。

DOCX文档,敏感词探测高亮功能实现

不多废话,先看效果(演示文档内容百度搜的新闻,没有其他用意)
实现功能: 1.可替换标题,文本中的敏感词。
          2.对表格中的单元格进行敏感词处理,如果单个格子里出现套娃字段也是可以处理。
不足: 对图片,图表直接跳过不做处理。
        

功能演示

原始文档

1680860742028.jpg

标记后文档

1680860753767.jpg

依赖

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>3.17</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>ooxml-schemas</artifactId>
    <version>1.1</version>
</dependency>

业务代码

此处是上传下载的业务代码 视情况个人自己修改,我这里是上传至存储桶。

需要将脱敏的词 转换为正则表达式

/**
 * regularExpression
 * 对文章处理,保留原格式 替换新文档,异常啥的就不做处理里
 * @param inputStream
 * @param fileName
 * @return
 */
public Map replaceAndRemark( String fileName) throws IOException{
    InputStream inputStream = new FileInputStream("C:\\Users\\Administrator\\Desktop\\数据分析报告.docx");
    //正则表达式,我是直接丢缓存中。
    String regularExpression = "[代][孕][妈][妈]|[借][腹][生][子]";
    //标记字段的字体 以及背景色
    String targetColor = "ff0000";        // 红字
    String targetBgColor = "ffff00";      // 黄底
   
  
    //用于存储返回结果:保存生成文件地址 和 敏感词出现数量
    Map result = new HashMap<String, Object>();
    //初始化数量
    int num = 0;
    //使用try with resource 管理
    try (XWPFDocument docx = new XWPFDocument(inputStream);
             ByteArrayOutputStream out = new ByteArrayOutputStream()
        ) {
            // 获取正则表式,获取pattern 供以下match使用
            Pattern pattern = Pattern.compile(regularExpression);

    //获取段落
    List<XWPFParagraph> paragraphs = docx.getParagraphs();
    //对单个段落遍历
    for (XWPFParagraph paragraph : paragraphs) {
        // 注: paragraph为文章中的段落     run 可以理解为给段落自动拆分的句子 但是他随机长度不一定以句号结尾  格式 指原文章中排版 比如段落字体 标题 大小等....
        List<XWPFRun> runs = paragraph.getRuns();
        doFilter(runs,pattern,paragraph);

    }

    //对文档表格处理
    Iterator itTable = docx.getTablesIterator();
    while (itTable.hasNext()) { //遍历表格

        XWPFTable table = (XWPFTable) itTable.next();

        int count = table.getNumberOfRows();//获得表格总行数

        for (int i = 0; i < count; i++) { //遍历表格的每一行

            XWPFTableRow row = table.getRow(i);//获得表格的行

            List<XWPFTableCell> cells = row.getTableCells();//在行元素中,获得表格的单元格

            for (XWPFTableCell cell : cells) { //遍历单元格
                String text = cell.getText();
                Matcher matcher = pattern.matcher(text);
                if(matcher.find()) {
                    List<XWPFParagraph> cellParagraphs = cell.getParagraphs();
                    for(XWPFParagraph paragraph:cellParagraphs){
                        List<XWPFRun> runs = paragraph.getRuns();
                        doFilter(runs,pattern,paragraph);
                    }
                }

            }

        }

    }

    // 保存文档
    OutputStream os = new FileOutputStream("C:\\Users\\Administrator\\Desktop\\test2.docx");
    docx.write(os);
    inputStream.close();
    os.close();

    } catch (Exception e) {
        e.printStackTrace();
    }
    return  result;
}

doFliter业务代码

public void doFilter(List<XWPFRun> runs,Pattern pattern,XWPFParagraph paragraph){
    Boolean flag = Boolean.FALSE;

    for (int i = 0; i < runs.size(); ) {

        // 以下逻辑:首先获取该run的String文本,如若存在关键词会拆分3段落 关键词前面为before 关键词为substring 关键词后为after
        // 由于需要保留原文档格式,只能删除原来的run 注意必须要用paragraph.insertNewRun新建的run去写文本  如果直接调用createRun去写文本会导致原格式消失。
        // 以下的flag 是标志位,用于判断是跳1个run 还是2个run  在for循环内 我们在判断出现关键词后 会进行run新增;如不出现 则run不新增
        // 所以在给for循环的 i 需要根据flag标识位去判断+1 还是+2

        flag = Boolean.FALSE;
        //获取原来的run的内容,字体,以及字体大小
        String text = runs.get(i).getText(0);
        String fontFamily = runs.get(i).getFontFamily();
        int fontSize = runs.get(i).getFontSize();

        if(Objects.nonNull(text)) {
            Matcher matcher = pattern.matcher(text);
            if (matcher.find()) {
                String substring = text.substring(matcher.start(), matcher.end());
                String before = text.substring(0, matcher.start());                    // 取出段落中关键字之前的内容
                String after = text.substring(matcher.end());
                //去除原来的内容,进行替换
                paragraph.removeRun(i);


                XWPFRun newRun = paragraph.insertNewRun(i);
                newRun.setText(before);

                XWPFRun newRun2 = paragraph.insertNewRun(i + 1);
                newRun2.setBold(true);
                newRun2.setColor("ff0000");
                newRun2.setFontFamily(fontFamily);
                // 部分字段比如标题 他会出现fontSize为-1导致出现问题 这样只能默认暂未找到解决方法
                if(fontSize != -1) {
                    newRun2.setFontSize(fontSize);
                }
                newRun2.setText(substring);

                XWPFRun newRun3 = paragraph.insertNewRun(i + 2);
                newRun3.setText(after);

                flag = true;

            }
            i = i + (flag ? 2 : 1);
        }else{
            i++;
        }


    }

WORD绘制表格 进行替换

自定义模板文档

此处是word 1680853988893.jpg

处理后

此处是转PDF后的

1680854049707.png

/**
    ** document 
    * map 为 KEY VALUE形式 如上图 map.put("X01","2023年04月03日");类推,自定义。
**/
    public static <K, V> void searchAndReplace(XWPFDocument document, Map<String,String> map) {

        try {

// 替换段落中的指定文字

            Iterator itPara = document.getParagraphsIterator();//获得word中段落

            while (itPara.hasNext()) { //遍历段落

                XWPFParagraph paragraph = (XWPFParagraph) itPara.next();

                Set set = map.keySet();

                Iterator iterator = set.iterator();

                while (iterator.hasNext()) {

                    String key = iterator.next().toString();

                    List<XWPFRun> run = paragraph.getRuns();
                    for (int i = 0; i < run.size(); i++) {

                        if (run.get(i).getText(run.get(i).getTextPosition()) != null &&
                                run.get(i).getText(run.get(i).getTextPosition()).equals(key)) {
                            /**
                             * 参数0表示生成的文字是要从哪一个地方开始放置,设置文字从位置0开始
                             * 就可以把原来的文字全部替换掉了
                             */
                            run.get(i).setText(map.get(key), 0);
                        }
                    }



                    Iterator itTable = document.getTablesIterator();
                    while (itTable.hasNext()) { //遍历表格

                        XWPFTable table = (XWPFTable) itTable.next();

                        int count = table.getNumberOfRows();//获得表格总行数

                        for (int i = 0; i < count; i++) { //遍历表格的每一行

                            XWPFTableRow row = table.getRow(i);//获得表格的行

                            List<XWPFTableCell> cells = row.getTableCells();//在行元素中,获得表格的单元格

                            for (XWPFTableCell cell : cells) { //遍历单元格

                                for (Map.Entry<String, String> e : map.entrySet()) {
                                    String xx = cell.getText();


                                    if (xx.equals(e.getKey())) {//如果单元格中的变量和‘键’相等,就用‘键’所对应的‘值’代替。

                                        cell.removeParagraph(0);//所以这里就要求每一个单元格只能有唯一的变量。
//这边不用cell.setText 设置内容是为了设置样式 
//主要是出现了 setText后他会默认宋体(正文)样式 然后导致我WORD转PDF失败
//所以后来指定黑体才能显示
                                        XWPFParagraph paragraph1 = cell.addParagraph();
                                        XWPFRun run1 = paragraph1.createRun();
                                        run1.setFontFamily("黑体");
                                        run1.setText(e.getValue());

                                        setAlignCenter(cell);

                                    }else if (xx.contains(e.getKey())){
                                        cell.removeParagraph(0);
                                        XWPFParagraph paragraph1 = cell.addParagraph();
                                        XWPFRun run1 = paragraph1.createRun();
                                        run1.setFontFamily("黑体");
                                        run1.setText(xx.replace(e.getKey(),e.getValue()));
                                        if (xx.contains("编号")){
                                            setAlignRight(cell);
                                        }
                                    }


                                }


                            }

                        }

                    }

                }

            }
            return;
        }catch (Exception e){
            e.printStackTrace();
        }
    }

Word转PDF就不细说了 ,注意点就是字体库问题

PdfConverter.getInstance().convert(xwpfDocument,fileOutputStream,pdfOptions);