java实现解析pdf格式发票

news2024/9/21 10:32:34

为了减少用户工作量及误操作的可能性,需要实现用户上传PDF格式的发票,系统通过解析PDF文件获取发票内容,并直接将其写入表单。以下文章记录了功能实现的代码。

发票样式

在这里插入图片描述

发票内容解析

引用Maven

使用pdfbox

<dependency>
    <groupId>org.apache.pdfbox</groupId>
    <artifactId>pdfbox</artifactId>
    <version>2.0.24</version> <!-- 请检查最新版本 -->
</dependency>

获取PDF内容

设置 sortByPosition 为 true 可以按文本位置提取内容,否则获取到的内容错乱,无法获取到真正需要的内容

    @RequestMapping("uploadReceiptsTest")
    @ResponseBody
    public 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;
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1995385.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

API可观察性对于现代应用程序的最大好处

API可观察性是提升性能、加速问题诊断和增强安全的关键。它在理解和管理错综复杂的API交互方面发挥着至关重要的作用。利用API可观察性&#xff0c;您可以深入洞察API的工作状态&#xff0c;保障服务的可靠性&#xff0c;并优化用户体验。 在当今的数字环境中&#xff0c;API …

kubernetes集群部署sql server数据库服务

背景&#xff1a; 因业务上线需要&#xff0c;研发中心要求在kubernetes测试集群部署一个sql server 2017的数据库&#xff0c;用于业务功能调试。 一、实施部署sql server数据库&#xff1a; 1、拉取sql server 2017的镜像&#xff1a; [rootharbor-02 ~]# docker pull mcr…

POI导出复杂Excel表格

记录在遇到复杂统计报表业务时&#xff0c;无法使用Excel模板生成对应报表&#xff0c;则采用最原始poi方式进行创建生成 业务所需统计报表如下图 麻烦所在各类型订单下方餐别为动态数据&#xff0c;废话不多说直接上代码 new CellRangeAddress(起始行号, 终止行号, 起始列号,…

Android Studio 设置打开layout.xml文件的默认视图split

Android Studio 设置打开layout.xml文件的默认视图 Android Studio 设置打开layout.xml文件的默认视图 androd studio 在使用的时候打开我们自己的布局文件默认展示的视图为Design,我们想要编辑的时候还有手动切换成splite 视图或者Code视图&#xff0c;不上很方便&#xff0c…

CA证书和openssl介绍

文章目录 一、加密和算法常见的安全攻击加密算法和协议对称加密非对称加密算法 二、CA和证书中间人攻击CA和证书安全协议SSL/TLS协议介绍HTTPS 三、opensslopenssl介绍使用openssl实现对称加密使用openssl命令生成加密密码生成随机密码建立私有CA证书申请颁发建立私有CA实际例子…

Java-数据库基本概念

数据库DataBase 定义: 保存一组数据的仓库就是数据库 例 BirdBoot项目中&#xff0c;我们为了保存一组用户信息&#xff0c;创建了一个目录users。里面用若干个文件保存每一个用户信息 此时users目录就可以称为是一个数据库 只不过对于这些数据的维护操作&#xff0c;要么…

【最多可以参加的会议数目】python刷题记录

R4-贪心篇 结束时间升序排列优先队列 class Solution:def maxEvents(self, events: List[List[int]]) -> int:dictdefaultdict(list)for i,val in enumerate(events):dict[val[0]].append(val[1])#优先队列&#xff08;小根堆&#xff09;h[]ret0for i in range(1,100001)…

C语言求平方和倒数

文章目录 1. 代码实现float类型数据double类型数据使用 double 类型的调整 2. 魔数与位级别操作浮点数表示位级别魔数操作 3. 牛顿迭代4. 复杂代码具体解释具体解释&#xff1a;目的&#xff1a;举例&#xff1a; 5.感谢 平方和倒数 广泛用于计算机图形学中&#xff0c;尤其是在…

Qt实现类似淘宝商品展示看板功能简版

前一篇文章的简化版本只有浏览功能&#xff0c;前一篇文章链接如下&#xff1a; Qt实现类似淘宝商品看板的界面&#xff0c;带有循环翻页以及点击某页跳转的功能 效果如下&#xff1a; 代码留给有需要的人。 #ifndef ModelDashboardGroup_h__ #define ModelDashboardGroup_…

