一:场景
通过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.文本内容和表格内容分别可以通过 getParagraphs() 和 getTables() 两个方法得到
XWPFDocument docx = new XWPFDocument(InputStream);
List<XWPFParagraph> allXWPFParagraphs = docx.getParagraphs();
List<XWPFTable> xwpfTables = docx.getTables();
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());
}
因此很可能最后修改表格和文本最后都汇集到一个相同的地方,就是修改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;
private StringBuffer context ;
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)){
int startIndex = context.indexOf(key);
int endIndex = startIndex+key.length();
RunChar startRunChar = runChars.get(startIndex);
XWPFRun startRun = startRunChar.getRun();
runChars.subList(startIndex, endIndex).clear();
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 = 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文件了.