公司前一段時間做的一個項目中應用到了這個技術,需要後臺獲取數據後在前臺直接下載word文檔,利用freemarker模版生成的doc文檔在電腦上可以正常打開,但是發送到手機上打開則全部變成“亂碼”。但實際上並不是亂碼,而是xml格式的代碼,在手機等移動端顯示不出正常的文檔信息。之後輾轉查詢使用了很多方案,比如利用poi操作,利用jacob進行格式轉換,但是效果都不盡如人意,而且處理過程非常複雜,浪費了大量時間。最終,通過不斷摸索,找到了非常完美的解決方案。不需要先生成xml格式的doc文檔再去轉換,而是戒指在後臺利用模版添加數據後,利用IO流進行替換,可以直接生成標準的docx文檔。
首先需要理解的一點是,docx文檔本身是一個壓縮文件。在利用這個方案前要先準備好docx模版。在這個docx模版中,將要替換的數據用${}的方式替換掉,先佔好位置。如圖:
將docx改後綴名爲.zip並解壓出來。在word文件夾下有一個文件爲、:document.xml
利用notpad等工具打開此文件,將格式修改好,比如在xml文件中${xxx}分別氛圍${ , xxx 以及 } 三部分存貯在不同的標籤下,要將他們合併到一起。
隨後將保存好的document.xml和之前定義好的zip文件放入工程的配置文件夾下,方便引用。
核心代碼如下
Configuration
configuration = new Configuration();
/** 設置編碼 **/
/** 我的ftl文件是放在D盤的**/
String fileDirectory = "D:/cache/qqChace/T1/xlsx";
/** 加載文件 **/
configuration.setDirectoryForTemplateLoading(new File(fileDirectory));
/** 加載模板 **/
Template template = configuration.getTemplate("document.xml");
/** 準備數據 **/
Map<String,String> dataMap = new HashMap<>();
/** 在ftl文件中有${textDeal}這個標籤**/
dataMap.put("id","哈士奇");
dataMap.put("number","20");
dataMap.put("language","java,php,python,c++.......");
dataMap.put("example","Hello World!");
/** 指定輸出word文件的路徑 **/
String outFilePath = "D:/cache/qqChace/T1/xlsx/data.xml";
File docFile = new File(outFilePath);
FileOutputStream fos = new FileOutputStream(docFile);
OutputStreamWriter oWriter = new OutputStreamWriter(fos);
Writer out = new BufferedWriter(new OutputStreamWriter(fos),10240);
template.process(dataMap,out);
if(out != null){
out.close();
}
// ZipUtils 是一個工具類,主要用來替換具體可以看github工程
ZipInputStream zipInputStream = ZipUtils.wrapZipInputStream(new FileInputStream(new File("D:/cache/qqChace/T1/xlsx/test.zip")));
ZipOutputStream zipOutputStream = ZipUtils.wrapZipOutputStream(new FileOutputStream(new File("D:/cache/qqChace/T1/xlsx/test.docx")));
String itemname = "word/document.xml";
ZipUtils.replaceItem(zipInputStream, zipOutputStream, itemname, new FileInputStream(new File("D:/cache/qqChace/T1/xlsx/data.xml")));
System.out.println("success");
這段代碼是轉來的,https://blog.csdn.net/u013076044/article/details/79236000
想要源碼可以去這個地址下載。
上面主要解決的問題事文字的轉換,下面要說的關於圖片的轉換問題。
將zip文件解壓後會發現,document.xml中沒有你原本保存在docx中的圖片,圖片是在word下的media文件夾下。document.xml中或許只是引用。但是仍可以通過這種方式,以流的形式替換圖片。只要保證圖片的順序不亂就可以了。
下面是我在上面這段代碼上進行的改動
String zipPath = this.getClass().getResource("/freemarkerDoc.zip").getPath();
try {
ZipInputStream zipInputStream = ZipUtils.wrapZipInputStream(new FileInputStream(new File(zipPath)));
ZipOutputStream zipOutputStream = ZipUtils.wrapZipOutputStream(new FileOutputStream(new File(basePath+patientInfo.getName()+"連續血氧檢測報告("+reportInfo.getId()+").docx")));//這個是要生成文檔的路徑和名稱
String itemname = "word/document.xml";
String itemname2 = "word/media/";
String picturePath = PropertiesUtil.getBinaryRoorPath("picturePath")+File.separator+patientInfo.getId()+File.separator+reportInfo.getId()+File.separator;
ZipUtils.replaceItem(zipInputStream, zipOutputStream, itemname, new FileInputStream(new File(url)), itemname2, picturePath);
System.out.println("success");
} catch (Exception e) {
System.out.println(e.toString());
}
我的docx文檔中工有四張圖片,所以在zipUtil中要傳入你要替換上去的圖片路徑。代碼如下
public static void replaceItem(ZipInputStream zipInputStream,
ZipOutputStream zipOutputStream,
String itemName,
InputStream itemInputStream
,
String itemName2,
String picturePath
){
//
if(null == zipInputStream){return;}
if(null == zipOutputStream){return;}
if(null == itemName){return;}
if(null == itemInputStream){return;}
InputStream itemIS = null;
//
ZipEntry entryIn;
try {
while((entryIn = zipInputStream.getNextEntry())!=null)
{
String entryName = entryIn.getName();
ZipEntry entryOut = new ZipEntry(entryName);
// 只使用 name
zipOutputStream.putNextEntry(entryOut);
// 緩衝區
byte [] buf = new byte[8*1024];
int len;
//image1.png等是media目錄下的圖片名稱。
String ima1 = itemName2+"image1.png";
String ima2 = itemName2+"image2.png";
String ima3 = itemName2+"image3.png";
String ima4 = itemName2+"image4.png";
if(entryName.equals(itemName)){
// 使用替換流
while((len = (itemInputStream.read(buf))) > 0) {
zipOutputStream.write(buf, 0, len);
}
}else if(entryName.equals(ima1)){
// 使用替換流,將需要替換上去的圖片路徑以流的形式獲取
itemIS = new FileInputStream(new File(picturePath+"spo2PiePicture.png"));
while((len = (itemIS.read(buf))) > 0) {
zipOutputStream.write(buf, 0, len);
}
}else if(entryName.equals(ima2)){
// 使用替換流
itemIS = new FileInputStream(new File(picturePath+"prPiePicture.png"));
while((len = (itemIS.read(buf))) > 0) {
zipOutputStream.write(buf, 0, len);
}
}else if(entryName.equals(ima3)){
// 使用替換流
itemIS = new FileInputStream(new File(picturePath+"spo2Picture.png"));
while((len = (itemIS.read(buf))) > 0) {
zipOutputStream.write(buf, 0, len);
}
}else if(entryName.equals(ima4)){
// 使用替換流
itemIS = new FileInputStream(new File(picturePath+"prPicture.png"));
while((len = (itemIS.read(buf))) > 0) {
zipOutputStream.write(buf, 0, len);
}
}else {
// 輸出普通Zip流
while((len = (zipInputStream.read(buf))) > 0) {
zipOutputStream.write(buf, 0, len);
}
}
// 關閉此 entry
zipOutputStream.closeEntry();
}
} catch (IOException e) {
e.printStackTrace();
}finally {
//e.printStackTrace();
close(itemInputStream);
close(zipInputStream);
close(zipOutputStream);
close(itemIS);
}
}
轉換完成後要講所有的流全部關閉,就完成了。