笨方法学 Java -31-综合练习2-实现分析-docx 文本替换(续)
作者:谎言诞行    发布时间:2024年03月25日


这篇文章,讨论的是关于 docx 文件中的处理优化


我们知道,XWPF 模块是按照段落中,字符串的属性来区分的。那么如果一段很长的同样式内容,会怎么拆分呢?这个可以看下图:

image.png

上图中,studentName 变量出现了3次,并且只有第一个添加了 下划线 和 粗体。我们看看程序的解析结果:

image.png

我们可以看到,第2个第3个 studentName 变量,拆分出了前缀“{{”和 “}}”后缀出现在同一行的情况。所以,在上一篇的文章《笨方法学 Java -30-综合练习2-实现分析-docx 文本替换》中,的解析方法,对于这种情况,可能出现解析出错,匹配不到位的问题。


那么,我们应该怎样处理呢?本人觉得,其实区分一下,字符串出现的可能性,再处理字符串的合并即可。根据归纳整理,可能的类型有以下几种:

1、开,...{{...

2、开...闭,...{{...}}...

3、开...闭...开,...{{...}}...{{...

4、闭,...}}...

5、闭...开,...}}...{{...

6、闭...开...闭,...}}...{{...}}...

7、无开闭信息, ...

8、其它无法识别的类型。

在字符串识别上,我们可以使用正则表达式。测试程序如下:

public static void main(String[] args) {
    //开 1
    String s0 = "{{";
    String s00= "塑料袋封口就是{{";
    String s01= "{{student1";
    String s1 = "塑料袋封口就是{{student1";
    //开...闭 2
    String s2 = "是独立访客时间{{student2}}灯笼裤{{student3}}飞机";
    String s20 = "{{student2}}{{student3}}";
    //开...闭...开 3
    String s3 = "是独立访客访客{{student4}}时间裤{{student5}}灯笼裤飞机{{student6";
    String s30 = "是独立访客访客{{student4}}时间裤{{student5}}灯笼裤飞机{{";
    //闭 4
    String s40 = "}}";
    String s4 = "student7}}飞机反馈就";
    //闭...开 5
    String s5 = "student8}}飞机反馈就{{student9";
    String s50 = "student8}}飞机反访客{{student00}}时间访客{{student01}}时间馈就{{student9";
    String s51 = "student8}}飞机反访客时间馈就{{student9";
    //闭...开...闭 6
    String s6 = "student10}}飞机反馈就{{student11}}打开{{student12}}的法律纠纷";
    //无 7
    String s7 = "斯洛伐克江东父老";
    
    String[] arr = new String[] {s0,s00,s01, s1, s2, s20, s3,s30, s40, s4, s5,s50,s51, s6, s7};
    
    for(String s : arr) {
        System.out.println(String.format("结果:%s, 字符串:%s", 
                                         MyWordUtil.identifyTypeInfoFromText(s), s));
    }
}

执行的结果如下:

image.png

这样我们就可以根据 不同的 字符串 情况,来编写关于 字符串合并的处理。关于正则校验的处理,如下:

/**
 * >>&nbsp;根据传入的字符串信息,识别出该信息符合哪种识别类型。<br/>
 * 在参数识别上,如果字符串带有开始符“{{”,或者结束符“}}”,<strong>要么不带参数名,要么必带参数名。</strong><br/>
 * 参数名,是一个有数字和字母组合的内容。
 * 根据归纳整理,可能的类型有以下几种:<br/>
 * 1、开,...{{...<br/>
 * 2、开...闭,...{{...}}...<br/>
 * 3、开...闭...开,...{{...}}...{{...<br/>
 * 4、闭,...}}...<br/>
 * 5、闭...开,...}}...{{...<br/>
 * 6、闭...开...闭,...}}...{{...}}...<br/>
 * 7、无开闭信息, ...<br/>
 * 8、其它无法识别的类型。<br/>
 * @param oriText 原始的文本
 * @return 整数的类型信息。如果参数为 null ,则返回 -1 。
 */
