Java解析pdf格式發(fā)票的代碼實(shí)現(xiàn)
發(fā)票樣式

發(fā)票內(nèi)容解析
引用Maven
使用pdfbox
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.24</version> <!-- 請(qǐng)檢查最新版本 -->
</dependency>
獲取PDF內(nèi)容
設(shè)置 sortByPosition 為 true 可以按文本位置提取內(nèi)容,否則獲取到的內(nèi)容錯(cuò)亂,無(wú)法獲取到真正需要的內(nèi)容
@RequestMapping("uploadReceiptsTest")
@ResponseBody
public Map<String,String> uploadReceiptsTest() throws Exception{
String filePath = "D:/apache-tomcat-8.5.98/webapps/tspspic/發(fā)票文件/24372000000145100092.pdf"; // 保存路徑
PDDocument document = PDDocument.load(new File(filePath));
PDFTextStripper pdfStripper = new PDFTextStripper();
// 排序文本行按其位置
pdfStripper.setSortByPosition(true);
String text = pdfStripper.getText(document);
document.close();
Map<String, String> map = pdfStr(text);
return map;
}
文本內(nèi)容(部分內(nèi)容以*替代)
public static void main(String[] args) {
String invoiceInfo = "電子發(fā)票(普通發(fā)票) 發(fā)票號(hào)碼:******\n" +
"開(kāi)票日期:******\n" +
"購(gòu) 名稱:****** 銷 名稱:******\n" +
"買 售\n" +
"方 方\n" +
"信 統(tǒng)一社會(huì)信用代碼/納稅人識(shí)別號(hào):****** 信 統(tǒng)一社會(huì)信用代碼/納稅人識(shí)別號(hào):******\n" +
"息 息\n" +
"項(xiàng)目名稱 規(guī)格型號(hào) 單 位 數(shù) 量 單 價(jià) 金 額 稅率/征收率 稅 額\n" +
"******* 100ml:5g 袋 800 4.070796 3256.64 13% 423.36\n" +
"******\n" +
"合 計(jì) ¥3256.64 ¥423.36\n" +
"價(jià)稅合計(jì)(大寫) 叁仟陸佰捌拾圓整 (小寫)¥3680.00\n" +
"批號(hào):******/ 生產(chǎn)日期:2024-05-15/ 有效期至:2026-04-30/ 含稅單價(jià):4.6000/ 生產(chǎn)廠家:******/ 批準(zhǔn)文號(hào):******/\n" +
"備 注\n" +
"開(kāi)票人:王寧\n" +
"王寧";
解析文件內(nèi)容,返回?cái)?shù)據(jù)
初版測(cè)試
public Map<String,String> pdfStr(String invoiceInfo) {
//因解析出的括號(hào)不確定為中文還是英文,統(tǒng)一替換為英文字符
invoiceInfo = invoiceInfo.replaceAll("(","(").replaceAll(")",")");
// 定義正則表達(dá)式模式
Pattern patternInvoiceNumber = Pattern.compile("發(fā)票號(hào)碼:(\\d+)");
Pattern patternInvoiceDate = Pattern.compile("開(kāi)票日期:(\\d{4}年\\d{1,2}月\\d{1,2}日)");
Pattern patternBuyerName = Pattern.compile("購(gòu) 名稱:(.+?) 銷 名稱:(.+?)\n");
//由上圖可以發(fā)現(xiàn)“項(xiàng)目名稱 規(guī)格型號(hào) 單 位 數(shù) 量 單 價(jià) 金 額 稅率/征收率 稅 額”所需要的內(nèi)容在下一行數(shù)據(jù),使用笨方法直接獲取“稅額與合計(jì)”之間的數(shù)據(jù)通過(guò)之后的空格分割進(jìn)行獲取數(shù)據(jù)
Pattern patternItemDetails = Pattern.compile("稅 額\\s+(.*?)合 計(jì)", Pattern.DOTALL);
Pattern patternTotal = Pattern.compile("\\(小寫\\)¥(\\d+(\\.\\d+)?)");
Pattern patternBatchNumber = Pattern.compile("批號(hào):(.+?)/");
Pattern patternProductionDate = Pattern.compile("生產(chǎn)日期:(\\d{4}-\\d{1,2}-\\d{1,2})/");
Pattern patternExpirationDate = Pattern.compile("有效期至:(\\d{4}-\\d{1,2}-\\d{1,2})/");
Pattern patternTaxIncludedPrice = Pattern.compile("含稅單價(jià):(\\d+(\\.\\d+)?)");
Pattern patternManufacturer = Pattern.compile("生產(chǎn)廠家:(.+?)/");
Pattern patternApprovalNumber = Pattern.compile("批準(zhǔn)文號(hào):(.+?)/");
Pattern patternIssuer = Pattern.compile("開(kāi)票人:(.+)");
// 創(chuàng)建Matcher對(duì)象
Matcher matcherInvoiceNumber = patternInvoiceNumber.matcher(invoiceInfo);
Matcher matcherInvoiceDate = patternInvoiceDate.matcher(invoiceInfo);
Matcher matcherBuyerName = patternBuyerName.matcher(invoiceInfo);
Matcher matcherItemDetails = patternItemDetails.matcher(invoiceInfo);
Matcher matcherTotal = patternTotal.matcher(invoiceInfo);
Matcher matcherBatchNumber = patternBatchNumber.matcher(invoiceInfo);
Matcher matcherProductionDate = patternProductionDate.matcher(invoiceInfo);
Matcher matcherExpirationDate = patternExpirationDate.matcher(invoiceInfo);
Matcher matcherTaxIncludedPrice = patternTaxIncludedPrice.matcher(invoiceInfo);
Matcher matcherManufacturer = patternManufacturer.matcher(invoiceInfo);
Matcher matcherApprovalNumber = patternApprovalNumber.matcher(invoiceInfo);
Matcher matcherIssuer = patternIssuer.matcher(invoiceInfo);
// 提取數(shù)據(jù)
String invoiceNumber = "";
String invoiceDate = "";
String buyerName = "";
String sellerName = "";
String productName = "";
String specification = "";
String unit = "";
int quantity = 0;
double unitPrice = 0.0;
double amount = 0.0;
String taxRate = "";
double taxAmount = 0.0;
double total = 0.0;
String batchNumber = "";
String productionDate = "";
String expirationDate = "";
double taxIncludedPrice = 0.0;
String manufacturer = "";
String approvalNumber = "";
String issuer = "";
if (matcherInvoiceNumber.find()) {
invoiceNumber = matcherInvoiceNumber.group(1);
}
if (matcherInvoiceDate.find()) {
invoiceDate = matcherInvoiceDate.group(1);
}
if (matcherBuyerName.find()) {
buyerName = matcherBuyerName.group(1);
sellerName = matcherBuyerName.group(2);
}
// 處理項(xiàng)目名稱、規(guī)格型號(hào)、單位、數(shù)量、單價(jià)、金額、稅率/征收率、稅額
if (matcherItemDetails.find()) {
String itemDetailsLine = matcherItemDetails.group(1).trim();
itemDetailsLine = itemDetailsLine.replace("\n"," ");
String[] details = itemDetailsLine.split(" "); // 按空格分割
if (details.length >= 8) { // 確保有足夠的字段
//因部分名稱過(guò)長(zhǎng),換行數(shù)據(jù)解析到最后進(jìn)行拼接
productName = details[0].trim(); // 項(xiàng)目名稱
if (details.length >= 9){
productName = details[0].trim()+details[8].trim(); // 項(xiàng)目名稱
}
specification = details[1].trim(); // 規(guī)格型號(hào)
unit = details[2].trim(); // 單位
quantity = Integer.parseInt(details[3].trim()); // 數(shù)量
unitPrice = Double.parseDouble(details[4].trim()); // 單價(jià)
amount = Double.parseDouble(details[5].trim()); // 金額
taxRate = details[6].trim(); // 稅率/征收率
taxAmount = Double.parseDouble(details[7].trim()); // 稅額
System.out.println("項(xiàng)目名稱: " + productName);
System.out.println("規(guī)格型號(hào): " + specification);
System.out.println("單位: " + unit);
System.out.println("數(shù)量: " + quantity);
System.out.println("單價(jià): " + unitPrice);
System.out.println("金額: " + amount);
System.out.println("稅率/征收率: " + taxRate);
System.out.println("稅額: " + taxAmount);
}
}
if (matcherTotal.find()) {
total = Double.parseDouble(matcherTotal.group(1));
}
if (matcherBatchNumber.find()) {
batchNumber = matcherBatchNumber.group(1);
}
if (matcherProductionDate.find()) {
productionDate = matcherProductionDate.group(1);
}
if (matcherExpirationDate.find()) {
expirationDate = matcherExpirationDate.group(1);
}
if (matcherTaxIncludedPrice.find()) {
taxIncludedPrice = Double.parseDouble(matcherTaxIncludedPrice.group(1));
}
if (matcherManufacturer.find()) {
manufacturer = matcherManufacturer.group(1);
}
if (matcherApprovalNumber.find()) {
approvalNumber = matcherApprovalNumber.group(1);
}
if (matcherIssuer.find()) {
issuer = matcherIssuer.group(1);
}
// 輸出其他結(jié)果
System.out.println("發(fā)票號(hào)碼: " + invoiceNumber);
System.out.println("開(kāi)票日期: " + invoiceDate);
System.out.println("購(gòu)買方名稱: " + buyerName);
System.out.println("銷售方名稱: " + sellerName);
System.out.println("價(jià)稅合計(jì): " + total);
System.out.println("批號(hào): " + batchNumber);
System.out.println("生產(chǎn)日期: " + productionDate);
System.out.println("有效期至: " + expirationDate);
System.out.println("含稅單價(jià): " + taxIncludedPrice);
System.out.println("生產(chǎn)廠家: " + manufacturer);
System.out.println("批準(zhǔn)文號(hào): " + approvalNumber);
System.out.println("開(kāi)票人: " + issuer);
}
優(yōu)化代碼
Map存儲(chǔ)正則表達(dá)式:將所有正則表達(dá)式模式和對(duì)應(yīng)的字段名稱存儲(chǔ)在一個(gè)Map中,遍歷Map并執(zhí)行匹配,從而避免了為每個(gè)字段都寫單獨(dú)的匹配代碼。
抽取通用邏輯:將匹配邏輯抽象成一個(gè)通用方法,簡(jiǎn)化了代碼結(jié)構(gòu),減少了重復(fù)代碼。
處理商品詳情:在匹配完itemDetails后,再拆分字符串并填充對(duì)應(yīng)的字段。
public static Map<String, String> pdfStr(String invoiceInfo) {
invoiceInfo = invoiceInfo.replaceAll("(", "(").replaceAll(")", ")");
// 定義正則表達(dá)式模式
Map<String, String> patterns = new HashMap<>();
patterns.put("invoiceNumber", "發(fā)票號(hào)碼:(\\d+)");
patterns.put("invoiceDate", "開(kāi)票日期:(\\d{4}年\\d{1,2}月\\d{1,2}日)");
patterns.put("buyerName", "購(gòu) 名稱:(.+?) 銷 名稱:(.+?)\n");
patterns.put("itemDetails", "稅 額\\s+(.*?)合 計(jì)");
patterns.put("total", "\\(小寫\\)¥(\\d+(\\.\\d+)?)");
patterns.put("batchNumber", "批號(hào):(.+?)/");
patterns.put("productionDate", "生產(chǎn)日期:(\\d{4}-\\d{1,2}-\\d{1,2})/");
patterns.put("expirationDate", "有效期至:(\\d{4}-\\d{1,2}-\\d{1,2})/");
patterns.put("taxIncludedPrice", "含稅單價(jià):(\\d+(\\.\\d+)?)");
patterns.put("manufacturer", "生產(chǎn)廠家:(.+?)/");
patterns.put("approvalNumber", "批準(zhǔn)文號(hào):(.+?)/");
patterns.put("issuer", "開(kāi)票人:(.+)");
// 提取數(shù)據(jù)
Map<String, String> result = new HashMap<>();
for (Map.Entry<String, String> entry : patterns.entrySet()) {
Pattern pattern = Pattern.compile(entry.getValue(), Pattern.DOTALL);
Matcher matcher = pattern.matcher(invoiceInfo);
if (matcher.find()) {
result.put(entry.getKey(), matcher.group(1).trim());
}
}
// 處理項(xiàng)目名稱、規(guī)格型號(hào)、單位、數(shù)量、單價(jià)、金額、稅率/征收率、稅額
if (result.containsKey("itemDetails")) {
String[] details = result.get("itemDetails").replace("\n", " ").split(" ");
if (details.length >= 8) {
result.put("productName", details[0].trim() + (details.length > 8 ? details[8].trim() : ""));
result.put("specification", details[1].trim());
result.put("unit", details[2].trim());
result.put("quantity", details[3].trim());
result.put("unitPrice", details[4].trim());
result.put("amount", details[5].trim());
result.put("taxRate", details[6].trim());
result.put("taxAmount", details[7].trim());
}
}
// 打印結(jié)果
for (Map.Entry<String, String> entry : result.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
return result;
}
到此這篇關(guān)于Java解析pdf格式發(fā)票的代碼實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Java解析pdf格式發(fā)票內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解如何用spring Restdocs創(chuàng)建API文檔
這篇文章將帶你了解如何用spring官方推薦的restdoc去生成api文檔。具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05
JAVA 生成隨機(jī)數(shù)并根據(jù)后臺(tái)概率靈活生成的實(shí)例代碼
本篇文章主要介紹了JAVA 生成隨機(jī)數(shù)并根據(jù)后臺(tái)概率靈活生成的實(shí)例代碼,具有一定的參考價(jià)值,有興趣的可以了解一下2017-08-08
SpringBoot整合Kaptcha實(shí)現(xiàn)圖形驗(yàn)證碼功能
這篇文章主要介紹了SpringBoot整合Kaptcha實(shí)現(xiàn)圖形驗(yàn)證碼功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-09-09
java springmvc 注冊(cè)中央調(diào)度器代碼解析
這篇文章主要介紹了java springmvc 注冊(cè)中央調(diào)度器代碼解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08
Java使用路徑通配符加載Resource與profiles配置使用詳解
這篇文章主要介紹了Java使用路徑通配符加載Resource與profiles配置使用詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06
Java實(shí)現(xiàn)圖章或簽名插在pdf的固定位置
使用Java技術(shù)在word轉(zhuǎn)換成pdf過(guò)程中實(shí)現(xiàn)將圖章或者簽名插入在pdf中,并生成帶圖章或者簽名的pdf,來(lái)完成某些特定場(chǎng)景的需求,文中有詳細(xì)的代碼示例,需要的朋友可以參考下2023-10-10
Spring Boot MyBatis 連接數(shù)據(jù)庫(kù)配置示例
本篇文章主要介紹了Spring Boot MyBatis 連接數(shù)據(jù)庫(kù)示例的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-02-02
詳解Java如何實(shí)現(xiàn)基于Redis的分布式鎖
在不同進(jìn)程需要互斥地訪問(wèn)共享資源時(shí),分布式鎖是一種非常有用的技術(shù)手段。這篇文章運(yùn)用圖文和實(shí)例代碼介紹了Java如何實(shí)現(xiàn)基于Redis的分布式鎖,文章介紹的很詳細(xì),對(duì)Java和Redis剛興趣的朋友們可以參考借鑒,下面來(lái)一起看看。2016-08-08

