百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

发票助手:使用.NET处理PDF文件和二维码解析

yuyutoo 2025-01-21 22:36 1 浏览 0 评论

本文以发票助手获取发票信息为例,详细介绍如何使用 .NET 技术处理 PDF 文件并进行二维码解析。文章介绍的相关代码已开源在GitHub,欢迎查看和收藏。

1. 背景

在日常工作中,我们经常需要处理发票信息,比如日常报销和出差等等场景。如果发票比较多,能有一款工具可以方便的帮助我们处理 PDF 发票就非常棒了,当然我们其实有很多的选则,比如微信的卡包,还有QQ邮箱、WPS的发票工具等等,但是这些工具都有一些局限性。因此,我们可以自己开发一款发票助手,通过读取 PDF 文件中的发票二维码并解析其内容,实现发票信息的提取和处理。

2. 发票的变化

电子发票越来越普及,我们可以通过扫码获取电子发票,也可以通过邮件或者网站下载电子发票。电子发票的好处是方便快捷,不需要纸质发票,可以随时随地查看和打印。

不知道大家有没有发现,最近的发票都已经换成了新的数电发票,增值税专用发票也使用了电子化,省去了邮寄的环节,减少了开票时间和开票成本。数电发票增加XML的数据电文格式便利交付,更加方便了信息的提取,同时也保留PDF/OFD格式。


数电发票


3. 发票中的二维码

无论是之前的电子发票还是新的数电发票,在发票的左上角一般都会有一个二维码,里面通常包含了发票的关键信息,如发票代码、发票号码、金额、日期等。我们可以通过解析二维码来获取这些信息,从而实现发票信息的提取和处理。当然,这些信息我们也可以直接通过微信扫码来获取和测试。

以下是一个发票二维码的扫码获取的信息的示例,部分数字信息已经做了模糊处理,使用x代替了数字:

01,10,011002400xxx,35602xxx,1058.34,20240xxx,016258xxx15879380xxx,Fxxx,01,31,,24117000000xxx133xxx,476.60,20241xxx,,7xxx01,32,,24117000000xxx771xxx,1472.24,20241xxx,,6xxx

上面的第一个发票是今年早些时候的老款电子发票,第二个和第三个是新款数电发票。下面,我们重点分析一下属性对应的内容及含义,他们的信息以逗号为分隔符:

这里第一个固定属性值 01,第二个属性值 10 则是发票类型,具体含义如下:

属性值
发票类型
01
增值税专用发票
04
增值税普通发票
10
增值税普通发票(电子)
08
增值税专用发票(电子)
31
数电专票
32
数电普票

后面几个分别是发票的代码、号码、金额、日期和校验码等信息。

4. 使用 .NET 处理 PDF 文件和二维码解析

了解了发票中二维码的信息后,我们可以使用 .NET 技术来处理 PDF 文件并进行二维码解析。在这里,我们将使用 UglyToad.PdfPig 库来读取 PDF 文件,使用 ZXing 库来解析二维码。

4.1. 准备工作

在开始之前,请确保你已经安装了以下 NuGet 包:

oUglyToad.PdfPigoZXing.Net

你可以通过以下命令安装这些包:

dotnet add package PdfPigdotnet add package ZXing.Net

4.2. 获取PDF文件中的二维码图片并解析

首先,我们需要读取 PDF 文件中的第一个页面,并获取其中的第一个图像。然后,我们将该图像转换为 Bitmap 对象,并使用 ZXing 库的 BarcodeReader 对象解析二维码。最后,我们将解析出的发票信息添加到 DataGridView 控件中。

using (PdfDocument document = PdfDocument.Open(fullname)){ Page firstPage = document.GetPages().FirstOrDefault(); if (firstPage != ) { var firstImage = firstPage.GetImages().FirstOrDefault(); if (firstImage != ) { var bitmap = ConvertPdfImageToBitmap(firstImage); var result = reader.Decode(bitmap); if (result != ) { string[] values = result.Text.Split(','); if (values.Length < 8) break; dgvPdfFiles.Rows.Add(file.FullName, file.Name, values[3], values[5], values[4]); } } }}

