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

голоса
16

Типичный способ создания 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();
}

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

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

Задан 07/08/2008 в 06:49
источник пользователем
На других языках...                            


14 ответов

голоса
19

Вы можете использовать LINQ к объектам :

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

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

Ответил 07/08/2008 в 06:56
источник пользователем

голоса
9

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

Для генерации надлежащего 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;
}

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

Ответил 09/08/2008 в 11:47
источник пользователем

голоса
5

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

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

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

Ответил 20/08/2008 в 03:14
источник пользователем

голоса
5

Не забывайте наш старый друг «для». Это не так хорошо выглядящий, как Еогеасп но имеет преимущество того, чтобы начать со вторым элементом.

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();
}

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

Ответил 07/08/2008 в 13:00
источник пользователем

голоса
3

Кроме того, можно сделать массив 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());
Ответил 07/08/2008 в 08:37
источник пользователем

голоса
3

Вы могли бы вместо того, чтобы добавить запятую как первое, что внутри вашего Еогеасп.

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

Ответил 07/08/2008 в 06:54
источник пользователем

голоса
1

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

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());
        }
    }
}
Ответил 26/06/2012 в 20:34
источник пользователем

голоса
1

Я использовал этот метод прежде. Длина свойство 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();  
}
Ответил 20/08/2008 в 02:47
источник пользователем

голоса
1

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

Ответил 07/08/2008 в 12:18
источник пользователем

голоса
1

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

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

Ответил 07/08/2008 в 07:25
источник пользователем

голоса
0

Я использую CSVHelper - это большая библиотека с открытым исходным кодом , который позволяет создавать совместимые CSV потоки одного элемента за один раз или на заказ на карту классов:

public string ReturnAsCSV(ContactList contactList)
{
    StringBuilder sb = new StringBuilder();
    using (StringWriter stringWriter = new StringWriter(sb))
    {
        using (var csvWriter = new CsvHelper.CsvWriter(stringWriter))
        {
            csvWriter.Configuration.HasHeaderRecord = false;
            foreach (Contact c in contactList)
            {
                csvWriter.WriteField(c.Name);
            }
        }
    }
    return sb.ToString();
}

или если вы карту, то что-то вроде этого: csvWriter.WriteRecords<ContactList>(contactList);

Ответил 26/06/2012 в 21:06
источник пользователем

голоса
0

Как насчет обрезки?

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

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

    return sb.ToString().Trim(',');
}
Ответил 07/08/2008 в 09:19
источник пользователем

голоса
0

К сожалению, PHP конкретный пример, но может кому-то помочь.

Ответил 07/08/2008 в 07:25
источник пользователем

голоса
0

Как насчет отслеживания ли вы на первом пункте, и только добавить запятую перед тем элемента , если это не первый один.

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

    foreach (Contact c in contactList) {
        if (!isFirst) { 
          // Only add comma before item if it is not the first item
          sb.Append(","); 
        } else {
          isFirst = false;
        }

        sb.Append(c.Name);
    }

    return sb.ToString();
}
Ответил 07/08/2008 в 06:54
источник пользователем

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more