Обработка строки CSV - PullRequest
       30

Обработка строки CSV

17 голосов
/ 07 августа 2008

Типичный способ создания строки CSV (псевдокод):

  1. Создайте объект контейнера CSV (например, StringBuilder в C #).
  2. Прокручивайте строки, которые вы хотите добавить, добавляя запятую после каждой.
  3. После цикла удалите эту последнюю лишнюю запятую.

Пример кода:

public string ReturnAsCSV(ContactList contactList)
{
    StringBuilder sb = new StringBuilder();
    foreach (Contact c in contactList)
    {
        sb.Append(c.Name + ",");
    }

    sb.Remove(sb.Length - 1, 1);
    //sb.Replace(",", "", sb.Length - 1, 1)

    return sb.ToString();
}

Мне нравится идея добавить запятую, проверив, пуст ли контейнер, но разве это не означает, что требуется больше обработки, поскольку необходимо проверять длину строки в каждом случае?

Я чувствую, что должен быть более простой / чистый / более эффективный способ удаления этой последней запятой. Есть идеи?

Ответы [ 13 ]

19 голосов
/ 07 августа 2008

Вы можете использовать LINQ to Objects :

string [] strings = contactList.Select(c => c.Name).ToArray();
string csv = string.Join(",", strings);

Очевидно, что все это можно сделать в одной строке, но с двумя это немного яснее.

9 голосов
/ 09 августа 2008

Ваш код не соответствует полному формату CSV . Если вы просто генерируете CSV из данных, у которых нет запятых, пробелов, пробелов, символов новой строки или кавычек, все будет в порядке. Однако в большинстве реальных сценариев обмена данными вам нужна полная реализация.

Для генерации на правильный CSV, вы можете использовать это:

public static String EncodeCsvLine(params String[] fields)
{
    StringBuilder line = new StringBuilder();

    for (int i = 0; i < fields.Length; i++)
    {
        if (i > 0)
        {
            line.Append(DelimiterChar);
        }

        String csvField = EncodeCsvField(fields[i]);
        line.Append(csvField);
    }

    return line.ToString();
}

static String EncodeCsvField(String field)
{
    StringBuilder sb = new StringBuilder();
    sb.Append(field);

    // Some fields with special characters must be embedded in double quotes
    bool embedInQuotes = false;

    // Embed in quotes to preserve leading/tralining whitespace
    if (sb.Length > 0 && 
        (sb[0] == ' ' || 
         sb[0] == '\t' ||
         sb[sb.Length-1] == ' ' || 
         sb[sb.Length-1] == '\t' ))
    {
        embedInQuotes = true;
    }

    for (int i = 0; i < sb.Length; i++)
    {
        // Embed in quotes to preserve: commas, line-breaks etc.
        if (sb[i] == DelimiterChar || 
            sb[i]=='\r' || 
            sb[i]=='\n' || 
            sb[i] == '"') 
        { 
            embedInQuotes = true;
            break;
        }
    }

    // If the field itself has quotes, they must each be represented 
    // by a pair of consecutive quotes.
    sb.Replace("\"", "\"\"");

    String rv = sb.ToString();

    if (embedInQuotes)
    {
        rv = "\"" + rv + "\"";
    }

    return rv;
}

Возможно, это не самый эффективный код в мире, но он был протестирован. Реальный мир - отстой по сравнению с быстрым примером кода:)

5 голосов
/ 20 августа 2008

Почему бы не использовать одну из библиотек CSV с открытым исходным кодом?

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

Я использовал Open CSV в одном из моих проектов ранее (но есть из множества других на выбор). Это, безусловно, сделало мою жизнь проще. ;)

5 голосов
/ 07 августа 2008

Не забудь нашего старого друга "за". Это не так красиво, как foreach, но имеет преимущество в том, что может начинать со второго элемента.

public string ReturnAsCSV(ContactList contactList)
{
    if (contactList == null || contactList.Count == 0)
        return string.Empty;

    StringBuilder sb = new StringBuilder(contactList[0].Name);

    for (int i = 1; i < contactList.Count; i++)
    {
        sb.Append(",");
        sb.Append(contactList[i].Name);
    }

    return sb.ToString();
}

