JAVA实现word和pdf的数据解析(图片,表格等)转HTML存储以及海量搜索
业务需求:需求方有大量的word和pdf文件,每次找资料电子手册很麻烦,需要做一个导入文件后根据关键字搜索(类似于Baidu)查看文件的功能,详情有左侧文件目录 右侧文件详细内容
一、java代码层面的解析
导入文件把文件转html数据
实现文件转html并解析表格等需要一个Jacob的jar包和dll文件 配置
链接:https://pan.baidu.com/s/1IZPtVomOIWdDWqD4XkPSlw
提取码:jayj
-
下载JACOB的JAR和DLL文件:
-
从JACOB的官方网站或上面我提供的网盘下载最新的JAR和DLL文件。
个人选择了比较新版本的jacob的jar包和dll文件 可以下面自行下载或者去jacob官网下载
-
-
将JAR文件添加到项目的libs目录中:
- 在你的Gradle项目中创建一个
libs
目录(如果还没有的话)。 - 将下载的JAR文件复制到
libs
目录中。
- 在你的Gradle项目中创建一个
-
在Gradle构建脚本中添加JAR作为依赖:
编辑你的build.gradle
文件,并在dependencies
部分添加对JAR文件的引用这个是gradle的,如果是maven自行转一下
-
配置DLL文件的路径:
- 由于JACOB包含本地代码(DLL文件),你需要确保Java能够找到它。你可以将DLL文件放在Java的系统路径上也就是你的jdk文件的bin下面,或者在你的Java代码中通过
System.setProperty("java.library.path", "<path_to_dll>")
来设置它。
- 由于JACOB包含本地代码(DLL文件),你需要确保Java能够找到它。你可以将DLL文件放在Java的系统路径上也就是你的jdk文件的bin下面,或者在你的Java代码中通过
-
同步Gradle项目:
- 在IDE中(如IntelliJ IDEA或Android Studio),执行Gradle同步操作,以便IDE能够识别新的依赖。
-
编写代码以使用JACOB:
一旦JAR文件被添加到项目中,你就可以像你在示例中那样使用ActiveXComponent
类来启动Word并与之交互了dependencies { // ... 其他依赖 ... // 添加jsoup的依赖 implementation 'org.jsoup:jsoup:1.17.2' // 添加JACOB的依赖 implementation files('libs/jacob.jar') // 假设你的JAR文件名为jacob.jar }
写一个main测试一下解析的结果 以及一些业务需要的方法附上代码
import com.jacob.activeX.ActiveXComponent;
import com.jacob.com.ComThread;
import com.jacob.com.Dispatch;
import com.jacob.com.Variant;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public static void main(String[] args) {
//
String filepath = "C:\\Users\\Administrator\\Desktop\\文件\\";
String htmlpath = "C:\\Users\\Administrator\\Desktop\\文件\\html\\";
String filename = "(无水印)01.《图片定价议价规则(试行)》(国办函【2019】11号)";
String ext = ".docx";
try {
filedata(filepath, filename,htmlpath,ext);
} catch (Exception e) {
throw new RuntimeException(e);
}
String htmlFilePath = "C:\\Users\\Administrator\\Desktop\\文件\\html\\(无水印)01.《图片定价议价规则(试行)》(国办函【2019】11号).html";
String htmlString = toHtmlString(htmlFilePath);
List<String> dlList = null;
try {
dlList = getDLList(htmlFilePath);
} catch (IOException e) {
throw new RuntimeException(e);
}
for (int i = 0; i < dlList.size(); i++) {
System.out.println("第"+i+"条:"+dlList.get(i));
}
/**
* 获取所有表格
* */
List<String> bgList = null;
Elements allTable =null;
try {
allTable = getAllTable(htmlFilePath);
} catch (IOException e) {
throw new RuntimeException(e);
}
for (int i = 0; i < allTable.size(); i++) {
System.out.println("第"+i+"条:"+allTable .get(i));
}
}
public String getRemoveTableStr(String htmlFilePath) throws IOException {
String content = new String(Files.readAllBytes(Paths.get(htmlFilePath)),"GBK");
String specialStr = removeContentStyle(content);
// 使用 Jsoup 解析 HTML 字符串
Document doc = Jsoup.parse(specialStr);
// 获取纯文本
String plainText = doc.text();
// System.out.println(plainText);
return plainText;
}
/**
* 获取所有的Table
* @param htmlFilePath
* @return
* @throws IOException
* 2024年6月19日下午3:36:39
*/
public Elements getAllTable(String htmlFilePath) throws IOException {
List<String> bgList = new ArrayList<>();
// 加载 HTML 文件
File input = new File(htmlFilePath);
Elements tables=null;
try {
// 解析 HTML 文件
Document doc = Jsoup.parse(input, null);
// 选择所有的表格元素
tables = doc.select("table");
// 遍历每个表格元素
for (Element table : tables) {
// 获取当前表格中的所有行
Elements rows = table.select("tr");
//创建StringBuilder类的实例
StringBuilder builder = new StringBuilder();
// 遍历每行
for (Element row : rows) {
// 输出行内容
// System.out.println("行内容:");
// System.out.println(row.text());
//将获取的text写入StringBuilder容器
builder.append(row.text());
builder.append("\r\n");
}
// System.out.println("-----------------------------------------------");
// System.out.println(builder.toString());
// System.out.println("-----------------------------------------------");
bgList.add(builder.toString());
}
} catch (IOException e) {
e.printStackTrace();
}
return tables;
}
/**
* 获取以10个逗号为一个段落的list
* @param
* @return
* 2024年6月19日下午2:04:17
* @throws IOException
*/
public List<String> getDLList(String htmlFilePath) throws IOException {
/**
* 删除Table表格
* */
String content = getRemoveTableStr(htmlFilePath);
// System.out.println(content);
// String[] jhArr = content.split("。");
// List<String> dlList = new ArrayList<String>();//10个句号为一个段落
// String jhStrTen=new String();
// int lastDl=0;
// for (int i = 0; i < jhArr.length; i++) {
// if (i % 10 == 0 && i>0) {
// dlList.add(jhStrTen);
// if (jhArr.length-i>10) {
// jhStrTen="";
// }
// if (jhArr.length-i<=10 && lastDl==0) {
// lastDl = 1;
// jhStrTen = "";
// }
// }else{
// jhStrTen = jhStrTen +jhArr[i]+"。";
// if ((i+1) == jhArr.length) {
// dlList.add(jhStrTen);
// }
// }
// }
List<String> dlList = new ArrayList<>();
int index = 0;
while (index < content.length()) {
int endIndex = index + 1; // 初始化结束索引
for (int i = 0; i < 10; i++) {
endIndex = content.indexOf("。", endIndex + 1); // 查找句号
if (endIndex == -1) {
break; // 如果没有找到句号,跳出循环
}
}
if (endIndex == -1) {
endIndex = content.length(); // 如果不满十个句号,结束索引为字符串长度
}
dlList.add(content.substring(index, endIndex)); // 将段落添加到列表中
index = endIndex + 1; // 更新起始索引
}
return dlList;
}
/**
* @param
* @param filename 不带后缀名的文件
* @throws Exception
* 2024年6月18日下午3:31:55
*/
public void filedata(String fileDir, String filename,String htmlPath,String ext) throws Exception{
final ExecutorService exec = Executors.newFixedThreadPool(1);
Callable<String> call = new Callable<String>() {
public String call() throws Exception {
//开始执行耗时操作
// 文件路径不存在则进行创建
File dir = new File(htmlPath);
if (!dir.exists()) {
if (!dir.mkdirs()) {
throw new IOException("无法创建此目录: " + htmlPath);
}
}
if(ext.equals(".pdf")){
//pdf
PDFtoWord(fileDir + filename + ".PDF", htmlPath + filename + ".docx");
Path htmlFilePath = Paths.get(htmlPath, filename + ".html");
if (!Files.exists(htmlFilePath)) {
wordToHtml(fileDir + filename + ext, htmlPath + filename + ".html");
}
}else if(ext.equals(".docx")){
//docx
Path htmlFilePath = Paths.get(htmlPath, filename + ".html");
if (!Files.exists(htmlFilePath)) {
wordToHtml(fileDir + filename + ext, htmlPath + filename + ".html");
}
}
return "线程执行完成.";
}
};
try {
Future<String> future = exec.submit(call);
String obj = future.get(1000 * 600, TimeUnit.MILLISECONDS); //任务处理超时时间设为 1 秒
System.out.println("文件转换:" + obj);
} catch (Exception e) {
//关闭Acrobat
String command = "taskkill /f /im Acrobat.exe";
Runtime.getRuntime().exec(command);
throw e;
} finally {
// 关闭线程池
exec.shutdown();
}
}
public boolean PDFtoWord(String source, String target) {
ComThread.InitSTA();//初始化com的线程
// pdfActiveX PDDoc对象 主要建立PDF对象
ActiveXComponent app = null ;
try {
File inPath = new File(source);
File outPath = new File(target);
app = new ActiveXComponent("AcroExch.PDDoc");
// PDF控制对象
Dispatch pdfObject = app.getObject();
long start = System.currentTimeMillis();
// 打开PDF文件,建立PDF操作的开始
Dispatch.call(pdfObject, "Open", new Variant(inPath.getAbsolutePath()));
Variant jsObj = Dispatch.call(pdfObject, "GetJSObject");
Dispatch.call(jsObj.getDispatch(), "SaveAs", outPath.getPath(), "com.adobe.acrobat.docx");
app.invoke("Close");
// 关闭PDF
app.invoke("Close", new Variant[] {});
long end = System.currentTimeMillis();
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
ComThread.Release();//关闭com的线程 真正kill进程
}
return true;
}
public boolean wordToHtml(String inPath, String toPath) {
ComThread.InitSTA();//初始化com的线程
// 启动word
ActiveXComponent axc = new ActiveXComponent("Word.Application");
boolean flag = false;
try {
// 设置word不可见
axc.setProperty("Visible", new Variant(false));
Dispatch docs = axc.getProperty("Documents").toDispatch();
// 打开word文档
Dispatch doc = Dispatch.invoke(
docs,
"Open",
Dispatch.Method,
new Object[] { inPath, new Variant(false), new Variant(true) },
new int[1]).toDispatch();
// 作为html格式保存到临时文件
Dispatch.invoke(doc, "SaveAs", Dispatch.Method, new Object[] {
toPath, new Variant(8) }, new int[1]);
Variant f = new Variant(false);
Dispatch.call(doc, "Close", f);
axc.invoke("Quit", new Variant[] {});
flag = true;
return flag;
} catch (Exception e) {
e.printStackTrace();
return flag;
} finally {
ComThread.Release();//关闭com的线程 真正kill进程
}
}
/**
* 读取本地html文件里的html代码
* @return
*/
public String toHtmlString(String htmlFilePath) {
// 获取HTML文件流
Path filePath = Paths.get(htmlFilePath);
StringBuffer htmlSb = new StringBuffer();
try {
BufferedReader br = new BufferedReader(new InputStreamReader(
new FileInputStream(filePath.toFile()), "GBK"));
while (br.ready()) {
htmlSb.append(br.readLine());
}
// HTML文件字符串
String htmlStr = htmlSb.toString();
//todo 处理html文本 添加预处理代码
Document doc = Jsoup.parse(htmlStr);
// 选择所有<p>标签
for (Element p : doc.select("p")) {
// 添加ID属性(例如,使用"id_" + 索引作为ID值)
p.attr("id", "id_" + doc.select("p").indexOf(p));
if (p.text().matches("(?i)第[\\u4e00-\\u9fa5]+章.*")) {
if(p.hasClass("MsoNormal")) {
p.addClass("level1");
}
}
// 检查文本是否包含“第*条”这样的模式(这里假设*是文本)
if (p.text().matches("(?i)第[\\u4e00-\\u9fa5]+条.*")) { // 使用正则表达式匹配“第”后面跟着一个或多个数字,然后是“条”
// 添加class="level2"属性
p.addClass("level2"); // 如果元素已经有其他class,这将替换它。如果你想追加,请使用p.addClass("level2");
}
}
htmlStr=doc.html();
// 返回经过清洁的html文本
try (BufferedWriter writer = Files.newBufferedWriter(filePath, StandardCharsets.UTF_8)) {
writer.write(htmlStr);
}
br.close();
// 删除临时文件
//file.delete();
return htmlStr;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 清除文件中的table
*
* @param content
* 公告内容
* @return 字符串结果集
*/
public static String removeContentStyle(String content) {
String regEx = "<table(.*?)</table>";
Pattern p = Pattern.compile(regEx);
Matcher m = p.matcher(content);
if (m.find()) {
content = m.replaceAll("");
}
// String regEx2 = " style=\"([\\s\\S]*?)\"";
// Pattern p2 = Pattern.compile(regEx2);
// Matcher m2 = p2.matcher(content);
// if (m2.find()) {
// content = m2.replaceAll("");
// }
// String regEx3 = " border=\"(.*?)\"";
// Pattern p3 = Pattern.compile(regEx3);
// Matcher m3 = p3.matcher(content);
// if (m3.find()) {
// content = m3.replaceAll(" border=\"1\" ");
// }
//
// String regEx4 = " class=.*?\\>";
// Pattern p4 = Pattern.compile(regEx4);
// Matcher m4 = p4.matcher(content);
// if (m4.find()) {
// content = m4.replaceAll("\\>");
// }
// String regEx5 = "\\<!--(.*?)--\\>";
// Pattern p5 = Pattern.compile(regEx5);
// Matcher m5 = p5.matcher(content);
// if (m5.find()) {
// content = m5.replaceAll("");
// }
// String regEx6 = "\\<o:p(.*?)/o:p\\>";
// Pattern p6 = Pattern.compile(regEx6);
// Matcher m6 = p6.matcher(content);
// if (m6.find()) {
// content = m6.replaceAll("");
// }
// String regEx7 = "\\<!(.*?)\\>";
// Pattern p7 = Pattern.compile(regEx7);
// Matcher m7 = p7.matcher(content);
// if (m7.find()) {
// content = m7.replaceAll("");
// }
// String regEx8 = "\\<font(.*?)\\>";
// Pattern p8 = Pattern.compile(regEx8);
// Matcher m8 = p8.matcher(content);
// if (m8.find()) {
// content = m8.replaceAll("");
// }
return content;
}
二、 解析完的html数据或者是纯文本数据导入es
目前es和kibana我选择最新版的
PUT /word_index
{
"settings": {
"number_of_shards": 1,
"analysis": {
"analyzer": {
"ik_max_word":{
"type": "ik_max_word"
},
"ik_smart":{
"type":"ik_smart"
}
}
}
},
"mappings": {
"properties": {
"content":{
"type": "text",
"analyzer": "ik_smart"
}
}
}
}
先来建一个索引(相当于数据库的表 用来存导入的word文本数据 后续搜索关键字用)
ik分词器用ik_smart智能 的就够了
然后测试一下搜索和高亮
GET /word_index/_search
{
"query": {
"match": {
"content": "我的关键字"
}
},
"highlight": {
"fields": {
"content":{
"fragment_size": 100,
"number_of_fragments": 5 ,
"fragmenter": "simple"
}
},
"pre_tags": ["<span style='color:#F00'>"],
"post_tags": ["</span>"]
}
}
一般来我喜欢返回前端高亮文本的样式是红色
解释一下这些参数
fragment_size
fragment_size
参数定义了在每个高亮片段中返回的字符数。这个值是一个近似值,因为Elasticsearch会尝试在单词边界上分割文本,而不是简单地按照字符数来截断文本。
number_of_fragments
number_of_fragments
参数定义了每个字段应返回的高亮片段的数量。默认情况下,Elasticsearch只返回一个片段,但你可以通过增加这个值来返回更多的片段。
fragmenter类型
1. simple
- 行为:简单地按照
fragment_size
参数指定的字符数将文本分割成片段。 - 示例:如果你设置
fragment_size
为100,则每个片段大约包含100个字符。
2. span
- 行为:尝试在单词边界上分割文本,同时尽量遵守
fragment_size
参数。如果单词太大,它可能会超过fragment_size
。 - 优点:通常会产生更有意义的片段,因为它们在单词边界上分割。
3. sentence
- 行为:尝试在句子边界上分割文本。如果句子太短,可能会返回少于
fragment_size
的片段。 - 适用场景:当你想在句子级别进行高亮时,这个片段器特别有用。
测试完成之后转成java代码 需要一些步骤
java进行http请求可能需要证书 上面网盘内下载并操作
implementation 'co.elastic.clients:elasticsearch-java:8.13.0'
implementation 'org.elasticsearch.client:elasticsearch-rest-client:8.13.0'
implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch'
依赖是我用的gradle的 自行gpt转maven
public void main(String[] args) throws Exception {
queryByContent("关键字")
initEsConnection();
transport.close();
}
public static void initEsConnection() throws Exception {
// 获取客户端对象
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
// 注意这里改成自己的账号密码
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));//es的账号密码
//Path caCertificatePath = Paths.get("D:\\devlop\\jdk17\\lib\\security\\http_ca.crt"); 这个证书先放到本地,部署的时候线上放一个
Path caCertificatePath = Paths.get("线上路径证书地址");//例如 /home/code/item/cart/http_cart
CertificateFactory factory = CertificateFactory.getInstance("X.509");
Certificate trustedCa;
try (InputStream is = Files.newInputStream(caCertificatePath)) {
trustedCa = factory.generateCertificate(is);
}
KeyStore trustStore = KeyStore.getInstance("pkcs12");
trustStore.load(null, null);
trustStore.setCertificateEntry("ca", trustedCa);
SSLContextBuilder sslContextBuilder = SSLContexts.custom().loadTrustMaterial(trustStore, null);
final SSLContext sslContext = sslContextBuilder.build();
// 主机名改成自己的
RestClientBuilder builder = RestClient.builder(
new HttpHost(hostAddress, hostPort, "http"))
.setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {
@Override
public HttpAsyncClientBuilder customizeHttpClient(
HttpAsyncClientBuilder httpClientBuilder) {
return httpClientBuilder.setSSLContext(sslContext)
.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
.setDefaultCredentialsProvider(credentialsProvider);
}
});
RestClient restClient = builder.build();
transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
// 将新创建的ElasticsearchClient实例赋值给类级别的静态变量client
client = new ElasticsearchClient(transport);
// 异步
// ElasticsearchAsyncClient asyncClient = new ElasticsearchAsyncClient(transport);
//查询索引
GetIndexRequest getIndexRequest = new GetIndexRequest.Builder().index("dev_test_index").build();
final GetIndexResponse getIndexResponse = client.indices().get(getIndexRequest);
System.out.println( "索引查询成功: :" + getIndexResponse.result());
// IndexRequest.Builder<Object> index = new IndexRequest.Builder<>().index("my_index");
}
//新增文档根据的文本内容
public static String createDoc(String htmlString)throws Exception{
Map<String, Object> jsonMap = new HashMap<>();
jsonMap.put("content",htmlString);
String id = SpringContextUtils.getSnowflakeGenerator().next().toString();
IndexRequest indexRequest = new IndexRequest.Builder<>()
.index("word_index")
.id(id)
.document(jsonMap)
.build();
final IndexResponse index = client.index(indexRequest);
//关闭连接
transport.close();
return id;
}
//根据id查看文档
public static Object queryDocById(String id)throws Exception{
SearchResponse<Object> response = client.search(s -> s
.index("word_index")
.query(q -> q.term(t -> t.field("_id").value(id))),
Object.class);
List<Hit<Object>> hits = response.hits().hits();
// 解析数据
Object source=null;
for (Hit<Object> hit : hits) {
source = hit.source();
System.out.println("查询结果为:"+source);
}
return source;
}
public void deleteDoc(Long id)throws Exception{
// 删除文档
DeleteRequest deleteRequest = new DeleteRequest.Builder().index("word_index").id(id.toString()).build();
DeleteResponse deleteResponse = client.delete(deleteRequest);
System.out.println("删除操作"+deleteResponse);
//关闭连接
transport.close();
}
//根据搜索的关键字查询文档并返回一些相关数据 伪代码,可以自己根据业务修改
public static PageData queryByContent(int offset,int pageSize,String value,List<String> dictIds){
SearchRequest request = SearchRequest.of(t->t
.index("word_index").size(10000)
.query(nq->nq.match(mtq->mtq
.field("content")
.query(value).analyzer("ik_smart")))
.highlight(h->h.fields("content",f->f .preTags("<span style='color:#F00'>")
.postTags("</span>")))
);
SearchResponse searchResponse = null;
try {
PageData<Object> page = new PageData<>();
ArrayList<Object> list = new ArrayList<>();
searchResponse = client.search(request, JSONObject.class);
List<Hit<Object>> hits = searchResponse.hits().hits();
int total=0;
for (Hit<Object> hit : hits) {
HashMap<String, Object> map = new HashMap<>();
map.put("id",hit.id());
}
page.setTotal(searchResponse.hits().total().value());
return page;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
三、java的word转html另一种方式不需要引入jacob(jacob这个jar包只适用于windows插件,部署linux的时候不管用)
// Apache POI 依赖项
implementation 'org.apache.poi:poi:3.14'
implementation 'org.apache.poi:poi-scratchpad:3.14'
implementation 'org.apache.poi:poi-ooxml:3.14'
implementation 'org.apache.poi:ooxml-schemas:1.3'
// xdocreport 依赖项
implementation group: 'fr.opensagres.xdocreport', name: 'xdocreport', version: '1.0.6'
implementation 'org.apache.pdfbox:pdfbox:2.0.29' //解析pdf
implementation group: 'org.odftoolkit', name: 'odfdom-java', version: '0.8.7'
// 添加jsoup的依赖
implementation 'org.jsoup:jsoup:1.17.2'
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.14</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>3.14</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.14</version>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>xdocreport</artifactId>
<version>1.0.6</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>3.14</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>ooxml-schemas</artifactId>
<version>1.3</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.11.3</version>
</dependency>
两种格式的依赖
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.poi.xwpf.converter.core.BasicURIResolver;
import org.apache.poi.xwpf.converter.core.FileImageExtractor;
import org.apache.poi.xwpf.converter.core.IXWPFConverter;
import org.apache.poi.xwpf.converter.xhtml.XHTMLConverter;
import org.apache.poi.xwpf.converter.xhtml.XHTMLOptions;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.jky.common.utils.SpringContextUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.TextNode;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@Service
public class ExtractDoc {
@Value("${file.sysPath}")
private String sysPath;
@Value("${file.uploadPath}")
private String uploadPath;
public static void main(String[] args) {
String fileDir = "C:\\Users\\Administrator\\Desktop\\解析docx数据\\";
String htmlPath = "C:\\Users\\Administrator\\Desktop\\解析docx数据\\html\\";
String filename="xxxdocx数据文件";
docToHtml(fileDir,filename,htmlPath);
}
public static String docToHtml(String fileDir, String filename,String htmlPath) {
//把docx文件转成html存放到指定路径
try {
XWPFDocument document = new XWPFDocument(new FileInputStream(fileDir+filename+".docx"));
XHTMLOptions options = XHTMLOptions.create();
// 存放图片的文件夹
options.setExtractor(new FileImageExtractor(new File(htmlPath+"images")));
// html中图片的路径
options.URIResolver(new BasicURIResolver(sysPath+"images"));
IXWPFConverter<XHTMLOptions> xhtmlConverter = XHTMLConverter.getInstance();
xhtmlConverter.convert(document, new FileOutputStream(htmlPath+filename+".html"), options);
} catch (Exception e) {
throw new RuntimeException(e);
}
return htmlPath+filename+".html";
}
/**
* 读取本地html文件里的html代码
* @return
*/
//中间一坨是我的业务需求代码,需要给内容的样式加一个id,搜索之后可以定位到html的哪个p标签 (先给p标签加上id和等级,然后给纯文本加上id,返回纯文本存到es)
public String toHtmlString(String htmlFilePath) {
// 获取HTML文件流
Path filePath = Paths.get(htmlFilePath);
StringBuffer htmlSb = new StringBuffer();
try {
BufferedReader br = new BufferedReader(new InputStreamReader(
new FileInputStream(filePath.toFile()), "UTF-8"));
while (br.ready()) {
htmlSb.append(br.readLine());
}
// HTML文件字符串
String htmlStr = htmlSb.toString();
//todo 处理html文本 添加预处理代码
Document doc = Jsoup.parse(htmlStr);
// 选择<head>元素,如果不存在则创建一个
Element head = doc.head();
if (head == null) {
head = new Element("head");
doc.appendChild(head); // 添加到文档的开头
}
// 创建一个<meta charset="utf-8">元素
Element meta = new Element("meta")
.attr("charset", "utf-8")
.attr("http-equiv", "Content-Type")
.attr("content", "text/html; charset=utf-8"); // 完整版本,包括http-equiv和content属性
// 将<meta>元素添加到<head>元素中
head.appendChild(meta);
// 选择所有<p>标签
for (Element p : doc.select("p")) {
// // 添加ID属性(例如,使用"id_" + 索引作为ID值)
p.attr("id", doc.select("p").indexOf(p)+"");
if (p.text().matches("(?i)第[\\u4e00-\\u9fa5]+章.*")) {
if(p.hasClass("MsoNormal")) {
p.addClass("level1");
}
}
// 检查文本是否包含“第*条”这样的模式(这里假设*是文本)
if (p.text().matches("(?i)第[\\u4e00-\\u9fa5]+节.*")) { // 使用正则表达式匹配“第”后面跟着一个或多个数字,然后是“节”
// 添加class="level2"属性
p.addClass("level2"); // 如果元素已经有其他class,这将替换它。如果你想追加,请使用p.addClass("level2");
}
// 检查文本是否包含“第*条”这样的模式(这里假设*是文本)
if (p.text().matches("(?i)第[\\u4e00-\\u9fa5]+条.*")) { // 使用正则表达式匹配“第”后面跟着一个或多个数字,然后是“条”
// 添加class="level3"属性
p.addClass("level3");
}
}
htmlStr=doc.html();
// 返回经过清洁的html文本
try (BufferedWriter writer = Files.newBufferedWriter(filePath, StandardCharsets.UTF_8)) {
writer.write(htmlStr);
}
for (Element p : doc.select("p")) {
String id = "" + doc.select("p").indexOf(p); // 这里的 index 可能会因为动态文档而不准确
TextNode idTextNode = new TextNode("<id=" + id + "> "); // 第二个参数是基准 URL,这里不需要
TextNode contentTextNode = new TextNode(p.text()); // 第二个参数是基准 URL,这里不需要
// 将 ID 文本节点和原始文本节点添加到 <p> 标签中
p.appendChild(idTextNode);
// p.appendChild(contentTextNode);
}
String content=doc.body().text();
br.close();
// 删除临时文件
//file.delete();
return content;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
前端就可以根据p标签的格式和等级进行一个目录树以及章节锁定的业务
当然这个word文章格式规则是后端来制定的
ok到此 java的解析工作和存入es以及用es查询工作就到这里了
希望可以帮助你。