configurar - Tratar con comas en un archivo CSV




2013 2010 (19)

Estoy buscando sugerencias sobre cómo manejar un archivo csv que se crea, que luego suben nuestros clientes y que puede tener una coma en un valor, como el nombre de una empresa.

Algunas de las ideas que estamos analizando son: Identificadores citados (valor "," valores "," etc.) o usar una | en lugar de una coma. El mayor problema es que tenemos que hacerlo fácil, o el cliente no lo hará.


Answers

La solución más sencilla que he encontrado es la que utiliza LibreOffice:

  1. Reemplazar todo literal " por
  2. Pon comillas dobles alrededor de tu cadena

También puedes usar el que usa Excel:

  1. Reemplazar todo literal " por ""
  2. Pon comillas dobles alrededor de tu cadena

Tenga en cuenta que otras personas recomendaron hacer solo el paso 2 anterior, pero eso no funciona con líneas en las que a " va seguido de a, como en un CSV donde desea tener una sola columna con la cadena hello",world , como CSV diría:

"hello",world"

Lo que se interpreta como una fila con dos columnas: hello y world"


Primero, preguntémonos: "¿Por qué sentimos la necesidad de manejar las comas de manera diferente para los archivos CSV?"

Para mí, la respuesta es: "Porque cuando exporto datos a un archivo CSV, las comas en un campo desaparecen y mi campo se separa en varios campos donde las comas aparecen en los datos originales". (Eso es porque la coma es el carácter separador de campo CSV.)

Dependiendo de su situación, los puntos y coma también se pueden usar como separadores de campo CSV.

Dados mis requisitos, puedo usar un carácter, por ejemplo, una comilla simple de menos de 9, que parece una coma.

Así que, así es como puedes hacerlo en Go:

// Replace special CSV characters with single low-9 quotation mark
func Scrub(a interface{}) string {
    s := fmt.Sprint(a)
    s = strings.Replace(s, ",", "‚", -1)
    s = strings.Replace(s, ";", "‚", -1)
    return s
}

El segundo carácter de coma en la función Reemplazar es decimal 8218.

Tenga en cuenta que si tiene clientes que pueden tener lectores de texto solo ASCII, este carácter de Décimo 8218 no se verá como una coma. Si este es su caso, entonces recomiendo rodear el campo con la coma (o el punto y coma) con comillas dobles según RFC 4128: https://tools.ietf.org/html/rfc4180


En caso de que esté en un sistema * nix , tenga acceso a sed y puede haber una o más comas no deseadas solo en un campo específico de su CSV, puede usar la siguiente frase para encerrarlas en " as RFC4180 Sección 2 propone:

sed -r 's/([^,]*,[^,]*,[^,]*,)(.*)(,.*,.*)/\1"\2"\3/' inputfile

Según el campo en el que se encuentren las comas no deseadas, debe modificar / ampliar los grupos de captura de la expresión regular (y la sustitución).
El ejemplo anterior incluirá el cuarto campo (de seis) entre comillas.

En combinación con la --in-place-option puede aplicar estos cambios directamente al archivo.

Para "construir" la expresión regular correcta, hay un principio simple a seguir:

  1. Para cada campo en su CSV que viene antes del campo con la (s) coma (s) no deseada (s), escriba un [^,]*, y póngalas todas juntas en un grupo de captura.
  2. Para el campo que contiene las comas no deseadas, escriba (.*) .
  3. Para cada campo después del campo con la (s) coma (s) no deseada (s) escriba uno,. ,.* Y póngalos todos juntos en un grupo de captura.

Aquí hay una breve descripción de diferentes posibles expresiones regulares / sustituciones dependiendo del campo específico. Si no se da, la sustitución es \1"\2"\3 .

([^,]*)(,.*)                     #first field, regex
"\1"\2                           #first field, substitution

(.*,)([^,]*)                     #last field, regex
\1"\2"                           #last field, substitution


([^,]*,)(.*)(,.*,.*,.*)          #second field (out of five fields)
([^,]*,[^,]*,)(.*)(,.*)          #third field (out of four fields)
([^,]*,[^,]*,[^,]*,)(.*)(,.*,.*) #fourth field (out of six fields)

