Files
lserp_cs_6.0/其他程序/ExecPythonPDF/ExecPythonPDF/PythonConsole.cs
T
tdx ad75fa356a SVN r1157
SVN-Revision: r1157
2026-04-23 05:52:53 +00:00

591 lines
23 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using iText.Kernel.Pdf;
using iText.Kernel.Pdf.Canvas.Parser;
using iText.Kernel.Pdf.Canvas.Parser.Data;
using iText.Kernel.Pdf.Canvas.Parser.Listener;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
namespace ExecPythonPDF
{
public class BankRecord
{
public string BankName { get; set; } = "";
public string RefId { get; set; } = "";
public string Amount { get; set; } = "";
public string Date { get; set; } = "";
public string PayeeAccount { get; set; } = "";
public string PayeeName { get; set; } = "";
public string PayeeBank { get; set; } = "";
public string Remark { get; set; } = "";
}
public static class PDFConvert
{
// ==== 静态构造函数:强制加载亚洲字体包,防止 IIS 懒加载导致 UniGB-UTF16-H 报错 ====
static PDFConvert()
{
try
{
System.Reflection.Assembly.Load("itext.font_asian");
}
catch
{
// 忽略异常,防止主流程中断
}
}
public static JObject ExecutePdfExtraction(string fileUrl, string fileName)
{
JObject rtnJobject = new JObject();
rtnJobject.Add("success", "false");
rtnJobject.Add("code", "0");
rtnJobject.Add("msg", "");
rtnJobject.Add("data", new JArray());
try
{
if (!Path.GetExtension(fileName).Equals(".pdf", StringComparison.OrdinalIgnoreCase))
{
throw new Exception("不是pdf文件");
}
byte[] fileData;
if (fileUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
using (WebClient webClient = new WebClient())
{
fileData = webClient.DownloadData(fileUrl);
}
}
else
{
fileData = File.ReadAllBytes(fileUrl);
}
using (MemoryStream pdfStream = new MemoryStream(fileData))
{
string content = ConvertPdfToText(pdfStream);
string bankName = BankExtractorFactory.IdentifyBank(fileName);
if (string.IsNullOrEmpty(bankName))
{
throw new Exception("未知银行");
}
List<BankRecord> records = BankExtractorFactory.GetExtractor(bankName).Extract(content);
JArray dataArray = rtnJobject["data"] as JArray;
foreach (BankRecord record in records)
{
dataArray.Add(JObject.FromObject(record));
}
rtnJobject["success"] = "true";
rtnJobject["code"] = "1";
rtnJobject["msg"] = "操作成功";
}
}
catch (Exception ex)
{
rtnJobject["success"] = "false";
rtnJobject["code"] = "0";
rtnJobject["msg"] = ex.Message;
}
return rtnJobject;
}
public static string ConvertPdfToText(Stream pdfStream)
{
StringBuilder textContent = new StringBuilder();
try
{
using (PdfReader reader = new PdfReader(pdfStream))
using (PdfDocument pdfDoc = new PdfDocument(reader))
{
for (int i = 1; i <= pdfDoc.GetNumberOfPages(); i++)
{
PdfPage page = pdfDoc.GetPage(i);
CustomLayoutStrategy strategy = new CustomLayoutStrategy(3.0f);
new PdfCanvasProcessor(strategy).ProcessPageContent(page);
string pageText = strategy.GetResultantText();
if (!string.IsNullOrWhiteSpace(pageText))
{
textContent.AppendLine($"--- 第{i}页 ---");
textContent.AppendLine(pageText);
}
}
}
return textContent.ToString();
}
catch (Exception ex)
{
throw new Exception($"PDF解析失败: {ex.Message}", ex);
}
}
}
/// <summary>
/// 自定义布局策略:模拟 Python pdfplumber 的 y_tolerance 逻辑
/// </summary>
public class CustomLayoutStrategy : IEventListener
{
private readonly List<TextChunk> _chunks = new List<TextChunk>();
private readonly float _yTolerance;
public CustomLayoutStrategy(float yTolerance = 3.0f)
{
_yTolerance = yTolerance;
}
public void EventOccurred(IEventData data, EventType type)
{
if (type == EventType.RENDER_TEXT)
{
var renderInfo = (TextRenderInfo)data;
var text = renderInfo.GetText();
if (string.IsNullOrWhiteSpace(text)) return;
var segment = renderInfo.GetBaseline();
var start = segment.GetStartPoint();
_chunks.Add(new TextChunk
{
Text = text,
X = start.Get(0),
Y = start.Get(1)
});
}
}
public ICollection<EventType> GetSupportedEvents()
{
return new HashSet<EventType> { EventType.RENDER_TEXT };
}
public string GetResultantText()
{
if (_chunks.Count == 0) return "";
var sortedByY = _chunks.OrderByDescending(c => c.Y).ToList();
var lines = new List<List<TextChunk>>();
var currentLine = new List<TextChunk>();
if (sortedByY.Count > 0)
{
currentLine.Add(sortedByY[0]);
float currentLineY = sortedByY[0].Y;
for (int i = 1; i < sortedByY.Count; i++)
{
var chunk = sortedByY[i];
if (Math.Abs(chunk.Y - currentLineY) <= _yTolerance)
{
currentLine.Add(chunk);
}
else
{
lines.Add(currentLine);
currentLine = new List<TextChunk> { chunk };
currentLineY = chunk.Y;
}
}
lines.Add(currentLine);
}
StringBuilder sb = new StringBuilder();
foreach (var line in lines)
{
var sortedLine = line.OrderBy(c => c.X).ToList();
sb.AppendLine(SmartJoin(sortedLine));
}
return sb.ToString();
}
private string SmartJoin(List<TextChunk> chunks)
{
if (chunks.Count == 0) return "";
StringBuilder lineSb = new StringBuilder();
lineSb.Append(chunks[0].Text);
for (int i = 1; i < chunks.Count; i++)
{
var prev = chunks[i - 1];
var curr = chunks[i];
if (curr.X - (prev.X + GetWidthEstimate(prev.Text)) > 5)
{
lineSb.Append(" ");
}
lineSb.Append(curr.Text);
}
return lineSb.ToString();
}
private float GetWidthEstimate(string text)
{
return text.Length * 4.0f;
}
private class TextChunk
{
public string Text { get; set; }
public float X { get; set; }
public float Y { get; set; }
}
}
// --- 基类 ---
public abstract class BankExtractor
{
public string BankName { get; }
protected BankExtractor(string bankName)
{
BankName = bankName;
}
public abstract List<BankRecord> Extract(string content);
protected string ExtractSingleValue(string content, string pattern)
{
var match = Regex.Match(content, pattern);
return match.Success ? match.Groups[1].Value.Trim() : "";
}
}
// --- 1. 中国银行 ---
public class BOCExtractor : BankExtractor
{
public BOCExtractor() : base("中国银行") { }
public override List<BankRecord> Extract(string content)
{
var records = new List<BankRecord>();
var pages = content.Split(new[] { "--- 第" }, StringSplitOptions.RemoveEmptyEntries);
foreach (var rawPage in pages)
{
string pageContent = rawPage;
if (string.IsNullOrWhiteSpace(pageContent)) continue;
string refId = ExtractSingleValue(pageContent, @"交易流水号[:][^\S\r\n]*([^\s]+)");
if (string.IsNullOrEmpty(refId))
{
refId = ExtractSingleValue(pageContent, @"回单编号[:][^\S\r\n]*([^\s]+)");
}
if (string.IsNullOrEmpty(refId)) continue;
var record = new BankRecord
{
BankName = BankName,
RefId = refId,
Date = ExtractSingleValue(pageContent, @"日期[:][^\S\r\n]*(\d{4}年\d{1,2}月\d{1,2}日)"),
};
string amountStr = ExtractSingleValue(pageContent, @"金额[:][^\S\r\n]*CNY([0-9,.]+)");
record.Amount = amountStr.Replace(",", "");
record.PayeeAccount = ExtractSingleValue(pageContent, @"收款人账号[:][^\S\r\n]*([a-zA-Z0-9]+)");
record.PayeeName = ExtractSingleValue(pageContent, @"收款人名称[:][^\S\r\n]*([^\r\n]+)");
record.PayeeBank = ExtractSingleValue(pageContent, @"收款人开户行[:][^\S\r\n]*([^\r\n]+)");
string yongtu = ExtractSingleValue(pageContent, @"用途[:][^\S\r\n]*([^\r\n]+)");
string beizhu = ExtractSingleValue(pageContent, @"备注[:][^\S\r\n]*([^\r\n]+)");
string fuyan = ExtractSingleValue(pageContent, @"附言[:][^\S\r\n]*([^\r\n]+)");
var remarkList = new List<string>();
if (!string.IsNullOrEmpty(yongtu)) remarkList.Add(yongtu);
if (!string.IsNullOrEmpty(beizhu)) remarkList.Add(beizhu);
if (!string.IsNullOrEmpty(fuyan)) remarkList.Add(fuyan);
record.Remark = string.Join("-", remarkList.Distinct());
records.Add(record);
}
return records;
}
}
// --- 2. 工商银行 ---
public class ICBCExtractor : BankExtractor
{
public ICBCExtractor() : base("工商银行") { }
public override List<BankRecord> Extract(string content)
{
var records = new List<BankRecord>();
var pages = content.Split(new[] { "--- 第" }, StringSplitOptions.None);
var extractedIds = new HashSet<string>();
foreach (var rawPage in pages)
{
string pageContent = rawPage;
if (string.IsNullOrWhiteSpace(pageContent)) continue;
if (!pageContent.StartsWith("页 ---")) pageContent = "--- 第" + pageContent;
var record = new BankRecord { BankName = BankName };
record.RefId = ExtractSingleValue(pageContent, @"回单编号[:]\s*([^\s]+)");
string amountStr = ExtractSingleValue(pageContent, @"小写[:]\s*([0-9,.]+)");
record.Amount = amountStr.Replace(",", "");
record.Date = ExtractSingleValue(pageContent, @"日期[:]\s*(\d{4}-\d{2}-\d{2})");
record.PayeeAccount = ExtractSingleValue(pageContent, @"收款人账号[:]\s*([a-zA-Z0-9]+)");
record.PayeeBank = ExtractSingleValue(pageContent, @"收款人开户行[:]\s*([^\s]+)");
record.PayeeName = ExtractSingleValue(pageContent, @"收款人户名[:]\s*([^\s]+)");
string summary = ExtractSingleValue(pageContent, @"摘要[:]\s*([^\s]+)");
string usage = ExtractSingleValue(pageContent, @"用途[:]\s*([^\s]+)");
if (summary.Contains("用途:") || summary.Contains("交易机构号:")) summary = "";
if (usage.Contains("交易机构号:") || usage.Contains("凭证号码:")) usage = "";
record.Remark = !string.IsNullOrEmpty(summary) ? summary : usage;
if (!string.IsNullOrEmpty(record.RefId) && !extractedIds.Contains(record.RefId))
{
if (!string.IsNullOrEmpty(record.Amount) || !string.IsNullOrEmpty(record.Date))
{
records.Add(record);
extractedIds.Add(record.RefId);
}
}
}
return records;
}
}
// --- 3. 成都银行 ---
public class ChengduBankExtractor : BankExtractor
{
public ChengduBankExtractor() : base("成都银行") { }
public override List<BankRecord> Extract(string content)
{
var records = new List<BankRecord>();
var pages = content.Split(new[] { "--- 第" }, StringSplitOptions.RemoveEmptyEntries);
foreach (var rawPage in pages)
{
string pageContent = rawPage;
if (string.IsNullOrWhiteSpace(pageContent)) continue;
string refPattern = @"参考号[^\S\r\n]*([^\s]+)[^\S\r\n]+交易日期[^\S\r\n]*(\d{4}-\d{2}-\d{2})";
var refMatches = Regex.Matches(pageContent, refPattern);
for (int i = 0; i < refMatches.Count; i++)
{
var match = refMatches[i];
string refId = match.Groups[1].Value;
string date = match.Groups[2].Value;
int currentStart = match.Index;
int prevEnd = (i > 0) ? (refMatches[i - 1].Index + refMatches[i - 1].Length) : 0;
int lengthBefore = currentStart - prevEnd;
string beforeSearchArea = (lengthBefore > 0) ? pageContent.Substring(prevEnd, lengthBefore) : "";
int nextStart = (i < refMatches.Count - 1) ? refMatches[i + 1].Index : pageContent.Length;
int afterStart = match.Index + match.Length;
int lengthAfter = nextStart - afterStart;
string afterSearchArea = (lengthAfter > 0) ? pageContent.Substring(afterStart, lengthAfter) : "";
var record = new BankRecord
{
BankName = BankName,
RefId = refId,
Date = date,
Amount = ExtractAmount(beforeSearchArea),
PayeeAccount = ExtractSingleValue(beforeSearchArea, @"收款方账户[^\S\r\n]*([a-zA-Z0-9]*)"),
PayeeName = ExtractSingleValue(beforeSearchArea, @"收款方户名[^\S\r\n]*([^\r\n]*)").Trim(),
Remark = ExtractSingleValue(afterSearchArea, @"摘要[^\S\r\n]*([^\r\n]*)").Trim()
};
var bankMatches = Regex.Matches(beforeSearchArea, @"开户行[^\S\r\n]*((?!开户行|方|款|付|收)[^\s]*)");
if (bankMatches.Count > 0)
{
record.PayeeBank = bankMatches[bankMatches.Count - 1].Groups[1].Value.Trim();
}
records.Add(record);
}
}
return records;
}
private string ExtractAmount(string searchArea)
{
int pos = searchArea.LastIndexOf("交易金额");
if (pos != -1)
{
string relevant = searchArea.Substring(0, pos);
var lines = relevant.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
string numPattern = @"([\d,]+\.?\d*)\s*$";
for (int j = lines.Length - 1; j >= 0; j--)
{
var m = Regex.Match(lines[j].Trim(), numPattern);
if (m.Success) return m.Groups[1].Value.Replace(",", "");
}
}
return "";
}
}
// --- 4. 民生银行 ---
public class CMBCExtractor : BankExtractor
{
public CMBCExtractor() : base("民生银行") { }
public override List<BankRecord> Extract(string content)
{
var records = new List<BankRecord>();
var pages = content.Split(new[] { "--- 第" }, StringSplitOptions.RemoveEmptyEntries);
foreach (var rawPage in pages)
{
string pageContent = rawPage;
if (string.IsNullOrWhiteSpace(pageContent)) continue;
var refMatch = Regex.Match(pageContent, @"交易流水号\s+([^\s]+)");
if (!refMatch.Success) continue;
var record = new BankRecord
{
BankName = BankName,
RefId = refMatch.Groups[1].Value,
Date = ExtractSingleValue(pageContent, @"记账时间\s+(\d{4}-\d{2}-\d{2})")
};
string amountStr = ExtractSingleValue(pageContent, @"金额 人民币[:]\s*([0-9,.]+)");
record.Amount = amountStr.Replace(",", "");
record.PayeeName = GetRightColumnValue(pageContent, "全称");
record.PayeeAccount = GetRightColumnValue(pageContent, "账号");
record.PayeeBank = GetRightColumnValue(pageContent, "开户行");
record.Remark = ExtractSingleValue(pageContent, @"(?:客户附言|附言)\s+([^\s]+)");
records.Add(record);
}
return records;
}
private string GetRightColumnValue(string content, string keyword)
{
var matches = Regex.Matches(content, keyword + @"[^\S\r\n]+([^\s]+)");
if (matches.Count >= 2)
{
return matches[1].Groups[1].Value.Trim();
}
else if (matches.Count == 1)
{
return matches[0].Groups[1].Value.Trim();
}
return "";
}
}
// --- 5. 浙商银行 ---
public class CZBankExtractor : BankExtractor
{
public CZBankExtractor() : base("浙商银行") { }
public override List<BankRecord> Extract(string content)
{
var records = new List<BankRecord>();
var pages = content.Split(new[] { "--- 第" }, StringSplitOptions.RemoveEmptyEntries);
foreach (var rawPage in pages)
{
string pageContent = rawPage;
if (string.IsNullOrWhiteSpace(pageContent)) continue;
var refMatch = Regex.Match(pageContent, @"交易流水号[:]\s*([^\s]+)");
if (!refMatch.Success) continue;
string refId = refMatch.Groups[1].Value;
var record = new BankRecord
{
BankName = BankName,
RefId = refId,
Date = ExtractDate(pageContent),
Amount = ExtractAmount(pageContent),
PayeeName = GetRightColumnValue(pageContent, "户名"),
PayeeAccount = GetRightColumnValue(pageContent, "账号"),
};
record.PayeeBank = ExtractPayeeBank(pageContent);
string yongtu = CleanBorderArtifacts(ExtractSingleValue(pageContent, @"用途[:][^\S\r\n]*([^\s]*)"));
string fuyan = CleanBorderArtifacts(ExtractSingleValue(pageContent, @"附言[:][^\S\r\n]*([^\s]*)"));
string zhaiyao = CleanBorderArtifacts(ExtractSingleValue(pageContent, @"摘要[:][^\S\r\n]*([^\s]*)"));
string beizhu = CleanBorderArtifacts(ExtractSingleValue(pageContent, @"备注[:][^\S\r\n]*([^\s]*)"));
var remarkList = new List<string>();
if (!string.IsNullOrEmpty(yongtu)) remarkList.Add(yongtu);
if (!string.IsNullOrEmpty(fuyan)) remarkList.Add(fuyan);
if (!string.IsNullOrEmpty(zhaiyao)) remarkList.Add(zhaiyao);
if (!string.IsNullOrEmpty(beizhu)) remarkList.Add(beizhu);
record.Remark = string.Join("-", remarkList.Distinct());
records.Add(record);
}
return records;
}
private string CleanBorderArtifacts(string text)
{
if (string.IsNullOrWhiteSpace(text)) return "";
if (text.Length == 1 && "本凭证作客户收付款通知人".Contains(text)) return "";
return text;
}
private string ExtractDate(string text)
{
var m = Regex.Match(text, @"(?:银行日期|交易时间)[:]\s*(\d{4}-\d{2}-\d{2})");
return m.Success ? m.Groups[1].Value : "";
}
private string ExtractAmount(string text)
{
var m = Regex.Match(text, @"金额\s+人民币[^\r\n¥]*¥([0-9,.\d]+)");
if (!m.Success) m = Regex.Match(text, @"金额\s+人民币(?:[^¥]*?)¥([0-9,.\d]+)");
return m.Success ? m.Groups[1].Value.Replace(",", "") : "";
}
private string GetRightColumnValue(string content, string keyword)
{
var matches = Regex.Matches(content, keyword + @"[:]");
if (matches.Count >= 2)
{
return GetValueAfterMatch(content, matches[1], null);
}
return "";
}
private string ExtractPayeeBank(string content)
{
var matches = Regex.Matches(content, @"开户银行[:]");
if (matches.Count >= 2)
{
string bank1 = GetValueAfterMatch(content, matches[0], matches[1]);
string bank2 = GetValueAfterMatch(content, matches[1], null);
if (string.IsNullOrEmpty(bank2)) return "";
if (content.Contains("(借)") || content.Contains("(借)"))
{
if (bank2.Contains("浙商银行") && !bank1.Contains("浙商银行")) return bank1;
if (bank1.Contains("浙商银行") && !bank2.Contains("浙商银行")) return bank2;
}
return bank2;
}
return "";
}
private string GetValueAfterMatch(string content, Match currentMatch, Match nextMatch)
{
int p = currentMatch.Index + currentMatch.Length;
while (p < content.Length && (content[p] == ' ' || content[p] == '\t')) p++;
int endLimit = nextMatch != null ? nextMatch.Index : content.Length;
int endP = p;