需求
需求这部分我就不发牢骚了,简单来说就是某些人因为看到别家的项目,觉得人家通过上传试卷的Word文档直接实现了录题功能眼馋了,想要我们也做一个.
现状
目前项目使用的是手工录题,一道一道的,比较繁琐,所以有些人就想要搬别人的东西过来.
通过几天的思考和查阅资料,知道POI有可以对Word处理的类和方法,但是呢,由于项目目前是通过富文本编辑器来录题,录过之后存入数据库的样式带有HTML标签的内容,在根据题目生成试卷的时候也是需要从标签中获取样式的.所以直接对Word处理是不现实的,于是考虑将Word转为HTML文件,再通过Jsoup中的方法对标签进行处理
设计思路
简单来说就是这个过程:
Word文档——HTML文件——Jsoup获取p标签——对标签内容进行处理
使用的类分别为
HWPFDocument获取Word文件
WordToHtmlConverter类实现转HTML
Jsoup的getElementsByTag获取HTML中的p标签
实现过程
97-2003版本Word(.doc)
对于Word2003(.doc)类型的文档的处理,如果导入文档报错,可将文档用Office打开,并另存为Word97-2003文档,即可使用.
(之前2007版本以上的.docx文档的转换报错,目前问题已经解决,见该部分后面.)
package com.education.modules.tool.oss.utils;
import com.education.common.utils.Util;
import com.education.modules.questions.entity.QuestionsEntity;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.converter.WordToHtmlConverter;
import org.apache.poi.xwpf.usermodel.IBodyElement;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFStyles;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import javax.lang.model.util.Elements;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class WordUtil {
public static void getDoc(){
//解析Word文档,暂时没有用了,用下面那个!
String path="D:\\testWord\\2019年江苏省南京市高一上学期期末考试_9099.docx";
InputStream is=null;
{
try {
is = new FileInputStream(path);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
XWPFDocument doc=null;
{
try {
doc = new XWPFDocument(is);
} catch (IOException e) {
e.printStackTrace();
}
}
List<XWPFParagraph> paragraph=doc.getParagraphs();
List<IBodyElement> bodyElements=doc.getBodyElements();
XWPFStyles styles=doc.getStyles();
System.out.println(paragraph);
System.out.println(bodyElements);
System.out.println(styles);
for(XWPFParagraph p:paragraph){
System.out.println(p.getParagraphText());
}
}
public static void wordToHtml(){
//读取Word文档转为HTML文件
String path="D:\\testWord\\2019年江苏省南京市高一上学期期末考试_90999.doc";
String imagePath="D:\\testWord\\image\\";
String htmlPath="D:\\testWord\\testHtml.html";
File file=new File(path);
OutputStreamWriter outputStreamWriter = null;
if(!file.exists()){
System.out.println("文件不存在");
}else try {
//加载word文档生成XWPF对象
InputStream in = new FileInputStream(file);
//XWPFDocument document = new XWPFDocument(in);
HWPFDocument doc=new HWPFDocument(in);
org.w3c.dom.Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
WordToHtmlConverter wordToHtmlConverter=new WordToHtmlConverter(document);
//保存图片,并返回图片的相对路径
wordToHtmlConverter.setPicturesManager((content, pictureType, name, width, height) -> {
try (FileOutputStream out = new FileOutputStream(imagePath + name)) {
out.write(content);
} catch (Exception e) {
e.printStackTrace();
}
return "image/" + name;
});
wordToHtmlConverter.processDocument(doc);
org.w3c.dom.Document htmlDocument = wordToHtmlConverter.getDocument();
DOMSource domSource = new DOMSource(htmlDocument);
StreamResult streamResult = new StreamResult(new File(htmlPath));
TransformerFactory tf = TransformerFactory.newInstance();
Transformer serializer = tf.newTransformer();
serializer.setOutputProperty(OutputKeys.ENCODING, "utf-8");
serializer.setOutputProperty(OutputKeys.INDENT, "yes");
serializer.setOutputProperty(OutputKeys.METHOD, "html");
serializer.transform(domSource, streamResult);
//XHTML的方法.用不了
/*XHTMLOptions options = XHTMLOptions.create();
//图片目录
options.setExtractor(new FileImageExtractor(new File(imagePath)));
//html中图片的路径
options.URIResolver((new BasicURIResolver("image")));
outputStreamWriter = new OutputStreamWriter(new FileOutputStream(htmlPaht), "utf-8");
XHTMLConverter xhtmlConverter = (XHTMLConverter) XHTMLConverter.getInstance();
xhtmlConverter.convert(document, outputStreamWriter, options);*/
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (TransformerConfigurationException e) {
e.printStackTrace();
} catch (TransformerException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
//getDoc();
//wordToHtml();
//读取解析之后的HTML文件,获取p标签并遍历.
File input = new File("D:\\testWord\\testHtml.html");
try {
Document doc = Jsoup.parse(input, "UTF-8", "http://example.com/");
org.jsoup.select.Elements pList=doc.getElementsByTag("p");
List<QuestionsEntity> questionList=new ArrayList<>();
int i=1;//题号
for(int j=0;j<pList.size();j++){
Element p=pList.get(j);
if(p.text().contains(i+".")){
QuestionsEntity question=new QuestionsEntity();
question.setStem(p.toString());
questionList.add(question);
i++;
}else if (p.text().contains("一、")||p.text().contains("二、")||p.text().contains("三、")||p.text().contains("四、")
||p.text().contains("五、")||p.text().contains("六、")||p.text().contains("七、")||p.text().contains("八、")
||p.text().contains("九、")||p.text().contains("十、")){
continue;
}else{
if(Util.isNotEmpty(questionList)&&questionList.size()>0){
QuestionsEntity q=questionList.get(i-2);
q.setStem(q.getStem()+p.toString());
}
}
}
System.out.println(questionList);
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 第一个 getDoc()方法,是我尝试解析Word文档时候用的,确实可以解析出文字和图片,但是没有样式,所以考虑了第二种方法.
- 第二个方法wordToHTML();是读取一个本地的Word文件,将它解析为HTML文件,并对其中的图片进行处理,返回相对路径.后续如果实际在项目中应用的话,应该会选择上传到云服务器,返回URL的方法.
- 最后main()方法里面,是我对HTML的一个处理,将HTML文件中的p标签的集合获取,将这个集合进行遍历,同时定义一个题号变量i,如果当前p标签中包含"题号."的字样,则说明这是一道题,将这个p标签中的内容set进一个新建Question对象中的题干属性.继续遍历,如果这一行不是大题题号什么的,就说明它是上一道题未完的部分,将它拼接进上一道Question对象的题干属性中.
最后返回一个Question的集合.
后期再项目中,可能考虑一个对象的其他各种各样的属性该如何赋值,例如科目,题型,创建人,知识点等等等等,以及如何通过读取标签,获取题型.
2007版本Word(.docx)
之前查看了对于.docx文件的处理方法,需要用到XWPF包和XHTMLConverter类,但是HTML相关的类导包一直出错,导包解决之后调用又出现错误,在参考了github上的一个例子之后得到了解决,这里贴一下代码顺便说一下解决过程.
首先是实现代码,与前面的类似,只是类不同而已,方法和过程都大概差不多
public static void docxToHtml(){
String path="D:\\testWord\\2019年江苏省南京市高一上学期期末考试_9099.docx";
String imagePath="D:\\testWord\\image\\";
String outPath="D:\\testWord\\testDocx.html";
File file=new File(path);
File imageFile=new File(imagePath);
if(!file.exists()){
System.out.println("文件不存在!");
}
try {
InputStream in=new FileInputStream(file);
XWPFDocument document=new XWPFDocument(in);
//存储图片
XHTMLOptions options=XHTMLOptions.create().URIResolver(new FileURIResolver(imageFile));
options.setExtractor(new FileImageExtractor(imageFile));
OutputStream out=new FileOutputStream(outPath);
/*Writer writer=new FileWriter(file,true);
IContentHandlerFactory factory = options.getContentHandlerFactory();
if (factory == null) {
factory = DefaultContentHandlerFactory.INSTANCE;
}
ContentHandler contentHandler = factory.create(out, writer, options);
//XHTMLConverter.getInstance().convert(document,out,options);*/
XHTMLConverter converter=new XHTMLConverter();
converter.convert(document,out,options);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
接下来就是问题,之前导包导的是这个
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>org.apache.poi.xwpf.converter.xhtml</artifactId>
<version>1.0.6</version>
</dependency>
运行会报错NoSuchMethodError.报错信息如下:
Exception in thread “main” java.lang.NoSuchMethodError: org.apache.poi.POIXMLDocumentPart.getPackageRelationship()Lorg/apache/poi/openxml4j/opc/PackageRelationship;
at org.apache.poi.xwpf.converter.core.styles.XWPFStylesDocument.getFontsDocument(XWPFStylesDocument.java:1479)
at org.apache.poi.xwpf.converter.core.styles.XWPFStylesDocument.(XWPFStylesDocument.java:190)
at org.apache.poi.xwpf.converter.xhtml.internal.styles.CSSStylesDocument.(CSSStylesDocument.java:100)
at org.apache.poi.xwpf.converter.xhtml.internal.XHTMLMapper.createStylesDocument(XHTMLMapper.java:147)
at org.apache.poi.xwpf.converter.core.XWPFDocumentVisitor.(XWPFDocumentVisitor.java:159)
at org.apache.poi.xwpf.converter.xhtml.internal.XHTMLMapper.(XHTMLMapper.java:137)
at org.apache.poi.xwpf.converter.xhtml.XHTMLConverter.convert(XHTMLConverter.java:72)
at org.apache.poi.xwpf.converter.xhtml.XHTMLConverter.doConvert(XHTMLConverter.java:63)
at org.apache.poi.xwpf.converter.xhtml.XHTMLConverter.doConvert(XHTMLConverter.java:38)
at org.apache.poi.xwpf.converter.core.AbstractXWPFConverter.convert(AbstractXWPFConverter.java:45)
at com.education.modules.tool.oss.utils.WordUtilForDOCX.docxToHtml(WordUtilForDOCX.java:47)
at com.education.modules.tool.oss.utils.WordUtilForDOCX.main(WordUtilForDOCX.java:92)
后来参考了https://github.com/opensagres/xdocreport/issues/208 这里,将依赖换成了
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.poi.xwpf.converter.xhtml</artifactId>
<version>2.0.1</version>
</dependency>
再次运行,未报错,且HTML文件正确生成,完美解决!!!
被旧的教程坑了!!!
特此记录!
局限
-
文档格式受限
在翻阅了很多类似的案例之后,看到Word转HTML基本都是使用POI来实现的,对于2003版本(.doc)使用HWPF,对于2007版本使用XWPF.
但在实际实现的过程中遇到了一个问题,那就是别人项目中的org.apache.poi.xwpf.converter.xhtml.XHTMLConverter
的这个类,找不到对应的Jar包,所以目前只实现了使用HWPF的方法,后续可能会再找相关资料尽量实现出来. -
对象操作不可预期
目前对于p标签整合为对象的过程过于简单,后续实际操作可能有更加复杂繁琐的属性需要设置,只能到时候遇到再说了.