一个健壮的CSV阅读器






3.52/5 (6投票s)
这个库中的例程可以解析我能遇到的任何字符串,包括从X.509数字证书中读取的通用名称字符串。
引言
在编写一个程序来生成关于Windows证书存储内容的属性报告的过程中,我发现我手头没有一个CSV阅读器能够处理许多数字证书中发现的异常CSV字符串。我转向The Code Project,找到了一个快速的CSV阅读器,我迫不及待地下载、构建和测试了它。
遗憾的是,我很快就发现它也无法处理这些类型的string
,于是我开始自己编写另一个CSV阅读器。
背景
我尝试过的每个CSV阅读器都无法处理的字符串类型,如下面来自生产中的受信任根证书存储的实际示例。
CN=RapidSSL CA, O="GeoTrust, Inc.", C=US
具体来说,中间的子字符串O="GeoTrust, Inc."
是非标准的,因为旨在保护组织名称中逗号的开引号,GeoTrust, Inc. 是子字符串中的第四个字符。这是设计使然,因为通用名称字符串是一系列名称-值对,许多其他证书属性也是如此。
尽管CodeProject以及其他地方我审查过的所有CSV库都模仿了基类,例如System.IO.StreamReader
,但我选择遵循单一职责原则,完全专注于解析string
,让您自由决定如何获取和管理它们。
获取代码
尽管这个库已经证明非常稳定,但为了预见到它可能需要维护,
源代码位于我最早(也是最稳定)的GitHub存储库之一,地址是https://github.com/txwizard/AnyCSV。最近,我添加了MSDN风格的
文档,该文档已发布在 https://txwizard.github.io/AnyCSV/。
还有一个NuGet包可用,地址是:https://nuget.net.cn/packages/WizardWrx.AnyCSV/。
Using the Code
工作代码在一个类库中,我使用Microsoft .NET Framework 2.0构建了它,并且可以在针对任何新版本框架的项目中使用。为了方便您,我包含了库和测试程序的调试和发布版本。
该包包含一个测试程序AnyCSVTestStand.exe,它从单个源文件Program.cs和一组string
资源构建而成。由于它旨在证明算法的健壮性,测试程序仅限于static
方法。
类库WizardWrx.AnyCSV.dll公开了一个名为Parser
的类,该类有九个构造函数重载,全部为可选,以及一个重载的static Parse
方法,它完成了实际工作。由于实际工作方法是static
的,并且它所需的一切都可以传递给该方法或接受默认值,因此使用实例的唯一原因是当您希望预先为具有相应类属性的参数中的一个或多个指定非默认值时,这样您就可以在循环中调用Parse
方法处理一组string
时省略它们。
Parse
方法有四个重载,所有重载都返回一个string[]
,其中包含子字符串。我将从第四个也是最简单的重载开始,简要介绍这些重载。
- 最简单的重载接受两个参数:要解析的
string
和分隔符,一个pchrDelimiter
. - 稍微复杂一点,第三个重载添加了第二个
char
参数pchrProtector
,它指定了保护子字符串中间出现的pchrDelimiter
的字符,该字符必须被忽略。 - 第三个重载添加了
penmGuardDisposition
,它使用GuardDisposition
枚举的成员来管理保护字符的处理方式。其有效值是直接且不言自明的:Keep
和Strip
。 - 最复杂的重载添加了最后一个参数
penmTrimWhiteSpace
,它使用TrimWhiteSpace
枚举来指定四种可能的选项来处理子字符串中的前导和尾随空白。
值 | 成果 |
Leave | 修剪前导空白。此功能专门用于X.509数字证书的发行者和使用者字段。 |
TrimLeading | 修剪前导空白。此功能专门用于X.509数字证书的发行者和使用者字段。 |
TrimTrailing | 修剪尾随空白。此选项尤其适用于Microsoft Excel生成的CSV文件,这些文件通常包含大量无意义的空白,特别是当工作表在其UsedRange中包含空白行或列时。 |
TrimBoth | 考虑到TrimLeading 和TrimTrailing 是必需的用例,修剪两端基本上是免费的。此标志的实现方式使其可以逻辑上处理为TrimLeading | TrimTrailing 。 |
最后一个static
方法StandardCSVParse
专门用于解析真正的逗号分隔值string
。因此,它只有一个string
参数pstrAnyCSV
。分隔符是逗号,保护字符是双引号。
关注点
所有关键操作都在一个方法中完成,即四个static Parse
方法中最复杂的一个。所有其他方法,包括实例Parse
方法,都调用它,为省略的参数指定默认值,或者,对于实例方法,使用相应的实例属性。
Parse
方法通过一个简单的状态机获得其健壮性,该状态机使用一对简单的布尔变量fInProgress
和fProtectDelimiters
作为状态变量。关键在于,当fProtectDelimiters
为TRUE
时,表示已找到保护字符但尚未找到匹配项,此时将忽略分隔符字符。字符串通过将字符追加到StringBuilder
来组合,该StringBuilder
以足以容纳一个退化情况string
(即一个没有分隔符的字符串)的大小进行初始化。预留如此大的内存量的目标是使StringBuilder
永远不需要扩展;因此,它永远不需要将部分构建的string
移动到更大的缓冲区。
为了简化常规使用,该类将常见的定界符和保护字符公开为public
常量。如果您更愿意避免使用原始字符,则可以根据DelimiterChar
和GuardChar
这对枚举来指定它们。常量和枚举在指定保护字符时特别有用,因为引号字面量相当挑剔。
通过DelimiterMap
和GuardMap
结构数组来翻译DelimiterChar
和GuardChar
枚举,这些数组由静态Parser
构造函数填充。
实例属性使用s_objSyncLock
(一个private
泛型object
)使其具有线程安全。我从其他使用此类对象来实现单例设计模式的类中借鉴了这种经过验证的概念。
有改进空间
我预计其他人会找到许多改进此库的方法。我想到的一个方法是添加一个static StringBuilder
,该StringBuilder
在生命周期内会增长但不会收缩。至少,这需要Parse
方法测试其是否存在和大小,并使用s_objSyncLock
对象使其成为线程安全的。
历史
- 2015年5月1日,星期五 - 首次发布
- 2019年4月29日,星期一 - 添加“获取代码”部分,因为代码位于GitHub存储库中
- 2020年1月14日,星期二 - 从文本中删除多余的标记。