Java:使用POI实现word的docx文件的模板功能

一:场景 
通过Word模板来实现动态的word生成

二: 基本要求 
1:替换文本中的内容 
2:替换表格中的内容(不用动态生成表格) 
3:替换后的内容应该与替换前的内容格式相同 
4:模板修改方便 
5:效果如下: 
模板: 
这里写图片描述 
结果: 
这里写图片描述 
三:poi分析 
使用方法:直接读取word文件,替换里面各个部分的内容 
优点:直接使用word文件作为模板 
缺点:本身的替换逻辑无法保留格式

四:为什么选择封装POI 
1:因为时间和学习成本(懒)的问题,没有研究docx的xml规则,因此决定直接对现有的工具进行封装,来实现需求.

2:freeMarker本身只是对通用的模板进行处理,底层并不能直接解析word文件. 
而poi本身就是对word文件进行操作的,因此可以对直接在poi的api上进一步的封装.

五:可行性分析 
1.POI使用XWPFDocument对象来解析docx的文件,通过在构造时传入docx文件的读取流完成解析过程,将解析后将信息分门别类的存储在不同的对象中.并提供write(OutputStream stream)方法将这些对象重新转换为xml写入到文件中.

2.XWPFDocument中的对象存储有文本和格式等信息,能够拿到或修改这些信息

3.结合上述1,2两点,表示了POI存在修改word文本并保留格式的可能,通过编写Demo修改了XWPFDocument中的一个XWPFParagraph对象中的XWPFRun文本内容后证明了这一点.

XWPFDocument docx = new XWPFDocument(InputStream);
XWPFRun run = docx.getParagraphs().get(0).getRuns().get(0);
run.setText("modify");
docx.write(OutputStream);
 
 
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

六: 实现原理 
1.文本内容和表格内容分别可以通过 getParagraphs() 和 getTables() 两个方法得到

XWPFDocument docx = new XWPFDocument(InputStream);
//文本内容
List<XWPFParagraph> allXWPFParagraphs = docx.getParagraphs();
//表格内容
List<XWPFTable> xwpfTables = docx.getTables();
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

2.查看XWPFTable发现,最后表格中的文本内容还是用XWPFParagraph对象存储

//获得每行
    List<XWPFTableRow> xwpfTableRows = = xwpfTable.getRows();
    List<XWPFTableCell> xwpfTableCells = new ArrayList<XWPFTableCell>();
    for(int i = 0 ; i < xwpfTableRows.size() ; i++){
            //获得一行的所有单元
        xwpfTableCells.addAll(getCells(i));
    }
    List<XWPFParagraph> xwpfParagraphs = new ArrayList<XWPFParagraph>();
    for(XWPFTableCell cell : xwpfTableCells){
            //获得每个单元中的文本
        xwpfParagraphs.addAll(cell.getParagraphs());
    }
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

因此很可能最后修改表格和文本最后都汇集到一个相同的地方,就是修改XWPFParagraph的文本内容.进过简单的测试,证明了无论是表格还是文本,其内容都可以通过XWPFParagraph进行修改.

3.研究XWPFParagraph中的内容发现,每个XWPFParagraph对象中存储的都是一整段的内容,而这一整段的内容被分割成多个部分存入了XWPFParagraph对象中的集合List runs,也许因为docx文件转换成xml的时候很大几率的会将相同格式的文本分割开来,所以每个XWPFRun展示的内容都是不连续的!!而这正是实现word模板的难点所在.

4.因为没打算再深入研究docx转xml的原理,因此打算在现有的不连续的基础上进行封装,将其组合起来,模拟成”连续”的对象,最后对”连续”的对象进行replace操作.我的实现方法如下 
(1)获得完整的文本内容,用于判断是否包含${key} 
(2)对文本内容的每个字符标记所属的XWPFRun对象 
(3)如果匹配,则从标记中获取匹配的第一个字符,得到字符对应的标记对象,将替换的内容全部标记为该对象 
(4)替换完成后,遍历所有的标记,将标记对象所属的字符重新组合成String后重新设置,并将无用的标记对象文本设置为空

public class XWPFParagraphUtils {

    private XWPFParagraph paragraph;

    private List<XWPFRun> allXWPFRuns;
    //所有run的String合并后的内容
    private StringBuffer context ;
    //长度与context对应的RunChar集合
    List<RunChar> runChars ;

    public XWPFParagraphUtils(XWPFParagraph paragraph){
        this.paragraph = paragraph;
        initParameter();
    }

    /**
     * 初始化各参数
     */
    private void initParameter(){
        context = new StringBuffer();
        runChars = new ArrayList<XWPFParagraphUtils.RunChar>();
        allXWPFRuns = new ArrayList<XWPFRun>();
        setXWPFRun();
    }


