基于poi-tl根据word模板动态生成图表

目录

一、前言

二、添加依赖

三、poi-tl源码改造部分说明

原理解析

方法改造及部分截图

1、resolveDocument:增加对图表的解析

2、resolveCharts:循环word中所有的图表,找出需要解析的图表封装成对象

3、resolveChart:找到图表数据对应EXCEL的第一个sheet的第一个单元格,按照poi-tl解析模板的方式解析,若满足条件,则封装成poi-tl所需的MetaTemplate对象

4、resolveXWPFChart:完全参考自TemplateResolver.resolveXWPFRuns方法

5、parseTemplateFactory:增加XWPFChart的判断

6、createRunTemplate:参考DefaultRunTemplateFactory.createRunTemplate方法,主要是将chart对象封装到pol-tl所需对象中

完整源码

TemplateResolver.java

MyRunTemplate.java

四、插件部分

原理解析

完整源码

MyChartPolicy.java

MyChartAxis.java

MyChartSeries.java

插件使用源码

WordTest.java

图表部分源码解读

扩展

github源码


一、前言

项目上有这么一个需求:根据word模板动态生成word,模板中存在图表,需要动态修改图表的数据,并保持原有图表的样式不变,如图表的系列颜色、字体大小、字体颜色等。

技术上选用了开源的poi-tl作为主要word模板引擎,满足了最基本的功能需求,有一些额外的需求也能够通过插件的形式完成。由于当前使用的poi-tl版本(v1.7.3)不支持图表模板的替换,网上搜索了相关资料后,改造了poi-tl的部分源码并以开发插件的形式完成了图表模板替换的功能。

若项目上不使用poi-tl,但是有需求需要对word中的图表进行动态数据替换的,也可以参考此文的第四节插件部分的关键代码,插件部分和poi-tl的关系不大,主要是poi4.0.0+操作图表的相关功能,使用poi-tl是为了将XWPFChart对象及数据传给插件,插件对数据进行单独解析。

参考资料:

1、poi-tl官方文档:http://deepoove.com/poi-tl

2、poi在word中生成图表:https://blog.csdn.net/u014427811/article/details/100771314

二、添加依赖

maven依赖

1
2
3
4
5
6
7
8
9
10
<dependency>
    <groupId>com.deepoove</groupId>
    <artifactId>poi-tl</artifactId>
    <version>1.7.3</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.58</version>
</dependency>

三、poi-tl源码改造部分说明

原理解析

poi-tl的原理是读取了word中所有的段落、表格,找出其中符合模板规则的字符串,如:{{data}}。找到对应字符串后,和相应数据一起传给对应的解析插件解析。由于poi-tl只解析了段落和表格部分,因此我们需要改造源码来使poi-tl多解析图表部分。

图表部分的定义规则为:在图表对应的EXCEL的第一个单元格按照poi-tl的定义方式填写模板字符串,对应的数据格式为一个二维数组,程序来解析二维数组重写EXCEL数据以及刷新图表。

如图所示的模板定义为{{chartdata}}:

对应chartdata的JSON数据格式参考:

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
{
    "chartdata": [
        [
            "这是第一个单元格",
            "系列1",
            "系列2"
        ],
        [
            "一月",
            "100",
            "50"
        ],
        [
            "二月",
            "200",
            "100"
        ],
        [
            "三月",
            "300",
            "150"
        ],
        [
            "四月",
            "400",
            "200"
        ],
        [
            "五月",
            "500",
            "250"
        ],
        [
            "六月",
            "600",
            "300"
        ]
    ]
}

导出效果:

方法改造及部分截图

1、resolveDocument:增加对图表的解析

2、resolveCharts:循环word中所有的图表,找出需要解析的图表封装成对象

3、resolveChart:找到图表数据对应EXCEL的第一个sheet的第一个单元格,按照poi-tl解析模板的方式解析,若满足条件,则封装成poi-tl所需的MetaTemplate对象

