java使用freemarker导出word

梦想的初衷 提交于 2019-11-29 10:39:02

生成标准格式word请戳这里==>java使用freemarker导出word(标准格式版)

需求背景:根据模板的样式,导出不同内容的word。内容包括文字,图片,页眉等。
经过不同的尝试,最终选择使用freemarker来实现。
选择的原因:支持多图片,可以根据编辑的word模板导出,样式不会错乱。
主要分为以下几步:

  • 创建一个docx的word文档,编辑好模板,比如trip.docx。
  • trip.docx另存为trip.xml
  • 格式化trip.xml(方便阅读和替换里面的变量)
  • 替换变量,更改为trip.ftl放入项目中
  • java代码填充数据,并保存word

具体操作如下:

  1. 第一步:创建word文档trip.docx
    如图:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    从图中可以看出,这个文档包含的内容。对于数据结构相同的内容在模板中可以只写一次。
    比如:图中的日期和城市,只要写出第一天就行。
    图中的行程设计也只需要写DAY1的全部内容即可。其他相同数据结构,在freemarker中进行循环遍历。

  2. 第二步:把trip.docx另存为trip.xml
    在这里插入图片描述

  3. 使用文本编辑工具打开trip.xml,可以notepad++或者sublime text3或者其他编辑器打开。我使用的是sublime text3。打开后会发现xml格式是压缩的,可以进行格式化一下(百度xml格式化或者使用编辑器的插件)。如图:
    格式化前:
    在这里插入图片描述
    格式化后:
    在这里插入图片描述

  4. 第四步,找到对应的内容用变量替代。

    我们在模板中可以看到,需要替换的内容主要包含:文字内容,表格内容,页眉,单个的图片,列表图片。
    这当中比较复杂的就是列表图片。
    这里给出一个mock数据结构:
    