当然,实际情况可能更加复杂,你可能需要多个图像的问题,并不一定所有的PDF文件第一个图片就是二维码,你可能需要根据具体的情况来处理。比如可以通过判断图片的大小来确定是否是二维码。

var images = firstPage.GetImages().Where(i => i.HeightInSamples == i.WidthInSamples && i.WidthInSamples > 100 && i.HeightInSamples > 100);var firstImage = images.FirstOrDefault();

4.3. 发票其他信息提取

除了二维码中的信息,我们还可以通过读取 PDF 文件的文本内容来提取发票的其他信息,比如项目明细或是在非数电发票的情况下,我们需要通过文本内容来提取发票信息的含税金额信息。因为数电发票是含税的,之前的发票是不含税的,所以我们需要根据具体的情况来处理。这里我们可以使用 UglyToad.PdfPig 库的 Page.Text 属性来获取页面的文本内容。

以下是相关的正则表达式,用于匹配发票的日期、号码、类目和金额等信息:

/// <summary>/// 正则匹配年月日/// 开票日期[::]\s*\d{4}年\d{2}月\d{2}日/// </summary>private static readonly Regex dateRegex = new Regex(@"\d{4}年\d{2}月\d{2}日", RegexOptions.Compiled);
/// <summary>/// 匹配发票号码/// </summary>private static readonly Regex noRegex = new Regex(@"发票号码[::]\s*(\d+)", RegexOptions.Compiled);
/// <summary>/// 匹配类目/// 匹配到第一个,然后去除两边的*号/// </summary>private static readonly Regex typeRegex = new Regex(@"\*.*?\*", RegexOptions.Compiled);
/// <summary>/// 匹配金额/// </summary>private static readonly Regex amountRegex2 = new Regex(@"[yen¥]\s*([0-9]+[.][0-9]{2})", RegexOptions.Compiled );

这里简单介绍一下类目和发票号码的匹配,其他的匹配可以根据具体的情况来处理:

// 处理类目var typeMatch = typeRegex.Match(text);if (typeMatch.Success){ var type = typeMatch.Value.Trim('*'); dgvPdfFiles.Rows[dgvPdfFiles.Rows.Count - 1].Cells["InvoiceType"].Value = type;}// 处理发票号码if(dgvPdfFiles.Rows[dgvPdfFiles.Rows.Count - 1].Cells["InvoiceNo"].Value.ToString() == "?"){ var noMatch = noRegex.Match(text); if (noMatch.Success) { var no = noMatch.Groups[1].Value; dgvPdfFiles.Rows[dgvPdfFiles.Rows.Count - 1].Cells["InvoiceNo"].Value = no; }}

当然,实际的情况可能更加复杂,比如之前的发票可能存在密码区,会造成文本内容的提取不准确等。不过,后面的发票都是数电发票,不存在这个问题了。而且出了XML的数据电文格式,更加方便了信息的提取,没必要这么麻烦了。

4.4. 发票信息表

将提取的发票信息添加到 DataGridView 控件中,除了方便我们查看和管理外。这里我们也可以通过 DataGridView 导出 Excel 表格,以下代码展示了如何将发票信息导出为 CSV 文件:

/// <summary>/// 导出/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void btnExport_Click(object sender, EventArgs e){ // 将列表导出CSV文件
using (SaveFileDialog sfd = new SaveFileDialog() { FileName = "发票数据.csv", Filter = "CSV文件|*.csv", Title = "保存CSV文件" }) { if (sfd.ShowDialog() == DialogResult.OK) { string outputFilePath = sfd.FileName;
using (StreamWriter sw = new StreamWriter(outputFilePath, false, Encoding.UTF8)) { // 带 BOM 的 UTF-8 文件头 sw.WriteLine("\uFEFF文件名,发票号码,开票日期,开票类目,金额");
foreach (DataGridViewRow row in dgvPdfFiles.Rows) { string fileName = row.Cells["FileName"].Value.ToString(); string invoiceNo = row.Cells["InvoiceNo"].Value.ToString(); string invoiceDate = row.Cells["InvoiceDate"].Value.ToString(); string invoiceType = row.Cells["InvoiceType"].Value.ToString(); string invoiceAmount = row.Cells["InvoiceAmount"].Value.ToString();
sw.WriteLine($"{fileName},{invoiceNo},{invoiceDate},{invoiceType},{invoiceAmount}"); } }
// 询问是否打开文件 if (MessageBox.Show("CSV文件导出完成,是否打开?", "提示", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) { System.Diagnostics.Process.Start(outputFilePath); }
txtStatus.Text = "CSV文件导出完成"; } }}