4、resolveXWPFChart:完全参考自TemplateResolver.resolveXWPFRuns方法

5、parseTemplateFactory:增加XWPFChart的判断

6、createRunTemplate:参考DefaultRunTemplateFactory.createRunTemplate方法,主要是将chart对象封装到pol-tl所需对象中

完整源码

TemplateResolver.java

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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
/*
 * Copyright 2014-2020 Sayi
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.deepoove.poi.resolver;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;

import org.apache.commons.lang3.StringUtils;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.xwpf.usermodel.BodyElementType;
import org.apache.poi.xwpf.usermodel.IBodyElement;
import org.apache.poi.xwpf.usermodel.XWPFChart;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFFooter;
import org.apache.poi.xwpf.usermodel.XWPFHeader;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.deepoove.poi.config.Configure;
import com.deepoove.poi.exception.ResolverException;
import com.deepoove.poi.template.BlockTemplate;
import com.deepoove.poi.template.IterableTemplate;
import com.deepoove.poi.template.MetaTemplate;
import com.deepoove.poi.template.run.MyRunTemplate;
import com.deepoove.poi.template.run.RunTemplate;

/**
 * Resolver
 *
 * @author Sayi
 * @version 1.7.0
 */
public class TemplateResolver extends AbstractResolver {

    private static Logger logger = LoggerFactory.getLogger(TemplateResolver.class);

    private RunTemplateFactory<?> runTemplateFactory;

    public TemplateResolver(Configure config) {
        this(config, config.getRunTemplateFactory());
    }

    private TemplateResolver(Configure config, RunTemplateFactory<?> runTemplateFactory) {
        super(config);
        this.runTemplateFactory = runTemplateFactory;
    }

    @Override
    public List<MetaTemplate> resolveDocument(XWPFDocument doc) {
        List<MetaTemplate> metaTemplates = new ArrayList<>();
        if (null == doc)
            return metaTemplates;
        logger.info("Resolve the document start...");
        metaTemplates.addAll(resolveBodyElements(doc.getBodyElements()));
        metaTemplates.addAll(resolveHeaders(doc.getHeaderList()));
        metaTemplates.addAll(resolveFooters(doc.getFooterList()));
        // 增加对图表的解析
        metaTemplates.addAll(resolveCharts(doc.getCharts()));
        logger.info("Resolve the document end, resolve and create {} MetaTemplates.", metaTemplates.size());
        return metaTemplates;
    }

    @Override
    public List<MetaTemplate> resolveBodyElements(List<IBodyElement> bodyElements) {
        List<MetaTemplate> metaTemplates = new ArrayList<>();
        if (null == bodyElements)
            return metaTemplates;

        // current iterable templates state
        Deque<BlockTemplate> stack = new LinkedList<BlockTemplate>();

        for (IBodyElement element : bodyElements) {
            if (element == null)
                continue;
            if (element.getElementType() == BodyElementType.PARAGRAPH) {
                XWPFParagraph paragraph = (XWPFParagraph) element;
                RunningRunParagraph runningRun = new RunningRunParagraph(paragraph, templatePattern);
                List<XWPFRun> refactorRuns = runningRun.refactorRun();
                if (null == refactorRuns)
                    continue;
                Collections.reverse(refactorRuns);
                resolveXWPFRuns(refactorRuns, metaTemplates, stack);
            } else if (element.getElementType() == BodyElementType.TABLE) {
                XWPFTable table = (XWPFTable) element;
                List<XWPFTableRow> rows = table.getRows();
                if (null == rows)
                    continue;
                for (XWPFTableRow row : rows) {
                    List<XWPFTableCell> cells = row.getTableCells();
                    if (null == cells)
                        continue;
                    cells.forEach(cell -> {
                        List<MetaTemplate> visitBodyElements = resolveBodyElements(cell.getBodyElements());
                        if (stack.isEmpty()) {
                            metaTemplates.addAll(visitBodyElements);
                        } else {
                            stack.peek().getTemplates().addAll(visitBodyElements);
                        }
                    });
                }
            }
        }

        checkStack(stack);
        return metaTemplates;
    }