    /**
     * 设置XWPFRun相关的参数
     * @param run
     * @throws Exception
     */
    private void setXWPFRun() {

        allXWPFRuns = paragraph.getRuns();
        if(allXWPFRuns == null || allXWPFRuns.size() == 0){
            return;
        }else{
            for (XWPFRun run : allXWPFRuns) {
                int testPosition = run.getTextPosition();
                String text = run.getText(testPosition);
                if(text == null || text.length() == 0){
                    return;
                }

                this.context.append(text);
                for(int i = 0 ; i < text.length() ; i++){
                    runChars.add(new RunChar(text.charAt(i), run));
                }
            }
        }
        System.out.println(context.toString());
    }

    public String getString(){
        return context.toString();
    }

    public boolean contains(String key){
        return context.indexOf(key) >= 0 ? true : false;
    }

    /**
     * 所有匹配的值替换为对应的值
     * @param key(匹配模板中的${key})
     * @param value 替换后的值
     * @return
     */
    public boolean replaceAll(String key,String value){
        boolean replaceSuccess = false;
        key = "${" + key + "}";
        while(replace(key, value)){
            replaceSuccess = true;
        }
        return replaceSuccess;
    }

    /**
     * 所有匹配的值替换为对应的值(key匹配模板中的${key})
     * @param param 要替换的key-value集合
     * @return
     */
    public boolean replaceAll(Map<String,String> param){
        Set<Entry<String, String>> entrys = param.entrySet();
        boolean replaceSuccess = false;
        for (Entry<String, String> entry : entrys) {
            String key = entry.getKey();
            boolean currSuccessReplace = replaceAll(key,entry.getValue());
            replaceSuccess = replaceSuccess?replaceSuccess:currSuccessReplace;
        }
        return replaceSuccess;
    }

    /**
     * 将第一个匹配到的值替換为对应的值
     * @param key 
     * @param value
     * @return
     */
    private boolean replace(String key,String value){
        if(contains(key)){
            /*
             * 1:得带key对应的开始和结束下标
             */
            int startIndex = context.indexOf(key);
            int endIndex = startIndex+key.length();
            /*
             * 2:获取第一个匹配的XWPFRun
             */
            RunChar startRunChar = runChars.get(startIndex);
            XWPFRun startRun = startRunChar.getRun();
            /*
             * 3:将匹配的key清空
             */
            runChars.subList(startIndex, endIndex).clear();
            /*
             * 4:将value设置到startRun中
             */
            List<RunChar> addRunChar = new ArrayList<XWPFParagraphUtils.RunChar>();
            for(int i = 0 ; i < value.length() ; i++){
                addRunChar.add(new RunChar(value.charAt(i), startRun));
            }
            runChars.addAll(startIndex, addRunChar);
            resetRunContext(runChars);
            return true;
        }else{
            return false;
        }
    }

    private void resetRunContext(List<RunChar> newRunChars){
        /**
         * 生成新的XWPFRun与Context的对应关系
         */
        HashMap<XWPFRun, StringBuffer> newRunContext = new HashMap<XWPFRun, StringBuffer>();
        //重设context
        context = new StringBuffer();
        for(RunChar runChar : newRunChars){
            StringBuffer newRunText ;
            if(newRunContext.containsKey(runChar.getRun())){
                newRunText = newRunContext.get(runChar.getRun());
            }else{
                newRunText = new StringBuffer();
            }
            context.append(runChar.getValue());
            newRunText.append(runChar.getValue());
            newRunContext.put(runChar.getRun(), newRunText);
        }

        /**
         * 遍历旧的runContext,替换context
         * 并重新设置run的text,如果不匹配,text设置为""
         */
        for(XWPFRun run : allXWPFRuns){
            if(newRunContext.containsKey(run)){
                String newContext = newRunContext.get(run).toString();
                XWPFRunUtils.setText(run,newContext);
            }else{
                XWPFRunUtils.setText(run,"");
            }
        }
    }

    /**
     * 实体类:存储字节与XWPFRun对象的对应关系
     * @author JianQiu
     */
    class RunChar{
        /**
         * 字节
         */
        private char value;
        /**
         * 对应的XWPFRun
         */
        private XWPFRun run;
        public RunChar(char value,XWPFRun run){
            this.setValue(value);
            this.setRun(run);
        }
        public char getValue() {
            return value;
        }
        public void setValue(char value) {
            this.value = value;
        }
        public XWPFRun getRun() {
            return run;
        }
        public void setRun(XWPFRun run) {
            this.run = run;
        }

    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194

5.最后只需调用XWPFDocument.write(OutputStream)方法即可得到基于模板生成的docx文件了.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值