使用Velocity+zip+xml快速构建word2007文档
2010-05-05 10:43
330 查看
技术介绍
在一般的Java项目中,生成word文档的时候,我们会使用到jacob来作为我们生成word文档的工具,但是jacob是通过使用JNI调用dll文件来实现,这样的工作方式带来了极大的性能开销.这里我们采用Velocity+zip+xml快速构建word2007文档,所以说这个技术实现有一定的局限性,生成的必须是word2007文档.
我们先来了解下word2007文档,word2007文档本身是以xml文件格式存储的,然后将相关的xml文件和资源文件压缩得到.为了让大家更加明白,先来做个小实验,我们新建一个word2007文档,然后打开后随便输入一些内容,保存后,将其扩展名docx修改为zip.然后你会发现真的可以用压缩工具查看.目录结构如下.
│ [Content_Types].xml
│
├─docProps
│ app.xml
│ core.xml
│
├─word
│ │ document.xml
│ │ endnotes.xml
│ │ fontTable.xml
│ │ footnotes.xml
│ │ settings.xml
│ │ styles.xml
│ │ webSettings.xml
│ │
│ ├─theme
│ │ theme1.xml
│ │
│ └─_rels
│ document.xml.rels
│
└─_rels
.rels
这里文件比较多,但是我们需要关心的文件只有document.xml文件.这样我们就将构建word2007文档的问题转化成修改一个压缩文件中的xml问题,所以我们会使用到zip来处理压缩文件,由于对document.xml文档的定义比较复杂,这里我们采用模板的方式来修改document.xml文档,所以我们用到了强大的模板引擎,velocity.
基本上用到的技术和实现策略已经介绍得差不多了,下面我们看看如何实现.
第一步:创建模板
新建一个目录,我们这里命名为mytemplate,
在目录下新建一个word2007文档,输入下面内容
姓名:${name}
性别:${sex}
保存文章内容后,将其扩展名改为zip,使用压缩工具打开,将其中的document.xml文件解压一份到当前文件夹下的word目录.(注意不要将所有文件解压,只解压一个文件即可,当所有文件解压后,重新压缩后,word会无法解析,具体原因还不是很清楚)
然后从压缩文件中删除docuemnt.xml,
然后将压缩文件名修改为template.docx
看下目录结构
mytemplate
│ template.docx
│
└─word
│ document.xml
│
└─temp
第一步:编写代码
创建工具代码
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.nio.channels.FileChannel;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
/**
* 通过word模板生成word文件的类
* @author wangchao
* Create Date 2010-4-29
*/
public class Word07Tools {
private static String SPLITER = "/";
private static String WORD = "word";
private static String DOCUMENT = "document.xml";
private static String ENCODING = "UTF-8";
private static String LODERPATH = "file.resource.loader.path";
private static String TEMPDIR = "/temp";
private static String TEMPLATEFILENAME = "template.docx";
/**
* 使用word文件模板生成word文件
* @param dataMap 数据
* @param templatePath 模板文件路径
* @param outFilePath 目标文件
* @throws Exception
*/
public static synchronized void createWord(HashMap<String,Object> dataMap,String templatePath,String outFilePath) throws Exception{
if(templatePath!=null&&!templatePath.endsWith(SPLITER))
{
templatePath+=SPLITER;
}
//定义加入数据后的document.xml文件
File documentFile = new File(injectData(dataMap,templatePath));
//定义目标文件
File targetFile = new File(outFilePath);
//定义模板文件
File templateFile = new File(templatePath+TEMPLATEFILENAME);
if(targetFile.exists()){
targetFile.delete();
}
//将模板文件拷贝一份作为目标文件
fileCopy(templateFile,targetFile);
//在目标文件中加入新的document.xml文件
addDocumentToTemplate(targetFile,documentFile);
//删除临时的document.xml文件
documentFile.delete();
}
/**
* 想模板装入数据的方法
* @param dataMap 要装入的数据
* @param templatePath 模板路径
* @return 返回装入数据完成的xml文件路径
* @throws Exception
*/
private static String injectData(HashMap<String,Object> dataMap, String templatePath)throws Exception {
//Init Velocity
Properties p = new Properties();
p.setProperty(LODERPATH, templatePath+WORD);
Velocity.init(p);
VelocityContext context = new VelocityContext();
//Build Velocity Context
for (Iterator<Map.Entry<String,Object>> iter = dataMap.entrySet().iterator(); iter.hasNext();) {
Map.Entry<String,Object> entry = (Map.Entry<String,Object>) iter.next();
context.put(entry.getKey(), entry.getValue());
}
Template template = null;
template = Velocity.getTemplate(DOCUMENT, ENCODING);
String templateDocPath = templatePath+WORD+TEMPDIR+SPLITER+DOCUMENT;
File tempdir = new File(templatePath+WORD+TEMPDIR+SPLITER);
tempdir.mkdirs();
OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(templateDocPath),ENCODING);
if (template != null)
template.merge(context, writer);
writer.flush();
writer.close();
return templateDocPath;
}
/**
* 将要document.xml文件的数据装入到模板中
* @param targetFile 目标模板文件
* @param document document.xml文件
* @throws IOException
*/
private static void addDocumentToTemplate(File targetFile, File document)throws IOException {
// get a temp file
File tempFile = File.createTempFile(targetFile.getName(), null);
// delete it, otherwise you cannot rename your existing zip to it.
tempFile.delete();
boolean renameOk = targetFile.renameTo(tempFile);
if (!renameOk) {
throw new RuntimeException("could not rename the file " + target
4000
File.getAbsolutePath() + " to "+ tempFile.getAbsolutePath());
}
byte[] buf = new byte[1024];
ZipInputStream zin = new ZipInputStream(new FileInputStream(tempFile));
ZipOutputStream out = new ZipOutputStream(new FileOutputStream(targetFile));
ZipEntry entry = zin.getNextEntry();
while (entry != null) {
String name = entry.getName();
boolean notInFiles = true;
if (document!=null) {
if (document.getName().equals(name)) {
notInFiles = false;
break;
}
}
if (notInFiles) {
// Add ZIP entry to output stream.
out.putNextEntry(new ZipEntry(name));
// Transfer bytes from the ZIP file to the output file
int len;
while ((len = zin.read(buf)) > 0) {
out.write(buf, 0, len);
}
}
entry = zin.getNextEntry();
}
// Close the streams
zin.close();
// Compress the files
if(document!=null) {
InputStream in = new FileInputStream(document);
// Add ZIP entry to output stream.
out.putNextEntry(new ZipEntry(WORD+SPLITER + document.getName()));
// Transfer bytes from the file to the ZIP file
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
// Complete the entry
out.closeEntry();
in.close();
}
// Complete the ZIP file
out.close();
tempFile.delete();
}
/**
* 文件拷贝
* @param f1 拷贝源文件
* @param f2 拷贝目标文件
* @return
* @throws Exception
*/
private static long fileCopy(File f1,File f2) throws Exception{
long time=new Date().getTime();
int length=2097152;
FileInputStream in=new FileInputStream(f1);
FileOutputStream out=new FileOutputStream(f2);
FileChannel inC=in.getChannel();
FileChannel outC=out.getChannel();
int i=0;
while(true){
if(inC.position()==inC.size()){
inC.close();
outC.close();
return new Date().getTime()-time;
}
if((inC.size()-inC.position())<20971520)
length=(int)(inC.size()-inC.position());
else
length=20971520;
inC.transferTo(inC.position(),length,outC);
inC.position(inC.position()+length);
i++;
}
}
}
第三步:编写测试代码
import java.util.HashMap;
public class TestWordTools {
public static void main(String args[]) throws Exception {
HashMap<String,Object> dataMap = new HashMap<String,Object>();
dataMap.put("name", "王超");
dataMap.put("sex", "M");
Word07Tools.createWord(dataMap,"D:/TEMP/mytemplate/","D:/TEMP/目标.docx");
}
}
然后执行以下,可以看到,在指定的目录会生产 "目标.docx"文件
由于文章篇幅关系,这里只是做了一个简单的例子,对于一些复杂和循环,大家可以通过修改document.xml文件来实现.
例如需要表格,我们可以先制作一个含表格的word文档,然后取出document.xml文档进行修改.通过velocity的循环语句输出表格内容.
我这里只是抛砖引玉,希望大家能多提宝贵意见.
如需要源代码和实例,请发邮件给我 Email:wangchaomail@foxmail.com
我的QQ:328054944,欢迎大家与我交流....
auth:wangchao
版权声明:欢迎转载,但是请在转载的同时在请注明出处和作者.
在一般的Java项目中,生成word文档的时候,我们会使用到jacob来作为我们生成word文档的工具,但是jacob是通过使用JNI调用dll文件来实现,这样的工作方式带来了极大的性能开销.这里我们采用Velocity+zip+xml快速构建word2007文档,所以说这个技术实现有一定的局限性,生成的必须是word2007文档.
我们先来了解下word2007文档,word2007文档本身是以xml文件格式存储的,然后将相关的xml文件和资源文件压缩得到.为了让大家更加明白,先来做个小实验,我们新建一个word2007文档,然后打开后随便输入一些内容,保存后,将其扩展名docx修改为zip.然后你会发现真的可以用压缩工具查看.目录结构如下.
│ [Content_Types].xml
│
├─docProps
│ app.xml
│ core.xml
│
├─word
│ │ document.xml
│ │ endnotes.xml
│ │ fontTable.xml
│ │ footnotes.xml
│ │ settings.xml
│ │ styles.xml
│ │ webSettings.xml
│ │
│ ├─theme
│ │ theme1.xml
│ │
│ └─_rels
│ document.xml.rels
│
└─_rels
.rels
这里文件比较多,但是我们需要关心的文件只有document.xml文件.这样我们就将构建word2007文档的问题转化成修改一个压缩文件中的xml问题,所以我们会使用到zip来处理压缩文件,由于对document.xml文档的定义比较复杂,这里我们采用模板的方式来修改document.xml文档,所以我们用到了强大的模板引擎,velocity.
基本上用到的技术和实现策略已经介绍得差不多了,下面我们看看如何实现.
第一步:创建模板
新建一个目录,我们这里命名为mytemplate,
在目录下新建一个word2007文档,输入下面内容
姓名:${name}
性别:${sex}
保存文章内容后,将其扩展名改为zip,使用压缩工具打开,将其中的document.xml文件解压一份到当前文件夹下的word目录.(注意不要将所有文件解压,只解压一个文件即可,当所有文件解压后,重新压缩后,word会无法解析,具体原因还不是很清楚)
然后从压缩文件中删除docuemnt.xml,
然后将压缩文件名修改为template.docx
看下目录结构
mytemplate
│ template.docx
│
└─word
│ document.xml
│
└─temp
第一步:编写代码
创建工具代码
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.nio.channels.FileChannel;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
/**
* 通过word模板生成word文件的类
* @author wangchao
* Create Date 2010-4-29
*/
public class Word07Tools {
private static String SPLITER = "/";
private static String WORD = "word";
private static String DOCUMENT = "document.xml";
private static String ENCODING = "UTF-8";
private static String LODERPATH = "file.resource.loader.path";
private static String TEMPDIR = "/temp";
private static String TEMPLATEFILENAME = "template.docx";
/**
* 使用word文件模板生成word文件
* @param dataMap 数据
* @param templatePath 模板文件路径
* @param outFilePath 目标文件
* @throws Exception
*/
public static synchronized void createWord(HashMap<String,Object> dataMap,String templatePath,String outFilePath) throws Exception{
if(templatePath!=null&&!templatePath.endsWith(SPLITER))
{
templatePath+=SPLITER;
}
//定义加入数据后的document.xml文件
File documentFile = new File(injectData(dataMap,templatePath));
//定义目标文件
File targetFile = new File(outFilePath);
//定义模板文件
File templateFile = new File(templatePath+TEMPLATEFILENAME);
if(targetFile.exists()){
targetFile.delete();
}
//将模板文件拷贝一份作为目标文件
fileCopy(templateFile,targetFile);
//在目标文件中加入新的document.xml文件
addDocumentToTemplate(targetFile,documentFile);
//删除临时的document.xml文件
documentFile.delete();
}
/**
* 想模板装入数据的方法
* @param dataMap 要装入的数据
* @param templatePath 模板路径
* @return 返回装入数据完成的xml文件路径
* @throws Exception
*/
private static String injectData(HashMap<String,Object> dataMap, String templatePath)throws Exception {
//Init Velocity
Properties p = new Properties();
p.setProperty(LODERPATH, templatePath+WORD);
Velocity.init(p);
VelocityContext context = new VelocityContext();
//Build Velocity Context
for (Iterator<Map.Entry<String,Object>> iter = dataMap.entrySet().iterator(); iter.hasNext();) {
Map.Entry<String,Object> entry = (Map.Entry<String,Object>) iter.next();
context.put(entry.getKey(), entry.getValue());
}
Template template = null;
template = Velocity.getTemplate(DOCUMENT, ENCODING);
String templateDocPath = templatePath+WORD+TEMPDIR+SPLITER+DOCUMENT;
File tempdir = new File(templatePath+WORD+TEMPDIR+SPLITER);
tempdir.mkdirs();
OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(templateDocPath),ENCODING);
if (template != null)
template.merge(context, writer);
writer.flush();
writer.close();
return templateDocPath;
}
/**
* 将要document.xml文件的数据装入到模板中
* @param targetFile 目标模板文件
* @param document document.xml文件
* @throws IOException
*/
private static void addDocumentToTemplate(File targetFile, File document)throws IOException {
// get a temp file
File tempFile = File.createTempFile(targetFile.getName(), null);
// delete it, otherwise you cannot rename your existing zip to it.
tempFile.delete();
boolean renameOk = targetFile.renameTo(tempFile);
if (!renameOk) {
throw new RuntimeException("could not rename the file " + target
4000
File.getAbsolutePath() + " to "+ tempFile.getAbsolutePath());
}
byte[] buf = new byte[1024];
ZipInputStream zin = new ZipInputStream(new FileInputStream(tempFile));
ZipOutputStream out = new ZipOutputStream(new FileOutputStream(targetFile));
ZipEntry entry = zin.getNextEntry();
while (entry != null) {
String name = entry.getName();
boolean notInFiles = true;
if (document!=null) {
if (document.getName().equals(name)) {
notInFiles = false;
break;
}
}
if (notInFiles) {
// Add ZIP entry to output stream.
out.putNextEntry(new ZipEntry(name));
// Transfer bytes from the ZIP file to the output file
int len;
while ((len = zin.read(buf)) > 0) {
out.write(buf, 0, len);
}
}
entry = zin.getNextEntry();
}
// Close the streams
zin.close();
// Compress the files
if(document!=null) {
InputStream in = new FileInputStream(document);
// Add ZIP entry to output stream.
out.putNextEntry(new ZipEntry(WORD+SPLITER + document.getName()));
// Transfer bytes from the file to the ZIP file
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
// Complete the entry
out.closeEntry();
in.close();
}
// Complete the ZIP file
out.close();
tempFile.delete();
}
/**
* 文件拷贝
* @param f1 拷贝源文件
* @param f2 拷贝目标文件
* @return
* @throws Exception
*/
private static long fileCopy(File f1,File f2) throws Exception{
long time=new Date().getTime();
int length=2097152;
FileInputStream in=new FileInputStream(f1);
FileOutputStream out=new FileOutputStream(f2);
FileChannel inC=in.getChannel();
FileChannel outC=out.getChannel();
int i=0;
while(true){
if(inC.position()==inC.size()){
inC.close();
outC.close();
return new Date().getTime()-time;
}
if((inC.size()-inC.position())<20971520)
length=(int)(inC.size()-inC.position());
else
length=20971520;
inC.transferTo(inC.position(),length,outC);
inC.position(inC.position()+length);
i++;
}
}
}
第三步:编写测试代码
import java.util.HashMap;
public class TestWordTools {
public static void main(String args[]) throws Exception {
HashMap<String,Object> dataMap = new HashMap<String,Object>();
dataMap.put("name", "王超");
dataMap.put("sex", "M");
Word07Tools.createWord(dataMap,"D:/TEMP/mytemplate/","D:/TEMP/目标.docx");
}
}
然后执行以下,可以看到,在指定的目录会生产 "目标.docx"文件
由于文章篇幅关系,这里只是做了一个简单的例子,对于一些复杂和循环,大家可以通过修改document.xml文件来实现.
例如需要表格,我们可以先制作一个含表格的word文档,然后取出document.xml文档进行修改.通过velocity的循环语句输出表格内容.
我这里只是抛砖引玉,希望大家能多提宝贵意见.
如需要源代码和实例,请发邮件给我 Email:wangchaomail@foxmail.com
我的QQ:328054944,欢迎大家与我交流....
auth:wangchao
版权声明:欢迎转载,但是请在转载的同时在请注明出处和作者.
相关文章推荐
- 使用Spring Boot&Swagger快速构建REST API并生成优美的API文档
- python 6-4 如何构建xml文档使用标准库中的xml.etree.ElementTree 构建ElementTree,使用write方法写入文档
- 使用dom4j遍历xml 文档快速入门
- 使用Office Open XML将XML数据写入Word2007文档表格之一
- 使用Office Open XML将XML数据写入Word2007文档表格之二
- 使用build.xml自动拷贝文件并打包成zip
- 使用docker快速构建rails开发环境
- 使用XPATH查找xml文档节点
- 使用安卓内置的pull解析器解析xml文档,并自动映射成bean
- WebService生成XML文档时出错。不应是类型XXXX。使用XmlInclude或SoapInclude属性静态指定非已知的类型。
- 使用 flow.ci 快速发布你的项目文档
- react---学习笔记:使用create-react-app快速构建开发环境并创建项目
- 使用Dom4j实现对XML文档的解析
- 解析在.net中使用XSLT转换xml文档的示例详解
- 使用XmlDocument创建XML文档及增加删除更新节点
- ML的DOM解析 Java实现 使用递归解析一个XML文档
- Flex3 快速入门:构建简单的用户界面 使用容器
- 一段C++ Builder使用XML文档的代码(尚未试验)
- 使用DataSet和DataView搜索XML文档
- xml解析2:使用递归解析给定的任意一个xml文档并且将其内容输出到命令行上