Si desea eliminar las comas no deseadas con sed lugar de incluirlas entre comillas, consulte esta respuesta .


Para 2017, csv está completamente especificado: RFC 4180.

Es una especificación muy común, y está completamente cubierta por muchas bibliotecas ( github.com/Flinesoft/CSVImporter ).

Simplemente use cualquier biblioteca csv fácilmente disponible , es decir RFC 4180.

En realidad, hay una especificación para el formato CSV y cómo manejar las comas:

Los campos que contienen saltos de línea (CRLF), comillas dobles y comas deben incluirse entre comillas dobles.

http://tools.ietf.org/html/rfc4180

Entonces, para tener valores foo y bar,baz , haces esto:

foo,"bar,baz"

Otro requisito importante a considerar (también de la especificación):

Si se usan comillas dobles para encerrar campos, entonces una comilla doble que aparece dentro de un campo debe escaparse precediéndolo con otra comilla doble. Por ejemplo:

"aaa","b""bb","ccc"

Como se trata de prácticas generales, comencemos por las reglas generales:

  1. No use CSV, use XML con una biblioteca para leer y escribir el archivo xml en su lugar.

  2. Si debe utilizar CSV. Hágalo correctamente y use una biblioteca gratuita para analizar y almacenar los archivos CSV.

Para justificar 1), la mayoría de los analizadores CSV no son conscientes de la codificación, por lo que si no está tratando con US-ASCII está solicitando problemas. Por ejemplo, excel 2002 es almacenar el CSV en codificación local sin ninguna nota sobre la codificación. El estándar CSV no se adopta ampliamente :(. Por otro lado, el estándar xml está bien adoptado y maneja las codificaciones bastante bien.

Para justificar 2), hay un montón de analizadores csv para casi todos los idiomas, por lo que no hay necesidad de reinventar la rueda, incluso si las soluciones parecen bastante simples.

Para nombrar algunos:

  • para uso en python construir en modulo csv

  • para perl cheque CPAN y Text::CSV

  • para php use build en funciones fgetcsv / fputcsv

  • para la biblioteca SuperCVS cheque java

Realmente no hay necesidad de implementar esto a mano si no va a analizarlo en un dispositivo integrado.


Utilice un carácter de tabulación (\ t) para separar los campos.


Como mencioné en mi comentario a la respuesta de Harpo, su solución es buena y funciona en la mayoría de los casos, sin embargo, en algunos casos, cuando las comas están directamente adyacentes entre sí, no se puede dividir en comas.

Esto se debe a que la cadena Regex se comporta inesperadamente como una cadena vertabim. Para que este comportamiento sea correcto, todos los caracteres de la cadena de expresiones regulares deben escaparse manualmente sin utilizar el escape vertabim.

Es decir. La expresión regular debe ser esto usando escapes manuales:

",(?=(?:[^\"\"]*\"\"[^\"\"]*\"\")*(?![^\"\"]*\"\"))"

que se traduce en ",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))"

Cuando se utiliza una cadena vertabim @",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))" Se comporta como la siguiendo como puede ver si depura la expresión regular:

",(?=(?:[^"]*"[^"]*")*(?![^"]*"))"

Así que, en resumen, recomiendo la solución de Harpo, pero ¡cuidado con este pequeño gotcha!

He incluido en el CsvReader un poco opcional a prueba de fallos para notificarle si ocurre este error (si tiene un número de columnas conocido):

if (_expectedDataLength > 0 && values.Length != _expectedDataLength) 
throw new DataLengthException(string.Format("Expected {0} columns when splitting csv, got {1}", _expectedDataLength, values.Length));

Esto se puede inyectar a través del constructor:

public CsvReader(string fileName, int expectedDataLength = 0) : this(new FileStream(fileName, FileMode.Open, FileAccess.Read))
{
    _expectedDataLength = expectedDataLength;
}

