最近做的项目,需要将一些信息导出到word中。在网上找了好多解决方案,现在将这几天的总结分享一下。
目前来看,java导出word大致有6种解决方案:
1:Jacob是Java-COM Bridge的缩写,它在Java与微软的COM组件之间构建一座桥梁。使用Jacob自带的DLL动态链接库,并通过JNI的方式实现了在Java平台上对COM程序的调用。DLL动态链接库的生成需要windows平台的支持。该方案只能在windows平台实现,是其局限性。
2:Apache POI包括一系列的API,它们可以操作基于MicroSoft OLE 2 Compound Document Format的各种格式文件,可以通过这些API在Java中读写Excel、Word等文件。他的excel处理很强大,对于word还局限于读取,目前只能实现一些简单文件的操作,不能设置样式。
3:Java2word是一个在java程序中调用 MS Office Word 文档的组件(类库)。该组件提供了一组简单的接口,以便java程序调用他的服务操作Word 文档。 这些服务包括: 打开文档、新建文档、查找文字、替换文字,插入文字、插入图片、插入表格,在书签处插入文字、插入图片、插入表格等。填充数据到表格中读取表格数据 ,1.1版增强的功能: 指定文本样式,指定表格样式。如此,则可动态排版word文档。是一种不错的解决方案。
4:iText是著名的开放源码的站点sourceforge一个项目,是用于生成PDF文档的一个java类库。通过iText不仅可以生成PDF或rtf的文档,而且可以将XML、Html文件转化为PDF文件。功能强大。
5:JSP输出样式,该方案实现简单,但是处理样式有点缺陷,简单的导出可以使用。
6:用XML做。Word从2003开始支持XML格式,大致的思路是先用office2003或者2007编辑好word的样式,然后另存为xml,将xml翻译为FreeMarker模板,最后用java来解析FreeMarker模板并输出Doc。经测试这样方式生成的word文档完全符合office标准,样式、内容控制非常便利,打印也不会变形,生成的文档和office中编辑文档完全一样。
综合以上资料的参考,和网上的一些意见,最后我选择了,第2种用POI做导出方案。
使用POI导出到word,首先你要设置导出的word模板,在要填充的模板中填入表达式,如下图所示。
占位符尽量用英文,并且保证在同一个模板里不重复。
POI的思路就是取到模板文件,然后将数据替换占位符填充模板,生成一个新的word文档。
数据放到一个map里,key就是占位符名字,value如果是文字,就是字符串,如果是图片,还得特殊处理(见下文)
/**
* * 读取word模板并替换变量字段
*
* @param fileName 模板名
* @param contentMap 要替换的内容
* @return word的Document
* @throws IOException
*/
public XWPFDocument replaceDocForDispatch(String fileName, Map<String, Object> contentMap) throws IOException {
try {
Map<String, Object> newContentMap = new HashMap<String, Object>();
Resource filePath = resourceLoader
.getResource("classpath:config" + System.getProperty("file.separator") + fileName);
File file = filePath.getFile();
String path = file.getPath();
// 读取模板
CustomXWPFDocument doc = null;
try {
OPCPackage pack = POIXMLDocument.openPackage(path);
doc = new CustomXWPFDocument(pack);
//处理会签,会签里有多个审批意见,领导签名,审批时间
if(contentMap.containsKey("hq")) {
List list = (List)contentMap.get("hq");
List leaderList = getLeaderList();
for(int i=0;i<list.size();i++) {
String operator = list.get(i).toString();
if(leaderList.contains(operator)) {
Object o = this.getSignImageByAccount(operator);
list.remove(i);
list.add(i, o);
}
}
}
// 处理段落
List<XWPFParagraph> paragraphList = doc.getParagraphs();
processParagraphs2(paragraphList, contentMap, doc);
// 处理表格
Iterator<XWPFTable> it = doc.getTablesIterator();
while (it.hasNext()) {
XWPFTable table = it.next();
List<XWPFTableRow> rows = table.getRows();
for (XWPFTableRow row : rows) {
List<XWPFTableCell> cells = row.getTableCells();
for (XWPFTableCell cell : cells) {
List<XWPFParagraph> paragraphListTable = cell.getParagraphs();
processParagraphs2(paragraphListTable, contentMap, doc);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return doc;
} catch (Exception e) {
return null;
}
}
/**
* * 通过人员名字获取对应的签名图片
*
* @param fileName 模板名
* @param contentMap 要替换的内容
* @return word的Document
* @throws IOException
*/
private Object getSignImageByAccount(String name) {
String account = dao.selectacountBylastname(name);
String url = dao.selectpicurlByacount(account);
// 获取电子签名url异常处理
try {
if (url == null) {
return name;
}
else {
Map<String, Object> imageMap = this.getImageMap(url, 70, 25);
return imageMap;
}
} catch (Exception e) {
return name;
}
}
/**
* 获取图片参数
*
* @param url
* @param width
* @param height
* @return
* @throws IOException
*/
private Map<String, Object> getImageMap(String url, int width, int height) throws IOException {
Map<String, Object> imageMap = new HashMap<String, Object>();
imageMap.put("width", width);
imageMap.put("height", height);
imageMap.put("type", "jpg");
imageMap.put("content", CustomXWPFDocument.inputStream2ByteArray(new FileInputStream(url), true));
return imageMap;
}
/**
* 处理段落
*
* @param paragraphList
*/
public static void processParagraphs2(List<XWPFParagraph> paragraphList, Map<String, Object> param,
CustomXWPFDocument doc) {
List leaderList = getLeaderList();
if (paragraphList != null && paragraphList.size() > 0) {
for (XWPFParagraph paragraph : paragraphList) {
List<XWPFRun> runs = paragraph.getRuns();
for (int i = 0; i < runs.size(); i++) {
XWPFRun run = runs.get(i);
String text = run.getText(0);
if (text != null&¶m.containsKey(text)) {
Object value = param.get(text);
if (value instanceof String) {// 文本替换
text = value.toString();
run.setText(text,0);
} else if (value instanceof List) {
run.setText("",0);
List list = (List)value;
for(int j=0;j<list.size();j++) {
Object o = list.get(j);
if(o instanceof String) {
XWPFRun run1 = paragraph.createRun();
run1.setText(o.toString());
run1.setFontSize(run.getFontSize());
run1.setFontFamily(run.getFontFamily());
run1.setBold(run.isBold());
run1.addTab();
}
else if (o instanceof Map) {// 图片替换
creactPic(doc,paragraph,(Map)o);
}
if(j%3==0&&j!=0) {
XWPFRun run1 = paragraph.createRun();
run1.addBreak();
}
}
}
}
}
}
}
}
/**
* 添加图片
* @param map
*/
private static void creactPic(CustomXWPFDocument doc,XWPFParagraph paragraph,Map picParam) {
Map pic = (Map) picParam;
int width = Integer.parseInt(pic.get("width").toString());
int height = Integer.parseInt(pic.get("height").toString());
byte[] byteArray = (byte[]) pic.get("content");
ByteArrayInputStream byteInputStream = new ByteArrayInputStream(byteArray);
try {
String ind = doc.addPictureData(byteInputStream, 5);
int id = doc.getAllPictures().size() - 1;
doc.createPicture(id, width, height, paragraph, ind);
} catch (Exception e) {
e.printStackTrace();
}
}
在表格里写图片的关键方法是createPicture
/**
* @param id
* @param width 宽
* @param height 高
* @param paragraph 段落
* @param blipId
*/
public void createPicture(int id,int width, int height, XWPFParagraph paragraph,String blipId) {
final int EMU = 9525;
width *= EMU;
height *= EMU;
CTInline inline = paragraph.createRun().getCTR().addNewDrawing().addNewInline();
String picXml = "" + "<a:graphic xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\">"
+ " <a:graphicData uri=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">"
+ " <pic:pic xmlns:pic=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">"
+ " <pic:nvPicPr>" + " <pic:cNvPr id=\"" + id + "\" name=\"Generated\"/>"
+ " <pic:cNvPicPr/>" + " </pic:nvPicPr>" + " <pic:blipFill>"
+ " <a:blip r:embed=\"" + blipId
+ "\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\"/>"
+ " <a:stretch>" + " <a:fillRect/>" + " </a:stretch>"
+ " </pic:blipFill>" + " <pic:spPr>" + " <a:xfrm>"
+ " <a:off x=\"0\" y=\"0\"/>" + " <a:ext cx=\"" + width + "\" cy=\""
+ height + "\"/>" + " </a:xfrm>" + " <a:prstGeom prst=\"rect\">"
+ " <a:avLst/>" + " </a:prstGeom>" + " </pic:spPr>"
+ " </pic:pic>" + " </a:graphicData>" + "</a:graphic>";
inline.addNewGraphic().addNewGraphicData();
XmlToken xmlToken = null;
try {
xmlToken = XmlToken.Factory.parse(picXml);
} catch (XmlException xe) {
xe.printStackTrace();
}
inline.set(xmlToken);
inline.setDistT(0);
inline.setDistB(0);
inline.setDistL(0);
inline.setDistR(0);
CTPositiveSize2D extent = inline.addNewExtent();
extent.setCx(width);
extent.setCy(height);
CTNonVisualDrawingProps docPr = inline.addNewDocPr();
docPr.setId(id);
docPr.setName("图片" + id);
}
图片被替换后效果如下:
word的模型是
整个是一个document
然后里面有多很多个paragraph,
段落
段落和段落之间按照回车换行分割
每一段内,又可以有多个range,如果是文字,就放入XWPFRun里,然后run1.setText(o.toString());
还可以设置字体,大小,对齐方式,空格,换行
run1.setFontSize(run.getFontSize());
run1.setFontFamily(run.getFontFamily());
run1.setBold(run.isBold());
run1.addTab();
run1.addBreak(); //换行