摘要:本文以发票助手获取发票信息为例,详细介绍如何使用 .NET 技术处理 PDF 文件并进行二维码解析。文章介绍的相关代码已开源在GitHub,欢迎查看和收藏。
本文以发票助手获取发票信息为例,详细介绍如何使用 .NET 技术处理 PDF 文件并进行二维码解析。文章介绍的相关代码已开源在GitHub,欢迎查看和收藏。
在日常工作中,我们经常需要处理发票信息,比如日常报销和出差等等场景。如果发票比较多,能有一款工具可以方便的帮助我们处理 PDF 发票就非常棒了,当然我们其实有很多的选则,比如微信的卡包,还有QQ邮箱、WPS的发票工具等等,但是这些工具都有一些局限性。因此,我们可以自己开发一款发票助手,通过读取 PDF 文件中的发票二维码并解析其内容,实现发票信息的提取和处理。
电子发票越来越普及,我们可以通过扫码获取电子发票,也可以通过邮件或者网站下载电子发票。电子发票的好处是方便快捷,不需要纸质发票,可以随时随地查看和打印。
不知道大家有没有发现,最近的发票都已经换成了新的数电发票,增值税专用发票也使用了电子化,省去了邮寄的环节,减少了开票时间和开票成本。数电发票增加XML的数据电文格式便利交付,更加方便了信息的提取,同时也保留PDF/OFD格式。
无论是之前的电子发票还是新的数电发票,在发票的左上角一般都会有一个二维码,里面通常包含了发票的关键信息,如发票代码、发票号码、金额、日期等。我们可以通过解析二维码来获取这些信息,从而实现发票信息的提取和处理。当然,这些信息我们也可以直接通过微信扫码来获取和测试。
以下是一个发票二维码的扫码获取的信息的示例,部分数字信息已经做了模糊处理,使用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
数电普票
后面几个分别是发票的代码、号码、金额、日期和校验码等信息。
了解了发票中二维码的信息后,我们可以使用 .NET 技术来处理 PDF 文件并进行二维码解析。在这里,我们将使用UglyToad.PdfPig库来读取 PDF 文件,使用ZXing库来解析二维码。4.1. 准备工作在开始之前,请确保你已经安装了以下 NuGet 包:
•UglyToad.PdfPig•ZXing.Net你可以通过以下命令安装这些包:
dotnet add package PdfPigdotnet add package ZXing.Net4.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 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属性来获取页面的文本内容。以下是相关的正则表达式,用于匹配发票的日期、号码、类目和金额等信息:
/// /// 正则匹配年月日/// 开票日期[::]\s*\d{4}年\d{2}月\d{2}日/// private static readonly Regex dateRegex = new Regex(@"\d{4}年\d{2}月\d{2}日", RegexOptions.Compiled);/// /// 匹配发票号码/// private static readonly Regex noRegex = new Regex(@"发票号码[::]\s*(\d+)", RegexOptions.Compiled);/// /// 匹配类目/// 匹配到第一个,然后去除两边的*号/// private static readonly Regex typeRegex = new Regex(@"\*.*?\*", RegexOptions.Compiled);/// /// 匹配金额/// private static readonly Regex amountRegex2 = new Regex(@"[¥¥]\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 文件:
/// /// 导出/// /// /// 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文件导出完成"; } }}在我们提取了类目之后,我们也可以通过类目来统计发票的总额,这样可以方便我们进行发票管理和统计。
/// /// 导出发票类目及金额信息/// /// /// 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.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}"); } } txtStatus.Text = "CSV文件导出完成"; } }除了提取发票信息,我们还可以使用 .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 { builder.AddPage(inputDocument, i + 1); } } } //保存PDF文件 var documentBytes = builder.Build; File.WriteAllBytes(outputFilePath, documentBytes);}其实打印PDF文件也很简单,当然这个只是最简单的实现方式,调用系统打开PDF文件,然后发送打印指令,这样就可以打印PDF文件了。
/// /// 打印指定文件/// /// private async void PrintPdfFile(string tempPdfFile){ System.Diagnostics.Process.Start("explorer", tempPdfFile); await Task.Delay(1000); // 发送 Ctrl + P SendKeys.SendWait("^(p)");}通过以上代码,我们展示了如何使用 .NET 结合UglyToad.PdfPig和ZXing库从 PDF 文件获取图片,并解析二维码信息,同时介绍了如何提取发票的其他信息,如日期、号码、类目和金额等。最后,我们还展示了如何将提取的发票信息导出为 CSV 文件,以及如何合并和打印 PDF 文件。希望这篇文章能帮助你更好地理解和实现发票信息的提取和处理。如果你有任何问题或建议,欢迎在评论区留言来源:opendotnet