将CSV文件导入c#

我正在建立一个网站,其中一个要求是用户从他们的电子邮件客户端导出他们的联系人以将其导入网站。

因为每个电子邮件客户端以稍微不同的格式导出他们的联系人,所以我的头脑不得不以最好的方式来接近它。 因为我不知道这些字段是什么,或者分隔符是什么。

我只想找到主要的电子邮件客户端/地址簿(outlook,apple mail,entourage,thunderbird)。 所有这些都有完全不同的格式。 Entourage使用tab作为分隔符,其余的使用逗号等。我只需要拔出电子邮件地址和(如果有的话)名称。 名称变得棘手,因为一些客户端具有名字/姓氏的单独字段。

使用FileHelper是理想的,但在我可以连接解决方​​案之前,我似乎需要知道csv的结构。 如果可能的话,我宁愿不去编写我自己的csv解析器。

这是我对集体蜂巢思想的看法:

计划A.

  • 阅读csv文件的第一行(所有格式都有一个标题作为第一行)并计算标签与逗号的数量。 从中确定分隔符。
  • 使用某种类型的csv阅读器(如Lumenworks)为我提供文件其余部分的基本csv读取function。
  • 在每个字段上执行正则表达式匹配以确定电子邮件列。
  • 不知道如何找出用户的名字……

B计划

  • 提示用户输入电子邮件客户端的类型,并为每个不同的客户端单独编码< – 看起来真的很笨重。

计划C.

….使用/购买已经执行此操作的现有组件?! (我肯定找不到一个!)

思考?

我会选择B计划(我不同意它是笨重的)。

恕我直言,最好的方法是询问用户他/她需要从哪个电子邮件客户端导出。 因此,您可以识别分隔符。 你自己发现虽然不同的客户端使用不同的分隔符,但是单个客户端总是使用相同的分隔符(除非他们决定引出一个非向后兼容的版本)因此,创建一个面向对象的类应该不难接受分隔符作为参数,并相应地解析输入(逻辑应保持几乎相同,无论分隔符如何)。

即使解析每种类型的导出文件的逻辑差异很大,似乎您可以创建一个抽象基类,它包含所有常用function和派生类,它们只是覆盖客户端特定的function。

即使您使用FileHelpers等自定义库,您也应该能够通过传递分隔符的类型来完成它。

我觉得你不应该依赖可能的分隔符的相对计数来识别实际的分隔符是什么(如在计划A中)。

编辑:刚想到的另一个选择是提供一种类似MS Excel的选项界面。 您可以选择分隔符,并根据选择实时预览数据的解析方式。

我首先看看比赛是如何做到的。

谷歌: “我们支持以CSV文件格式导入联系人(逗号分隔值)。为了获得最佳效果,请使用由Outlook,Outlook Express,Yahoo!或Hotmail生成的CSV文件。对于Apple地址簿,有一个有用的实用程序称为“A到G”。“
所以我猜他们会选择你的计划A,并对上述CSV文件进行检查。

实时邮件/ hotmail:他们选择B和支持: Microsoft Outlook(使用CSV),Outlook Express(使用CSV),Windows联系人,Windows Live Hotmail,Yahoo! 邮件(使用Outlook CSV格式和逗号分隔),Gmail(使用Outlook CSV格式)

Facebook:他们允许您输入您的电子邮件地址,如果他们知道(yahoo,gmail,hotmail等),他们会询问您的密码,并自动检索您的联系人。 (选项D)如果他们不支持您的电子邮件提供商,您仍然可以从Outlook或其他格式上传CSV文件(选项B的种类)。

我猜Facebook的版本真的很酷。 但是如果这太多了,您可以选择支持的CSV格式的选项A(您必须检查不同的CSV文件),否则如果您不识别它,请提示用户您识别的不同columsn的含义。

如果您需要更改将导入的CSV文件的分隔符,请使用以下代码:

GenericConnection connection = new GenericConnection(); OleDbConnection con = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=\"" + file.DirectoryName + "\"; Extended Properties='text;HDR=Yes;FMT=" + strDelimiter + "(,)';"); connection.DBConn = con; connection.Filename = strFilePath; FileInfo file = new FileInfo(conn.Filename); DataTable dt = new DataTable(); string selectFields = "Name, email"; using (OleDbCommand cmd = new OleDbCommand(string.Format("SELECT {0} FROM [{1}]", selectFields, file.Name), (OleDbConnection)conn.DBConn)) { conn.DBConn.Open(); using (OleDbDataAdapter adp = new OleDbDataAdapter(cmd)) { adp.Fill(dt); } } 

创建像“IContactImporter”这样的接口可能是有意义的,它有一个方法“Import(File / whatever …)”。 然后,对于每种类型的联系人文件,创建实现导入方法的类来处理每种格式。

如果有某种方法可以告诉用户正在上传哪种类型的文件,您可能不需要询问用户。

对于实际的实现,我会找到一个现有的CSV库,并根据每种格式对其进行相应的配置。 我工作的人使用LINQtoCSV ,但我不确定是否有更好的选择。

B计划是最好的,另一种方法是查看整个文件并计算一个字符的出现次数,这可以与streamreader类一起逐行完成,然后您可以将生成的字符串拆分为一个数组。

你需要将字符限制为不是字母数字Az 0-9“并查看字符

然后你可以确定分隔符。 另外请注意,如果某个字段为空,某些程序不会导出“cell”,例如ms office 2007

计划A似乎是明智的。 我不认为会有太多的字段名称(如果有的话)用逗号或制表符。 因此,统计数据在90%的时间内都是准确的。 如果统计数据足够“接近”(例如15个逗号和12个标签),您可以做的是:

 int i = line.IndexOf("email", StringCompareOptions.CultureInvariantIgnoreCase); if(i == -1) i = line.IndexOf("e-mail", StringCompareOptions.CultureInvariantIgnoreCase); else i += 5; // Length of "email" if(i == -1) throw new Exception("You should select the email field when exporting."); else i += 6; // Length of "e-mail" // Find the next delimeter. string delim = null; for(int k = i; k < line.Count; k++) { char c = line[k]; if(c == '\t' || c == ',') { delim = c.ToString(); break; } } if(delim == null) throw new Exception("Unrecognised file format."); 

最重要的是,你说第一个名字和姓氏字段存在问题 - 以及电子邮件和电子邮件等问题。 你需要一个非常好的设计模式。 为了规范化数据的真正利益,我会存储名字和姓氏(并将它们组合在UI中)。 从而:

 interface IField { string[] Accepts { get; } // Gets the fields that this can accept. string[] Gives { get; } // Gets the field that this would give. IEnumerable> Handle(IEnumerable> fields); } class NameField { string[] Accepts { get return new string[] { "FirstName", "LastName", "Name", "First Name", etc. }; } string[] Gives { get return new string[] { "FirstName", "LastName" }; } IEnumerable> Handle(IEnumerable> fields) { string firstName = null, lastName = null; foreach(KeyValuePair field in fields) { switch(field.Key) { case "FirstName": case "First Name": firstName = field.Value; break; // ... case "FullName": case "Full Name": // Split into fn and ln. break; // ... } } yield return new KeyValuePair("FirstName", firstName); yield return new KeyValuePair("LastName", lastName); } } 

无论如何,我相信你明白了。 一堆变换将字段转换为已识别的变换。