2009-04-10 27 views
23

Tengo un archivo de texto que está en un formato separado por comas, delimitado por " en la mayoría de los campos. Intento incluir eso en algo que pueda enumerar a través de (Generic Collection, por ejemplo). No tengo control sobre cómo se genera el archivo ni el carácter que utiliza para el delimitador.Parse Delimited CSV en .NET

En este caso, los campos están separados por una coma y los campos de texto están encerrados en " marcas. El problema al que me estoy enfrentando es que algunos campos tienen comillas (es decir, 8 " Bandeja) y se recogen accidentalmente como el siguiente campo. En el caso de los campos numéricos, no tienen comillas a su alrededor, pero comienzan con un signo + o un signo (que representa un número positivo/negativo).

Estaba pensando en un RegEx, pero mis habilidades no son tan buenas, así que con suerte alguien puede proponer algunas ideas. Hay aproximadamente 19,000 registros en este archivo, por lo que estoy tratando de hacerlo de la manera más eficiente posible. Aquí hay un par de ejemplos de filas de datos:

"00","000000112260 ","Pie Pumpkin        ","RET","6.99 ","  ","ea ",+0000000006.99000 
"00","000000304078 ","Pie Apple caramel      ","RET","9.99 ","  ","ea ",+0000000009.99000 
"00","StringValue here","8" Tray of Food        ","RET","6.99 ","  ","ea ",-00000000005.3200 

Hay muchos más campos, pero se puede obtener la imagen ....

estoy usando VB.NET y tengo una lista genérica configuración para aceptar los datos. He intentado usar CSVReader y parece funcionar bien hasta que tocas un registro como el tercero (con una comilla en el campo de texto). Si de alguna manera puedo hacer que maneje las cotizaciones adicionales, la opción CSVReader funcionará bien.

Gracias!

+2

Ayudaría tener archivos CSV formateados correctamente para empezar. – FlySwat

+3

Se supone que las comillas dobles dentro de una cadena entre comillas se escapen doblando. Entonces "8" Bandeja de comida "no está permitido en el formato. De esta manera, una cadena como hola", "no puede existir. Escapada y citada, se convierte en" hola "", "" allí ". , se convierte en "hola", "allí" que se ve como dos cadenas. – UncleO

+0

Estoy de acuerdo con los dos comentarios anteriores, pero desafortunadamente no tengo control sobre cómo se exporta el archivo. Así es como sale del archivo. software. – hacker

Respuesta

7

De here:..