{
    "title": "西班牙10日精彩游",
    "desc": "共8天, 1个国家, 10个城市",
    "coverImg": "https://dpic.tiankong.com/39/x9/QJ8302262920.jpg",
    "qrCode": "https://dpic.tiankong.com/5a/we/QJ6256685144.jpg",
    "userName": "兴国",
    "levelName": "旅行定制师",
    "dayBrief": [
      {
        "date": "DAY1",
        "cities": [
          "马德里"
        ]
      },
      {
        "date": "DAY2",
        "cities": [
          "马德里"
        ]
      },
      {
        "date": "DAY3",
        "cities": [
          "马德里",
          "塞哥维亚",
          "马德里"
        ]
      }
    ],
    "googleMap": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1548689372&di=bc427f35f193dcae40177e4cf8b0e2e9&imgtype=jpg&er=1&src=http%3A%2F%2Fpicuser.city8.com%2Fnews%2Fimage%2F201562%2F%E8%A5%BF%E7%8F%AD%E7%89%99%E8%AF%A6%E5%9B%BE.jpg",
    "cityLine": [
      "马德里",
      "塞哥维亚",
      "马德里",
      "托莱多",
      "科尔多瓦",
      "塞维利亚",
      "龙达",
      "格拉纳达",
      "瓦伦西亚",
      "巴塞罗那"
    ],
    "dayDetail": [
      {
        "date": "DAY1",
        "cities": [
          {
            "name": "马德里",
            "type": "city"
          }
        ],
        "contents": [
          {
            "nameZh": "马德里普拉多博物馆",
            "nameEn": "Museo del Prado",
            "url": "https://imgs.qunarzz.com/douxing/i7/1504/36/8caf68d5261aad.jpg_450x300_a76236b6.jpg",
            "content": "普拉多博物馆是世界上最好的博物馆之一,是马德里参观人数最多的景点。这里云集了16世纪至19世纪西班牙艺术史上最好的作品,比如:委拉斯开兹、戈雅、格雷考的作品,当然还收集了大量国外文艺复兴时期大画家的作品,比如:意大利威尼斯画派的提香、德国巴洛克派的鲁本斯、荷兰超现实主义画家耶罗尼米斯·博斯等等。博物馆现收藏作品总数达到27509件,其中油画有7825幅,数目之多令人眼花缭乱。\n\n和欧洲其他的大型博物馆(巴黎卢浮宫、伦敦国家美术馆等等收藏有各个时代、不同风格画派的作品)不同,普拉多博物馆内的作品最初是喜爱艺术的国王皇室捐赠的,所以风格也较相近。博物馆最初是卡洛斯三世国王于1786年下令建造,1819年起对外开放。著名画家如莫奈、雷诺阿、洛特雷克、毕加索、马蒂斯、达利都来此参观过,并对他们的画作风格产生了一定的影响。",
            "type": "poi"
          }
        ]
      },
      {
        "date": "DAY2",
        "cities": [
          {
            "name": "马德里",
            "type": "city"
          }
        ],
        "contents": [
          {
            "nameZh": "",
            "nameEn": "",
            "url": "https://imgs.qunarzz.com/douxing/i7/1504/36/8caf68d5261aad.jpg_450x300_a76236b6.jpg",
            "content": "",
            "type": "image"
          },
          {
            "nameZh": "",
            "nameEn": "",
            "url": "",
            "content": "游览西班牙首都马德里(Madrid)。随后前往建在曼萨莱斯河左岸的山岗上的马德里皇宫*(Royal Palace of Madrid),马德里皇宫是世界上保存最完整、最精美的宫殿之一。继而前往古老的西班牙广场,它的正中央竖立着文艺复兴时期著名的西班牙文学大师、《堂•吉诃德》的作者塞万提斯的纪念碑,纪念碑下是骑着马的堂•吉诃德和仆人桑丘的塑像。夜宿马德里。\n\n\n浏览景点:马德里皇宫  西班牙广场   格兰比亚大街",
            "type": "text"
          }
        ]
      },
      {
        "date": "DAY3",
        "cities": [
          {
            "name": "马德里",
            "type": "city"
          },
          {
            "name": "91km",
            "type": "distance"
          },
          {
            "name": "塞哥维亚",
            "type": "city"
          },
          {
            "name": "90km",
            "type": "distance"
          },
          {
            "name": "马德里",
            "type": "city"
          }
        ],
        "contents": [
          {
            "nameZh": "",
            "nameEn": "",
            "url": "https://imgs.qunarzz.com/douxing/i7/1504/36/8caf68d5261aad.jpg_450x300_a76236b6.jpg",
            "content": "",
            "type": "image"
          },
          {
            "nameZh": "",
            "nameEn": "",
            "url": "",
            "content": "距离马德里90公里左右的塞哥维亚,有迪斯尼动画片《白雪公主》之城的原型城堡——阿尔卡萨城堡,在西班牙特有的庄严外表下,散发着一种幽香飘荡在空气中。这里的另一个景观是罗马人修建的巨大高架渠。在远离意大利的这片土地上,至今仍残留着他们生活过的痕迹,这不得不让人惊叹。塞戈维亚还是西班牙烤乳猪的发祥地,品尝这道源自当地的名菜,是游单上必不可少的项目哦!\n\n\n\n\n浏览景点:阿尔卡萨城堡",
            "type": "text"
          }
        ]
      }
      
    ]
  }

然后把对应的内容替换,如图:
在这里插入图片描述
在这里插入图片描述
单个图片替换:
这里面最重要的就是r:embed,这个指向图片。descr可以用url替换,也可以为空。
注意:如果直接使用url,切记要进行urlencode,否则可能出现xml无法识别的连接符,导致导出的word无法打开
在这里插入图片描述
在这里插入图片描述
其中rId6的指向为:
在这里插入图片描述
然后再看下image1的指向:
从xml可以看到大段的编码,这个是图片的base64编码。所以这里的变量要使用图片的base64编码。
在这里插入图片描述

<pkg:part pkg:name="/word/media/image1.jpeg" pkg:contentType="image/jpeg" pkg:compression="store">
        <pkg:binaryData>${coverImgBase64!''}</pkg:binaryData>
</pkg:part>

所以:单个图片,需要注意的是
1.id
2.Target
3.base64编码
4.url的encode

<Relationship Id="rId6" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/image1.jpeg"/>
<a:blip r:embed="rId6">
<pkg:part pkg:name="/word/media/image1.jpeg" pkg:contentType="image/jpeg" pkg:compression="store">
	<pkg:binaryData>${coverImgBase64!''}</pkg:binaryData>
</pkg:part>

表格的替换:
表格替换主要是<#list>的循环:
在这里插入图片描述
着重介绍下图片的循环。上面单个图片的注意点搞清楚后,多个图片就会少踩一些坑。主要包括三个部分,先上代码:
1.循环产生Relationship,Id不能重复(为防止重复,我使用了1开头加index),Target的image需要和图片base64编码对应

