1.需求
在一个项目中有个需求:复制word的内容到编辑器中。但是在复制过程中图片不能成功的复制过来,需要安装flash插件,但是吧又不能要求每个客户都安装上,这就比较麻烦了。所以考虑是不是可以把word转成html,这样放入编辑器中打开就没问题了把。
2.准备
麻烦的又来了,UEditor1.4之后才支持简便的二次开发扩展,但项目使用了老版本的UEditor,全面替换吧肯定又是一堆坑,没办法就只能改源码,顺便在这里记录一下。
3.实施
研究了一通之后发现主要业务逻辑写在了ueditor.all.js&ueditor.parse.js这两个js中,同时有大量的dialog去做了一些交互。
我打算在上传附件时增加一个tab页去专门处理转换的工作,这样可以做到改动比较小。
3.1 增加上传附件并转换
UEditor的上传附件交互主要在dialog下的attachment文件夹,包括了attachment.html;attachment.css;attachment.js三个文件,下面依次来改下。
3.1.1 更改attachment.html
参照原来的tab页增加一个我们自己的:
<span class="tab" data-content-id="transform"><var id="lang_tab_transform"></var></span>
Tab页对应的展示块:
<!-- 上传文件 -->
<div id="transform" class="panel focus">
<div id="queueListTrans" class="queueList">
<div class="statusBar element-invisible">
<div class="progress">
<span class="text">0%</span>
<span class="percentage"></span>
</div><div class="info"></div>
<div class="btns">
<div id="filePickerBtnTrans"></div>
<div class="uploadBtn"><var id="lang_word_upload"></var></div>
</div>
</div>
<div id="dndAreaTrans" class="placeholder">
<div class="filePickerContainer">
<div id="filePickerReadyTrans"></div>
</div>
</div>
<ul class="filelist element-invisible">
<li id="filePickerBlockTrans" class="filePickerBlock"></li>
</ul>
</div>
</div>
要想我们新增的内容匹配到文字描述,这里要配置下zh-cn.js和en.js
在en.js的'attachment':'static':中增加:
'lang_tab_transform': 'Word Trans Html',
'lang_word_upload':"Word upload",
在zh-cn.js的'attachment':'static':中增加:
'lang_tab_transform': 'Word转Html',
'lang_word_upload':"Word上传",
3.1.2 更改attachment.css
再添加相应的css,这个比较简单把原来upload的css复制一份,把"upload"改成我自己定义的"transform"添加进去即可。太多太长这里就不贴了。
3.1.3 更改attachment.js
首先增加一个对象transFile:
var uploadFile,
transFile,
onlineFile;
在设置tab的时候增加tansFile:
switch (id) {
case 'upload':
uploadFile = uploadFile || new UploadFile('queueList');
break;
case 'transform':
transFile = transFile || new UploadFile('queueListTrans');
break;
case 'online':
onlineFile = onlineFile || new OnlineFile('fileList');
break;
}
初始化dialog确认按钮时增加transFile:
/* 初始化onok事件 */
function initButtons() {
dialog.onok = function () {
var list = [], id, tabs = $G('tabhead').children;
for (var i = 0; i < tabs.length; i++) {
if (domUtils.hasClass(tabs[i], 'focus')) {
id = tabs[i].getAttribute('data-content-id');
break;
}
}
switch (id) {
case 'upload':
list = uploadFile.getInsertList();
var count = uploadFile.getQueueCount();
if (count) {
$('.info', '#queueList').html('<span style="color:red;">' + '还有2个未上传文件'.replace(/[\d]/, count) + '</span>');
return false;
}
editor.execCommand('insertfile', list);
break;
case 'transform':
list = transFile.getInsertList();
console.log("transFileUrl:"+list[0].url);
var wordHtml = wordTransHtml(list[0].url);
console.log("wordHtml:"+wordHtml);
editor.execCommand('insertHTML', wordHtml);
break;
case 'online':
list = onlineFile.getInsertList();
editor.execCommand('insertfile', list);
break;
}
// editor.execCommand('insertfile', list);
};
}
我不想写冗余的代码(主要是懒:)所以想复用原来上传的逻辑,但是原来代码里很多写死的ID,所以要改一下:
增加了个target记住当前的页面是哪个。
/* 上传附件 */
function UploadFile(target) {
this.target = target;
this.$wrap = target.constructor == String ? $('#' + target) : $(target);
this.init();
}
在定义中新定义了一些ID:
//设置不同ID
filePickerBtnId = 'filePickerBtn',
filePickerReadyId = 'filePickerReady',
filePickerBlockId = 'filePickerBlock',
判断当前页面是不是我新加的,是的话变更值:
//如果为转换上传重新设置相关ID
if ("queueListTrans"==this.target){
filePickerBtnId = 'filePickerBtnTrans';
filePickerReadyId = 'filePickerReadyTrans';
filePickerBlockId = 'filePickerBlockTrans';
}
把下面用到写死的ID的地方替换成我定义的变量:
if (!WebUploader.Uploader.support()) {
$('#'+filePickerReadyId).after($('<div>').html(lang.errorNotSupport)).hide();
return;
} else if (!editor.getOpt('fileActionName')) {
$('#'+filePickerReadyId).after($('<div>').html(lang.errorLoadConfig)).hide();
return;
}
uploader = _this.uploader = WebUploader.create({
pick: {
id: '#'+filePickerReadyId,
label: lang.uploadSelectFile
},
swf: '../../third-party/webuploader/Uploader.swf',
server: actionUrl,
fileVal: editor.getOpt('fileFieldName'),
duplicate: true,
fileSingleSizeLimit: fileMaxSize,
compress: false
});
uploader.addButton({
id: '#'+filePickerBlockId
});
uploader.addButton({
id: '#'+filePickerBtnId,
label: lang.uploadAddFile
});
最后的效果便是这个样子的了:
前端请求后端的ajax方法:
function wordTransHtml(path) {
console.log("In wordTransHtml:"+path);
$.ajax({
type:"GET",
url:"../../../WordTransHtml",
data:{type:"docx",path:path},
success:function (data) {
console.log("wordTransHtml data:"+data);
console.log(data);
console.log("wordTransHtml data.body:"+data.body);
// $('#token').html(data);
},
error: function (err) {
$('#token').html(err);
}
});
}
3.2 实现后端的word转换html的服务
本来想用SpringMVC实现,看了下系统的架构还是老老实实的写个servlet吧。(:
3.2.1 所需jar包如下:
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.poi.xwpf.converter.xhtml</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.0</version>
</dependency>
3.2.2 后端Servlet
import com.alibaba.fastjson.JSONObject;
import com.ztesoft.util.WordToHtmlUtil;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
public class WordTransHtmlServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("In WordTransHtmlServlet.");
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
JSONObject resultJson = new JSONObject();
resultJson.put("code", "2");
resultJson.put("msg", "参数不合法");
resultJson.put("htmlStr", "");
try {
String path = request.getParameter("path");
path = System.getProperty("user.dir") + "/webapps" + path;
String[] paths = path.split("\\.");
System.out.println("WordTransHtmlServlet paths:" + paths + " length:" + paths.length);
System.out.println("WordTransHtmlServlet Class1:" + System.getProperty("user.dir"));
System.out.println("WordTransHtmlServlet Class2:" + this.getClass().getResource("/").getPath());
System.out.println("WordTransHtmlServlet Class3:" + request.getSession().getServletContext().getRealPath("../"));
String type = "";
if (paths.length > 1) type = paths[1];
System.out.println("WordTransHtmlServlet type:" + type + " path:" + path);
resultJson = wordTransformHtml(type, path);
} catch (Exception e) {
e.printStackTrace();
}
PrintWriter out = response.getWriter();
out.write(resultJson.toString());
}
/**
* 转换doc格式word为html
*/
private JSONObject wordTransformHtml(String type, String filePath) {
JSONObject resultJson = new JSONObject();
resultJson.put("code", "1");
resultJson.put("msg", "转换失败");
resultJson.put("html", "");
try {
String wordHtml = "";
WordToHtmlUtil wordToHtmlUtil = new WordToHtmlUtil();
if ("doc".equals(type)) {
wordHtml = wordToHtmlUtil.docToHtml(filePath);
} else if ("docx".equals(type)) {
wordHtml = wordToHtmlUtil.docxToHtml(filePath);
}
System.out.println("docTransformHtml wordHtml:" + wordHtml);
resultJson.put("code", "0");
resultJson.put("msg", "转换成功");
resultJson.put("html", wordHtml);
} catch (Exception e) {
e.printStackTrace();
}
return resultJson;
}
/**
* 获取文件
*/
private void getFile(String fileName) {
try {
String path = System.getProperty("user.dir");
System.out.println("path1:" + path);
path = path.replaceAll("\\\\", "/");
System.out.println("path1:" + path);
String filePath = path + "/webapp" + fileName;
System.out.println("filePath:" + filePath);
File file = new File(filePath);
PrintFileContext(file);
} catch (Exception e) {
e.printStackTrace();
}
}
private static void PrintFileContext(File file) throws Exception {
InputStream in = new FileInputStream(file);
InputStreamReader isr = new InputStreamReader(in);
BufferedReader br = new BufferedReader(isr);
String line = "";
while (true) {
line = br.readLine();
if (line == null) {
break;
}
System.out.println(line);
}
br.close();
}
}
3.2.3 后端Word转换Html的工具
import fr.opensagres.poi.xwpf.converter.xhtml.Base64EmbedImgManager;
import fr.opensagres.poi.xwpf.converter.xhtml.XHTMLConverter;
import fr.opensagres.poi.xwpf.converter.xhtml.XHTMLOptions;
import org.apache.commons.io.FileUtils;
import org.apache.poi.hwpf.HWPFDocumentCore;
import org.apache.poi.hwpf.converter.WordToHtmlConverter;
import org.apache.poi.hwpf.converter.WordToHtmlUtils;
import org.apache.poi.hwpf.usermodel.Picture;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Base64;
public class WordToHtmlUtil {
public String docToHtml(String fileName) throws IOException, ParserConfigurationException, TransformerException {
String htmlStr = null;
try{
HWPFDocumentCore wordDocument = WordToHtmlUtils.loadDoc(new FileInputStream(fileName));
WordToHtmlConverter wordToHtmlConverter = new ImageConverter(
DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument()
);
wordToHtmlConverter.processDocument(wordDocument);
Document htmlDocument = wordToHtmlConverter.getDocument();
ByteArrayOutputStream out = new ByteArrayOutputStream();
DOMSource domSource = new DOMSource(htmlDocument);
StreamResult streamResult = new StreamResult(out);
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer serializer = transformerFactory.newTransformer();
serializer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
serializer.setOutputProperty(OutputKeys.INDENT, "yes");
serializer.setOutputProperty(OutputKeys.METHOD, "html");
serializer.transform(domSource, streamResult);
out.close();
htmlStr = new String(out.toByteArray());
}catch (Exception e){
e.printStackTrace();
}
return htmlStr;
}
//docx转换html
public String docxToHtml(String fileName) throws IOException {
String htmlStr = null;
try{
XWPFDocument docxDocument = new XWPFDocument(new FileInputStream(fileName));
XHTMLOptions options = XHTMLOptions.create();
//图片转base64
options.setImageManager(new Base64EmbedImgManager());
// 转换htm1
ByteArrayOutputStream htmlStream = new ByteArrayOutputStream();
XHTMLConverter.getInstance().convert(docxDocument, htmlStream, options);
htmlStr = htmlStream.toString();
}catch (Exception e){
e.printStackTrace();
}
return htmlStr;
}
public class ImageConverter extends WordToHtmlConverter {
public ImageConverter(Document document) {
super(document);
}
@Override
protected void processImageWithoutPicturesManager(Element currentBlock, boolean inlined, Picture picture){
try{
Element imgNode = currentBlock.getOwnerDocument().createElement("img");
StringBuffer sb = new StringBuffer();
sb.append(Base64.getMimeEncoder().encodeToString(picture.getRawContent()));
sb.insert(0, "data:" + picture.getMimeType() + ";base64,");
imgNode.setAttribute("src", sb.toString());
currentBlock.appendChild(imgNode);
}catch (Exception e){
e.printStackTrace();
}
}
}
}
4.总结
至此,便通过把word转换成html实现了复制word的内容到编辑器中的功能,图片也完美的复制过去了。