    @Override
    public List<MetaTemplate> resolveXWPFRuns(List<XWPFRun> runs) {
        List<MetaTemplate> metaTemplates = new ArrayList<>();
        if (runs == null)
            return metaTemplates;

        Deque<BlockTemplate> stack = new LinkedList<BlockTemplate>();
        resolveXWPFRuns(runs, metaTemplates, stack);
        checkStack(stack);
        return metaTemplates;
    }

    private void resolveXWPFRuns(List<XWPFRun> runs, final List<MetaTemplate> metaTemplates,
            final Deque<BlockTemplate> stack) {
        for (XWPFRun run : runs) {
            String text = null;
            if (null == run || StringUtils.isBlank(text = run.getText(0)))
                continue;
            RunTemplate runTemplate = parseTemplateFactory(text, run);
            if (null == runTemplate)
                continue;
            char charValue = runTemplate.getSign().charValue();
            if (charValue == config.getIterable().getLeft()) {
                IterableTemplate freshIterableTemplate = new IterableTemplate(runTemplate);
                stack.push(freshIterableTemplate);
            } else if (charValue == config.getIterable().getRight()) {
                if (stack.isEmpty())
                    throw new ResolverException(
                            "Mismatched start/end tags: No start mark found for end mark " + runTemplate);
                BlockTemplate latestIterableTemplate = stack.pop();
                if (StringUtils.isNotEmpty(runTemplate.getTagName())
                        && !latestIterableTemplate.getStartMark().getTagName().equals(runTemplate.getTagName())) {
                    throw new ResolverException("Mismatched start/end tags: start mark "
                            + latestIterableTemplate.getStartMark() + " does not match to end mark " + runTemplate);
                }
                latestIterableTemplate.setEndMark(runTemplate);
                if (latestIterableTemplate instanceof IterableTemplate) {
                    latestIterableTemplate = ((IterableTemplate) latestIterableTemplate).buildIfInline();
                }
                if (stack.isEmpty()) {
                    metaTemplates.add(latestIterableTemplate);
                } else {
                    stack.peek().getTemplates().add(latestIterableTemplate);
                }
            } else {
                if (stack.isEmpty()) {
                    metaTemplates.add(runTemplate);
                } else {
                    stack.peek().getTemplates().add(runTemplate);
                }
            }
        }
    }

    /**
     * 参考resolveXWPFRuns方法
     *
     * @param chart
     * @param tagName
     * @param metaTemplates
     * @param stack
     */
    private void resolveXWPFChart(XWPFChart chart, String tagName, final List<MetaTemplate> metaTemplates,
            final Deque<BlockTemplate> stack) {
        if (StringUtils.isBlank(tagName)) {
            return;
        }
        RunTemplate runTemplate = this.parseTemplateFactory(tagName, chart);
        if (runTemplate == null) {
            return;
        }
        char charValue = runTemplate.getSign().charValue();
        if (charValue == config.getIterable().getLeft()) {
            IterableTemplate freshIterableTemplate = new IterableTemplate(runTemplate);
            stack.push(freshIterableTemplate);
        } else if (charValue == config.getIterable().getRight()) {
            if (stack.isEmpty())
                throw new ResolverException(
                        "Mismatched start/end tags: No start mark found for end mark " + runTemplate);
            BlockTemplate latestIterableTemplate = stack.pop();
            if (StringUtils.isNotEmpty(runTemplate.getTagName())
                    && !latestIterableTemplate.getStartMark().getTagName().equals(runTemplate.getTagName())) {
                throw new ResolverException("Mismatched start/end tags: start mark "
                        + latestIterableTemplate.getStartMark() + " does not match to end mark " + runTemplate);
            }
            latestIterableTemplate.setEndMark(runTemplate);
            if (latestIterableTemplate instanceof IterableTemplate) {
                latestIterableTemplate = ((IterableTemplate) latestIterableTemplate).buildIfInline();
            }
            if (stack.isEmpty()) {
                metaTemplates.add(latestIterableTemplate);
            } else {
                stack.peek().getTemplates().add(latestIterableTemplate);
            }
        } else {
            if (stack.isEmpty()) {
                metaTemplates.add(runTemplate);
            } else {
                stack.peek().getTemplates().add(runTemplate);
            }
        }
    }

