word模板格式
模板中${}为占位符,占位符可能会出现分在多个<w:r>的情况。使用占位符时,通过无格式的文本编辑器将占位符编辑好,在复制到word中。
另一种方式通过修改xml文件修改
将word文件另存为xml文件通过修改xml文件去修改占位符,然后在转化为word。(此方法我还未实现过我通过此方法遇到过bug,转换成word后占位符依旧是分开的,有些原来好的占位符也会变成分开状态。)
xml文件打开如下:
//部分xml文件
<w:p>
<w:pPr>
<w:jc w:val="center"/>
<w:rPr>
<w:rFonts w:hint="eastAsia" w:eastAsiaTheme="minorEastAsia"/>
<w:lang w:val="en-US" w:eastAsia="zh-CN"/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
</w:rPr>
<w:t>${</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
</w:rPr>
<w:t>contractName</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
</w:rPr>
<w:t>}</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
<w:lang w:val="en-US" w:eastAsia="zh-CN"/>
</w:rPr>
<w:t xml:space="preserve"> </w:t>
</w:r>
</w:p>
<w:p>
<w:pPr>
<w:jc w:val="center"/>
<w:rPr>
<w:rFonts w:hint="eastAsia" w:eastAsiaTheme="minorEastAsia"/>
<w:lang w:val="en-US" w:eastAsia="zh-CN"/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
</w:rPr>
<w:t>${changeName}</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
<w:lang w:val="en-US" w:eastAsia="zh-CN"/>
</w:rPr>
<w:t xml:space="preserve"> </w:t>
</w:r>
</w:p>
占位符分在多个run中,此时数据无法替换。需修改在用一个run中
工具类
@Slf4j
public class DOCUtils {
/**
* 导出docx
* @param templatePath
* @param
* @param map
*/
public static boolean getDocx(String templatePath, String fileName, Map<String, String> map , HttpServletResponse response){
XWPFDocument document = null;
try{
File file = new File(templatePath);
InputStream in = new FileInputStream(file);
document = new XWPFDocument(in);
List<XWPFParagraph> paragraphs = document.getParagraphs();
handleParagraphs(document,map);
//setContentType:设置响应内容类型。这表示是一个pdf文件
response.setContentType("application/msword");
//setCharacterEncoding:设置字符集。
response.setCharacterEncoding("utf-8");
//设置表名
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName);
OutputStream out = null;
out=response.getOutputStream();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// OutputStream out = new FileOutputStream(fileName);
document.write(bos);
out.write(bos.toByteArray());
response.getOutputStream();
bos.close();
out.close();
return true;
}
catch (Exception e){
e.printStackTrace();
return false;
} finally{
try
{
if ( document != null )
{
document.close();
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
/**
* @param wordValue ${...} 带${}的变量
* @param map 存储需要替换的数据
* @return java.lang.String
* @Description 有${}的值匹配出替换的数据,没有${contractName}就返回原来的数据
* @author hacah
* @Date 2021/6/15 16:02
*/
public static String matchesValue(String wordValue, Map<String, String> map) {
for (String s : map.keySet()) {
String s1 = new StringBuilder("${").append(s).append("}").toString();
if (s1.equals(wordValue)) {
wordValue = map.get(s);
}
}
return wordValue;
}
/**
* @return boolean
* @Description 测试是否包含需要替换的数据
* @author hacah
* @Date 2021/6/15 15:30
*/
public static boolean isReplacement(String text) {
boolean check = false;
if (text.contains("$")) {
check = true;
}
return check;
}
/**
* @Description 处理所有文段数据,除了表格
* @param xwpfDocument
* @param insertTextMap
* @author hacah
* @Date 2021/6/17 10:04
*/
public static void handleParagraphs(XWPFDocument xwpfDocument, Map<String, String> insertTextMap) {
for (XWPFParagraph paragraph : xwpfDocument.getParagraphs()) {
String text = paragraph.getText();
if (isReplacement(text)) {
for (XWPFRun run : paragraph.getRuns()) {
// 判断带有${}的run
// System.out.println(run);
run.setText(matchesValue(run.text(), insertTextMap), 0);
}
}
}
}
/**
* 替换段落里面的变量
* @param paragraph
* @param map
*/
public static void replaceParagraph(XWPFParagraph paragraph, Map<String,Object> map){
List<XWPFRun> runs = paragraph.getRuns();
for (int i = 0; i < runs.size(); i++) {
XWPFRun run = runs.get(i);
String tkey = run.toString();
if(tkey==null){
return;
}
for (String key : map.keySet()) {
if(tkey.equals(key)){
int size = run.getFontSize();
if(size==-1){
size=14;
}
String fontFamily = run.getFontFamily();
//因为直接调用setText()方法替换文本时,会在底层重新创建一个run,所以在设置文本之前要先删除当前run
paragraph.removeRun(i);
if(map!=null && map.get(key)!=null){
String runText = map.get(key).toString();
if(runText!=null){
if(runText.indexOf("\r\n")!=-1){
String[] texts = runText.split("\r\n");
List<String> tmp = new ArrayList<>();
for (String text : texts) {
if(text!=null && text.length()!=0){
tmp.add(text);
}
}
texts = tmp.toArray(new String[0]);
for (int n = 0; n < texts.length; n++) {
XWPFRun newRun = paragraph.createRun();
newRun.setText(" "+texts[n].trim());
if(texts.length>1 && n!=texts.length-1){
newRun.addBreak();
}
newRun.setText(runText);
newRun.setFontSize(size);
newRun.setFontFamily(fontFamily);
}
}else if(runText.indexOf("\n")!=-1){
String[] texts = runText.split("\n");
List<String> tmp = new ArrayList<>();
for (String text : texts) {
if(text!=null && text.length()!=0){
tmp.add(text.trim());
}
}
texts = tmp.toArray(new String[0]);
for (int n = 0; n < texts.length; n++) {
XWPFRun newRun = paragraph.createRun();
newRun.setText(" "+texts[n].trim());
if(texts.length>1 && n!=texts.length-1){
newRun.addBreak();
}
newRun.setFontSize(size);
newRun.setFontFamily(fontFamily);
}
}else{
//重新创建一个run用于设置文本
XWPFRun newrun = paragraph.insertNewRun(i);
newrun.setText(runText);
newrun.setFontSize(size);
newrun.setFontFamily(fontFamily);
}
}
}
}
}
}
}
/**
* 替换表格里面的变量
* @param document
* @param map
*/
public static void replaceInTable(XWPFDocument document,Map<String,Object> map) throws Exception {
Iterator<XWPFTable> iterator = document.getTablesIterator();
while (iterator.hasNext()){
//获取表
XWPFTable table = iterator.next();
//获取行
List<XWPFTableRow> rows = table.getRows();
for (XWPFTableRow row : rows) {
//获取单元格
List<XWPFTableCell> cells = row.getTableCells();
for (XWPFTableCell cell : cells) {
List<XWPFParagraph> paragraphs = cell.getParagraphs();
for (XWPFParagraph paragraph : paragraphs) {
//paragraph.setAlignment(ParagraphAlignment.LEFT);
replaceParagraph(paragraph,map);
}
}
}
}
}
/**
* 替换页眉里面的变量
* @param Header
* @param map
*/
public static void replaceHeader(XWPFHeader Header,Map<String,Object> map){
//获取表
List<XWPFTable> tableList = Header.getTables();
if(tableList!=null && tableList.size()>0){
for (int i = 0; i < tableList.size(); i++) {
XWPFTable table = tableList.get(i);
//获取行
List<XWPFTableRow> rows = table.getRows();
for (XWPFTableRow row : rows) {
//获取单元格
List<XWPFTableCell> cells = row.getTableCells();
for (XWPFTableCell cell : cells) {
//获取单元格内容
List<XWPFParagraph> paragraphs = cell.getParagraphs();
for (XWPFParagraph paragraph : paragraphs) {
// paragraph.setAlignment(ParagraphAlignment.LEFT);
replaceParagraph(paragraph,map);
}
}
}
}
}
else {
List<XWPFParagraph> paragraphs = Header.getParagraphs();
for (XWPFParagraph paragraph : paragraphs) {
// paragraph.setAlignment(ParagraphAlignment.LEFT);
replaceParagraph(paragraph,map);
}
}
}
}
以上代码大部分是copy一位大佬的。大佬看见请见谅。
private static void replaceParagraph(XWPFParagraph para,LinkedHashMap<String,XmlObject> newParaMap){
if(CollectionUtils.isNotEmpty(para.getRuns())) {
//删除所有文本段
while(para.removeRun(0)) {
}
}
newParaMap.forEach((k,v)->{
//创建新文本段,并恢复格式
XWPFRun run = para.createRun();
run.getCTR().set(v);
run.setText(k,0);
});
}
private static void replaceInParagraph(XWPFParagraph para, Map<String, String> parameterMap) {
String paraText = para.getParagraphText();
if(!paraText.contains("${") || !paraText.contains("}") ){
return ;
}
log.info("paraText:【{}】",paraText);
List<XWPFRun> xwpfRuns = para.getRuns();
StringBuilder keySb = new StringBuilder();
boolean hasKey = false;
LinkedHashMap<String, XmlObject> newParaMap = new LinkedHashMap<>();
for(int i=0;i<xwpfRuns.size();i++){
XWPFRun run = xwpfRuns.get(i);
String str = run.text();
int bIndex =0;
int eIndex =0;
do{
int end = str.indexOf("}",eIndex);
//已经有关键字内容
if(hasKey){
if(end>=0) { //末尾
keySb.append(str.substring(bIndex,end));
eIndex = end + 1;
bIndex = eIndex;
String value = String.valueOf(parameterMap.getOrDefault(keySb.toString(),""));
//替换关键字内容
newParaMap.put(value,run.getCTR().copy());
keySb.setLength(0);
hasKey = false;
continue;
}else{
//有内容,但没有结束符,当前部分字符串作为关键字内容
keySb.append(str.substring(bIndex));
bIndex = str.length();
eIndex = bIndex;
}
}else{
//没有关键字内容
int begin = str.indexOf("$",eIndex);
if(end>=0 && end<begin){
//修正无效的结束符
newParaMap.put(str.substring(bIndex,end+1),run.getCTR().copy());
eIndex = end + 1;
bIndex = eIndex;
continue;
}
//存在起始符
if(begin>=0){
int begin1 = str.indexOf("${",begin);
//存在关键字内容起始符
if(begin1>=0){
//保存关键字起始符
hasKey = true;
bIndex = begin1+2;
eIndex = bIndex ;
continue;
}else{
//不存在关键字内容起始符,
//$ 是当前字符串末尾且不是数组最后一个元素
if(begin==str.length()-1 && i+1<xwpfRuns.size()){
int begin2 = xwpfRuns.get(i+1).text().indexOf("{");
// { 必须是下一个字符串元素的首字母
if(begin2==0){
hasKey = true;
i++;
run = xwpfRuns.get(i);
str = run.text();
bIndex=1;
eIndex=1;
continue;
}else{
//下一个数组元素的第一个字符不是{
newParaMap.put(str.substring(bIndex),run.getCTR().copy());
break;
}
}else{
//不是最后一个字符或者是最后一个元素
newParaMap.put(str.substring(bIndex),run.getCTR().copy());
break;
}
}
}else{
//不存在起始符
newParaMap.put(str.substring(bIndex),run.getCTR().copy());
break;
}
}
}while(bIndex<str.length());
}
replaceParagraph(para,newParaMap);
log.info("replaced paraText:【{}】",para.getParagraphText());
}
以上代码完全是copy另一位大佬的。但是此段代码有个问题处理不了。
文字加${xxx},文字很少且在同一行时,这段代码的处理是吧文字和${}去掉留下需要替换的内容。
我的处理方式是:把“文字加${xxx}”封装在一个string中把整体看做一个占位符,进行替换。