DC-DC PCB 布线参考

在DC-DC芯片的应用设计中&#xff0c;PCB布板是否合理对于芯片能否表现出其最优性能有着至关重要的影响。不合理的PCB布板会造成芯片性能变差如线性度下降&#xff08;包括输入线性度以及输出线性度&#xff09;、带载能力下降、工作不稳定、EMI辐射增加、输出噪声增加等&#…

容量 动效 仪表盘 Canvas 2D API

容量动效 <!DOCTYPE html> <html lang"zh"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>Canvas 内部圆形波浪</title><…

【区块链+乡村振兴】基于区块链的农权抵押贷款平台 | FISCO BCOS应用案例

当前两权抵押贷款业务逐渐活跃&#xff0c;但因参与两权抵押的相关方独立运营各自的管理系统&#xff0c;办理农权抵押贷款业务时&#xff0c; 存在多方信息不对称、业务办理过程繁琐、低效等问题&#xff0c;且不利于相关部门对农权抵押情况进行监督管理。具体表现在&#xff…

《数据资产管理核心技术与应用》相关分享章节

【图书推荐】《数据资产管理核心技术与应用》-CSDN博客 图书《数据资产管理核心技术与应用》介绍-CSDN博客 本文用于快速了解数据资产管理的概念及其技术范围。 数据资产通常是指那些可以通过分析来揭示价值、支持企业决策制定、优化企业流程、预测行业的未来趋势或产生更大…

dhcp+checkkickstar的实验理解

文章目录 实验介绍使用的服务介绍PXE服务dhcp服务Kickstart 服务tftp服务 第一部分&#xff08;基础部分&#xff09;代码展示注意点第一点![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/13c0f4aabb664655a4dd285dd8e5527a.png)第二点 结果展示 第二部分&#xff08…

元岳食堂采购供应链系统-智慧食堂数据化解决方案

随着社会的发展和科技的进步&#xff0c;在数字化浪潮的推动下&#xff0c;智慧食堂供应链系统逐渐成为食堂管理的重要工具。在此背景下&#xff0c;元岳食堂采购供应链系统应运而生&#xff0c;该系统通过其独特的数字化和自动化功能&#xff0c;能够对食堂的采购、储存、配送…

重庆市合川区第二届网络安全“钓鱼城“杯部分题解

MISC 下载文件后&#xff0c;进行分析 往下划看见smb 最开始以为是通过smb协议下载的文件 找半天没发现&#xff0c;往前翻了翻&#xff0c;看见了flag 存储为原始数据 通过上述分析发现开头是pk&#xff0c;保存为zip压缩包 发现需要密码 感觉是伪加密 使用工具一把梭 再…

免费的GA/T1400视图库平台Easy1400软件又迭代出新版本了!

自从年初发布了这款免费的GA1400视图库平台&#xff08;https://blog.csdn.net/xiejiashu/article/details/135050381&#xff09;&#xff0c;本来就是想做一做能给大家差不多能用就行了&#xff0c;没成想总是忍不住要改一改、优化优化&#xff0c;于是又经过了几个月的优化&…

合宙Air201定位模组,千万不要忘记设置这个......

最近一个杭州的客户&#xff0c;使用合宙的 Air201——4G资产定位模组&#xff0c;做室内wifi定位&#xff0c;发现在同一园区不同楼栋内定位结果相同&#xff0c;找到我们希望做问题排查。 排查过程记录在这里了&#xff0c;如果你也有类似问题&#xff0c;希望可以帮助到你~ …

Pinterest:从 Druid 到 StarRocks,实现 6 倍成本效益比提升

导读&#xff1a; 开源无国界&#xff0c;StarRocks 自开源以来&#xff0c;近3年的时间里已在全球数据技术领域崭露头角。我们欣喜地发现&#xff0c;越来越多的海外用户正在使用并积极推广着 StarRocks。为了促进知识共享&#xff0c;StarRocks中文社区将精选优秀文章与大家共…

华为手机平板开启下方提示条(横条)

打开设置&#xff0c;系统与更新&#xff0c;系统导航方式&#xff0c;手势导航&#xff0c;更多设置&#xff0c;显示提示条。 该功能部分机型不支持&#xff0c;我的MatePad SE支持&#xff08;别买&#xff0c;割韭菜&#xff09;