2008-11-25 16 views
5

Estoy tratando de usar C# para analizar CSV. Usé expresiones regulares para encontrar "," y leí la cadena si mis recuentos de encabezado eran iguales a mi recuento de coincidencias.CSV Parsing

Ahora bien, esto no va a funcionar si tengo un valor como:

"a",""b","x","y"","c" 

entonces mi salida es:

'a' 
'"b' 
'x' 
'y"' 
'c' 

pero lo que yo quiero es:

'a' 
'"b","x","y"' 
'c' 

¿Hay ¿Alguna expresión regular o cualquier otra lógica que pueda usar para esto?

+0

@ Matt: No todo el mundo es un nativo Inglés hablante. No sé qué causó la manía de edición. Lo restauré a una versión que refleja la intención original, ya que el significado de la pregunta comenzó a degradarse. – Tomalak

+0

@xyz: Lo siento por los cambios anárquicos que se hicieron a su pregunta sin una buena razón. Espero que esto haya llegado a su fin ahora. – Tomalak

+1

Su CSV no es válido, debe ser "a", "" "b" "," "x" "," "y" "", "c" – dalle

Respuesta

1

Con el fin de tener un archivo CSV analizable, las comillas dobles dentro de un valor necesitan ser adecuadamente escapó de alguna manera. Las dos formas estándar de hacerlo son mediante la representación de una comilla doble, ya sea como dos comillas dobles seguidas o una comilla doble invertida. Esa es una de las dos formas siguientes:

""

\"

En la segunda forma de su cadena inicial se vería así:

"a", "\" B \ ", \" x \ ", \" y \ "", "c"

Si su cadena de entrada no está formateada en un formato riguroso como este, tiene muy pocas posibilidades de analizarlo correctamente en un entorno automatizado.

+0

no, no creo que sea cierto. en su ejemplo, siempre y cuando suponga que el CSV es válido durante el mayor tiempo posible (y no solo abandonando en la parte '' '' '', b ''), aún podría analizarlo. – nickf

+0

Hay muchas posibilidades de que pueda lograr el resultado correcto, es solo un trabajo más. Tengo un código que lo hace con éxito (aunque no usa regex). – Murph

0

Bueno, no soy un experto en regex, pero estoy seguro de que tienen una respuesta para esto.

Procesalmente está yendo letra por letra. Establezca una variable, digamos dontMatch, en FALSE.

Cada vez que se encuentra con una cotización alternar dontMatch.

cada vez que se encuentra con una coma, marque dontMatch. Si es VERDADERO, ignora la coma. Si es FALSO, divídelo en la coma.

Esto funciona para el ejemplo que das, pero la lógica que utilizas para las comillas es fundamentalmente defectuosa: debes escapar de ellas o usar otro delimitador (comillas simples, por ejemplo) para separar las comillas principales de las comillas menores.

Por ejemplo,

"a", ""b", ""c", "d"", "e""

rendirá malos resultados.

Esto se puede arreglar con otro parche. En lugar de simplemente mantener un verdadero falso, tienes que hacer coincidir las comillas.

Para que coincida con las comillas, debe saber lo que se vio por última vez, lo que lo lleva a un territorio de análisis bastante profundo. Probablemente, en ese momento, querrá asegurarse de que su lenguaje esté bien diseñado, y si es así, puede usar una herramienta de compilación para crear un analizador para usted.

-Adam

1

Si todos los valores son garantizados a estar entre comillas, busque los valores, no por comas:

("".*?""|"[^"]*") 

Esto se aprovecha del hecho de que "el partido más largo más temprano gana "- busca primero los valores de cotización doble y con una prioridad más baja para los valores cotizados normales.

Si no desea que la cita de cerramiento para formar parte del partido, uso:

"(".*?"|[^"]*)" 

e ir a por el valor en el grupo 1. partido

Como ya he dicho: requisito previo para esto para trabajar es una entrada bien formada con cotizaciones garantizadas o comillas dobles alrededor de cada valor. ¡Los valores vacíos deben ser citados también! Un buen efecto secundario es que no le importa el carácter separador. Comas, TABs, punto y coma, espacios, lo que sea. Todo funcionará.

+0

Gracias ... por su respuesta informativa, me parece que esto funciona. –

12

CSV, cuando se trata de cosas como varias líneas, citas, delimitadores diferentes * etc. - puede ser más complicado de lo que se piensa ... quizás considerar una respuesta pre-laminada? Yo uso this, y funciona muy bien.

* = recordar que algunos sitios utilizan [tab] como el C en CSV ...

+0

Mi configuración regional usa puntos y comas para la "C" ... no me inicie con Excel y _comma_ archivos separados que no se analizan correctamente porque la coma es una coma real;) – VVS

+0

+1 Gracias por la sugerencia del lector CSV de Lumenworks Marc , funciona bien Por cierto, está disponible en NuGet: Install-Package LumenWorksCsvReader –

+0

@Jonathan ah, genial - eso es genial, no sabía que –

1

Hay un muy citado diciendo:

Algunas personas, cuando se enfrentan a un problema , piensan "Yo sé, voy a utilizar expresiones regulares." Ahora tienen dos problemas. (Jamie Zawinski)

Dado que no hay un estándar oficial para archivos CSV (en cambio hay un gran número de estilos ligeramente incompatibles), lo que necesita para asegurarse de que lo que se implementa trajes de los archivos que va a recibir. No tiene sentido implementar nada más elegante que lo que necesita, y estoy seguro de que no necesita expresiones regulares.

Aquí está mi puñalada en un método simple para extraer los términos - básicamente, se realiza un bucle a través de la línea en busca de comas, hacer el seguimiento de si el índice actual está dentro de una cadena o no:

public IEnumerable<string> SplitCSV(string line) 
    { 
     int index = 0; 
     int start = 0; 
     bool inString = false; 

     foreach (char c in line) 
     { 
      switch (c) 
      { 
       case '"': 
        inString = !inString; 
        break; 

       case ',': 
        if (!inString) 
        { 
         yield return line.Substring(start, index - start); 
         start = index + 1; 
        } 
        break; 
      } 
      index++; 
     } 

     if (start < index) 
      yield return line.Substring(start, index - start); 
    } 

salvedad Estándar - código no probado, puede haber errores uno a uno.

Limitaciones

  • Las comillas alrededor de un valor que no se eliminan automáticamente.
    Para hacer esto, agregue una marca justo antes de la declaración yield return cerca del final.

  • Las comillas simples no son compatibles en la misma forma que las comillas dobles
    Se podría añadir un booleano separada inSingleQuotedString, cambiando el nombre del booleano existente para inDoubleQuotedString y el tratamiento tanto de la misma manera. (No se puede hacer que los existentes booleano hacer doble trabajo porque necesita la cadena que termina con la misma cita que lo inició.)

  • espacio en blanco no se elimina automáticamente
    Algunas herramientas de introducir un espacio en blanco alrededor de las comas en CSV archivos para "bonito" el archivo; luego se vuelve difícil decir que el espacio en blanco intencional formatee el espacio en blanco.

+0

No olvides que la multilínea también es una opción para la csv citada, y necesitarías probar con citas escapadas como "algunos" "datos", etc ... –

+0

Y luego están aquellas personas que hacen su propia rutina de manejo de cadenas cada vez porque han escuchado que las expresiones regulares simplemente no lo cortan. Si * conoce los datos con los que está tratando * las expresiones regulares están bien. @xyz no preguntó cómo escribir un analizador CSV completo listo para todas las eventualidades. – Tomalak

+0

¿Qué pasa con la variable 'inString'? Falta parte de la lógica. – saku

0

El analizador Lumenworks CSV (fuente abierta, libre pero necesita un inicio de sesión CodeProject) es, con mucho, el mejor que' ve usado. Le ahorrará tener que escribir la expresión regular y es intuitivo de usar.

3

Yo usaría FileHelpers si fuera usted. Las expresiones regulares son buenas pero difíciles de leer, especialmente si regresas, después de un tiempo, para una solución rápida.

sólo por el bien de ejercer mi mente, rápida & sucio trabajo Procedimiento en C#:

public static List<string> SplitCSV(string line) 
{ 
    if (string.IsNullOrEmpty(line)) 
     throw new ArgumentException(); 

    List<string> result = new List<string>(); 

    bool inQuote = false; 
    StringBuilder val = new StringBuilder(); 

    // parse line 
    foreach (var t in line.Split(',')) 
    { 
     int count = t.Count(c => c == '"'); 

     if (count > 2 && !inQuote) 
     { 
      inQuote = true; 
      val.Append(t); 
      val.Append(','); 
      continue; 
     } 

     if (count > 2 && inQuote) 
     { 
      inQuote = false; 
      val.Append(t); 
      result.Add(val.ToString()); 
      continue; 
     } 

     if (count == 2 && !inQuote) 
     { 
      result.Add(t); 
      continue; 
     } 

     if (count == 2 && inQuote) 
     { 
      val.Append(t); 
      val.Append(','); 
      continue; 
     } 
    } 

    // remove quotation 
    for (int i = 0; i < result.Count; i++) 
    { 
     string t = result[i]; 
     result[i] = t.Substring(1, t.Length - 2); 
    } 

    return result; 
} 
+2

No me gustan realmente los FileHelpers. Demasiada configuración manual. –

0

Acabo de probar su expresión regular en mis code..its funcionan bien para el texto con formato citando .. .

pero se pregunta si podemos analizar por debajo del valor de expresiones regulares ..

 
"First_Bat7679",""NAME","ENAME","FILE"","","","From: "DDD,_Ala%as"@sib.com" 

Busco para el resultado como:

 
'First_Bat7679' 
'"NAME","ENAME","FILE"' 
'' 
'' 
'From: "DDD,_Ala%as"@sib.com' 

Gracias

+0

No, eso es imposible con mi solución, porque mi expresión regular depende de las comillas para delimitar los valores. Deberías pensar realmente en usar una de las otras soluciones (por ejemplo, usar un analizador). – Tomalak

+0

Si puede hacer algo al respecto, cambie el formato CSV a algo menos ambiguo. No use comas o comillas cuando ambos puedan ocurrir dentro de los valores, o al menos de manera consistente, salgan comas y comillas dentro de los valores. – Tomalak

+0

Si usa un delimitador en su DSV que no va a aparecer en sus valores (o va a ocurrir muy raramente, y solo lo escapa si eso sucede), ni siquiera necesita preocuparse por las comillas. Los dos puntos son bastante comunes como delimitadores, en mi experiencia. –

1

Pruebe CsvHelper (una biblioteca que mantengo) o FastCsvReader. Ambos funcionan bien. CsvHelper también escribe. Como todos los demás han estado diciendo, no hagas lo tuyo. : P

1

FileHelpers admite campos multilínea.

Se podría analizar archivos como estos:

a,"line 1 
line 2 
line 3" 
b,"line 1 
line 2 
line 3" 

Aquí está la declaración de tipo de datos:

[DelimitedRecord(",")] 
public class MyRecord 
{ 
public string field1; 
[FieldQuoted('"', QuoteMode.OptionalForRead, MultilineMode.AllowForRead)] 
public string field2; 
} 

Aquí es el uso:

static void Main() 
{ 
FileHelperEngine engine = new FileHelperEngine(typeof(MyRecord)); 
MyRecord[] res = engine.ReadFile("file.csv");  
}