    private void checkStack(Deque<BlockTemplate> stack) {
        if (!stack.isEmpty()) {
            throw new ResolverException(
                    "Mismatched start/end tags: No end iterable mark found for start mark " + stack.peek());
        }
    }

    List<MetaTemplate> resolveHeaders(List<XWPFHeader> headers) {
        List<MetaTemplate> metaTemplates = new ArrayList<>();
        if (null == headers)
            return metaTemplates;

        headers.forEach(header -> {
            metaTemplates.addAll(resolveBodyElements(header.getBodyElements()));
        });
        return metaTemplates;
    }

    List<MetaTemplate> resolveFooters(List<XWPFFooter> footers) {
        List<MetaTemplate> metaTemplates = new ArrayList<>();
        if (null == footers)
            return metaTemplates;

        footers.forEach(footer -> {
            metaTemplates.addAll(resolveBodyElements(footer.getBodyElements()));
        });
        return metaTemplates;
    }

    /**
     * 循环word中所有图表,找出需要进行模板解析的图表
     *
     * @param charts
     * @return
     */
    List<MetaTemplate> resolveCharts(List<XWPFChart> charts) {
        List<MetaTemplate> metaTemplates = new ArrayList<>();
        if (null == charts)
            return metaTemplates;

        for (XWPFChart chart : charts) {
            List<MetaTemplate> tempMetaTemplates = resolveChart(chart);
            metaTemplates.addAll(tempMetaTemplates);
        }
        return metaTemplates;
    }

    public List<MetaTemplate> resolveChart(XWPFChart chart) {
        List<MetaTemplate> metaTemplates = new ArrayList<>();
        if (null == chart) {
            return metaTemplates;
        }
        // current iterable templates state
        Deque<BlockTemplate> stack = new LinkedList<BlockTemplate>();

        XSSFWorkbook workbook = null;
        try {
            workbook = chart.getWorkbook();
            if (workbook == null || workbook.getNumberOfSheets() <= 0) {
                return metaTemplates;
            }
            XSSFSheet sheet = workbook.getSheetAt(0);
            if (sheet == null || sheet.getLastRowNum() <= 0) {
                return metaTemplates;
            }
            XSSFRow row = sheet.getRow(0);
            if (row == null) {
                return metaTemplates;
            }
            XSSFCell cell = row.getCell(0);
            if (cell == null) {
                return metaTemplates;
            }
            // 找到图表对应的EXCEL的第一个sheet对应的第一个单元格
            String cellValue = cell.getStringCellValue();
            if (StringUtils.isBlank(cellValue)) {
                return metaTemplates;
            }
            // 判断单元格的值
            Matcher matcher = templatePattern.matcher(cellValue);
            if (matcher.find()) {
                String tagName = matcher.group();
                this.resolveXWPFChart(chart, tagName, metaTemplates, stack);
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("", e);
        }
        checkStack(stack);
        return metaTemplates;
    }

    <T> RunTemplate parseTemplateFactory(String text, T obj) {
        logger.debug("Resolve where text: {}, and create ElementTemplate", text);
        if (templatePattern.matcher(text).matches()) {
            String tag = gramerPattern.matcher(text).replaceAll("").trim();
            if (obj.getClass() == XWPFRun.class) {
                return (RunTemplate) runTemplateFactory.createRunTemplate(tag, (XWPFRun) obj);
            } else if (obj.getClass() == XWPFTableCell.class) {
                // return CellTemplate.create(symbol, tagName, (XWPFTableCell)
                // obj);
                return null;
            } else if (obj.getClass() == XWPFChart.class) {
                XWPFChart chart = (XWPFChart) obj;
                return this.createRunTemplate(tag, chart);
            }
        }
        return null;
    }

    /**
     * 参考DefaultRunTemplateFactory.createRunTemplate方法
     *
     * @param tag
     * @param chart
     * @return
     */
    private RunTemplate createRunTemplate(String tag, XWPFChart chart) {
        MyRunTemplate template = new MyRunTemplate();
        Set<Character> gramerChars = config.getGramerChars();
        Character symbol = Character.valueOf(DefaultRunTemplateFactory.EMPTY_CHAR);
        if (!"".equals(tag)) {
            char fisrtChar = tag.charAt(0);
            for (Character chara : gramerChars) {
                if (chara.equals(fisrtChar)) {
                    symbol = Character.valueOf(fisrtChar);
                    break;
                }
            }
        }
        template.setSource(config.getGramerPrefix() + tag + config.getGramerSuffix());
        template.setTagName(
                symbol.equals(Character.valueOf(DefaultRunTemplateFactory.EMPTY_CHAR)) ? tag : tag.substring(1));
        template.setSign(symbol);
        template.setRun(null);
        template.setChart(chart);
        return template;
    }

}

MyRunTemplate.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.deepoove.poi.template.run;

import org.apache.poi.xwpf.usermodel.XWPFChart;

public class MyRunTemplate extends RunTemplate {
    private XWPFChart chart;

