java实现解析pdf格式发票
为了减少用户工作量及误操作的可能性,需要实现用户上传PDF格式的发票,系统通过解析PDF文件获取发票内容,并直接将其写入表单。以下文章记录了功能实现的代码。
发票样式
发票内容解析
引用Maven
使用pdfbox
<dependency><groupId>org.apache.pdfbox</groupId><artifactId>pdfbox</artifactId><version>2.0.24</version> <!-- 请检查最新版本 -->
</dependency>
获取PDF内容
设置 sortByPosition 为 true 可以按文本位置提取内容,否则获取到的内容错乱,无法获取到真正需要的内容
@RequestMapping("uploadReceiptsTest")@ResponseBodypublic Map<String,String> uploadReceiptsTest() throws Exception{String filePath = "D:/apache-tomcat-8.5.98/webapps/tspspic/发票文件/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;}
文本内容(部分内容以*替代)
public static void main(String[] args) {String invoiceInfo = "电子发票(普通发票) 发票号码:******\n" +"开票日期:******\n" +"购 名称:****** 销 名称:******\n" +"买 售\n" +"方 方\n" +"信 统一社会信用代码/纳税人识别号:****** 信 统一社会信用代码/纳税人识别号:******\n" +"息 息\n" +"项目名称 规格型号 单 位 数 量 单 价 金 额 税率/征收率 税 额\n" +"******* 100ml:5g 袋 800 4.070796 3256.64 13% 423.36\n" +"******\n" +"合 计 ¥3256.64 ¥423.36\n" +"价税合计(大写) 叁仟陆佰捌拾圆整 (小写)¥3680.00\n" +"批号:******/ 生产日期:2024-05-15/ 有效期至:2026-04-30/ 含税单价:4.6000/ 生产厂家:******/ 批准文号:******/\n" +"备 注\n" +"开票人:王宁\n" +"王宁";
解析文件内容,返回数据
初版测试
public Map<String,String> pdfStr(String invoiceInfo) {//因解析出的括号不确定为中文还是英文,统一替换为英文字符invoiceInfo = invoiceInfo.replaceAll("(","(").replaceAll(")",")");// 定义正则表达式模式Pattern patternInvoiceNumber = Pattern.compile("发票号码:(\\d+)");Pattern patternInvoiceDate = Pattern.compile("开票日期:(\\d{4}年\\d{1,2}月\\d{1,2}日)");Pattern patternBuyerName = Pattern.compile("购 名称:(.+?) 销 名称:(.+?)\n");//由上图可以发现“项目名称 规格型号 单 位 数 量 单 价 金 额 税率/征收率 税 额”所需要的内容在下一行数据,使用笨方法直接获取“税额与合计”之间的数据通过之后的空格分割进行获取数据Pattern patternItemDetails = Pattern.compile("税 额\\s+(.*?)合 计", Pattern.DOTALL);Pattern patternTotal = Pattern.compile("\\(小写\\)¥(\\d+(\\.\\d+)?)");Pattern patternBatchNumber = Pattern.compile("批号:(.+?)/");Pattern patternProductionDate = Pattern.compile("生产日期:(\\d{4}-\\d{1,2}-\\d{1,2})/");Pattern patternExpirationDate = Pattern.compile("有效期至:(\\d{4}-\\d{1,2}-\\d{1,2})/");Pattern patternTaxIncludedPrice = Pattern.compile("含税单价:(\\d+(\\.\\d+)?)");Pattern patternManufacturer = Pattern.compile("生产厂家:(.+?)/");Pattern patternApprovalNumber = Pattern.compile("批准文号:(.+?)/");Pattern patternIssuer = Pattern.compile("开票人:(.+)");// 创建Matcher对象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);// 提取数据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);}// 处理项目名称、规格型号、单位、数量、单价、金额、税率/征收率、税额if (matcherItemDetails.find()) {String itemDetailsLine = matcherItemDetails.group(1).trim();itemDetailsLine = itemDetailsLine.replace("\n"," ");String[] details = itemDetailsLine.split(" "); // 按空格分割if (details.length >= 8) { // 确保有足够的字段//因部分名称过长,换行数据解析到最后进行拼接productName = details[0].trim(); // 项目名称if (details.length >= 9){productName = details[0].trim()+details[8].trim(); // 项目名称}specification = details[1].trim(); // 规格型号unit = details[2].trim(); // 单位quantity = Integer.parseInt(details[3].trim()); // 数量unitPrice = Double.parseDouble(details[4].trim()); // 单价amount = Double.parseDouble(details[5].trim()); // 金额taxRate = details[6].trim(); // 税率/征收率taxAmount = Double.parseDouble(details[7].trim()); // 税额System.out.println("项目名称: " + productName);System.out.println("规格型号: " + specification);System.out.println("单位: " + unit);System.out.println("数量: " + quantity);System.out.println("单价: " + 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);}// 输出其他结果System.out.println("发票号码: " + invoiceNumber);System.out.println("开票日期: " + invoiceDate);System.out.println("购买方名称: " + buyerName);System.out.println("销售方名称: " + sellerName);System.out.println("价税合计: " + total);System.out.println("批号: " + batchNumber);System.out.println("生产日期: " + productionDate);System.out.println("有效期至: " + expirationDate);System.out.println("含税单价: " + taxIncludedPrice);System.out.println("生产厂家: " + manufacturer);System.out.println("批准文号: " + approvalNumber);System.out.println("开票人: " + issuer);}
优化代码
-
Map存储正则表达式:将所有正则表达式模式和对应的字段名称存储在一个Map中,遍历Map并执行匹配,从而避免了为每个字段都写单独的匹配代码。
-
抽取通用逻辑:将匹配逻辑抽象成一个通用方法,简化了代码结构,减少了重复代码。
-
处理商品详情:在匹配完itemDetails后,再拆分字符串并填充对应的字段。
public static Map<String, String> pdfStr(String invoiceInfo) {invoiceInfo = invoiceInfo.replaceAll("(", "(").replaceAll(")", ")");// 定义正则表达式模式Map<String, String> patterns = new HashMap<>();patterns.put("invoiceNumber", "发票号码:(\\d+)");patterns.put("invoiceDate", "开票日期:(\\d{4}年\\d{1,2}月\\d{1,2}日)");patterns.put("buyerName", "购 名称:(.+?) 销 名称:(.+?)\n");patterns.put("itemDetails", "税 额\\s+(.*?)合 计");patterns.put("total", "\\(小写\\)¥(\\d+(\\.\\d+)?)");patterns.put("batchNumber", "批号:(.+?)/");patterns.put("productionDate", "生产日期:(\\d{4}-\\d{1,2}-\\d{1,2})/");patterns.put("expirationDate", "有效期至:(\\d{4}-\\d{1,2}-\\d{1,2})/");patterns.put("taxIncludedPrice", "含税单价:(\\d+(\\.\\d+)?)");patterns.put("manufacturer", "生产厂家:(.+?)/");patterns.put("approvalNumber", "批准文号:(.+?)/");patterns.put("issuer", "开票人:(.+)");// 提取数据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());}}// 处理项目名称、规格型号、单位、数量、单价、金额、税率/征收率、税额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());}}// 打印结果for (Map.Entry<String, String> entry : result.entrySet()) {System.out.println(entry.getKey() + ": " + entry.getValue());}return result;}
相关文章:
java实现解析pdf格式发票
为了减少用户工作量及误操作的可能性,需要实现用户上传PDF格式的发票,系统通过解析PDF文件获取发票内容,并直接将其写入表单。以下文章记录了功能实现的代码。 发票样式 发票内容解析 引用Maven 使用pdfbox <dependency><groupI…...
数据结构初阶——算法复杂度超详解
文章目录 1. 数据结构前言1. 1 数据结构1. 2 算法 2. 算法效率2. 1 复杂度的概念 3. 时间复杂度3. 1 大O的渐进表示法3. 2 时间复杂度计算示例3. 2. 1 示例13. 2. 2 示例23. 2. 3 示例33. 2. 4 示例43. 2. 5 示例53. 2. 6 示例63. 2. 7 示例7 4. 空间复杂度4. 1 空间复杂度计算…...
ArcGIS Pro SDK (十二)布局 4 预定义的形状和箭头
ArcGIS Pro SDK (十二)布局 4 预定义的形状和箭头 文章目录 ArcGIS Pro SDK (十二)布局 4 预定义的形状和箭头1 创建预定义的形状图形元素2 创建预定义的形状图形元素3 创建预定义的形状图形元素4 创建线箭头元素环境:Visual Studio 2022 + .NET6 + ArcGIS Pro SDK 3.0 1 …...
在 Ubuntu 14.04 服务器上安装 ISPConfig3 的方法
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。 简介 虽然命令行是一个强大的工具,可以让您在许多情况下快速轻松地工作,但在某些情况下,可视化界面…...
ELK学习笔记
ElasticStack分布式日志系统概述 Elasticsearch: 一个分布式搜索引擎,能够快速存储、搜索和分析大量数据。核心概念包括索引(Index)、文档(Document)和分片(Shard)。使用 RESTful API 进行数据操…...
Python+Selenium+Pytest+POM自动化测试框架封装详解
1、测试框架简介 1)测试框架的优点 代码复用率高,如果不使用框架的话,代码会显得很冗余。可以组装日志、报告、邮件等一些高级功能。提高元素等数据的可维护性,元素发生变化时,只需要更新一下配置文件。使用更灵活的…...
Hidden Marlov Model(HMM)
一、Model 1、将声学特征设为X,经过语音识别得到的tokens设为Y,目标是找到通过X得到Y的最大概率,可以通过概率公式改变为 分为两个概率 2、将tokens序列Y转化为states序列S,声学特征分得更细 3、从states到声学特征的过程 二、HM…...
mamba的安装及下载速度慢问题解决
同事反馈mamba的安装时网络慢 mamba是conda的加速工具,相比于conda 对包和环境的管理,mamba可以实现并行运算。相比于 conda,mamba 是用C重写了 conda 的部分功能,运行效率显著提高,可以进行并行的下载,使…...
【Linux入门】Linux环境搭建
目录 前言 一、发行版本 二、搭建Linux环境 1.Linux环境搭建方式 2.虚拟机安装Ubuntu 22.02.4 1)安装VMWare 2)下载镜像源 3)添加虚拟机 4)换源 5)安装VM Tools 6)添加快照 总结 前言 Linux是一款自由和开放…...
CPU缓存一致性机制详解
CPU缓存一致性机制详解 在多核处理器中,缓存一致性是保证系统正常运行的重要环节。本文详细介绍了缓存一致性协议、写入策略、总线嗅探、目录协议等相关概念,并通过示例代码解释了这些机制是如何在实际应用中工作的。通过学习本文,读者可以深…...
Android 12系统源码_屏幕设备(一)DisplayManagerService的启动
前言 DisplayManagerService是Android Framework中管理显示屏幕相关的模块,各种Display的连接、配置等,都是通过DMS和来管理控制。 在DMS模块中,对显示屏幕的描述分为物理显示屏(physical display)和逻辑显示屏(logical display),…...
《AI视频类工具之十——D-ID》
一.简介 官网:D-ID | The #1 Choice for AI Generated Video Creation Platform D-ID是一个人工智能生成的视频创建平台,可以轻松快速地从文本输入中创建高质量、高性价比和引人入胜的视频,背后的Al技术是由Stable Difusion和GPT.3提供支持,可以在没有任何技术知识的情况…...
【网络】局域网LAN、广域网WAN、TCP/IP协议、封装和分用
文章目录 局域网 LAN广域网 WAN网络中的重要概念IP 地址端口号 认识协议协议分层是什么OSI 七层网络模型TCP/IP 五层网络模型(或四层)物理层传输层网络层数据链表层应用层网络设备所在分层 封装和分用[站在发送方视角](封装)[站在…...
我司搜索中台的三次演变
本人从入职就开始负责我司的搜索中台了,总共是经历了三个大版本的迭代。 分别是: 基于阿里云智能开放搜索OpenSearch实现的第一代自研,开源canal(数据同步) 底层阿里云elasticsearch 对索引封装 实现的第二代自研&a…...
html+css+js网页设计 电商模版4个页面
htmlcssjs网页设计 电商模版4个页面 带js 网页作品代码简单,可使用任意HTML编辑软件(如:Dreamweaver、HBuilder、Vscode 、Sublime 、Webstorm、Text 、Notepad 等任意html编辑软件进行运行及修改编辑等操作)。 获取源码 1&…...
区块链技术在Facebook中的潜力:未来趋势与挑战
数据安全的新高度 区块链技术以其去中心化和不可篡改的特性,正在成为提升数据安全和隐私保护的重要工具。Facebook作为全球最大的社交媒体平台之一,正积极探索如何将区块链技术应用于其平台,以增强用户数据的安全性和隐私保护。Facebook在应…...
dockerfile自定义镜像
目录 概念 基于dockerfile创建 dockerfile的命令 构建容器 FROM ENTRYPOINT和CMD RUN COPY和ADD 工作目录和环境变量以及容器卷(挂载卷) EXPOSE 实战 概念 dockerfile就是自定义镜像,通过dockerfile创建的都是镜像,而…...
【工作状态】如何保持专注?
好的睡眠计划主题化 1、保持足够的睡眠,才能头脑清晰和有精力,工作不是纯拼体力,要用脑力的。 2、脑力主要工作放在午餐前,在脑力充足的时候使用脑力,下午五点后可以安排脑力活动较低的工作,比如听课读书。…...
欧科云链研究院对话:风浪越大鱼越贵—链上数据洞悉加密市场规律
作者 Hedy 出品 OKG Research “我们从来就不是理性人。但可以用最简单的工具——链上数据做‘最猛’的分析。” 在经历了超级宏观周之后,金融市场产生了巨大的震荡,加密市场的表现也越来越受到宏观经济因素的影响。欧科云链研究院OKG Research 集结多…...
SQLite库笔记:日期和时间函数
1. 函数概述 SQLite支持7个日期和时间函数,如下: 1 date(time-value, modifier, modifier, ...) 返回YYYY-MM-DD格式的日期 2 time(time-value, modifier, modifier, ...) 返回HH:MM:SS格式的时间 3 datetime(time-value, modifier, modifier, ...…...
Linux开发板运行报错/system/bin/sh: can‘t create /dev/leds: Permission denied
Linux开发板运行报错/system/bin/sh: cant create /dev/leds: Permission denied 1.问题解决 1.问题解决 /system/bin/sh: can’t create /dev/leds: Permission denied 在linux开发板下运行驱动报错没有权限,需要运行 su,同时也会停止打印 在运行: ch…...
月薪5W的项目经理是如何面试的?这份面试攻略请收好!
面试是项目经理求职必须经历的一关,但很多经验不够丰富的项目经理不知道面试会问些什么问题,也不知道要怎么回答,无疑会直接影响面试企业的判断,使项目经理求职受阳.所以,项目经理想要顺利求职,还是有必要掌…...
接口隔离原则
接口隔离原则 接口隔离原则就是客户端不应该依赖它不需要的接口,或者说类间的依赖关系应该建立在最小的接口上。 我们以搜索美女为例,设计了如下的类图: 源代码如下。美女及其实现类: 搜索程序及其子类源代码如下: 最…...
【C++ 面试 - 基础题】每日 3 题(十六)
✍个人博客:Pandaconda-CSDN博客 📣专栏地址:http://t.csdnimg.cn/fYaBd 📚专栏简介:在这个专栏中,我将会分享 C 面试中常见的面试题给大家~ ❤️如果有收获的话,欢迎点赞👍收藏&…...
端到端测试自动化:使用JavaScript打造可靠的Web应用
端到端测试(End-to-End Testing,简称E2E测试)是软件测试的一种形式,用于验证整个应用从端到端的流程是否按预期工作。在JavaScript生态系统中,E2E测试通常使用专门的框架来模拟用户交互,验证应用的业务逻辑…...
『大模型笔记』基于LLM生成真实世界数据的合成问答数据!
基于LLM生成真实世界数据的合成问答数据! 文章目录 一. 基于LLM生成真实世界数据的合成问答数据!1. 概述(Overview)2. 场景(Scenario)2.1. 概述2.1.1. 第1阶段:构建种子数据集(Stage 1. Constructing a seed dataset)2.1.2. 第2阶段:数据增强(可选)(Data Augmentation…...
一个使用Python和`scikit-learn`库实现KMeans聚类算法的简单示例
一个使用Python和scikit-learn库实现KMeans聚类算法的简单示例。首先,请确保您已经安装了scikit-learn库。如果没有安装,可以通过pip安装它: pip install scikit-learn下面是一个简单的例子,展示如何使用scikit-learn中的KMeans进…...
记录一下vue2和vue3中如何配置keep-alive及写法上的差异
应用场景 跳转下一个页面后,返回当前页面,或者在多个页面之间切换,保留页面中的所有状态,常见的就是填写到一半的表单。h5的tabbar页面缓存,避免重复加载数据。 主要实现思路 这个东西很常见,动态路由一…...
面试题:Rabbitmq怎么保证消息的可靠性?
1.消费端消息可靠性保证: 消息确认(Acknowledgements):(自动(默认),手动) 消费者在接收到消息后,默认情况下RabbitMQ会自动确认消息(autoAcktrue)。为保证消息可靠性,可以设置auto…...
性能测试工具之JMeter
JMeter Apache JMeter应用程序是开源软件,是一个100%纯Java应用程序,旨在负载测试功能行为和衡量性能。它最初是为测试Web应用程序而设计的,但后来扩展到其他测试功能。 JMeter是一个免费、开源、跨平台的性能测试工具,于20世纪90年代后期面世。这是一个成熟、健全且具有…...
网站营销公司简介/seo排名软件
系列文章目录 Hive3第一章:环境安装 Hive3第二章:简单交互 Hive3第三章:DML数据操作 提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录系列文章目录前言一、数据导入1. 向表中装载…...
国外在线crm酒店系统/南宁seo推广优化
目录1. 什么是设计模式?2. 设计原则概述3. 设计模式核心思想4. 设计模式分类1. 什么是设计模式? 设计模式是一套被反复使用、多数人知晓、经过分类编目的、代码设计经验的总结。它是为了可重用代码,让代码更容易的被他人理解并保证代码的可靠…...
做asp动态网站制作流程/建站软件可以不通过网络建设吗
栈与队列就像一对兄弟一样,学习的时候可以一块学习。 栈的特点:先进后出 队列的特点:先进先出 一、头文件 栈的头文件 #include<stack> 队列的头文件 #include<queue> 二、定义 栈的定义:stack<TYPE> s; 队列…...
清华大学有关网站建设的书/java培训机构
Java 条件语句 - if...else 一个 if 语句包含一个布尔表达式和一条或多条语句。 语法 if 语句的语法如下: if(布尔表达式) { //如果布尔表达式为true将执行的语句 } 如果布尔表达式的值为 true,则执行 if 语句中的代码块,否则执行 if 语…...
电脑怎么下免费wordpress/广州seo网络推广员
有关51单片机中断的形式和C语言编程格式void INT0()interrupt 0 using 1 {.... ..... } interrupt 0 指明是外部中断0; interrupt 1 指明是定时器中断0; interrupt 2 指明是外部中断1; interrupt 3 指明是定时器中断1; interrupt …...
培训教材网站建设/培训心得模板
原地址:http://www.cnblogs.com/yk250/p/5773425.html 在mvvm模式下弹出窗体,有使用接口模式传入参数new一个对象的,还有的是继承于一个window,然后在window里面添加方法字段返回值注入之类的来间接实现。当然prism之类的弹窗管理…...