代码之家  ›  专栏  ›  技术社区  ›  ChadT

将csv文件导入c#

  •  2
  • ChadT  · 技术社区  · 16 年前

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

    因为每一个电子邮件客户端都以稍微不同的格式导出他们的联系人,这让我抓耳挠腮,不得不用最好的方法来处理它。因为我不知道字段是什么,或者分隔符是什么。

    我只想瞄准主要的电子邮件客户机/地址簿(Outlook、Apple Mail、随行人员、Thunderbird)。所有这些都有一个完全不同的格式。随行人员使用制表符作为分隔符,其他人使用逗号等。我只需要提取电子邮件地址和(如果有的话)名称。名称变得更加复杂,因为有些客户机的名字/姓氏有单独的字段。

    使用 FileHelpers 是理想的,但在我找到解决方案之前,我似乎需要知道csv的结构。如果可能的话,我宁愿不写自己的csv解析器。

    以下是我对集体蜂巢思维的看法:

    计划A

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

    B计划

    • 提示用户输入电子邮件客户机的类型,并为每个不同的客户机分别进行编码<-看起来真的很笨拙。

    C计划

    ….使用/购买已经这样做的现有组件?!(我真的找不到!!)

    思想?

    6 回复  |  直到 13 年前
        1
  •  7
  •   Cerebrus    16 年前

    我同意B计划(我不同意这是笨拙的)。

    imho,最好的方法是询问用户需要从哪种电子邮件客户端导出。因此,您可以标识分隔符。您自己已经发现,尽管不同的客户机使用不同的分隔符,但是一个客户机总是使用相同的分隔符(除非他们决定使用不向后兼容的版本),因此,创建一个接受分隔符作为参数并相应地解析输入的面向对象类并不困难。(不管分隔符是什么,逻辑应该保持几乎相同。

    即使解析每种类型的导出文件时的逻辑有很大的不同,似乎您可以创建一个抽象的基类,它包含所有公共功能和派生类,这些类只覆盖特定于客户端的功能。

    即使使用自定义库(如filehelpers),也应该能够通过传递分隔符类型来完成它。

    我认为您不应该依赖可能的分隔符的相对计数来确定实际的分隔符是什么(如计划A所示)。

    编辑: 另一个刚想到的选择是提供类似于MS Excel的选项界面。您可以选择分隔符,并实时预览如何根据所选内容分析数据。

        2
  •  2
  •   Gideon    16 年前

    我先看看比赛是如何进行的。

    谷歌: “我们支持以csv文件格式导入联系人(逗号分隔值)。为了获得最佳结果,请使用由Outlook、Outlook Express、Yahoo!生成的csv文件。或者Hotmail。对于Apple通讯簿,有一个名为“a to g”的实用程序。
    所以我想他们会选择你的计划A,并对上面提到的csv文件进行检查。

    实时邮件/hotmail: 它们支持您的选项B,并支持: Microsoft Outlook(使用csv)、Outlook Express(使用csv)、Windows联系人、Windows Live Hotmail、Yahoo!邮件(使用outlook csv格式和逗号分隔)、gmail(使用outlook csv格式)

    脸谱网: 他们允许你输入你的电子邮件地址,如果他们知道(雅虎、Gmail、Hotmail等),他们会要求你输入密码,并自动检索你的联系人。(选项D)如果他们不支持您的电子邮件提供商,您仍然可以从Outlook或其他格式(选项B的类型)上载csv文件。

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

        3
  •  1
  •   Shailesh Rama    13 年前

    如果需要更改要导入的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);
        }
    }
    
        4
  •  0
  •   Andy White    16 年前

    创建一个类似“icontactimporter”的接口,它有一个方法“import(file/whatever…)”。然后,对于每种类型的联系人文件,创建实现导入方法的类来处理每种格式。

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

    对于实际的实现,我将找到一个现有的csv库,并为每个格式相应地配置它。我工作的人用过 LINQtoCSV 但我不确定是否有更好的选择。

        5
  •  0
  •   Jim    16 年前

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

    您需要将字符限制为非字母数字a-z 0-9“,并查看字符

    然后可以确定分隔符。还要注意,如果某个字段为空,则某些程序不会导出“cell”,例如MS Office 2007

        6
  •  0
  •   Jonathan C Dickinson    16 年前

    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<KeyValuePair<string, string>> Handle(IEnumerable<KeyValuePair<string, string>> fields);
    }
    
    class NameField
    {
        string[] Accepts { get return new string[] { "FirstName", "LastName", "Name", "First Name", etc. }; }
        string[] Gives { get return new string[] { "FirstName", "LastName" }; }
    
        IEnumerable<KeyValuePair<string, string>> Handle(IEnumerable<KeyValuePair<string, string>> fields)
        {
           string firstName = null, lastName = null;
           foreach(KeyValuePair<string, string> 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<string, string>("FirstName", firstName);
           yield return new KeyValuePair<string, string>("LastName", lastName);
        }
    }
    

    无论如何,我相信你会明白的。将字段转换为可识别字段的一组转换。