manipulação de strings CSV

votos
16

Maneira típica de se criar um CSV string (pseudocódigo):

  1. Criar um objecto recipiente CSV (como um StringBuilder em C #).
  2. Loop através das cordas que você deseja adicionar acrescentando uma vírgula após cada um.
  3. Após o loop, remover a última vírgula supérfluo.

Exemplo de código:

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

Eu gosto da idéia de adicionar a vírgula, verificando se o recipiente está vazio, mas isso não significa mais processamento como ele precisa verificar o comprimento da corda em cada ocorrência?

Eu sinto que deve haver um limpador de maneira mais fácil / / mais eficiente de remover a última vírgula. Alguma ideia?

Publicado 07/08/2008 em 06:49
fonte usuário
Em outras línguas...                            


14 respostas

votos
19

Você pode usar LINQ to Objects :

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

Obviamente que tudo poderia ser feito em uma linha, mas é um pouco mais clara sobre dois.

Respondeu 07/08/2008 em 06:56
fonte usuário

votos
9

Seu código não é realmente compatível com o formato completo CSV . Se você está apenas gerando CSV a partir de dados que não tem vírgulas, levando / espaços à direita, tabulações, novas linhas ou citações, ele deve estar bem. No entanto, na maioria dos cenários de intercâmbio de dados do mundo real, você precisa fazer o imlementation completo.

Para a geração de CSV adequada, você pode usar este:

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

Pode não ser o código mais eficiente do mundo, mas ele foi testado. mundo real suga em comparação com código de amostra rápida :)

Respondeu 09/08/2008 em 11:47
fonte usuário

votos
5

Por que não usar uma das bibliotecas CSV de código aberto lá fora?

Eu sei que soa como um exagero para algo que parece tão simples, mas como você pode dizer pelos comentários e trechos de código, há mais do que encontra o olho. Além de lidar com o cumprimento integral CSV, você eventualmente querer lidar com ambos leitura e escrita CSVs ... e você pode querer manipulação de arquivos.

Eu usei Abrir CSV em um dos meus projetos antes (mas há muitos outros para escolher). Ele certamente fez a minha vida mais fácil. ;)

Respondeu 20/08/2008 em 03:14
fonte usuário

votos
5

Não se esqueça o nosso velho amigo "para". Não é tão bonito como foreach mas tem a vantagem de ser capaz de começar no segundo elemento.

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

Você também poderia envolver a segunda Anexar em um "se" que testa se a propriedade Nome contém um aspas ou uma vírgula, e se assim, escapar-los adequadamente.

Respondeu 07/08/2008 em 13:00
fonte usuário

votos
3

Você também pode fazer uma série de c.name dados e usar String.Join método para criar sua linha.

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

Isto pode não ser tão alto desempenho como o StringBuilder abordagem, mas ele definitivamente parece mais limpo.

Além disso, você pode querer considerar o uso de .CurrentCulture.TextInfo.ListSeparator vez de uma vírgula hard-coded - Se a sua saída vai ser importados para outras aplicações, você pode ter problemas com ele. ListSeparator pode ser diferente em diferentes culturas, e MS Excel, no mínimo, honras esta definição. Assim:

return String.Join(
    System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator,
    tmpList.ToArray());
Respondeu 07/08/2008 em 08:37
fonte usuário

votos
3

Você poderia, em vez adicionar a vírgula como a primeira coisa dentro do seu foreach.

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

Respondeu 07/08/2008 em 06:54
fonte usuário

votos
1

Eu escrevi uma pequena classe para isso no caso de alguém acha útil ...

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());
        }
    }
}
Respondeu 26/06/2012 em 20:34
fonte usuário

votos
1

Eu usei este método antes. A propriedade Comprimento de StringBuilder não é somente leitura tão subtraindo-lo por um significa truncar o último caractere. Mas você tem que se certificar que seu comprimento não zero é começar com (o que aconteceria se sua lista está vazia) porque a definição do comprimento para menos de zero é um erro.

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();  
}
Respondeu 20/08/2008 em 02:47
fonte usuário

votos
1

Apenas um pensamento, mas lembre-se de lidar com marcas de vírgulas e aspas ( ") nos valores de campo, caso contrário, seu arquivo CSV pode quebrar o leitor consumidores.

Respondeu 07/08/2008 em 12:18
fonte usuário

votos
1

Eu gosto da idéia de adicionar a vírgula, verificando se o recipiente está vazio, mas isso não significa mais processamento como ele precisa verificar o comprimento da corda em cada ocorrência?

Você está otimizando prematuramente, o impacto no desempenho seria insignificante.

Respondeu 07/08/2008 em 07:25
fonte usuário

votos
0

Eu uso CSVHelper - é uma grande biblioteca de código aberto que permite gerar CSV compatível córregos um elemento de cada vez ou custom-mapear suas classes:

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

ou se você mapear, em seguida, algo como isto: csvWriter.WriteRecords<ContactList>(contactList);

Respondeu 26/06/2012 em 21:06
fonte usuário

votos
0

Que tal um corte?

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

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

    return sb.ToString().Trim(',');
}
Respondeu 07/08/2008 em 09:19
fonte usuário

votos
0

Desculpe, PHP exemplo específico, mas pode ajudar alguém.

Respondeu 07/08/2008 em 07:25
fonte usuário

votos
0

Como cerca de rastreamento se você está no primeiro item, e apenas adicionar uma vírgula antes do item se ele não é o primeiro.

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();
}
Respondeu 07/08/2008 em 06:54
fonte usuário

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