Вы также можете заключить второе приложение в «если», которое проверяет, содержит ли свойство Name двойные кавычки или запятую, и, если это так, экранирует их соответствующим образом.

3 голосов
/ 07 августа 2008

Вы также можете создать массив данных c.Name и использовать метод String.Join для создания своей строки.

public string ReturnAsCSV(ContactList contactList)
{
    List<String> tmpList = new List<string>();

    foreach (Contact c in contactList)
    {
        tmpList.Add(c.Name);
    }

    return String.Join(",", tmpList.ToArray());
}

Это может быть не так эффективно, как подход StringBuilder , но, безусловно, выглядит чище.

Кроме того, вы можете рассмотреть возможность использования .CurrentCulture.TextInfo.ListSeparator вместо жестко запрограммированной запятой - если ваш вывод будет импортирован в другие приложения, у вас могут возникнуть проблемы с ним , ListSeparator может отличаться в разных культурах, и MS Excel, по крайней мере, учитывает этот параметр. Итак:

return String.Join(
    System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator,
    tmpList.ToArray());
3 голосов
/ 07 августа 2008

Вместо этого вы можете добавить запятую в качестве первой вещи в вашем foreach.

if (sb.Length > 0) sb.Append(",");

1 голос
/ 26 июня 2012

Я написал небольшой класс для этого на случай, если кто-то найдет это полезным ...

public class clsCSVBuilder
{
    protected int _CurrentIndex = -1;
    protected List<string> _Headers = new List<string>();
    protected List<List<string>> _Records = new List<List<string>>();
    protected const string SEPERATOR = ",";

    public clsCSVBuilder() { }

    public void CreateRow()
    {
        _Records.Add(new List<string>());
        _CurrentIndex++;
    }

    protected string _EscapeString(string str)
    {
        return string.Format("\"{0}\"", str.Replace("\"", "\"\"")
                                            .Replace("\r\n", " ")
                                            .Replace("\n", " ")
                                            .Replace("\r", " "));
    }

    protected void _AddRawString(string item)
    {
        _Records[_CurrentIndex].Add(item);
    }

    public void AddHeader(string name)
    {
        _Headers.Add(_EscapeString(name));
    }

    public void AddRowItem(string item)
    {
        _AddRawString(_EscapeString(item));
    }

    public void AddRowItem(int item)
    {
        _AddRawString(item.ToString());
    }

    public void AddRowItem(double item)
    {
        _AddRawString(item.ToString());
    }

    public void AddRowItem(DateTime date)
    {
        AddRowItem(date.ToShortDateString());
    }

    public static string GenerateTempCSVPath()
    {
        return Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString().ToLower().Replace("-", "") + ".csv");
    }

    protected string _GenerateCSV()
    {
        StringBuilder sb = new StringBuilder();

        if (_Headers.Count > 0)
        {
            sb.AppendLine(string.Join(SEPERATOR, _Headers.ToArray()));
        }

        foreach (List<string> row in _Records)
        {
            sb.AppendLine(string.Join(SEPERATOR, row.ToArray()));
        }

        return sb.ToString();
    }

    public void SaveAs(string path)
    {
        using (StreamWriter sw = new StreamWriter(path))
        {
            sw.Write(_GenerateCSV());
        }
    }
}
1 голос
/ 20 августа 2008

Я использовал этот метод раньше. Свойство Length объекта StringBuilder НЕ доступно только для чтения, поэтому его вычитание одним способом означает усечение последнего символа. Но вы должны убедиться, что ваша длина не равна нулю, чтобы начать (что произойдет, если ваш список пуст), потому что установка длины меньше нуля является ошибкой.

public string ReturnAsCSV(ContactList contactList)
{
    StringBuilder sb = new StringBuilder();

    foreach (Contact c in contactList)       
    { 
        sb.Append(c.Name + ",");       
    }

    if (sb.Length > 0)  
        sb.Length -= 1;

    return sb.ToString();  
}
1 голос
/ 07 августа 2008

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

1 голос
/ 07 августа 2008

Мне нравится идея добавить запятую, проверив, пуст ли контейнер, но разве это не означает больше обработки, поскольку необходимо проверять длину строки в каждом случае?

Вы преждевременно оптимизируете, снижение производительности будет незначительным.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...