public static int identifyTypeInfoFromText(String oriText) {
    
    int result = -1;
    
    //正则:变量名
    String regexpParamName = "[0-9a-zA-Z]+";
    //正则:可能有变量名的前缀字符串
    String regexpParamWithPre = PARAM_PRE_REGEXP+"("+regexpParamName+")?";
    //正则:可能有变量名的后缀字符串
    String regexpParamWithSuf = "("+regexpParamName+")?"+PARAM_SUF_REGEXP;
    //正则:带前缀、后缀的变量名
    String regexpParam = PARAM_PRE_REGEXP+regexpParamName+PARAM_SUF_REGEXP;
    //正则:没有前缀字符的可能字符串
    String regexpNoBegin = "((?!"+PARAM_PRE_REGEXP+").)*";
    //正则:没有后缀字符的可能字符串
    String regexpNoEnd = "((?!"+PARAM_SUF_REGEXP+").)*";
    //正则:没有前缀、后缀字符的字符串
    String regexpNoBeginEnd="((?!("+PARAM_SUF_REGEXP+"|"+PARAM_PRE_REGEXP+")).)*";
    
    /**
        * 以下是7种可能的字符串,所对应的正则
        */
    String regexp1 = regexpNoEnd+regexpParamWithPre; //只有 开 符号的正则
    String regexp2 = "("+regexpNoBeginEnd+regexpParam+regexpNoBeginEnd+")+"; //开...闭 的正则
    String regexp3 = "("+regexpNoBeginEnd+regexpParam+regexpNoBeginEnd+")+"+regexpParamWithPre; //开...闭...开 的正则
    String regexp4 = regexpParamWithSuf+regexpNoBegin; //只有 闭 符号的正则
    String regexp5 = regexpParamWithSuf+".*"+regexpParamWithPre; //闭...开 的正则
    String regexp6 = regexpParamWithSuf+"("+regexpNoBeginEnd+regexpParam+regexpNoBeginEnd+")+"; //闭...开...闭
    String regexp7 = regexpNoBeginEnd; // 无
    
    if(oriText!=null) {
        if(Pattern.matches(regexp1, oriText)) {
            result= 1;
        }else if(Pattern.matches(regexp2, oriText)) {
            result= 2;
        }else if(Pattern.matches(regexp3, oriText)) {
            result= 3;
        }else if(Pattern.matches(regexp4, oriText)) {
            result= 4;
        }else if(Pattern.matches(regexp5, oriText)) {
            result= 5;
        }else if(Pattern.matches(regexp6, oriText)) {
            result= 6;
        }else if(Pattern.matches(regexp7, oriText)) {
            result= 7;
        }else {
            result= 8;
        }
    }
    
    return result;
}

接下来,我们处理字符串合并。字符串合并,在原有的 getRunInfoFromParagrah 方法上修改。处理如下:

/**
 * >>&nbsp;在处理 office word 文档中,docx 类型的文档时,
 * 由于字符处理时,需要替换的变量字符串可能分割在几个对象中,需要合并
 * @param paragrah 段落对象
 * @return List<XWPFRun> 
 */
public static List<XWPFRun> getRunInfoFromParagrah(XWPFParagraph paragrah){
    //结果对象
    List<XWPFRun> result = null;
    //参数正常才开始处理
    if(paragrah!=null && paragrah.getRuns()!=null && paragrah.getRuns().size()>0) {
        //结果初始化
        result = paragrah.getRuns();
        //装载合并字符串的容器
        StringBuffer buffer = new StringBuffer();
        //合并字符串时,接收字符串的变量
        XWPFRun targetRun = null;
        //是否合并的标识
        boolean needAppend = false;
        //循环遍历,将字符串合并
        for(int i=0;i<result.size();i++) {
            XWPFRun tmp = result.get(i);
            //根据字符串内容,识别成对应的类型序号
            //根据 type 类型,处理字符串
            int type = identifyTypeInfoFromText(tmp.text());
            if(type==1) { // 1、开,...{{...
                targetRun = tmp;
                buffer.append(tmp.text());
                needAppend = true;
            }else if(type==2) { // 2、开...闭,...{{...}}...
                if(needAppend) {
                    buffer.append(tmp.text());
                    paragrah.removeRun(i);
                    paragrah.insertNewRun(i);
                }else {
                    continue;
                }
            }else if(type==3) { // 3、开...闭...开,...{{...}}...{{...
                targetRun = tmp;
                buffer.append(tmp.text());
                needAppend = true;
            }else if(type==4) { // 4、闭,...}}...
                buffer.append(tmp.text());
                paragrah.removeRun(i);
                paragrah.insertNewRun(i);//==删掉之后数组的序号就变了,需要插一个空的进去
                //将变量全部字符串合并到target
                targetRun.setText(buffer.toString(), 0);
                //清零
                buffer.setLength(0);
                targetRun = null;
                needAppend=false;
            }else if(type==5) { // 5、闭...开,...}}...{{...
                if(needAppend) {
                    buffer.append(tmp.text());
                    paragrah.removeRun(i);
                    paragrah.insertNewRun(i);
                }else {
                    continue;
                }
            }else if(type==6) { // 6、闭...开...闭,...}}...{{...}}...
                buffer.append(tmp.text());
                paragrah.removeRun(i);
                paragrah.insertNewRun(i);//==删掉之后数组的序号就变了,需要插一个空的进去
                //将变量全部字符串合并到target
                targetRun.setText(buffer.toString(), 0);
                //清零
                buffer.setLength(0);
                targetRun = null;
                needAppend=false;
            }else if(type==7 || type==8 || type==-1) {
                // 7、无开闭信息, ...  8、其它无法识别的类型。 -1、字符串为 null 。参数异常
                if(needAppend) {
                    buffer.append(tmp==null?"":tmp.text());
                    paragrah.removeRun(i);
                    paragrah.insertNewRun(i);//删掉之后序号就变了,需要插一个空
                }else {
                    continue;
                }
            }
        }
    }
    return result;
}

接下来,我们看下,改进后的处理程序,能识别出来什么:

image.png


这样,我们就把零散的 字符串 成功合并了,接下来,只需要 执行内容替换即可。


替换后的结果如下:

image.png


本章完结

填写一种颜色的字符(不区分大小写),1分钟有效,点击图片刷新