Encoding fileEncoding = GetFileEncoding(csvFile); 
// get rid of all doublequotes except those used as field delimiters 
string fileContents = File.ReadAllText(csvFile, fileEncoding); 
string fixedContents = Regex.Replace(fileContents, @"([^\^,\r\n])""([^$,\r\n])", @"$1$2"); 
using (CsvReader csv = 
     new CsvReader(new StringReader(fixedContents), true)) 
{ 
     // ... parse the CSV 
+0

Esto funciona bastante bien, pero por alguna razón, se arruina con un nombre como: Nombre del producto "A" Estoy seguro de que tiene que ver con el RegEx, pero Parece que no puedo hacerlo bien. – hacker

+0

Consulte mi respuesta a continuación para saber cómo pude implementar esto. – hacker

+0

Esta es una buena solución que utilicé pero la función GetFileEncoding no estuvo disponible. Lo publicaré más tarde si alguien lo necesita. – Daver

0

Hay al menos controladores ODBC para archivos CSV. Pero hay diferentes sabores de CSV.

¿Qué produjo estos archivos? No es poco probable que haya un controlador que coincida con los requisitos de la aplicación de origen.

+0

Es un antiguo paquete de contabilidad basado en DOS llamado Business Vision Delta. Desafortunadamente, la compañía ha sido vendida a nuevos proveedores y ya no es compatible con el viejo material de DOS. Esta es la única forma en que puedo extraer los datos para integrarlos en el software más nuevo. – hacker

+0

¿Puedes decir qué tipo de tablas de datos utiliza? Tal vez dbfs? Además, intente simplemente abrir los archivos CSV con Excel, Access, cualquier otra aplicación que tenga que pueda importar CSV. Intenta evitar escribir software como primera opción. – dkretz

5

Echa un vistazo a FileHelpers library.

+0

Se ve bien, pero me pareció muy frustrante de usar. La falta de soporte para auto-propiedades en lugar de campos privados es muy torpe. – Alex

+0

Y esto no fue un factor en la pregunta original, pero esa página dice que FileHelpers utiliza la generación de código dinámico. Eso significa que no es útil en algunos entornos restringidos (MonoTouch, para mí). –

0

Su problema con CSVReader es que la cita en el tercer registro no se escapa con otra cita (también conocida como comillas dobles). Si no escapar de ellos, entonces, ¿cómo se puede esperar para manejar", en medio de un campo de texto?

http://en.wikipedia.org/wiki/Comma-separated_values

(lo hice al final tener que trabajar con archivos (con diferentes delimitadores), pero los caracteres de comillas dentro de un valor de texto no se escaparon y terminé de escribir mi propio analizador personalizado no sé si esto era absolutamente necesario o no)

+0

Ese es mi problema ... No puedo escapar de ellos. No tengo control sobre cómo se exporta el archivo. Intento alejarme de escribir un analizador que va carácter por personaje para verificar si hay una coma después de una cita, etc., pero puede deberse a eso. – hacker

+0

Bueno, si vas por la ruta de hacer la tuya propia (todavía estoy convencido de que hay una solución en algún lugar que puede manejar este caso), solo asegúrate de validar el conteo de campo y los datos lo mejor que puedas. (Publicaba el mío pero lo hice en el trabajo.) – llamaoo7

71

recomiendo mirando el TextFieldParserClass en .Net. Es necesario incluir

Imports Microsoft.VisualBasic.FileIO.TextFieldParser 

He aquí una muestra rápida:

 Dim afile As FileIO.TextFieldParser = New FileIO.TextFieldParser(FileName) 
     Dim CurrentRecord As String() ' this array will hold each line of data 
     afile.TextFieldType = FileIO.FieldType.Delimited 
     afile.Delimiters = New String() {","} 
     afile.HasFieldsEnclosedInQuotes = True 

     ' parse the actual file 
     Do While Not afile.EndOfData 
      Try 
       CurrentRecord = afile.ReadFields 
      Catch ex As FileIO.MalformedLineException 
       Stop 
      End Try 
     Loop 
+8

+1, a pesar de que se siente sucio usando esto en C#. –

+5

FYI: TextFieldParser implementa IDisposable y debe ser envuelto en una declaración 'using' o explícitamente dispuesto. – chilltemp

+1

Esto no parece funcionar si hay saltos de línea dentro de los campos entre comillas. Gorrón. –

0

La lógica de este enfoque personalizado es: Leer a través de archivo de 1 línea a la vez, dividir cada línea en la coma, quitar el primer y el último carácter (eliminando las comillas externas pero sin afectar las comillas internas) y luego agregando los datos a su lista genérica. Es corto y muy fácil de leer y trabajar.

 Dim fr As StreamReader = Nothing 
     Dim FileString As String = "" 
     Dim LineItemsArr() as String 

     Dim FilePath As String = HttpContext.Current.Request.MapPath("YourFile.csv") 

     fr = New System.IO.StreamReader(FilePath) 

     While fr.Peek <> -1 
      FileString = fr.ReadLine.Trim 

      If String.IsNullOrEmpty(FileString) Then Continue While 'Empty Line 

      LineItemsArr = FileString.Split(",") 

      For Each Item as String In LineItemsArr 
       'If every item will have a beginning and closing " (quote) then you can just 
       'cut the first and last characters of the string here. 
       'i.e. UpdatedItems = Item. remove first and last character 

       'Then stick the data into your Generic List (Of String()?) 
      Next 
     End While 
+0

O antes de eliminar las cotizaciones externas, utilícelas como prueba para procesar cadenas o procesar números (si es necesario). –

+0

Esto no maneja los datos donde está el carácter separador en los datos – ChadD

1

Estoy publicar esto como una respuesta para que pueda explicar cómo lo hice y por qué .... La respuesta de Mitch trigo era el que me dio la mejor solución para este caso y sólo tenía que modificarlo ligeramente debido al formato de estos datos se exportó en

este es el código VB:.

Dim fixedContents As String = Regex.Replace(
          File.ReadAllText(csvFile, fileEncoding), 
          "(?<!,)("")(?!,)", 
          AddressOf ReplaceQuotes) 

la expresión regular que se utilizó es lo que tenía que cambiar porque ciertos campos tenían cotizaciones no escapó en ellos y el RegEx proporcionado no parecían funcionar en todos los ejemplos. Este utiliza 'Mira hacia adelante' y 'Mira detrás' para ver si la cita es justo después de una coma o justo antes. En este caso, ambos son negativos (es decir, muéstreme dónde la comilla doble no está antes o después de una coma). Esto debería significar que la cita está en el medio de una cadena.

En este caso, en lugar de hacer un reemplazo directo, estoy usando la función ReplaceQuotes para manejar eso por mí. La razón por la que estoy usando esto es porque necesitaba un poco de lógica extra para detectar si estaba al principio de una línea. Si hubiera dedicado aún más tiempo, estoy seguro de que podría haber ajustado el RegEx para tener en cuenta el comienzo de la línea (usando MultiLine, etc.) pero cuando lo intenté rápidamente, no pareció funcionar en todas.

Con esto en su lugar, usando el lector CSV en un archivo CSV de 32MB (aproximadamente 19000 filas), toma aproximadamente 2 segundos leer el archivo, realizar la expresión regular, cargarlo en el lector CSV, agregar todos los datos a mi clase genérica y acabado. ¡¡Muy rápido!!

11

Pruebe este sitio. http://kbcsv.codeplex.com/

He buscado una buena utilidad y esta es sin dudas la mejor que he encontrado y funciona correctamente. No pierdas el tiempo probando otras cosas, esto es gratis y funciona.

+1

¡Por qué, gracias! –

+2

En segundo lugar esto. 15 caracteres – Alex

+0

kbcsv movido a [link] (https://github.com/kentcb/KBcsv) – Luca

7

Como dice este enlace ... Don't roll your own CSV parser!

Uso TextFieldParser como Avi sugeridos. Microsoft ya ha hecho esto por ti. Si terminó escribiendo uno, y encuentra un error en él, considere reemplazarlo en lugar de corregir el error. Hice eso recientemente y me ahorró mucho tiempo.

0
 public static Encoding GetFileEncoding(String fileName) 
    { 
     Encoding Result = null; 
     FileInfo FI = new FileInfo(fileName); 
     FileStream FS = null; 

     try 
     { 
      FS = FI.OpenRead(); 
      Encoding[] UnicodeEncodings = { Encoding.BigEndianUnicode, Encoding.Unicode, Encoding.UTF8 }; 
      for (int i = 0; Result == null && i < UnicodeEncodings.Length; i++) 
      { 
       FS.Position = 0; 
       byte[] Preamble = UnicodeEncodings[i].GetPreamble(); 
       bool PreamblesAreEqual = true; 
       for (int j = 0; PreamblesAreEqual && j < Preamble.Length; j++) 
       { 
        PreamblesAreEqual = Preamble[j] == FS.ReadByte(); 
       } 
       if (PreamblesAreEqual) 
       { 
        Result = UnicodeEncodings[i]; 
       } 
      } 
     } 
     catch (System.IO.IOException) 
     { 
     } 
     finally 
     { 
      if (FS != null) 
      { 
       FS.Close(); 
      } 
     } 

     if (Result == null) 
     { 
      Result = Encoding.Default; 
     } 

     return Result; 
    } 
0

RegEx para excluir la primera y última cotización sería (?<!^)(?<!,)("")(?!,)(?!$). Por supuesto, necesita usar RegexOptions.Multiline.

De esta manera no hay necesidad de la función del evaluador. Mi código reemplaza las comillas dobles no deseadas con comillas simples.

El código completo de C# es el siguiente.

string fixedCSV = Regex.Replace(
      File.ReadAllText(fileName), 
      @"(?<!^)(?<!;)("")(?!;)(?!$)", "'", RegexOptions.Multiline); 
5

Usted podría dar CsvHelper (una biblioteca que mantener) una oportunidad y que está disponible a través de NuGet. Sigue el estándar RFC 4180 para CSV. Podrá manejar cualquier contenido dentro de un campo que incluya comas, comillas y nuevas líneas.

CsvHelper es fácil de usar, pero también es fácil de configurar para que funcione con muchos tipos diferentes de archivos delimitados.

CsvReader csv = new CsvReader(streamToFile); 
IEnumerable<MyObject> myObjects = csv.GetRecords<MyObject>(); 

Si desea leer archivos CSV en un nivel inferior, puede utilizar el analizador directamente, que devolverá cada fila como una matriz de cadenas.

var parser = new CsvParser(myTextReader); 
while(true) 
{ 
    string[] line = parser.ReadLine(); 
    if(line == null) 
    { 
     break; 
    } 
} 
+0

además es el analizador csv de propósito general más rápido que he visto hasta ahora. –