Creo que la solución más fácil para este problema es hacer que el cliente abra el csv en Excel, y luego ctrl + r para reemplazar todas las comas con el identificador que desee. Esto es muy fácil para el cliente y requiere solo un cambio en su código para leer el delimitador de su elección.


El formato CSV utiliza comas para separar los valores, los valores que contienen retornos de carro, saltos de línea, comas o comillas dobles están entre comillas dobles. Los valores que contienen comillas dobles se citan y cada cita literal se escapa mediante una comilla inmediatamente anterior: por ejemplo, los 3 valores:

test
list, of, items
"go" he said

sería codificado como:

test
"list, of, items"
"""go"" he said"

Se puede citar cualquier campo, pero solo deben citarse los campos que contienen comas, CR / NL o comillas.

No existe un estándar real para el formato CSV, pero casi todas las aplicaciones siguen las convenciones documentadas here . El RFC que se mencionó en otra parte no es un estándar para CSV, es un RFC para usar CSV dentro de MIME y contiene algunas limitaciones no convencionales e innecesarias que lo hacen inútil fuera de MIME.

Un hecho que muchos módulos CSV que he visto no tienen en cuenta es el hecho de que se pueden codificar varias líneas en un solo campo, lo que significa que no puede asumir que cada línea es un registro separado, o bien no debe permitir nuevas líneas en su datos o estar preparado para manejar esto.


Generalmente codifico en URL los campos que pueden tener comas o caracteres especiales. Y luego descodifíquelo cuando se esté utilizando / visualizando en cualquier medio visual.

(las comas se convierten en% 2C)

Cada idioma debe tener métodos para codificar y decodificar cadenas en URL.

por ejemplo, en java

URLEncoder.encode(myString,"UTF-8"); //to encode
URLDecoder.decode(myEncodedstring, "UTF-8"); //to decode

Sé que esta es una solución muy general y puede que no sea ideal para una situación en la que el usuario quiera ver el contenido del archivo csv manualmente.


Agregue una referencia a Microsoft.VisualBasic (sí, dice VisualBasic pero también funciona en C #, recuerde que al final solo es IL).

Use la clase Microsoft.VisualBasic.FileIO.TextFieldParser para analizar el archivo CSV. Aquí está el código de ejemplo:

 Dim parser As TextFieldParser = New TextFieldParser("C:\mar0112.csv")
 parser.TextFieldType = FieldType.Delimited
 parser.SetDelimiters(",")      

   While Not parser.EndOfData         
      'Processing row             
      Dim fields() As String = parser.ReadFields         
      For Each field As String In fields             
         'TODO: Process field                   

      Next      
      parser.Close()
   End While 


Si tiene ganas de reinventar la rueda, lo siguiente puede funcionar para usted:

public static IEnumerable<string> SplitCSV(string line)
{
    var s = new StringBuilder();
    bool escaped = false, inQuotes = false;
    foreach (char c in line)
    {
        if (c == ',' && !inQuotes)
        {
            yield return s.ToString();
            s.Clear();
        }
        else if (c == '\\' && !escaped)
        {
            escaped = true;
        }
        else if (c == '"' && !escaped)
        {
            inQuotes = !inQuotes;
        }
        else
        {
            escaped = false;
            s.Append(c);
        }
    }
    yield return s.ToString();
}

Puedes leer el archivo csv como este.

Esto hace uso de splits y cuida espacios.

ArrayList List = new ArrayList();
static ServerSocket Server;
static Socket socket;
static ArrayList<Object> list = new ArrayList<Object>();


public static void ReadFromXcel() throws FileNotFoundException
{   
    File f = new File("Book.csv");
    Scanner in = new Scanner(f);
    int count  =0;
    String[] date;
    String[] name;
    String[] Temp = new String[10];
    String[] Temp2 = new String[10];
    String[] numbers;
    ArrayList<String[]> List = new ArrayList<String[]>();
    HashMap m = new HashMap();

         in.nextLine();
         date = in.nextLine().split(",");
         name = in.nextLine().split(",");
         numbers = in.nextLine().split(",");
         while(in.hasNext())
         {
             String[] one = in.nextLine().split(",");
             List.add(one);
         }
         int xount = 0;
         //Making sure the lines don't start with a blank
         for(int y = 0; y<= date.length-1; y++)
         {
             if(!date[y].equals(""))
             {   
                 Temp[xount] = date[y];
                 Temp2[xount] = name[y];
                 xount++;
             }
         }

         date = Temp;
         name =Temp2;
         int counter = 0;
         while(counter < List.size())
         {
             String[] list = List.get(counter);
             String sNo = list[0];
             String Surname = list[1];
             String Name = list[2];
             for(int x = 3; x < list.length; x++)
             {           
                 m.put(numbers[x], list[x]);
             }
            Object newOne = new newOne(sNo, Name, Surname, m, false);
             StudentList.add(s);
             System.out.println(s.sNo);
             counter++;
         }

Hay una biblioteca disponible a través de nuget para tratar casi cualquier CSV bien formado (.net) - CsvHelper

Ejemplo para mapear a una clase:

var csv = new CsvReader( textReader );
var records = csv.GetRecords<MyClass>();

Ejemplo para leer campos individuales:

var csv = new CsvReader( textReader );
while( csv.Read() )
{
    var intField = csv.GetField<int>( 0 );
    var stringField = csv.GetField<string>( 1 );
    var boolField = csv.GetField<bool>( "HeaderName" );
}

Dejando que el cliente maneje el formato de archivo:
, es el delimitador de campo estándar, " es el valor estándar utilizado para escapar de los campos que contienen un delimitador, una comilla o un final de línea.

Para usar (por ejemplo) # para campos y ' para escapar:

var csv = new CsvReader( textReader );
csv.Configuration.Delimiter = "#";
csv.Configuration.Quote = ''';
// read the file however meets your needs

Más documentación


En Europa tenemos este problema antes que esta pregunta. En Europa usamos toda una coma para un punto decimal. Vea estos números a continuación:

| American      | Europe        |
| ------------- | ------------- |
| 0.5           | 0,5           |
| 3.14159265359 | 3,14159265359 |
| 17.54         | 17,54         |
| 175,186.15    | 175.186,15    |

Por lo tanto, no es posible utilizar el separador de coma para archivos CSV. Por ese motivo, los archivos CSV en Europa están separados por un punto y coma ( ; ) .

Programas como Microsoft Excel pueden leer archivos con un punto y coma y es posible cambiar de separador. Incluso podrías usar una pestaña ( \t ) como separador. Vea esta respuesta de Supper User .


Usualmente hago esto en mis rutinas de análisis de archivos CSV. Suponga que la variable 'línea' es una línea dentro de un archivo CSV y que todos los valores de las columnas están entre comillas dobles. Después de que se ejecuten las dos líneas siguientes, obtendrá columnas CSV en la colección 'valores'.

// The below two lines will split the columns as well as trim the DBOULE QUOTES around values but NOT within them
    string trimmedLine = line.Trim(new char[] { '\"' });
    List<string> values = trimmedLine.Split(new string[] { "\",\"" }, StringSplitOptions.None).ToList();

    public static IEnumerable<string> LineSplitter(this string line, char 
         separator, char skip = '"')
    {
        var fieldStart = 0;
        for (var i = 0; i < line.Length; i++)
        {
            if (line[i] == separator)
            {
                yield return line.Substring(fieldStart, i - fieldStart);
                fieldStart = i + 1;
            }
            else if (i == line.Length - 1)
            {
                yield return line.Substring(fieldStart, i - fieldStart + 1);
                fieldStart = i + 1;
            }

            if (line[i] == '"')
                for (i++; i < line.Length && line[i] != skip; i++) { }
        }

        if (line[line.Length - 1] == separator)
        {
            yield return string.Empty;
        }
    }

Prueba este código:

SELECT 'Column1', 'Column2', 'Column3', 'Column4', 'Column5'
UNION ALL
SELECT column1, column2,
column3 , column4, column5 FROM demo
INTO OUTFILE '/tmp/demo.csv'
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n';

Para obtener más información: http://dev.mysql.com/doc/refman/5.1/en/select-into.html





csv