    public XWPFChart getChart() {
        return chart;
    }

    public void setChart(XWPFChart chart) {
        this.chart = chart;
    }

}

四、插件部分

原理解析

word中图表的数据展现主要分为2部分,EXCEL数据部分、图表展现部分。

直接根据传入的二维表数据,完全按照二维表的格式写入到EXCEL中即可。需要注意的点:A1单元格没有太大的作用;除A1单元格外的第1行作为图表的系列名;除A1单元格外的A列作为序列;除A列、第1行外,剩余部分作为对应的数据,数据在EXCEL中需要转成数值类型,否则生成word后,右键图表打开对应的EXCEL会有数据刷新变没的情况。

完整源码

MyChartPolicy.java

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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
package com.deepoove.poi.policy;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.xwpf.usermodel.XWPFChart;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTAxDataSource;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTBarChart;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTBarSer;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTChart;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTNumData;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTNumDataSource;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTNumVal;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTSerTx;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTStrData;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTStrVal;

import com.alibaba.fastjson.JSONArray;
import com.deepoove.poi.render.RenderContext;
import com.deepoove.poi.template.ElementTemplate;
import com.deepoove.poi.template.run.MyRunTemplate;
import com.wordchart.vo.MyChartAxis;
import com.wordchart.vo.MyChartSeries;

public class MyChartPolicy extends AbstractRenderPolicy<JSONArray> {

    @Override
    public void doRender(RenderContext<JSONArray> context) throws Exception {
        ElementTemplate elementTemplate = context.getEleTemplate();
        if (elementTemplate != null && elementTemplate instanceof MyRunTemplate) {
            MyRunTemplate myRunTemplate = (MyRunTemplate) elementTemplate;
            XWPFChart chart = myRunTemplate.getChart();
            if (chart != null) {
                List<MyChartSeries> serList = this.resolveDatas(context.getData());
                String sheetName = this.refreshExcel(chart, context.getData());
                this.refreshGraphContent(chart, sheetName, context.getData(), serList);
            }
        }
    }

    /**
     * 刷新EXCEL数据
     *
     * @param chart
     * @param rows
     * @return sheet名
     */
    private String refreshExcel(XWPFChart chart, JSONArray rows) {
        String sheetName = null;
        if (chart == null || rows == null) {
            return sheetName;
        }
        try {
            XSSFWorkbook workbook = chart.getWorkbook();
            // 获取原sheet名
            sheetName = workbook.getSheetName(0);
            // 删除原有sheet
            workbook.removeSheetAt(0);
            // 根据原有sheet名新创建sheet
            Sheet sheet = workbook.createSheet(sheetName);
            this.createRows(rows, sheet);
            return sheetName;
        } catch (Exception e) {
            e.printStackTrace();
            return sheetName;
        }
    }

