jxl & poi & jacob
一、jxl 的用法:
缺点:仅限 excel2003
特点:代码中,没有用到 FileInputStream
<dependency>
<groupId>net.sourceforge.jexcelapi</groupId>
<artifactId>jxl</artifactId>
<version>2.6.3</version>
</dependency>
jxl - EXCEL2003 | |
步骤 |
a. 创建输出流 OutputStream os = response.getOutputStream();
response.setHeader("Content-Disposition",
"attachment; filename="+new String(fileName.getBytes("gb2312"), "iso-8859-1"));
|
b. 创建 WritableWorkbook (也可选择加载模板) WritableWorkbook wwb = Workbook.createWorkbook(os);
/** 加载模板
Workbook wb = Workbook.getWorkbook(new File(path));
WritableWorkbook wwb = Workbook.createWorkbook(os, wb)
*/
|
|
c. 创建 WritableSheet (或者选取指定已有sheet) WritableSheet sheet = wwb.createSheet(fileName, 0);
/** 指定 sheet
WritableSheet sheet = wwb.getSheet("Sheet1");
*/
|
|
d. 创建 WritableFont WritableFont wf = new WritableFont(WritableFont.createFont("Arial Unicode MS"), 9);
|
|
e. 创建 WritableCellFormat 用于 excel 单元格的格式 WritableCellFormat wcf = new WritableCellFormat(wf);
|
|
f. 创建 Label(列 行 从 0 开始) Label label = new Label(column_num, row_num, value, wcf);
|
|
g. 添加 cell sheet.addCell(label)
|
|
h. 输出,并关闭各文件,各流 wwb.write(); // 写入数据
wwb.close(); // 关闭文件
os.flush();
os.close(); // 关闭数据流
|
二、poi 处理(雷很多多多多多多。。。)
1. 生成 excel2003 & excel2007
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>3.9</version>
</dependency>
poi - EXCEL | |
步骤 |
a. 创建输出流 OutputStream os = response.getOutputStream();
response.setHeader("Content-Disposition",
"attachment; filename="+new String(fileName.getBytes("gb2312"), "iso-8859-1"));
|
b. 创建 HSSFWorkbook \ XSSFWorkbook(也可加载模板) Workbook workbook = new HSSFWorkbook();
// Workbook workbook = new XSSFWorkbook();// 用于2007
/** 加载模板(需要创建输入流)
FileInputStream fis = new FileInputStream(new File(path));
Workbook workbook = new HSSFWorkbook(fis);
// Workbook workbook = new XSSFWorkbook(fis);// 用于2007
*/
|
|
c. 创建 Sheet(或者选取指定已有 sheet) Sheet sheet = workbook.createSheet("testdata");
/** 指定 sheet
Sheet sheet = workbook.getSheetAt(0);
*/
|
|
d. 创建 Row(或者指定已有 row) 创建 Cell(或者指定已有 Cell) (**** 注:1. 赋值之前一定要判断空值,实际中,无缘无故说 cell 为空,非常无语。O__O "…) Row row = sheet.getRow(row_num);
if (row == null) {
row = sheet.createRow(row_num);
}
Cell cell = row.getCell(col_num);
if (cell == null) {
cell = row.createCell(col_num);
}
cell.setCellValue(value);
|
|
e. 合并单元格
(注:报“修复”的错误,是如下的原因。网上说的什么方法过时,什么导错包,都是扯淡!!! ①:excel 模板中的坐标已经合并了单元格,程序中,在相同坐标又重复合并了单元格 ②:在循环中用到了合并单元格,那么可能重复合并了单元格,用 debug 查下 ③:代码:sheet.addMergedRegion(new CellRangeAddress(12, 12,0, 0))的意思是 从 13 行 1 列到 13 行 1 列合并,也就是说值合并了一个单元格,也是错!! 根据上面三个原因,终归是因为一个原因:重复合并单元格!!!) /** 这个方法有点怪:先 行 后 列 */
sheet.addMergedRegion(new CellRangeAddress(rowStart, rowEnd, columnStart, columnEnd))
|
|
f. 设定单元格值 cell.setCellValue(value);
|
|
g. 输出,并关闭各流 workbook.write(outputStream); // 写入数据
outputStream.flush();
outputStream.close();
fileInputStream.close();
|
2. 生成 word2003(弊端:图片只能跟文字在一个层级上,而且不能旋转。最好用 jacob)
poi - WORD2003(以使用模板形式为例) | |
步骤 |
a. 创建输出流 OutputStream os = response.getOutputStream();
response.setHeader("Content-Disposition",
"attachment; filename="+new String(fileName.getBytes("gb2312"), "iso-8859-1"));
|
b. 导入模板(需要使用 FileInputStream) FileInputStream fis = new FileInputStream(new File(path));
HWPFDocument doc = new HWPFDocument(fis);
|
|
c. 创建 Rang: Range range = hwpfDocument.getRange();
|
|
d. 替换 指定文字: (注:网上有很多的 通过 paragraph 和 table 来替换的,但是我试了下,如果你有指定的key(比如自定义 @key@),那么可以直接替换) range.replaceText(key, value);
|
|
e. 输出,并关闭各流: hwpfDocument.write(outputStream);
|
3. 生成 word2007
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.9</version>
</dependency>
poi - WORD2007(以使用模板形式为例) | |
步骤 |
a. 创建输出流 OutputStream os = response.getOutputStream();
response.setHeader("Content-Disposition",
"attachment; filename="+new String(fileName.getBytes("gb2312"), "iso-8859-1"));
|
b. 加载模板: (注:网上打开模板的方式如下,但是这个方式会直接修改模板文件。也就是说,当你运行完一次以后,模板也会被修改。当第二次运行时,模板已经变成了第一次运行之后的文件了。因此要慎用) XWPFDocument xwpfDocument = new XWPFDocument(new File(path));
/** 网上打开模板的方式如下
OPCPackage opcPackage = POIXMLDocument.openPackage(path);
xwpfDocument = new XWPFDocument(opcPackage);
*/
|
|
c. 获得 paragraph list,并替换 pargraph 中的关键字 for (XWPFParagraph xwpfParagraph : paragraphList) {
xwpfParagraph = replaceKeyInParagraph(xwpfParagraph, paraList);
}
public XWPFParagraph replaceKeyInParagraph(XWPFParagraph xwpfParagraph,
List<RPTBean> paraList) {
List<XWPFRun> xwpfRunsList = xwpfParagraph.getRuns();
if (xwpfRunsList != null && !xwpfRunsList.isEmpty()) {
for (XWPFRun xwpfRun : xwpfRunsList) {
int testPosition = xwpfRun.getTextPosition();
String text = xwpfRun.getText(testPosition);
if (text !=null && !"".equals(text)){
for (RPTBean rptBean : paraList) {
if (text.contains(rptBean.getFieldName())){
int startIndex = text.indexOf(rptBean.getFieldName());
xwpfRun.setText(rptBean.getValue(), startIndex);
}
}
}
}
}
return xwpfParagraph;
}
|
|
d. 获得 table list,并替换 table 中的关键字 for (XWPFTable xwpfTable : xwpfTables) {
xwpfTable = replaceKeyInTable(xwpfTable, paraList);
}
public XWPFTable replaceKeyInTable(XWPFTable xwpfTable, List<RPTBean> paraList) {
for (int i = 0; i<xwpfTable.getNumberOfRows(); i++) {
XWPFTableRow row = xwpfTable.getRow(i);
List<XWPFTableCell> cellList = row.getTableCells();
if (cellList != null && !cellList.isEmpty()) {
for (XWPFTableCell cell : cellList) {
if (cell != null) {
// 下面有另一种方法
|
|
上述方法生成的 表中没有格式。要生成格式 两种方法: 1. 先获取格式,然后修改内容,最后添加格式 2. 用 paragraph 来做
List<XWPFParagraph> paragraphList = cell.getParagraphs(); for (XWPFParagraph xwpfParagraph : paragraphList) { replaceKeyInParagraph(xwpfParagraph, paraList); }
|
|
e. 输出,并关闭各流 xwpfDocument.write(outputStream);
outputStream.flush();
outputStream.close();
fileInputStream.close();
/** 如果用网上办法,连模板都修改的话,那么最好再关闭下面两个
opcPackage.flush();
opcPackage.close();
*/
|
|
ps1. 获取文本框(没测试过)(参考:http://www.cnblogs.com/liaokunhong/p/5403279.html) List<XWPFParagraph> paragraphs = document.getParagraphs(); // 遍历得到doc中的段落 for ( XWPFParagraph paragraph : paragraphs) { String text = paragraph.getText(); if (StringUtils.isBlank(text)) { continue; } List<CTR> rList = paragraph.getCTP().getRList(); for (CTR ctr : rList) { // xml文件对象读取 XmlObject xmlObject = ctr.copy(); // 拿到xml文件节点 Node domNode = xmlObject.getDomNode(); // 拿到子节点List列表对象 NodeList nodeList = domNode.getChildNodes(); for (int idx = 0 ; idx < nodeList.getLength(); idx++) { Node item = nodeList.item(idx); |
|
ps2. 简化 ps1,用递归,获得<w:t>(没测过)(参考:http://blog.csdn.net/calance_h/article/details/52808778) public void replaceParagraphText(final int paragraphPos, final Entry<String,String> text) throws IndexOutOfBoundsException { if(this.ctTc.sizeOfPArray() < paragraphPos){ throw new IndexOutOfBoundsException(); } final CTP ctP = this.ctTc.getPArray(paragraphPos); final XWPFParagraph par = new XWPFParagraph(ctP, this); List<XWPFRun> runs = par.getRuns(); for(XWPFRun run : runs){ for(int i = 0; i < run.getCTR().sizeOfTArray(); i++){ if(run.getText(i).equals(text.getKey())){ String replaceBy = run.getText(i).replaceAll(text.getKey(), text.getValue()); run.setText(replaceBy, i); } } } } |
|
ps3. 处理书签: https://wenku.baidu.com/view/2206d072f342336c1eb91a37f111f18583d00c7e.html http://elim.iteye.com/blog/2031335 |
|
ps4:通过xml来处理word http://www.infoq.com/cn/articles/cracking-office-2007-with-java |
三、jacob(在服务器端要安装 office )
参考:
1. http://men4661273.iteye.com/blog/2097871
2. https://wenku.baidu.com/view/ba3cb447fe4733687e21aa2e.html
3. http://blog.csdn.net/songbaojie/article/details/1842756
Java COM Bridge:即 java 和 com 组件间的桥梁,com 一般表现为 dll 或者 exe 等二进制文件。
这些文件合称为接口 api。
1. 需要2个文件,而且分 32 & 64 版本
下载地址:http://downloads.sourceforge.net/jacob-project/jacob_1.9.zip?modtime=1109437002&big_mirror=0
文件名称 | 存放位置 |
jacob.jar | 项目lib中 |
jacob-1.18-x64.dll | jdl的lib中 |
jacob-1.18-x86.dll |
2. 代码
jacob 以模板形式为例 | |
步
骤 |
Dispatch:调度处理类,封装了一些操作来操作 office ,里面所有的可操作对象基本都是这种类型,所以 jacob 是一种链式操作模式,就像 StringBuilder 对象,调用 append() 方法之后返回的还是 StringBuilder 对象。 Dispatch的几种静态方法:这些方法就是要用来操作office的。这些方法中有的有很多重载方法,调用不同的方法时需要放置不同的参数,至于哪些参数代表什么意思,具体放什么值,就需要参考vba代码了,仅靠jacob是无法进行变成的。 •call( ) / callN( ): 调用 com 对象的方法,返回 Variant 类型值。 call() / callN(): this.document = Dispatch.call(this.documents, "Open", templateFile).toDispatch();
this.document = Dispatch.callN(this.documents, "Open", new Object[]{templateFile}).toDispatch();
Variant:封装参数数据类型,因为操作 office 是的一些方法参数(可能是字符串类型,可能是数字类型)。可以通过 Variant 来进行转换通用的参数类型,new Variant(1),new Variant("1")。 Variant 对象中的 toDispatch():将以上方法返回的 Variant 类型转换为 Dispatch,进行下一次链式操作。 |
1. 初始化com线程:大概意思是打开冰箱门,准备放大象。。。 ComThread.InitSTA();
|
|
2. 创建office的一个应用,比如你操作的是 word 还是 excel ActiveXComponent word = new ActiveXComponent("Word.Application"); |
|
3. 设置编辑器是否可见:设置应用操作的文档不在明面上显示,只在后台静默处理。 word.setProperty("Visible", new Variant(false));
|
|
4. 获取文档属性 Dispatch documents = word.getProperty("Documents").toDispatch();
|
|
5. 打开激活文档 | |
// Dispatch doc = Dispatch.call(documents, "Open",new Variant(inputPath)).toDispatch();
Dispatch doc = Dispatch.invoke(documents, "Open", Dispatch.Method, new Object[]{inputPath, new Variant(false)}, new int[1]).toDispatch();
|
|
Selection:代表当前光标位置或者所选范围。该对象代表窗口或窗格中的当前所选内容。若文档中没有所选内容,则代表插入点。每个文档窗格只能有一个活动的 Selection对象,并且整个应用程序中只能有一个活动的 Selection对象。 | |
6. 选定的范围或插入点 Dispatch selection = Dispatch.get(word, "Selection").toDispatch();
|
|
7. 从选定内容或插入点开始查找文本 Dispatch find = Dispatch.call(this.selection, "Find").toDispatch();
|
|
8. 查找字符串(替换) Dispatch.put(find, "Text", "name"); //查找字符串"name"
Dispatch.call(find, "Execute"); //执行查询
Dispatch.put(selection, "Text", "111"); //替换为"111"
或者 Boolean f = new Boolean(false);
Boolean t = new Boolean(true);
int wdReplaceTime = 2; //1:查找替换一次;2:查找替换全部
int wdFindContinue = 1;
Object[] args={"被替换的值",t,f,f,f,f,t,new Integer(wdFindContinue),f,"替换为",new Integer(wdReplaceTime),f,f,f,f};
Dispatch.callN(this.find, "Execute",args);
|
|
9. 文本框,只能用书签形式(替换) Dispatch bookMarks = Dispatch.get(doc, "Bookmarks").toDispatch(); int bCount = Dispatch.get(bookMarks, "Count").getInt(); //获取书签数 for (int i = 1; i <= bCount; i++) { // 读取书签命名 Dispatch items = Dispatch.call(bookMarks, "Item", i).toDispatch(); |
|
10. 文件另存为 Dispatch.invoke(doc, "SaveAs", Dispatch.Method, new Object[] {"目标文件路径", new Variant(fileType)} , new int[1]);
或者以流方式输出:我能想到的就是,保存在本地的文件,用POI重新打开,然后以流方式输出。再将本地保存的文件删掉 删除文件: File file=new File("目标文件路径");
if (file.exists()) {
printWord();
file.delete();
}
|
|
11. 关闭模板文件(电脑任务管理器 - 前台应用) val 的可选值:0/false 不保存修改 -1 保存修改 -2 提示是否保存修改 Dispatch.call(doc, "Close", new Variant(val));
|
|
12. 关闭 office 应用(电脑任务管理器 - 后台进程) word.invoke("Quit", new Variant[] {});
|
|
13. 关闭线程 ComThread.Release();
|
3. 类似上面,第 9 步中,jacob 还能 获得的 com
Dispatch content = Dispatch.call(document, "content").getDispatch();
Open | 打开文档 |
ActiveXComponent.Visible | 设置编辑器是否可见 |
Tables | 获得所有的表格 |
Bookmarks | 所有标签 |
Selection | 光标所在处或选中的区域 |
select | 选中 |
typeParagraph | 设置为一个段落 |
ParagraphFormat | 段落格式,用alignment设置 |
alignment | 1、居中,2、靠右,3、靠左 |
Add | 新建一个word文档 |
Close |
关闭文档: 0/false 不保存,-1保存,-2弹出框确认 |
SaveAS | 另存为 |
save | 保存 |
printOut | 打印 |
Application | 得到ActiveXComponent的实例 |
WindowState |
Application的属性,表示窗口的大小, 0、default,1、maximize,2、minimize |
top、left、height、width | application的属性,表示窗口的位置 |
ActiveXComponent.Quit | 关闭所有word文档,但是不退出整个word程序 |
Range |
表示文档中的一个连续范围。 由一个起始字符位置和一个终止字符位置定义,进而可以得到格式的信息 |
Item | 得到指定的表格 |
Rows | 得到表格的所有行 |
Cell | 表格的一个单元格 |
Text | word的文本内容 |
InsertFile | 插入文件 |
InsertRowsBelow | 在指定的行下面插入一行 |
InsertAfter | 在指定对象后插入 |
Delete | 删除,可以是表格的行 |
Count | 返回数目,比如Rows、Tables的数目 |
Height | 返回高度,比如行高、表格行的高 |
Split | 拆分单元格,要指定行数和列数 |
Merge | 合并单元格 |
Exists | 指定的对象是否存在,返回bool值 |
Copy | 复制 |
Paste | 粘贴 |
Font | 字体 |
Name | 字体的名字 |
Bold | 字体是否为粗体 |
Italic | 字体是否为斜体 |
Underline | 字体是否有下划线 |
Color | 颜色 |
Size | 大小 |
Borders |
指定边框: -1为上边框,-2左边框,-3为下边框,-4有右边框,-5为横向边框, -6为纵向边框,-7从左上角开始的斜线,-8从左下角开始的斜线 |
AutoFitBehavior |
自动调整大小: 1为内容自动调整大小,2为窗口自动调整大小 |
Content | 去的内容 |
InLineShapes | |
AddPicture | 增加一张图片,需要制定路径 |
homeKey | 光标移到开头 |
moveDown | 光标往下一行 |
moveUp | 光标往上一行 |
moveRight | 光标往左一列 |
moveLeft | 光标往右一列 |
find | 要查找的文本 |
Forward | 向前查找 |
Format | 查找的文本格式 |
MatchCase | 大小写匹配 |
MatchWholeWord | 全字匹配 |
Execute | 开始执行查找 |
LineSpacingRule | 行间距 |
4. 遇到的错误
1. com.jacob.com.ComFailException: Can't map name to dispid: Open 或者,一直都卡在那里。 |
因为电脑的任务管理器中积压了很多 office 进程/应用程序,没有内存打开新 office 文件了。 方案:除了关闭 任务管理器中的 进程/应用程序外,程序: finally {
Dispatch.call(doc, "Close", new Variant(false)); // 关闭前台应用
this.word.invoke("Quit", new Variant[] {}); // 关闭后台进程
ComThread.Release(); // 关闭进程
}
|
2. 在开着 tomcat 时,调试程序,会报如下错: java.lang.NoClassDefFoundError: com.jacob.com.JacobObject |
方案:重启 tomcat |
3. java.lang.IllegalStateException: Dispatch not hooked to windows memory |
我没遇到,网上办法:http://blog.sina.com.cn/s/blog_49cc672f0100pp0p.html 然后每次操作完成后都会调用ComThread.Release()去释放,但释放后word和documents并不为null,所以每次使用jacob都只有第一次是正常的,后面就要报错,然后必须重启tomcat才行。 if (this.word == null || this.word.m_pDispatch==0) {
this.word = new ActiveXComponent("Word.Application");
this.word.setProperty("Visible", new Variant(false));
this.word.setProperty("DisplayAlerts", new Variant(false));
}
if (documents == null||documents.m_pDispatch==0) {
this.documents = this.word.getProperty("Documents").toDispatch();
}
|
4. com.jacob.com.ComFailException: Invoke of: SaveAs |
因为保存的类型出错了 Dispatch.invoke(doc, "SaveAs", Dispatch.Method, new Object[] {output, new Variant(fileType)} , new int[1]); fileType:数字表示不同类型
|
5. com.jacob.com.ComFailException: Invoke of: Item |
原因:将选中一段内容来作为标签,类似将“word”作为标签
方案:以光标形式作为标签 |