最近在做一个解析PDF文件的功能,试了很方法,最后终于成功,在这里给大家分享一下。很多PDF解析的API或工具都有一些问题,我尝试过如pdf2htmlEX、xpdf、pdfbox等API或工具,效果都不太理想,后来无意中发现了pdfdom,pdfdom是一个JavaAPI,它是在pdfbox的基础上进行了扩展,专门用于解析PDF文件生成HTML文件,效果非常好,下面我们来看一下具体如何实现。
net.sf.cssbox
pdf2dom
1.6
org.apache.pdfbox
pdfbox
2.0.4
org.apache.pdfbox
pdfbox-tools
2.0.4
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
Title
这里使用的是异步上传,使用了一个js插件ajaxfileupload.js
$(function () {
//当file的内容发生改变时,上传pdf文件并将文件转换成html文件
$('#file').change(function () {
uploadPdf();
});
});
/**
* 异步上传pdf文件
*/
function uploadPdf() {
//异步上传文件
$.ajaxFileUpload({
url: 'pdftohtml',//请求的url
secureuri: false,//设置是否需要安全协议,一般为false
type: 'post',//请求方式
fileElementId: 'file',//文件域的id属性值
dataType: 'json',
success: function (data) {
if (data.code == 0) {
alert(data.message);
} else {
//将返回的文件名设置到内嵌框架的src属性中进行展示(本来是想把html文件引入jsp的,结果发现生成的是xhtml,不兼容,就使用了内嵌框架)
$('#text_iframe').attr('src', 'pdfhtml/' + data.message);
}
}
});
//再次绑定改变事件
$('#file').change(function () {
uploadPdf();
});
}
package com.mengfei.controller;
import com.mengfei.util.PdfConvertUtil;
import com.mengfei.util.ResponseInfoUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
@Controller
public class PdfConvertController {
@PostMapping("/pdftohtml")
@ResponseBody
public ResponseInfoUtil pdftohtml(MultipartFile file, HttpServletRequest request) {
ResponseInfoUtil responseInfo = new ResponseInfoUtil();
responseInfo.setCode(0);
PdfConvertUtil pdfConvertUtil = new PdfConvertUtil();
String pdfName = file.getOriginalFilename();
int lastIndex = pdfName.lastIndexOf(".pdf");
String fileName = pdfName.substring(0, lastIndex);
String htmlName = fileName + ".html";
String realPath = request.getSession().getServletContext().getRealPath("/pdfhtml");
String htmlPath = realPath + "\\" + htmlName;
try {
pdfConvertUtil.pdftohtml(file.getBytes(), htmlPath);
responseInfo.setCode(1);
responseInfo.setMessage(htmlName);
} catch (Exception e) {
responseInfo.setMessage("读写文件出现异常,请重新尝试!");
e.printStackTrace();
}
return responseInfo;
}
}
package com.mengfei.util;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.fit.pdfdom.PDFDomTree;
import java.io.*;
public class PdfConvertUtil {
public void pdftohtml(byte[] bytes, String htmlPath) throws Exception {
//加载PDF文档
PDDocument document = PDDocument.load(bytes);
//将字节流转换成字符流
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(htmlPath)),"UTF-8"));
//实例化pdfdom树对象
PDFDomTree pdfDomTree = new PDFDomTree();
//开始写入html文件
pdfDomTree.writeText(document, out);
//在文件末尾写入要引入的js,因为我将转换的html文件放在了webapp/pdfhtml文件夹下,所以这两个js文件也要放在pdfhtml文件夹下
out.write("\n" +
"");
out.flush();
out.close();
document.close();
}
}
有了转换PDF文件的工具类,再去转换PDF文件并不难,但是要从html文件中提取json数据,并按照文本的顺序进行从上到下的排列并设置到表格中,这个过程还是蛮麻烦的。我们先观察转换好的HTML,会发现HTML文档中的节点都是使用了绝对定位,这也是保持文档不乱的最好方法。本来想使用每个节点的id属性进行从上到下的排序,结果发现有些地方不知道什么原因,id值很大跑到了最下面,但top值为负数,所以这里使用top值来进行分行,top值相同的肯定是在同一行,通过截取top值前面的数字来判断大小进行上下顺序的排列,行排出来了再分出列,同样的道理将每一行中的节点left值通过截取前面的数字来判断大小进行左右顺序的排列,下面看代码实现。
/**
* 获取整个html页面的json数据
* @returns {Array}
*/
function getPageJson() {
var pages = $('.page');
var pageJson = [];
$.each(pages, function () {
//将html中的数据提取成json格式的数组
var rowJson = [];
var rows = $(this).find('.p');
for (var i = 0; i < rows.length; i++) {
var topValue = $(rows[i]).css('top');
var leftValue = $(rows[i]).css('left');
var id = rows[i].id;
var textValue = $(rows[i]).text();
var topFloatValue = getFloatValue(topValue);
var leftFloatValue = getFloatValue(leftValue);
rowJson.push({
id: id,
topFloatValue: topFloatValue,
leftFloatValue: leftFloatValue,
textValue: textValue
});
}
//根据top值对json数组进行排序,默认为升序
rowJson.sort(sequenceTop);
//对json数组进行分组,分出行数据
var map = {},
groupJson = [];
for (var i = 0; i < rowJson.length; i++) {
var row = rowJson[i];
//如果为undefined,就添加一个以topFloatValue为键的数组,用来存放行数据
if (map[row.topFloatValue] == undefined) {
groupJson.push({trKey: row.topFloatValue, trData: [row]});
//为map[row.topFloatValue]添加值,防止添加重复的键
map[row.topFloatValue] = row;
} else {
for (var j = 0; j < groupJson.length; j++) {
var groupRow = groupJson[j];
//如果groupJson数组中已经存在以topFloatValue为键的数组,则在此行中添加行数据
if (row.topFloatValue == groupRow.trKey) {
groupRow.trData.push(row);
}
}
}
}
//根据left值对json数组再次进行排序,默认为升序
for (var t = 0; t < groupJson.length; t++) {
var trData = groupJson[t].trData;
if (trData.length > 1) {
trData.sort(sequenceLeft);
}
}
//将分组后的json添加进pageJson中
var pageId = this.id;
pageJson.push({pageId: pageId, pageData: groupJson});
});
return pageJson;
}
/**
* 根据top值进行排序的方法
* @param {Object} a
* @param {Object} b
*/
function sequenceTop(a, b) {
return a.topFloatValue - b.topFloatValue;
}
/**
* 根据left值进行排序的方法
* @param {Object} a
* @param {Object} b
*/
function sequenceLeft(a, b) {
return a.leftFloatValue - b.leftFloatValue;
}
/**
* 获取绝对定位的数值
* @param {Object} value
*/
function getFloatValue(value) {
var pxIndex = value.lastIndexOf('px');
var strValue = value.substring(0, pxIndex);
var floatValue = parseFloat(strValue);
return floatValue;
}
/**
* 将解析的json数据再次进行处理并添加到内嵌框架的table中
*/
function intoTable() {
var table = table_iframe.window.getTable();
//将table中的html节点清空
table.find('tbody').html('');
//获取iframe中pdfhtml文件中的json格式数据
var pageJson = text_iframe.window.getPageJson();
var maxLengths = [];
for (var t = 0; t < pageJson.length; t++) {
var pageData = pageJson[t].pageData;
//计算出trData中length最大的值,作为列的最大值
var lengths = [];
for (var k = 0; k < pageData.length; k++) {
lengths.push(pageData[k].trData.length);
}
var maxLength = Math.max.apply(null, lengths);
maxLengths.push(maxLength);
}
var finalMaxLength = Math.max.apply(null, maxLengths);
for (var t = 0; t < pageJson.length; t++) {
var pageData = pageJson[t].pageData;
for (var i = 0; i < pageData.length; i++) {
var trData = pageData[i].trData;
var tdHtml = '';
for (var j = 0; j < finalMaxLength; j++) {
//如果length<=1,一般为无用数据,排除掉
if (trData.length <= 1) {
continue;
}
//如果行数据下标的值 >=最大length时,才会使用下标取textValue,否则textValue值为''
var textValue = '';
if (j <= trData.length - 1) {
textValue = trData[j].textValue;
}
tdHtml += '' + textValue + ' ';
}
//如果是有用的数据,才会添加到table中
if (trData.length > 1) {
var trHtml = '' + tdHtml + ' ';
table.find('tbody').append(trHtml);
}
}
}
}
此方式仅供大家参考,如果有不对的地方,欢迎指正!