    /**
     * 创建行数据
     *
     * @param rows
     * @param sheet
     */
    private void createRows(JSONArray rows, Sheet sheet) {
        if (rows == null || sheet == null) {
            return;
        }
        for (int rowIndex = 0; rowIndex < rows.size(); rowIndex++) {
            JSONArray cols = rows.getJSONArray(rowIndex);
            Row row = sheet.createRow(rowIndex);
            this.createCells(cols, row);
        }
    }

    /**
     * 创建单元格数据
     *
     * @param cols
     * @param row
     */
    private void createCells(JSONArray cols, Row row) {
        if (cols == null || row == null) {
            return;
        }
        int rowNum = row.getRowNum();
        for (int colIndex = 0; colIndex < cols.size(); colIndex++) {
            String cellValueStr = cols.getString(colIndex);
            // 首行、首列分别代表系列名、横坐标,非首行首列均是数值
            // 避免值中可能存在非数值,因此做下数值判断
            if (rowNum == 0 || colIndex == 0 || !NumberUtils.isNumber(cellValueStr)) {
                row.createCell(colIndex).setCellValue(cellValueStr);
            } else {
                Double cellValue = cols.getDouble(colIndex);
                row.createCell(colIndex).setCellValue(cellValue);
            }

        }
    }

    /**
     * 将数据封装成MyChartSeries对象,便于word中图表解析时使用 数据格式参考<br>
     * (留空) 系列1 系列2<br>
     * 第一季度 5 10<br>
     * 第二季度 10 15<br>
     * 第三季度 15 16<br>
     * 第四季度 20 4<br>
     *
     * @param rows
     */
    private List<MyChartSeries> resolveDatas(JSONArray rows) {
        List<MyChartSeries> serAxisList = new ArrayList<MyChartSeries>();
        // 数据为空
        if (CollectionUtils.isEmpty(rows)) {
            return serAxisList;
        }

        // 第0行没有数据
        JSONArray serJsonArray = rows.getJSONArray(0);
        if (CollectionUtils.isEmpty(serJsonArray)) {
            return serAxisList;
        }

        for (int serIndex = 1; serIndex < serJsonArray.size(); serIndex++) {
            String seriesName = serJsonArray.getString(serIndex);
            MyChartSeries myChartData = new MyChartSeries(seriesName);
            serAxisList.add(myChartData);
        }

        // 第0行数据不全,没有系列名
        if (CollectionUtils.isEmpty(serAxisList)) {
            return serAxisList;
        }

        // 第一行开始,第0个单元格代表axis(横坐标)名称,后续的依次为每个系列数值
        for (int rowIndex = 1; rowIndex < rows.size(); rowIndex++) {
            JSONArray cols = rows.getJSONArray(rowIndex);
            String axisName = null;
            if (cols != null && cols.size() > 0) {
                axisName = cols.getString(0);
            }

            for (int serIndex = 0; serIndex < serAxisList.size(); serIndex++) {
                MyChartSeries myChartData = serAxisList.get(serIndex);

                int colIndex = serIndex + 1;
                String colValue = null;
                if (cols != null && cols.size() > colIndex) {
                    colValue = cols.getString(colIndex);
                }
                MyChartAxis myChartAxis = new MyChartAxis(axisName, colValue);
                myChartData.getAxisDataList().add(myChartAxis);
            }
        }
        return serAxisList;
    }

