2008-08-07 29 views
15

forma típica de la creación de un CSV cadena (pseudocódigo) cadena CSV:manipulación

  1. crear un objeto contenedor CSV (como un StringBuilder en C#).
  2. Pasa a través de las cadenas que deseas agregar agregando una coma después de cada una.
  3. Después del ciclo, elimine esa última coma superflua. muestra

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

me gusta la idea de añadir la coma comprobando si el contenedor está vacío, pero no significa eso más procesamiento, ya que necesita para comprobar la longitud de la cadena en cada caso?

Creo que debería haber una manera más fácil/limpia/más eficiente de eliminar esa última coma. ¿Algunas ideas?

Respuesta

19

Usted podría utilizar LINQ to Objects:

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

Obviamente todo eso podría hacerse en una sola línea, pero es un poco más claro en dos.

+4

Menos obvia es que en realidad no implem ent la especificación CSV. Es una gran manera de poner comas en una cadena, pero eso no es lo mismo que CSV, el formato. – rcreswick

+0

Funciona bien con la función EncodeCsvField() publicada por dbkk –

3

En su lugar, podría agregar la coma como la primera cosa dentro de su foreach.

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

0

¿Qué hay de seguimiento de si usted está en el primer elemento, y sólo agrega una coma antes de el artículo si no es la primera.

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

Disculpe, ejemplo específico de PHP, pero podría ayudar a alguien.

1

Me gusta la idea de agregar la coma comprobando si el contenedor está vacío, pero ¿no significa eso que necesita más procesamiento ya que necesita verificar la longitud de la cadena en cada aparición?

Optimización prematura, el rendimiento alcanzado sería insignificante.

3

También puede hacer una serie de datos c.Name y utilizar método string.join para crear su línea.

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

Esto podría no ser tan performante como el StringBuilder enfoque, pero definitivamente se ve más limpio.

Además, es posible que desee considerar el uso de .CurrentCulture.TextInfo.ListSeparator en lugar de una coma no modificable - Si la salida va a ser importados en otras aplicaciones, es posible que tenga problemas con él. ListSeparator puede ser diferente en diferentes culturas, y MS Excel por lo menos, respeta esta configuración.Entonces:

return String.Join(
    System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator, 
    tmpList.ToArray()); 
0

¿Qué tal un poco de recorte?

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

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

    return sb.ToString().Trim(','); 
} 
1

Es sólo una idea, pero recuerde que debe manejar por comas de y entre comillas (") en los valores de los campos, de lo contrario el archivo CSV puede romper el lector de los consumidores.

5

No se olvide de nuestro viejo amigo" de ". no es tan agradable de aspecto como foreach, pero tiene la ventaja de ser capaz de iniciar en el 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(); 
} 

también puede envolver la segunda Anexar en un 'si' que comprueba si la propiedad Name contiene una comilla doble o una coma, y ​​si es así, evítelos apropiadamente y

9

Tu código no es realmente compatible con full CSV format. Si solo está generando CSV a partir de datos que no tienen comas, espacios iniciales/finales, pestañas, nuevas líneas o comillas, debería estar bien. Sin embargo, en la mayoría de los escenarios de intercambio de datos del mundo real, necesita la implementación completa.

Para la generación de CSV adecuada, puede utilizar esto:

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

no podría ser de código más eficiente del mundo, pero ha sido probado. El mundo real apesta comparado con el código de muestra rápida :)

+0

Tal como se publicó en otra respuesta, hay bibliotecas para hacer esto (p. Ej .: OpenCSV) y en realidad también tienen pruebas de marco de pruebas/unidades. – rcreswick

+1

Estas dos subrutinas finalmente resolvieron el problema que he estado persiguiendo. Es cierto, un poco más largo que simplemente lamer y pegar todos los datos junto con las comas, pero manejó mi exportación de 400,000 filas sin problemas. – Lloyd

1

He usado este método antes. La propiedad Length de StringBuilder NO es de solo lectura, por lo que al restarla se truncan el último carácter. Pero debe asegurarse de que su longitud no sea cero para empezar (lo que sucedería si su lista está vacía) porque establecer la longitud en menos de cero es un error.

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

Por qué no utilizar una de las bibliotecas de código abierto CSV por ahí?

Sé que suena excesivo para algo que parece tan simple, pero como se puede ver por los comentarios y fragmentos de código, hay más de lo que parece. Además de manejar el cumplimiento completo de CSV, eventualmente querrá manejar CSV de lectura y escritura ... y es posible que desee la manipulación de archivos.

He usado Open CSV en uno de mis proyectos anteriores (pero hay muchos otros para elegir). Ciertamente hizo mi vida más fácil. ;)

1

escribí una pequeña clase para esto en caso de que alguien le resulta ú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()); 
     } 
    } 
} 
0

utilizo CSVHelper - es una gran biblioteca de código abierto que le permite generar CSV compatible con flujos de un elemento a la vez o por encargo mapear sus clases:

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

o si asigna entonces algo como esto: csvWriter.WriteRecords<ContactList>(contactList);