🦁

CSVHelperで空白をダブルクォーテーションで囲まないようにする

2023/04/09に公開

CSVHepler(C#)でCSVを書き込む場合に値の文頭・文末が空白の場合にダブルクォーテーションで囲む既定動作になっています。これを変更する方法を記載します。

C# のライブラリである CSVHelper に関して値の文頭・文末が空白の場合にダブルクォーテーションで囲まれるという現象を発見しました。

調べたところ、公式のIssue からCSVHelperの仕様であることが分かりました。
「,」、「"」、改行以外に文頭、文末がスペースで始まる場合はダブルクォーテーションで囲むようになっていました。

Yes. The default criteria for a field being quoted is:

  1. Contains a quote
  2. Starts with a space
  3. Ends with a space
  4. Contains any chars that require quotes (\r and \n by default)
  5. Contains the delimiter

https://github.com/JoshClose/CsvHelper/issues/1413

扱うプログラムの仕様上文頭・文末が空白の場合はダブルクォーテーションで囲まないようにする必要がありました。
そのため、上記のIssueの回答を元に csv.Configuration.ShouldQuote を使ってダブルクォーテーションで囲むルールを変更することにしました。

.net6向けのサンプルコード

using CsvHelper;
using CsvHelper.Configuration;
using System.Globalization;

public class Foo
{
    public int Id { get; set; }
    public string? Name { get; set; }
}

public class Program
{
    public static void Main(string[] args)
    {
        var records = new List<Foo>
        {
            new Foo { Id = 1, Name = "one" },
            new Foo { Id = 2, Name = " two" },
            new Foo { Id = 3, Name = "three " },
        };

        var config = new CsvConfiguration(CultureInfo.InvariantCulture)
        {
            ShouldQuote = MyShouldQuote
        };

        using (var writer = new StreamWriter("foo.csv"))
        using (var csv = new CsvWriter(writer, config))
        {
            csv.WriteRecords(records);
        }
    }

    private static readonly char[] lineEndingChars = new char[] { '\r', '\n' };
    // ConfigurationFunctions.ShouldQuote 参照
    private static bool MyShouldQuote(ShouldQuoteArgs args)
    {
        var config = args.Row.Configuration;

        var shouldQuote = !string.IsNullOrEmpty(args.Field) &&
        (
            args.Field.Contains(config.Quote) // Contains quote
            // || args.Field[0] == ' ' // Starts with a space
            // || args.Field[args.Field.Length - 1] == ' ' // Ends with a space
            || (config.Delimiter.Length > 0 && args.Field.Contains(config.Delimiter)) // Contains delimiter
            || !config.IsNewLineSet && args.Field.IndexOfAny(lineEndingChars) > -1 // Contains line ending characters
            || config.IsNewLineSet && args.Field.Contains(config.NewLine) // Contains newline
        );

        return shouldQuote;
    }
}

出力結果(設定前) Foo.csv

Id,Name
1,one
2," two"
3,"three "

出力結果(設定後) Foo.csv

Id,Name
1,one
2, two
3,three 

Discussion