    /**
     * 刷新图表数据
     *
     * @param chart
     * @param sheetName
     * @param rows
     * @param serList
     */
    private void refreshGraphContent(XWPFChart chart, String sheetName, JSONArray rows, List<MyChartSeries> serList) {
        CTChart ctChart = chart.getCTChart();
        CTBarChart ctBarChart = ctChart.getPlotArea().getBarChartArray(0);

        // 原有的所有系列
        List<CTBarSer> ctBarSers = ctBarChart.getSerList();
        // 按照新系列的数量,对应删减老系列的数量;保留老系列的原因是为了尽可能的保留原有系列样式
        if (ctBarSers != null) {
            List<CTBarSer> newCtBarSers = ctBarSers.subList(0, Math.min(serList.size(), ctBarSers.size()));
            CTBarSer[] newCtBarSersArray = new CTBarSer[newCtBarSers.size()];
            newCtBarSers.toArray(newCtBarSersArray);
            ctBarChart.setSerArray(newCtBarSersArray);
        }

        // 构造每个系列的序列、数据、系列名
        for (int serIndex = 0; serIndex < serList.size(); serIndex++) {
            MyChartSeries myChartData = serList.get(serIndex);
            CTBarSer ctBarSer = null;
            if (ctBarSers != null && ctBarSers.size() > serIndex) {
                ctBarSer = ctBarChart.getSerArray(serIndex);
            }
            if (ctBarSer == null) {
                ctBarSer = ctBarChart.addNewSer();
            } else {
                ctBarSer.unsetCat();
                ctBarSer.unsetVal();
                ctBarSer.unsetTx();
            }

            // Category Axis Data
            CTAxDataSource cat = ctBarSer.addNewCat();
            // 获取图表的值
            CTNumDataSource val = ctBarSer.addNewVal();
            // 系列名称
            CTSerTx ctSerTx = ctBarSer.addNewTx();

            CTStrData strData = cat.addNewStrRef().addNewStrCache();
            CTNumData numData = val.addNewNumRef().addNewNumCache();
            CTStrData txData = ctSerTx.addNewStrRef().addNewStrCache();

            // 构造序列项、数据
            int idx = 0;
            for (MyChartAxis mChartAxis : myChartData.getAxisDataList()) {
                String axisName = mChartAxis.getAxisName();
                String value = mChartAxis.getValue();

                CTStrVal sVal = strData.addNewPt();// 序列名称
                sVal.setIdx(idx);
                sVal.setV(axisName);

                CTNumVal numVal = numData.addNewPt();// 序列值
                numVal.setIdx(idx);
                numVal.setV(value);
                ++idx;
            }
            // 设置系列名称
            CTStrVal txVal = txData.addNewPt();
            txVal.setIdx(0);
            txVal.setV(myChartData.getSeries());

            numData.addNewPtCount().setVal(idx);
            strData.addNewPtCount().setVal(idx);

            // 序列区域
            String axisDataRange = new CellRangeAddress(1, rows.size() - 1, 0, 0).formatAsString(sheetName, true);
            cat.getStrRef().setF(axisDataRange);

            // 数据区域
            String numDataRange = new CellRangeAddress(1, rows.size() - 1, serIndex + 1, serIndex + 1)
                    .formatAsString(sheetName, true);
            val.getNumRef().setF(numDataRange);

            // 系列名区域
            String serDataRange = new CellRangeAddress(0, 0, serIndex + 1, serIndex + 1).formatAsString(sheetName,
                    true);
            ctSerTx.getStrRef().setF(serDataRange);
        }

    }

}

MyChartAxis.java

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
package com.wordchart.vo;

public class MyChartAxis {

    private String axisName;
    private String value;
   
    public MyChartAxis(String axisName,String value) {
        this.axisName = axisName;
        this.value = value;
    }

    public String getAxisName() {
        return axisName;
    }

    public void setAxisName(String axisName) {
        this.axisName = axisName;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

}

MyChartSeries.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.wordchart.vo;

import java.util.ArrayList;
import java.util.List;

public class MyChartSeries {
    // 系列名称
    private String series;

    private List<MyChartAxis> axisDataList = new ArrayList<MyChartAxis>();