<#if dayDetail?? && (dayDetail?size > 0)>
   <#list dayDetail as detail>
       <#if detail.contents?? && (detail.contents?size > 0)>
           <#list detail.contents as detailContent>
               <#if (detailContent.url)?? && (detailContent.url) !="">
                   <Relationship Id="rId1${detail_index+1}${detailContent_index+1}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/image${detail_index+1}${detailContent_index+1}.png"/>
               </#if>
           </#list>
       </#if>
   </#list>
</#if>

在这里插入图片描述
2.r:embed指向对应的Id
在这里插入图片描述
3.图片base64编码放置

 <#if dayDetail?? && (dayDetail?size >0)>
       <#list dayDetail as detail>
            <#if detail.contents?? && (detail.contents?size > 0)>
                <#list detail.contents as detailContent>
                    <#if (detailContent.url)?? && (detailContent.url) != "">
                        <pkg:part pkg:name="/word/media/image${detail_index+1}${detailContent_index+1}.png" pkg:contentType="image/png" pkg:compression="store">
                            <pkg:binaryData>${(detailContent.urlBase64)!''}</pkg:binaryData>
                        </pkg:part>
                    </#if>
                </#list>
            </#if>
        </#list>
    </#if>

在这里插入图片描述
到此,xml的内容更改已经完成。

  1. 把trip.xml重命名为trip.ftl,放到java工程中。(可以放到resources)
    在这里插入图片描述
  2. 引用maven依赖
<dependency>
	<groupId>org.freemarker</groupId>
	<artifactId>freemarker</artifactId>
	<version>2.3.28</version>
</dependency>

7.编写java代码。注意把url转为base64位。
代码如下:

public String getImageStrFromUrl(String imgURL) {  
     	ByteArrayOutputStream data = new ByteArrayOutputStream();
         try {
             // 创建URL
             URL url = new URL(imgURL);
             byte[] by = new byte[1024];
             // 创建链接
             HttpURLConnection conn = (HttpURLConnection) url.openConnection();
             conn.setRequestMethod("GET");
             conn.setConnectTimeout(5000);
             InputStream is = conn.getInputStream();
             // 将内容读取内存中
             int len = -1;
             while ((len = is.read(by)) != -1) {
                 data.write(by, 0, len);
             }
             // 关闭流
             is.close();
         } catch (IOException e) {
             e.printStackTrace();
         }
         // 对字节数组Base64编码
         BASE64Encoder encoder = new BASE64Encoder();
 		 return encoder.encode(data.toByteArray());
     }

核心代码如下:

@Override
	public String exportWord(TripExportDTO u) throws Exception {
		String filePath = "C:/Users/xingguo/Desktop/";
		if(StringUtils.isNotBlank(u.getGoogleMap())) {
			u.setGoogleMapBase64(this.getImageStrFromUrl(u.getGoogleMap()));
		}
		if(StringUtils.isNotBlank(u.getCoverImg())) {
			u.setCoverImgBase64(this.getImageStrFromUrl(u.getCoverImg()));
		}
		
		if(StringUtils.isNotBlank(u.getQrCode())) {
			u.setQrCodeBase64(this.getImageStrFromUrl(u.getQrCode()));
		}
		
		for(TripDetailDTO tripDetail : u.getDayDetail()) {
			if(CollectionUtils.isNotEmpty(tripDetail.getContents())) {
				for(TripContentDTO tripContent : tripDetail.getContents()) {
					if(StringUtils.isNotBlank(tripContent.getUrl())) {
						tripContent.setUrlBase64(this.getImageStrFromUrl(tripContent.getUrl()));
					}else {
						tripContent.setUrl(null);
					}
				}
			}
			
		}
		Template t = configuration.getTemplate("trip.ftl", "UTF-8");
		String fileName =  DateUtils.getCurrentSeconds()+".doc";
		String path = filePath+fileName;
		
		// 输出文档路径及名称
		File outFile = new File(path);
		FileOutputStream fos = new FileOutputStream(outFile);
		OutputStreamWriter oWriter = new OutputStreamWriter(fos, "UTF-8");
		Writer out = new BufferedWriter(oWriter);

		t.process(u, out);
		out.close();
		fos.close();
		
		return path;
	}

到此就可以生成一个word文档。
注意这样生成的word其实格式还是xml格式。

相关阅读:
生成标准格式word请戳这里==>java使用freemarker导出word(标准格式版)

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!