在我们提取了类目之后,我们也可以通过类目来统计发票的总额,这样可以方便我们进行发票管理和统计。

/// <summary>/// 导出发票类目及金额信息/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void btnExportType_Click(object sender, EventArgs e){ // 将列表导出CSV文件 using (SaveFileDialog sfd = new SaveFileDialog() { FileName = "发票类目金额.csv", Filter = "CSV文件|*.csv", Title = "保存CSV文件" }) { if (sfd.ShowDialog() == DialogResult.OK) { string outputFilePath = sfd.FileName; using (StreamWriter sw = new StreamWriter(outputFilePath, false, Encoding.UTF8)) { // 带 BOM 的 UTF-8 文件头 sw.WriteLine("\uFEFF开票类目,金额"); var query = dgvPdfFiles.Rows.Cast<DataGridViewRow>().GroupBy(r => r.Cells["InvoiceType"].Value.ToString()) .Select(g => new { InvoiceType = g.Key, Amount = g.Sum(r => Convert.ToDecimal(r.Cells["InvoiceAmount"].Value)) }); foreach (var item in query) { sw.WriteLine($"{item.InvoiceType},{item.Amount}"); } }
// 询问是否打开文件 if (MessageBox.Show("CSV文件导出完成,是否打开?", "提示", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) { System.Diagnostics.Process.Start(outputFilePath); }
txtStatus.Text = "CSV文件导出完成"; } }
}


发票信息


5. PDF合并和打印

除了提取发票信息,我们还可以使用 .NET 技术来实现 PDF 文件的合并和打印。比如,我们可以将多个发票 PDF 文件合并成一个 PDF 文件,或者直接打印发票 PDF 文件。这样可以方便我们进行发票管理和归档。

将PDF文件合并成一个PDF文件可以方便我们进行打印,这样在打印的时候可以方便调整每张纸打印的页数,比如可以打印两张或者四张等等。

// 合并PDF文件private void MergePdfFiles(string[] pdfFiles, string outputFilePath){ PdfDocumentBuilder builder = new PdfDocumentBuilder();
foreach (string pdfFile in pdfFiles) { using (PdfDocument inputDocument = PdfDocument.Open(pdfFile)) { for (var i = 0; i < inputDocument.NumberOfPages; i++) { builder.AddPage(inputDocument, i + 1); } } }
//保存PDF文件 var documentBytes = builder.Build(); File.WriteAllBytes(outputFilePath, documentBytes);}

其实打印PDF文件也很简单,当然这个只是最简单的实现方式,调用系统打开PDF文件,然后发送打印指令,这样就可以打印PDF文件了。

/// <summary>/// 打印指定文件/// </summary>/// <param name="tempPdfFile"></param>private async void PrintPdfFile(string tempPdfFile){ System.Diagnostics.Process.Start("explorer", tempPdfFile); await Task.Delay(1000); // 发送 Ctrl + P SendKeys.SendWait("^(p)");}

6. 总结

通过以上代码,我们展示了如何使用 .NET 结合 UglyToad.PdfPigZXing 库从 PDF 文件获取图片,并解析二维码信息,同时介绍了如何提取发票的其他信息,如日期、号码、类目和金额等。最后,我们还展示了如何将提取的发票信息导出为 CSV 文件,以及如何合并和打印 PDF 文件。希望这篇文章能帮助你更好地理解和实现发票信息的提取和处理。如果你有任何问题或建议,欢迎在评论区留言