    public MyChartSeries(String series) {
        this.series = series;
    }

    public String getSeries() {
        return series;
    }

    public List<MyChartAxis> getAxisDataList() {
        return axisDataList;
    }
}

插件使用源码

WordTest.java

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
package com.wordchart;

import java.io.IOException;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.config.ConfigureBuilder;
import com.deepoove.poi.policy.MyChartPolicy;

public class WordTest {
   
    public static void main(String[] args) throws IOException {
        String data = "{\r\n" +
                "    "khg": [\r\n" +
                "        [\r\n" +
                "            "这是第一个单元格",\r\n" +
                "            "系列1",\r\n" +
                "            "系列2"\r\n" +
                "        ],\r\n" +
                "        [\r\n" +
                "            "一月",\r\n" +
                "            "100",\r\n" +
                "            "50"\r\n" +
                "        ],\r\n" +
                "        [\r\n" +
                "            "二月",\r\n" +
                "            "200","100"\r\n" +
                "        ],\r\n" +
                "        [\r\n" +
                "            "三月",\r\n" +
                "            "300","150"\r\n" +
                "        ],\r\n" +
                "        [\r\n" +
                "            "四月",\r\n" +
                "            "400","200"\r\n" +
                "        ],\r\n" +
                "        [\r\n" +
                "            "五月",\r\n" +
                "            "500","250"\r\n" +
                "        ],\r\n" +
                "        [\r\n" +
                "            "六月",\r\n" +
                "            "600","300"\r\n" +
                "        ]\r\n" +
                "    ]\r\n" +
                "}";
       
        JSONObject params = JSON.parseObject(data);
       
        ConfigureBuilder configureBuilder = Configure.newBuilder();
        configureBuilder.bind("khg", new MyChartPolicy());
       
        Configure config = configureBuilder.build();
       
       
        // 核心API采用了极简设计,只需要一行代码
        XWPFTemplate.compile("D:\\2、工作目录\\2020年3月\\0323\\模板word-图表3.docx", config).render(params)
                .writeToFile("D:\\2、工作目录\\2020年3月\\0323\\生成word\\模板word-图表3-输出.docx");
    }

}

图表部分源码解读

先来一张图方便说明:

关键代码为:MyChartPolicy.refreshGraphContent。

CTBarSer:代表所有的系列对象,一个图表包含多个系列,每个系列包含"系列名"、"序列"、"数据"等。如图中就分为3个系列,系列名为:系列1、系列2、系列3;每个系列中包含4个序列:类别1、类别2、类别3、类别4;每个系列与序列两者唯一确定一个数据,如系列1和类别1确定数据为4.3。

CTAxDataSource:序列。

CTNumDataSource:数据。

CTSerTx:系列名。

序列、数据、系列名的java类使用方式都类似,了解了其中一项后,其余的就好理解多了,目前以序列为例:

CTAxDataSource.getStrRef().getStrCache().addNewPt():创建一个新的序列项,可以为该新建的序列项赋值索引及值。

CTAxDataSource.getStrRef().setF():为序列设置EXCEL区域,设置此项后,打开word,改变EXCEL数据时,对应的图表数据项也会自动改变。

扩展

目前测试的还是相对简单的图表,没有太深入去完善每一项。若看到此文章的人还有需要扩展的内容,但是不知道poi中对应的api是哪一个,其实可以将xxx.docx改下后缀为xxx.zip,然后找到xxx.zip\word\charts\chart1.xml(若存在多个图表则会有多个,找到想要找的那个就好)。如图为例,找到c:chart标签,此标签代表着XWPFChart.getCTChart对象,想要知道某项属性的api,只要知道你在word图表中设置的值,然后来这个xml里找到你写的那个值,就能找到对应的标签,例如想获取系列,就是XWPFChart.getCTChart.getPlotArea.getBarChartArray.getSerList,和xml很好对上。

github源码

https://github.com/Kong0305/wordchart