65.9K
CodeProject 正在变化。 阅读更多。
Home

一个用于根据文件内容移动和重命名扫描文档的 Windows 服务。

starIconstarIconstarIconstarIconstarIcon

5.00/5 (6投票s)

2013年8月13日

CPOL

5分钟阅读

viewsIcon

23415

downloadIcon

30

本文介绍了我如何编写一个小型 Windows 服务来处理根据内容对扫描(并经过 OCR)的文档进行排序和重命名的任务。

引言

在本文中,我将介绍我如何制作一个简单的 Windows 服务,该服务监视一个文件夹以接收传入的 PDF 文档(例如,来自扫描仪),然后根据文件内容重命名文件并将其移动到指定文件夹。该解决方案使用正则表达式来决定将文档移动到哪里(识别),然后使用它来提取对文件命名有用的信息,例如发票日期、客户名称等。

背景

在购买了一台新的扫描仪(绝佳的 ScanSnap IX500)以数字化超过 2000 页的旧发票和其他文件后,我面临着对所有扫描文档进行排序的问题,我意识到手动完成对我来说会非常枯燥,所以我决定用编程的方式来解决这个问题。

在对扫描仪及其附带的软件进行了一些研究后,我发现使用高分辨率扫描进行 OCR,然后缩小图像以生成实际的 PDF 是获得良好 OCR 质量的最佳方法。OCR 由 ABBYY 引擎完成,该引擎会在图像的相应位置之上放置一个透明文本层,从而创建一个可以标记、复制等操作的 PDF。

因此,当 ABBYY 完成后,我得到一个“可搜索 PDF”,而这个 PDF 又需要为我的项目进行解析。在研究了 PDF 文档软件的开源解决方案后,我发现 Apache PDFBox 最符合我的需求,而且碰巧在这里 codeproject.com 上有一篇文章(Converting-PDF-to-Text-in-C),其中包含了一些预编译的二进制文件,您在 .NET 项目中使用它所需的一切,所以我继续使用那里的示例。

使用代码

编译 

要能够编译我的项目,您需要从此处下载二进制文件,并将以下文件包含在您项目的资源文件夹中

  • IKVM.OpenJDK.Core.dll
  • IKVM.OpenJDK.SwingAWT.dll
  • pdfbox-1.7.0.dll 

另外,请务必将以下文件复制到您项目的 bin 文件夹中(否则它将无法运行)

  • commons-logging.dll
  • fontbox-1.7.0.dll
  • IKVM.OpenJDK.Util.dll
  • IKVM.Runtime.dll 

架构 

该解决方案创建了一个 Windows 服务,需要使用 .NET 相应框架文件夹中的 installutil.exe 命令进行安装。在调试模式下,代码像往常一样使用 F5 运行,但当编译为发布版本时,它会变成一个服务。 

总体流程 

该项目的整个想法是:

  1. 监视一个文件夹以查找新的 PDF 文件。
  2. 当出现新文件时,搜索文件中的特定标识符以决定如何处理它。
  3. 当标识符匹配时,选择文件中的重要信息并使用它来适当地命名文件。
  4. 将文件移动到取决于标识符的目标文件夹。

设置文件监视器 

由于我使用的 ABBYY(OCR 软件)的设置方式,文件在经过 OCR 处理后会被命名为 <prefix>_OCR.pdf,因此 FileSystemWatcher 对象如下设置:

FileSystemWatcher watcher = new FileSystemWatcher(watchFolder, "*_OCR.pdf");
watcher.NotifyFilter = NotifyFilters.LastWrite| NotifyFilters.FileName | NotifyFilters.DirectoryName;
watcher.Created += new FileSystemEventHandler(OnCreated);
watcher.EnableRaisingEvents = true;

在测试软件时,我经常发现由于标识符编写不当,文件会进入错误的目录并带有错误的文件名,为了能够重新处理文件而不考虑文件名,我还为 rematchFolder 设置了一个监视器,其过滤器仅设置为“*.pdf”。这样您就可以更改配置,然后将任何文件放入 rematch 文件夹,并使用新规则再次进行处理。

配置  

有两个配置部分来运行服务。一个是 app.config,它指向输入文件夹、不匹配文件夹、规则配置文件所在的位置以及日志文件的存放位置。  

规则配置存储在 XML 文件中,然后加载到一个 PDFTemplate 对象的列表(PDFTemplates)中。 PDFTemplate 类仅包含:

  • identifiers 中的字符串列表,其中每个字符串都是一个正则表达式,并且必须匹配文件中的所有标识符才能触发规则。
  • contentSelectors 中的字符串列表,其中每个字符串都是一个正则表达式,包含匹配组(在正则表达式中用“(...)”表示),第一个匹配到内容的选定器将用于重命名文件。
  • fileNamePrefix 中的字符串,用于设置规则应将文件重命名到的文件名的前缀。
  • destionationFolder 中的字符串,包含一个目录的完整路径,规则应将文件移动到该目录。

XML 文件因此如下所示:

<ArrayOfPDFTemplate xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <PDFTemplate>
    <identifiers>
      <string>[Ss]ome company</string>
    </identifiers>
    <contentSelectors>
      <string>\bInvoice date\W+(?:\w+\W+){0,20}?([0-9] *[0-9] *[0-9] *[0-9] *- *[0-9] *[0-9] *- *[0-9] *[0-9])\b</string>
      <string>([0-9] *[0-9] *[0-9] *[0-9] *- *[0-9] *[0-9] *- *[0-9] *[0-9])</string>
    </contentSelectors>
    <fileNamePrefix>Some Company</fileNamePrefix>
    <destinationFolder>C:\Sorted PDF Files\Some Company</destinationFolder>
  </PDFTemplate>

  ...
</ArrayOfPDFTemplate>  

运行匹配和重命名文件 

现在,当一个文件被处理时,它会遍历 PDFTemplates 列表中所有对象的标识符,并针对第一个匹配项应用规则。如果根本没有匹配到任何规则,该文件将被移动(但不重命名)到一个指定的“noMatch”文件夹进行手动处理。

用于搜索文件中的标识符并进行重命名的代码如下:

...
					
//Extract all text from the PDF document
org.apache.pdfbox.pdmodel.PDDocument doc = org.apache.pdfbox.pdmodel.PDDocument.load(fullPath);
org.apache.pdfbox.util.PDFTextStripper stripper = new org.apache.pdfbox.util.PDFTextStripper();
text = stripper.getText(doc);
doc.close();

...
	
//Go through all identifiers, looking for a match
foreach (string identifier in thisTemplate.identifiers)
{
    if (!Regex.IsMatch(text, identifier, RegexOptions.IgnoreCase))
    {
        identifiersFound = false;
        break;
    }
}

...

//Look for a matching contentselector
foreach (string contentSelector in thisTemplate.contentSelectors)
{
    Match thisMatch = Regex.Match(text, contentSelector, 
             RegexOptions.IgnoreCase | RegexOptions.Multiline);
    if (thisMatch.Captures.Count != 0)
    {
        string selection = thisMatch.Groups[1].Value;
        newFileName = newFileName + "_" + selection;
        break;
    }
}

然后就剩下重命名和移动正在处理的文件的问题了。

关注点

这篇文章实际上并非旨在优雅地解决问题(代码需要一些重构才能做到这一点 - 它只是一个临时解决方案),而是如果您面临类似情况,它为您提供了一个起点。我曾真的试图寻找可以为我完成这项工作的软件,但在使用正则表达式和设置我自己的规则集合来应用于文件方面,我一无所获。

© . All rights reserved.