相关推荐

史上最全的浏览器兼容性问题和解决方案

微信ID:WEB_wysj(点击关注)◎◎◎◎◎◎◎◎◎一┳═┻︻▄(页底留言开放,欢迎来吐槽)●●●...

平面设计基础知识_平面设计基础知识实验收获与总结
平面设计基础知识_平面设计基础知识实验收获与总结

CSS构造颜色,背景与图像1.使用span更好的控制文本中局部区域的文本:文本;2.使用display属性提供区块转变:display:inline(是内联的...

2025-02-21 16:01 yuyutoo

写作排版简单三步就行-工具篇_作文排版模板

和我们工作中日常word排版内部交流不同,这篇教程介绍的写作排版主要是用于“微信公众号、头条号”网络展示。写作展现的是我的思考,排版是让写作在网格上更好地展现。在写作上花费时间是有累积复利优势的,在排...

写一个2048的游戏_2048小游戏功能实现

1.创建HTML文件1.打开一个文本编辑器,例如Notepad++、SublimeText、VisualStudioCode等。2.将以下HTML代码复制并粘贴到文本编辑器中:html...

今天你穿“短袖”了吗?青岛最高23℃!接下来几天气温更刺激……

  最近的天气暖和得让很多小伙伴们喊“热”!!!  昨天的气温到底升得有多高呢?你家有没有榜上有名?...

CSS不规则卡片,纯CSS制作优惠券样式,CSS实现锯齿样式

之前也有写过CSS优惠券样式《CSS3径向渐变实现优惠券波浪造型》,这次再来温习一遍,并且将更为详细的讲解,从布局到具体样式说明,最后定义CSS变量,自定义主题颜色。布局...

柠檬科技肖勃飞:大数据风控助力信用社会建设

...

你的自我界限够强大吗?_你的自我界限够强大吗英文

我的结果:A、该设立新的界限...

行内元素与块级元素,以及区别_行内元素和块级元素有什么区别?

行内元素与块级元素首先,CSS规范规定,每个元素都有display属性,确定该元素的类型,每个元素都有默认的display值,分别为块级(block)、行内(inline)。块级元素:(以下列举比较常...

让“成都速度”跑得潇潇洒洒,地上地下共享轨交繁华
让“成都速度”跑得潇潇洒洒,地上地下共享轨交繁华

去年的两会期间,习近平总书记在参加人大会议四川代表团审议时,对治蜀兴川提出了明确要求,指明了前行方向,并带来了“祝四川人民的生活越来越安逸”的美好祝福。又是一年...

2025-02-21 16:00 yuyutoo

今年国家综合性消防救援队伍计划招录消防员15000名

记者24日从应急管理部获悉,国家综合性消防救援队伍2023年消防员招录工作已正式启动。今年共计划招录消防员15000名,其中高校应届毕业生5000名、退役士兵5000名、社会青年5000名。本次招录的...

一起盘点最新 Chrome v133 的5大主流特性 ?

1.CSS的高级attr()方法CSSattr()函数是CSSLevel5中用于检索DOM元素的属性值并将其用于CSS属性值,类似于var()函数替换自定义属性值的方式。...

竞走团体世锦赛5月太仓举行 世界冠军杨家玉担任形象大使

style="text-align:center;"data-mce-style="text-align:...

学物理能做什么?_学物理能做什么 卢昌海

作者:曹则贤中国科学院物理研究所原标题:《物理学:ASourceofPowerforMan》在2006年中央电视台《对话》栏目的某期节目中,主持人问过我一个的问题:“学物理的人,如果日后不...

你不知道的关于这只眯眼兔的6个小秘密
你不知道的关于这只眯眼兔的6个小秘密

在你们忙着给熊本君做表情包的时候,要知道,最先在网络上引起轰动的可是这只脸上只有两条缝的兔子——兔斯基。今年,它更是迎来了自己的10岁生日。①关于德艺双馨“老艺...

2025-02-21 16:00 yuyutoo

取消回复欢迎 发表评论: