экранирование хитрой строки в формат CSV - PullRequest
30 голосов
/ 16 июня 2011

Мне нужно создать CSV-файл из вывода веб-сервиса, а в CSV-файле используются строки в кавычках с разделителем-запятой. Я не могу изменить формат ...

Так что, если у меня есть string, он становится "string" ... Если значение уже имеет кавычки, они заменяются двойными кавычками.Например, str"ing становится "str""ing" ...

Однако в последнее время мой импорт не удался из-за следующей

  • исходной строки ввода: "","word1,word2,..."
  • каждая одиночная кавычка заменяется на двойную, в результате чего: """",""word1,word2,...""
  • затем с префиксом и суффиксом кавычка перед записью в файл CVS: """"",""word1,word2,..."""

По мере того как выможно увидеть окончательный результат таков:

""""",""word1,word2,..."""

, который прерывает мой импорт (видит его как другое поле) ... Я думаю, что проблема заключается в появлении "," в исходной строке ввода.

Существует ли escape-последовательность CVS для этого сценария?

Обновление

Причина вышеупомянутых разрывов связана с файлом отображения BCP (утилита BCP используется для загрузки файла CSV в базу данных SQL), в котором терминатор определен как ",".Поэтому вместо того, чтобы видеть 1 поле, он видит 2 ... Но я не могу изменить файл сопоставления ...

Ответы [ 6 ]

82 голосов
/ 16 июня 2011

Я использую этот код, и он всегда работал:

/// <summary>
/// Turn a string into a CSV cell output
/// </summary>
/// <param name="str">String to output</param>
/// <returns>The CSV cell formatted string</returns>
public static string StringToCSVCell(string str)
{
    bool mustQuote = (str.Contains(",") || str.Contains("\"") || str.Contains("\r") || str.Contains("\n"));
    if (mustQuote)
    {
        StringBuilder sb = new StringBuilder();
        sb.Append("\"");
        foreach (char nextChar in str)
        {
            sb.Append(nextChar);
            if (nextChar == '"')
                sb.Append("\"");
        }
        sb.Append("\"");
        return sb.ToString();
    }

    return str;
}
8 голосов
/ 09 ноября 2016

На основании ответа Эда Байятса:

/// <summary>
/// Turn a string into a CSV cell output
/// </summary>
/// <param name="value">String to output</param>
/// <returns>The CSV cell formatted string</returns>
private string ConvertToCsvCell(string value)
{
    var mustQuote = value.Any(x => x == ',' || x == '\"' || x == '\r' || x == '\n');

    if (!mustQuote)
    {
        return value;
    }

    value = value.Replace("\"", "\"\"");

    return string.Format("\"{0}\"", value);
}
2 голосов
/ 29 июля 2015

Основываясь на вкладе "Ed Bayiates", вот полезный класс для создания документа CSV:

/// <summary>
/// helpful class to build csv document
/// </summary>
public class CsvBuilder
{
    /// <summary>
    /// create the csv builder
    /// </summary>
    public CsvBuilder(char csvSeparator)
    {
        m_csvSeparator = csvSeparator;
    }

    /// <summary>
    /// append a cell
    /// </summary>
    public void appendCell(string strCellValue)
    {
        if (m_nCurrentColumnIndex > 0) m_strBuilder.Append(m_csvSeparator);

        bool mustQuote = (strCellValue.Contains(m_csvSeparator)
                        || strCellValue.Contains('\"') 
                        || strCellValue.Contains('\r') 
                        || strCellValue.Contains('\n'));

        if (mustQuote)
        {
            m_strBuilder.Append('\"');
            foreach (char nextChar in strCellValue)
            {
                m_strBuilder.Append(nextChar);
                if (nextChar == '"') m_strBuilder.Append('\"');
            }
            m_strBuilder.Append('\"');
        }
        else
        {
            m_strBuilder.Append(strCellValue);
        }
        m_nCurrentColumnIndex++;
    }

    /// <summary>
    /// end of line, new line
    /// </summary>
    public void appendNewLine()
    {
        m_strBuilder.Append(Environment.NewLine);
        m_nCurrentColumnIndex = 0;
    }

    /// <summary>
    /// Create the CSV file
    /// </summary>
    /// <param name="path"></param>
    public void save(string path )
    {
        File.WriteAllText(path, ToString());
    }

    public override string ToString()
    {
        return m_strBuilder.ToString();
    }

    private StringBuilder m_strBuilder = new StringBuilder();
    private char m_csvSeparator;
    private int m_nCurrentColumnIndex = 0;

}

Как это использовать:

void exportAsCsv( string strFileName )
{
    CsvBuilder csvStringBuilder = new CsvBuilder(';');
    csvStringBuilder.appendCell("#Header col 1 : Name");
    csvStringBuilder.appendCell("col 2 : Value");
    csvStringBuilder.appendNewLine();
    foreach (Data data in m_dataSet)
    {
        csvStringBuilder.appendCell(data.getName());
        csvStringBuilder.appendCell(data.getValue());
        csvStringBuilder.appendNewLine();
    }
    csvStringBuilder.save(strFileName);
}
2 голосов
/ 16 июня 2011

Моя копейка подумала:

String[] lines = new String[] { "\"\",\"word\",word,word2,1,34,5,2,\"details\"" };
for (int j = 0; j < lines.Length; j++)
{
    String[] fields=lines[j].Split(',');
    for (int i =0; i<fields.Length; i++)
    {
        if (fields[i].StartsWith("\"") && fields[i].EndsWith("\""))
        {
            char[] tmp = new char[fields[i].Length-2];
            fields[i].CopyTo(1,tmp,0,fields[i].Length-2);
            fields[i] =tmp.ToString();
            fields[i] = "\""+fields[i].Replace("\"","\"\"")+"\"";
        }
        else
            fields[i] = fields[i].Replace("\"","\"\"");

    }
    lines[j]=String.Join(",",fields);

}

1 голос
/ 16 июня 2011

первый шаг при разборе этого состоит в удалении дополнительных "" вокруг вашей строки. Как только вы это сделаете, вы сможете иметь дело как со встроенным ", так и с,".

0 голосов
/ 23 июня 2011

После долгих размышлений было решено исправить формат утилиты импорта. Экранирование строки было правильным (как указали пользователи), но файл формата, используемый утилитой импорта, был неправильным и приводил к прерыванию импорта.

Спасибо всем и особая благодарность @dbt (за голосование)

...