之前用過jacob 合並.doc,但是是有jacob有弊端:
- 服務器必須是Windows操作系統 —— 目前之所以web項目多用Java開發,就是因為服務器可以是Linux、Unix等非Windows的系統來降低項目的成本。
- 服務器上必須安裝Office —— Jacob的意思就是: Java COM Bridge,java中調用office提供的com接口來實現對Office文件的操作。
- 並發問題 —— 如果多用戶同時在線生成word文件就必須處理此並發問題,稍有不慎,就會在服務器端產生Office的死進程,死鎖服務器的內存資源。
我遇到的問題是下載並合並附件,這里的附件大多是doc文件,也包含少量的docx文件,但是文件路徑是從數據庫中讀取出來的,均不帶后綴名,傳統的xwpfdocument和hwpfdocument不能完全解決我的問題;尤其是將文件合並,不能輕易辦到,需要對文檔進行解析。
這里我采用的方法是將Word文件轉換為HTML文件,把Word的合並轉化為HTML的合並;這樣一來就減少了難度,不過就是還需要再把HTML文件轉化為doc文件或者docx文件(此時,你就可以指定是哪種文件了)。
在轉換的時候,分兩個方向,一個是doc文件轉換,另一個是docx文件轉換;這里的轉換時必須包含文檔中的格式的(圖片和表格我這里沒有進行測試)。如果是沒有文件后綴,那么就需要先判斷是doc文件還是docx文件,這里用到了一個工具類,就是通過文件的文件頭來判斷文件類型,因為我這里只是為了區別doc和docx,所以就比較了前四位的16進制數,按照if和else來走兩條轉換路線。(具體文件的文件頭可上網查資料,各種文件都有)。
轉換doc文件的時候,是按照字符讀取的,判斷每個字符的字體顏色和樣式,將其轉換為HTML的代碼,最后應該是整個穩當的HTML的字符串的累加,因為我這里是合並,所以我使用for循環進行了文件主體的疊加,最后在循環的外面加上HTML的頭部和尾部信息即可。除此之外,根據需求,需要在不同文檔之間插入分頁符,分頁符用HTML代碼可以表示,"<br clear=all style='page-break-before:always' mce_style='page-break-before:always'> ";需要的直接添加即可。
然后是docx文件的轉換,這里的就不像doc文件可以按照每個字符來讀了,而是將整個文檔直接轉換為HTML文件,通過打印字符串可以得知,轉換出來的HTML代碼就是一個大的div,如果直接使用這個代碼,合並的時候格式就不統一了,所以需要將圖中的style樣式去掉,直接用字符串截取,並加上一個空的<div>即可。

最后,再將HTML文件轉為doc或者docx,可以將路徑放在服務器上面,並且實現下載就行(建議每次下載完之后將該文件清空,可以循環利用)。詳細代碼如下。
package com.landray.kmss.km.doc.util;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.List;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.model.PicturesTable;
import org.apache.poi.hwpf.usermodel.CharacterRun;
import org.apache.poi.hwpf.usermodel.Paragraph;
import org.apache.poi.hwpf.usermodel.Picture;
import org.apache.poi.hwpf.usermodel.Range;
import org.apache.poi.hwpf.usermodel.Table;
import org.apache.poi.hwpf.usermodel.TableCell;
import org.apache.poi.hwpf.usermodel.TableIterator;
import org.apache.poi.hwpf.usermodel.TableRow;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
public class WordExcelToHtml {
/**
* 回車符ASCII碼
*/
private static final short ENTER_ASCII = 13;
/**
* 空格符ASCII碼
*/
private static final short SPACE_ASCII = 32;
/**
* 水平制表符ASCII碼
*/
private static final short TABULATION_ASCII = 9;
// public static String htmlText = "";
public static String mainText = "";
public static String htmlTextTbl = "";
public static int counter = 0;
public static int beginPosi = 0;
public static int endPosi = 0;
public static int beginArray[];
public static int endArray[];
public static String htmlTextArray[];
public static boolean tblExist = false;
public static void main(String argv[]) {
try {
String htmlText = "<html><head><meta http-equiv='Content-Type' content='text/html; charset=utf-8' />"
+ "</head><body>"; //將每一個Word中的主體部分拿出來,合並之后加上HTML的頭和尾,但是要注意編碼
List<String> list = new ArrayList<String>();
String file1 = "D://file8";
String file2 = "D://file9";
String file3 = "D://file11";
list.add(file1);
list.add(file2);
list.add(file3);
// String mainText1 = "";
for (int i = 0; i < list.size(); i++) {
htmlText += getWordAndStyle(list.get(i))
+ "<br clear=all style='page-break-before:always' mce_style='page-break-before:always'> ";
//每一個文檔讀取完之后,加上一個分頁符,繼續累加
}
htmlText += "</body></html>";
String filePath = "D://1.html";
writeFile(htmlText, filePath);
new HtmlToDoc().writeWordFile(filePath, "D://file10.doc");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 讀取每個文字樣式
*
* @param fileName
* @throws Exception
*/
public static String getWordAndStyle(String fileName) throws Exception {
String htmlText = "";
FileInputStream in = new FileInputStream(new File(fileName));
//根據文本內容判斷是doc還是docx
byte[] b = new byte[4];
in.read(b, 0, b.length);
in.close();
FileInputStream in1 = new FileInputStream(new File(fileName));
System.out.println(bytesToHexString(b) + ";;;");
if (bytesToHexString(b).equalsIgnoreCase("d0cf11e0")) {//"d0cf11e0"代表的是doc文件
HWPFDocument doc = new HWPFDocument(in1);
Range rangetbl = doc.getRange();// 得到文檔的讀取范圍
TableIterator it = new TableIterator(rangetbl);
int num =1;
beginArray = new int[num];
endArray = new int[num];
htmlTextArray = new String[num];
readTable(it, rangetbl);
// 取得文檔中字符的總數
int length = doc.characterLength();
// 創建圖片容器;
PicturesTable pTable = doc.getPicturesTable();
int cur = 0;
String tempString = "";
for (int i = 0; i < length - 1; i++) {
// 整篇文章的字符通過一個個字符的來判斷,range為得到文檔的范圍
Range range = new Range(i, i + 1, doc);
CharacterRun cr = range.getCharacterRun(0);
if (tblExist && cur < beginArray.length) {
if (i == beginArray[cur]) {
htmlText += tempString + htmlTextArray[cur];
tempString = "";
i = endArray[cur] - 1;
cur++;
continue;
}
}
if (pTable.hasPicture(cr)) {
//htmlText += tempString;
// 讀寫圖片
tempString = readPicture(pTable, cr);
//tempString = "";
htmlText += tempString;
} else {
Range range2 = new Range(i + 1, i + 2, doc);
// 第二個字符
CharacterRun cr2 = range2.getCharacterRun(0);
char c = cr.text().charAt(0);
// 判斷是否為回車符
if (c == ENTER_ASCII) {
tempString += "<br/>";
}
// 判斷是否為空格符
else if (c == SPACE_ASCII)
tempString += " ";
// 判斷是否為水平制表符
else if (c == TABULATION_ASCII)
tempString += " ";
// 比較前后2個字符是否具有相同的格式
boolean flag = compareCharStyle(cr, cr2);
String fontStyle = "<span class='text' style=\"font-family:"
+ cr.getFontName()
+ ";font-size:"
+ cr.getFontSize()
/ 2
+ "pt;color:"
+ ColorUtils.getHexColor(cr.getIco24()) + ";";
if (cr.isBold())
fontStyle += "font-weight:bold;";
if (cr.isItalic())
fontStyle += "font-style:italic;";
htmlText += fontStyle + "\" mce_style=\"font-family:"
+ cr.getFontName() + ";font-size:"
+ cr.getFontSize() / 2 + "pt;";
if (cr.isBold())
fontStyle += "font-weight:bold;";
if (cr.isItalic())
fontStyle += "font-style:italic;";
htmlText += fontStyle + "\">" + tempString + cr.text()
+ "</span>";
tempString = "";
}
}
htmlText += tempString;
return htmlText;
} else {
Word2007ToHtml w = new Word2007ToHtml();
String filepath = "";
String fileName1 = fileName;
String htmlName = "D://3.html";
w.Word2007ToHtml(fileName1, htmlName);
String result = w.readFileByBytes(htmlName);
int i = result.indexOf('>');
String realreasult = "<div>"+result.substring(i+1);
System.out.println(realreasult);
htmlText += realreasult;
return htmlText;
}
}
/**
* 讀寫文檔中的表格
*
* @param pTable
* @param cr
* @throws Exception
*/
public static void readTable(TableIterator it, Range rangetbl)
throws Exception {
htmlTextTbl = "";
// 迭代文檔中的表格
counter = -1;
while (it.hasNext()) {
tblExist = true;
htmlTextTbl = "";
Table tb = (Table) it.next();
beginPosi = tb.getStartOffset();
endPosi = tb.getEndOffset();
System.out.println("............" + beginPosi + "...." + endPosi);
counter = counter + 1;
// 迭代行,默認從0開始
beginArray[counter] = beginPosi;
endArray[counter] = endPosi;
htmlTextTbl += "<table border='1'>";
for (int i = 0; i < tb.numRows(); i++) {
TableRow tr = tb.getRow(i);
htmlTextTbl += "<tr >";
// 迭代列,默認從0開始
for (int j = 0; j < tr.numCells(); j++) {
TableCell td = tr.getCell(j);// 取得單元格
int cellWidth = td.getWidth();
// 取得單元格的內容
for (int k = 0; k < td.numParagraphs(); k++) {
Paragraph para = td.getParagraph(k);
String s = para.text().toString().trim();
if (s == "") {
s = " ";
}
System.out.println(s);
htmlTextTbl += "<td class='text' width=" + cellWidth
+ ">" + s + "</td>";
System.out.println(i + ":" + j + ":" + cellWidth + ":"
+ s);
} // end for
} // end for
} // end for
htmlTextTbl += "</table>";
htmlTextArray[counter] = htmlTextTbl;
} // end while
}
/**
* 讀寫文檔中的圖片
*
* @param pTable
* @param cr
* @throws Exception
*/
public static void readPicture(PicturesTable pTable, CharacterRun cr)
throws Exception {
// 提取圖片
Picture pic = pTable.extractPicture(cr, false);
// 返回POI建議的圖片文件名
String afileName = pic.suggestFullFileName();
OutputStream out = new FileOutputStream(new File("e://test"
+ File.separator + afileName));
pic.writeImageContent(out);
// htmlText += "<img src=\"e://test//" + afileName
// + "\" mce_src=\"e://test//" + afileName + "\"/>";
}
public static boolean compareCharStyle(CharacterRun cr1, CharacterRun cr2) {
boolean flag = false;
if (cr1.isBold() == cr2.isBold() && cr1.isItalic() == cr2.isItalic()
&& cr1.getFontName().equals(cr2.getFontName())
&& cr1.getFontSize() == cr2.getFontSize()) {
flag = true;
}
return flag;
}
/**
* 寫文件
*
* @param s
*/
public static void writeFile(String s, String filePath) {
FileOutputStream fos = null;
BufferedWriter bw = null;
try {
File file = new File(filePath);
fos = new FileOutputStream(file);
bw = new BufferedWriter(new OutputStreamWriter(fos));
bw.write(s);
} catch (FileNotFoundException fnfe) {
fnfe.printStackTrace();
} catch (IOException ioe) {
ioe.printStackTrace();
} finally {
try {
if (bw != null)
bw.close();
if (fos != null)
fos.close();
} catch (IOException ie) {
}
}
}
// 判斷文件類型
public static String bytesToHexString(byte[] src) {
StringBuilder stringBuilder = new StringBuilder();
if (src == null || src.length <= 0) {
return null;
}
for (int i = 0; i < src.length; i++) {
int v = src[i] & 0xFF;
String hv = Integer.toHexString(v);
if (hv.length() < 2) {
stringBuilder.append(0);
}
stringBuilder.append(hv);
}
return stringBuilder.toString();
}
}
獲取字體顏色的工具類:
package com.landray.kmss.km.doc.util;
public
class ColorUtils {
public
static
int red(
int c) {
return c & 0XFF;
}
public
static
int green(
int c) {
return (c >> 8) & 0XFF;
}
public
static
int blue(
int c) {
return (c >> 16) & 0XFF;
}
public
static
int rgb(
int c) {
return (
red(c) << 16) | (
green(c) <<8) |
blue(c);
}
public
static String rgbToSix(String rgb) {
int length = 6 - rgb.length();
String str = "";
while(length > 0){
str += "0";
length--;
}
return str + rgb;
}
public
static String getHexColor(
int color) {
color = color == -1 ? 0 : color;
int rgb =
rgb(color);
return "#" +
rgbToSix(Integer.
toHexString(rgb));
}
}
將HTML文件轉換為doc文件:
package com.landray.kmss.km.doc.util;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import org.apache.poi.poifs.filesystem.DirectoryEntry;
import org.apache.poi.poifs.filesystem.DocumentEntry;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
//將docx文件轉為HTML
package com.landray.kmss.km.doc.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.converter.core.FileImageExtractor;
import org.apache.poi.xwpf.converter.core.FileURIResolver;
import org.apache.poi.xwpf.converter.xhtml.XHTMLConverter;
import org.apache.poi.xwpf.converter.xhtml.XHTMLOptions;
import org.junit.Test;
public class Word2007ToHtml{
@Test
public void Word2007ToHtml(String fileName,String htmlName) throws IOException {
final String file = fileName;
File f = new File(file);
if (!f.exists()) {
System.out.println("Sorry File does not Exists!");
} else {
// ) 加載word文檔生成 XWPFDocument對象
InputStream in = new FileInputStream(f);
XWPFDocument document = new XWPFDocument(in);
// ) 解析 XHTML配置 (這里設置IURIResolver來設置圖片存放的目錄)
File imageFolderFile = new File("D://");
XHTMLOptions options = XHTMLOptions.create().URIResolver(new FileURIResolver(imageFolderFile));
options.setExtractor(new FileImageExtractor(imageFolderFile));
options.setIgnoreStylesIfUnused(false);
options.setFragment(true);
// ) 將 XWPFDocument轉換成XHTML
File file1 = new File(htmlName);
OutputStream out = new FileOutputStream(file1);
XHTMLConverter.getInstance().convert(document, out, options);
//也可以使用字符數組流獲取解析的內容
// ByteArrayOutputStream baos = new ByteArrayOutputStream();
// XHTMLConverter.getInstance().convert(document, baos, options);
// String content = baos.toString();
// System.out.println(content);
// baos.close();
}
}
public static void main(String[] args) throws IOException {
Word2007ToHtml w = new Word2007ToHtml();
String fileName = "D://file1.docx";
String htmlName = "D://3.html";
w.Word2007ToHtml(fileName,htmlName);
String result = readFileByBytes(htmlName);
System.out.println(result);
}
public static String readFileByBytes(String fileName) {
String s="";
File file = new File(fileName);
Reader reader = null;
try {
//System.out.println("以字符為單位讀取文件內容,一次讀一個字節:");
// 一次讀一個字符
reader = new InputStreamReader(new FileInputStream(file),"utf-8");
int tempchar;
while ((tempchar = reader.read()) != -1) {
// 對於windows下,\r\n這兩個字符在一起時,表示一個換行。
// 但如果這兩個字符分開顯示時,會換兩次行。
// 因此,屏蔽掉\r,或者屏蔽\n。否則,將會多出很多空行。
if (((char) tempchar) != '\r') {
s +=(char) tempchar;
}
}
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
return s;
}
}
/** * 將html文檔轉為doc * @author soildwang * */
public class HtmlToDoc {
/**
* 讀取html文件到word *
* @param filepath html文件的路徑 * @return
* * @throws Exception */
public boolean writeWordFile(String filepath,String outfile) throws Exception {
boolean flag = false;
ByteArrayInputStream bais = null;
FileOutputStream fos = null;
//String outfile = "D://file8.doc"; //根據實際情況寫路徑
try {
if (!"".equals(outfile)) {
File fileDir = new File(outfile);
if (fileDir.exists()) {
String content = readFile(filepath);
byte b[] = content.getBytes();
bais = new ByteArrayInputStream(b);
POIFSFileSystem poifs = new POIFSFileSystem();
DirectoryEntry directory = poifs.getRoot();
DocumentEntry documentEntry = directory.createDocument("WordDocument", bais);
fos = new FileOutputStream(outfile);
poifs.writeFilesystem(fos);
bais.close();
fos.close();
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fos != null) fos.close();
if(bais != null) bais.close();
} return flag;
}
/**
* * 讀取html文件到字符串 * @param filename
* * @return * @throws Exception
* */
public String readFile(String filename) throws Exception {
StringBuffer buffer = new StringBuffer("");
BufferedReader br = null;
try {
br = new BufferedReader(new InputStreamReader(new FileInputStream(new File(filename)),Charset.forName("utf-8")));
buffer = new StringBuffer();
while (br.ready())
buffer.append((char) br.read());
} catch (Exception e) {
e.printStackTrace();
} finally {
if(br!=null) br.close();
}
return buffer.toString();
}
//局部測試
public static void main(String[] args) throws Exception {
new HtmlToDoc().writeWordFile("d://1.html","D://file8.doc");//根據實際情況寫文件路徑
}
}