使用特定列分隔符解析csv文件&使用CsvHelper库的字符串封装

时间:2016-02-26 12:51:06

标签: c# csv

我在.NET C#项目中使用这个http://joshclose.github.io/CsvHelper/非常棒的库来满足我的CSV解析要求。

如果我的CSV文件如下所示:

SupplierSku,MappedSageSku
EG1234,EGCD1234
EG4567,EG-XZ567

我通常会创建一个像这样的DTO类:

public class SkuMapping
{
    public string SupplierSku { get; set; }
    public string MappedSageSku { get; set; }
}

并解析csv文件,如下所示:

// Open & parse selected csv file
var csvReader = new CsvReader(File.OpenText(selectSkuMapping.Text));
var skuMappings = csvReader.GetRecords<SkuMapping>();

// Do something with each row
foreach (SkuMapping skuMapping in skuMappings)
{
    // ...
}

这适用于预定义/结构化CSV文件。

我现在需要解析任意CSV文件,它们可能包含各种column delimiter&amp; string enclosure和csv上的确切列数未知,但包含我需要的数据的列索引是已知的。

示例#1

PartNumb,InStock,PrGroup
"A-X-1230",Y,103
"B-DD-1231",Y,103

其中; column delimiter = ,string enclosure = "

我需要的数据:列索引0(PartNumb)和列索引1(InStock)

示例#2

SupplierSku,CatIds,StockStatus,Active
%ADA-BB-124%|4,5,1|%AV%|1
%XAS-E4-S11%|97,41,65|%OS%|0

其中; column delimiter = |string enclosure = %

我需要的数据:列索引0(SupplierSku)和列索引2(StockStatus)

因此,如上所述,使用CsvHelper库解析任意csv文件(column delimiterstring enclosurecolumn indexes已知)的最佳方法是什么?我还需要在csv上跳过第一行的选项(有时csv包含标题行,有时它们不会)。

5 个答案:

答案 0 :(得分:3)

答案取决于你想要的:

  • 在开始解析之前,您知道允许哪些分隔符吗?

如果您不知道允许哪些分隔符,则您遇到了麻烦:A&#39; A&#39;分隔符?是&#39; B&#39;分隔符?让我们假设您认为有一组字符适合作为您要解析的实际CSV流的分隔符。

  1. 将所有出现的分隔符替换为相同的分隔符,例如&#39;;&#39;
  2. 您可以使用String.Replace(char,char)为每个接受的分隔符执行此操作,或使用正则表达式

    • 是否要按名称编号选择列?
  3. 如果您只需要某些列,请创建一个地图,告诉CsvHelper哪个列必须映射到哪个目的地。

    示例:如果您需要映射列&#34; MyColumn&#34; to Property YourProperty创建一个地图:

    private sealed class MyCsvConverterMap : CsvClassMap<MyDestinationType>
    {
        public MyCsvConverterMap()
        {
            Map(item => item.YourProperty).Name("MyColumn");
            // map all properties in your destination to a column
        }
    }
    
    using (TextReader txtReader = new StringReader(...))
    {
        CsvReader csvReader = new CsvReader(txtReader);
        csvReader.Configuration.Delimiter = ";";
        csvReader.Configuration.HasHeaderRecord = true;
        csvReader.Configuration.RegisterClassMap(new MyCsvConverterMap());
    
        while (csvReader.Read())
        {
             MyDestinationType convertedRecord = csvReader.GetRecord<MyDestinationType>();
             ...
    

    <强>加成

    也可以按列号映射,而不是按列名映射。查看各种地图的解释: CsvHelper Getting started

答案 1 :(得分:2)

这似乎有效,使用CsvHelper:

var textToParse = @"SupplierSku,CatIds,StockStatus,Active
%ADA-BB-124%|4,5,1|%AV%|1
%XAS-E4-S11%|97,41,65|%OS%|0";

string supplierSku;
string stockStatus;

using (var stringReader = new StringReader(textToParse))
{
    using (var reader = new CsvReader(stringReader))
    {
        reader.Configuration.Delimiter = ",";
        reader.Configuration.HasHeaderRecord = true; // If there is no header, set to false.

        while (reader.Read())
        {
            supplierSku = reader.GetField("SupplierSku"); // Or reader.GetField(0)
            stockStatus = reader.GetField("StockStatus"); // Or reader.GetField(2)

            Console.WriteLine($"SKU: {supplierSku}; Status: {stockStatus}");
        }
    }
}

但是,它不会自动修剪/删除引号字符 - 您可以使用Trim()Substring()轻松完成此操作。需要更多的手动工作,但它比手动操作更容易。

答案 2 :(得分:1)

通过更多努力,您还可以使用CsvClassMap创建一个类型安全的映射,并继承DefaultTypeConverter类,为CatIds(逗号分隔)创建转换器。

这是一个适用于您的示例#2的示例:

[TestClass]
public class CsvHelperTest
{
    [TestMethod]
    public void Test()
    {
        var textToParse = "SupplierSku,CatIds,StockStatus,Active" + Environment.NewLine;
        textToParse += "%ADA-BB-124%|4,5,1|%AV%|1" + Environment.NewLine;
        textToParse += "%XAS-E4-S11%|97,41,65|%OS%|0";

        using (var stringReader = new StringReader(textToParse))
        {
            using (var reader = new CsvReader(stringReader))
            {
                reader.Configuration.Quote = '%';
                reader.Configuration.Delimiter = "|";
                reader.Configuration.HasHeaderRecord = true; // If there is no header, set to false.
                reader.Configuration.RegisterClassMap<StockMap>();

                foreach(var stock in reader.GetRecords<Stock>())
                {
                    // normally do something with data, now just test

                    Assert.IsNotNull(stock.SupplierSku);
                    Assert.IsTrue(stock.SupplierSku.IndexOf('%') == -1, "Quotes should be stripped");
                    Assert.IsNotNull(stock.CatIds);
                    Assert.AreEqual(3, stock.CatIds.Length, "Expected 3 CatIds");
                }
            }
        }
    }

    public class StockMap : CsvClassMap<Stock>
    {
        public StockMap()
        {
            Map(stock => stock.SupplierSku).Index(0);
            Map(stock => stock.CatIds).Index(1).TypeConverter<CatIdsConverter>();
            Map(stock => stock.StockStatus).Index(2);
            Map(stock => stock.Active).Index(3); // 1 is true, 0 is false
        }
    }

    public class Stock
    {
        public string SupplierSku { get; set; }
        public int[] CatIds { get; set; }
        public StockStatus StockStatus { get; set; }
        public bool Active { get; set; }
    }

    public enum StockStatus
    {
        AV, OS
    }

    public class CatIdsConverter : DefaultTypeConverter
    {
        public override bool CanConvertFrom(Type type)
        {
            return type == typeof(string);
        }

        public override object ConvertFromString(TypeConverterOptions options, string text)
        {
            if (string.IsNullOrEmpty(text))
                return null;

            var catIds = text.Split(',').Select(c=> Convert.ToInt32(c)).ToArray();
            return catIds;
        }
    }
}

例如#1只需配置Quote ='“',Delimiter =”,“,添加另一个类&amp; CsvClassMap&lt;&gt;实现并在另一个CsvReader中配置。

答案 3 :(得分:1)

有一个包升级,新语法现在是:

using (var reader = new StreamReader("./myfile.csv", Encoding.UTF8))
using (var csv = new CsvReader(reader, new CsvConfiguration(CultureInfo.InvariantCulture, delimiter: ";", encoding: Encoding.UTF8)))
{
    //do something with csv
}

答案 4 :(得分:0)

CsvReader csv = new CsvReader(new StreamReader(stream), true, ';')

Configuration.Delimiter它不再起作用,现在您必须在CsvReader初始化中将分隔符作为参数传递