Работа с запятыми в файле CSV

Я ищу предложения о том, как обрабатывать созданный csv файл, затем загружать наши клиенты, и может иметь запятую в значении, например название компании.

Некоторые из идей, которые мы рассматриваем, это: Идентификаторы (значения "," значения "и т.д.) или использование | вместо запятой. Самая большая проблема заключается в том, что мы должны сделать это легко, или клиент не сделает этого.

Ответ 1

Как говорили другие, вам нужно избежать значений, которые включают в себя кавычки. Здесь хранится небольшой CSV-ридер в C♯, который поддерживает цитируемые значения, включая встроенные кавычки и возврат каретки.

Кстати, это проверенный модулем код. Я отправляю его сейчас, потому что этот вопрос, похоже, очень много, и другие могут не захотеть целой библиотеки, когда будет работать простая поддержка CSV.

Вы можете использовать его следующим образом:

using System;
public class test
{
    public static void Main()
    {
        using ( CsvReader reader = new CsvReader( "data.csv" ) )
        {
            foreach( string[] values in reader.RowEnumerator )
            {
                Console.WriteLine( "Row {0} has {1} values.", reader.RowIndex, values.Length );
            }
        }
        Console.ReadLine();
    }
}

Вот классы. Обратите внимание, что вы можете использовать функцию Csv.Escape для записи правильного CSV.

using System.IO;
using System.Text.RegularExpressions;

public sealed class CsvReader : System.IDisposable
{
    public CsvReader( string fileName ) : this( new FileStream( fileName, FileMode.Open, FileAccess.Read ) )
    {
    }

    public CsvReader( Stream stream )
    {
        __reader = new StreamReader( stream );
    }

    public System.Collections.IEnumerable RowEnumerator
    {
        get {
            if ( null == __reader )
                throw new System.ApplicationException( "I can't start reading without CSV input." );

            __rowno = 0;
            string sLine;
            string sNextLine;

            while ( null != ( sLine = __reader.ReadLine() ) )
            {
                while ( rexRunOnLine.IsMatch( sLine ) && null != ( sNextLine = __reader.ReadLine() ) )
                    sLine += "\n" + sNextLine;

                __rowno++;
                string[] values = rexCsvSplitter.Split( sLine );

                for ( int i = 0; i < values.Length; i++ )
                    values[i] = Csv.Unescape( values[i] );

                yield return values;
            }

            __reader.Close();
        }
    }

    public long RowIndex { get { return __rowno; } }

    public void Dispose()
    {
        if ( null != __reader ) __reader.Dispose();
    }

    //============================================


    private long __rowno = 0;
    private TextReader __reader;
    private static Regex rexCsvSplitter = new Regex( @",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))" );
    private static Regex rexRunOnLine = new Regex( @"^[^""]*(?:""[^""]*""[^""]*)*""[^""]*$" );
}

public static class Csv
{
    public static string Escape( string s )
    {
        if ( s.Contains( QUOTE ) )
            s = s.Replace( QUOTE, ESCAPED_QUOTE );

        if ( s.IndexOfAny( CHARACTERS_THAT_MUST_BE_QUOTED ) > -1 )
            s = QUOTE + s + QUOTE;

        return s;
    }

    public static string Unescape( string s )
    {
        if ( s.StartsWith( QUOTE ) && s.EndsWith( QUOTE ) )
        {
            s = s.Substring( 1, s.Length - 2 );

            if ( s.Contains( ESCAPED_QUOTE ) )
                s = s.Replace( ESCAPED_QUOTE, QUOTE );
        }

        return s;
    }


    private const string QUOTE = "\"";
    private const string ESCAPED_QUOTE = "\"\"";
    private static char[] CHARACTERS_THAT_MUST_BE_QUOTED = { ',', '"', '\n' };
}

Ответ 2

В течение 2017 года csv полностью определен - RFC 4180.

Это очень распространенная спецификация и полностью покрывается многими библиотеками (пример).

Просто используйте любую легкодоступную библиотеку csv, то есть RFC 4180.


На самом деле есть спецификация для CSV-формата и как обрабатывать запятые:

Поля, содержащие разрывы строк (CRLF), двойные кавычки и запятые, должны быть заключены в двойные кавычки.

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

Итак, чтобы иметь значения foo и bar,baz, вы делаете это:

foo,"bar,baz"

Еще одно важное требование для рассмотрения (также из спецификации):

Если для приложения полей используются двойные кавычки, то двойная кавычка возникающие внутри поля, должны быть экранированы, другая двойная цитата. Например:

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

Ответ 3

Формат CSV использует запятые для разделения значений, значения, которые содержат возврат каретки, переводы строк, запятые или двойные кавычки, окружены двойными кавычками. Значения, содержащие двойные кавычки, цитируются, и каждая буквальная цитата экранируется непосредственно предшествующей цитатой: например, 3 значения:

test
list, of, items
"go" he said

будет кодироваться как:

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

Любое поле может быть процитировано, но должны быть указаны только поля, содержащие запятые, CR/NL или кавычки.

Нет никакого реального стандарта для формата CSV, но почти все приложения следуют за документами здесь. RFC, который упоминался в другом месте, не является стандартом для CSV, это RFC для использования CSV в MIME и содержит некоторые нетрадиционные и ненужные ограничения, которые делают его бесполезным вне MIME.

Полученная информация о том, что многие модули CSV, которые я видел, не учитываются, заключается в том, что несколько строк могут быть закодированы в одном поле, что означает, что вы не можете предположить, что каждая строка является отдельной записью, вам либо не нужно разрешать новые строки в ваших данных или быть готовыми к этому.

Ответ 4

Поместите двойные кавычки вокруг строк. Обычно что делает Excel.

Ала Эли,

вы избегаете двойной кавычки как две двойные кавычки. Например. "Test1", "Foo" "бар", "test2"

Ответ 5

Вы можете поместить двойные кавычки вокруг полей. Мне не нравится этот подход, поскольку он добавляет еще один специальный символ (двойная кавычка). Просто определите escape-символ (как правило, обратную косую черту) и используйте его везде, где вам нужно что-то избежать:

data,more data,more data\, even,yet more

Вам не нужно пытаться сопоставить кавычки, и у вас меньше исключений для синтаксического анализа. Это также упрощает ваш код.

Ответ 6

Существует библиотека, доступная через nuget для работы с почти любым хорошо сформированным CSV (.net) - CsvHelper

Пример для сопоставления с классом:

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

Пример для чтения отдельных полей:

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

Предоставление клиенту диска формата файла:
, - стандартный разделитель полей, " - стандартное значение, используемое для удаления полей, содержащих разделитель, цитату или завершение строки.

Чтобы использовать (например) # для полей и ' для экранирования:

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

Дополнительная документация

Ответ 7

Добавьте ссылку на Microsoft.VisualBasic(да, он говорит VisualBasic, но он также работает на С#), помните, что в конце это всего лишь IL).

Используйте класс Microsoft.VisualBasic.FileIO.TextFieldParser для разбора CSV файла. Вот пример кода:

 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 

Ответ 8

Вы можете использовать альтернативные "разделители", такие как ";" или "|" но простейшим может быть просто цитирование, которое поддерживается большинством (порядочными) CSV-библиотеками и наиболее приличными электронными таблицами.

Для больше о разделителях CSV и спецификации стандартного формата для описания разделителей и цитирования см. эту веб-страницу

Ответ 9

Если вы находитесь в * nix-системе, получите доступ к sed и может быть одна или несколько нежелательных запятых только в определенном поле вашего CSV, вы можете использовать следующий однострочный слой, чтобы заключить их в " как RFC4180 Раздел 2 предлагает:

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

В зависимости от того, в каком поле могут находиться нежелательные запятые (ы), вам нужно изменить/расширить группы захвата регулярного выражения (и подстановки).
В приведенном выше примере будет заключено четвертое поле (из шести) в кавычки.

enter image description here

В сочетании с --in-place -option вы можете применить эти изменения непосредственно к файлу.

Чтобы "построить" правильное регулярное выражение, существует простой принцип:

  • Для каждого поля в CSV, которое появляется перед полем с помощью нежелательных запятых, вы пишете один [^,]*, и объединяете их в группу захвата.
  • Для поля, содержащего нежелательные запятые, вы пишете (.*).
  • Для каждого поля после поля с помощью нежелательной запятой вы пишете один ,.* и соедините их все вместе в группе захвата.

Ниже приведен краткий обзор различных возможных регулярных выражений/замещений в зависимости от конкретного поля. Если не задано, подстановка \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)

Если вы хотите удалить ненужные запятые с помощью sed вместо того, чтобы заключать их в кавычки, обратитесь к этому ответу.

Ответ 10

Как упоминалось в моем комментарии к ответу на harpo, его решение хорошо и работает в большинстве случаев, однако в некоторых сценариях, когда запятые, находящиеся непосредственно рядом друг с другом, не разделяются запятыми.

Это из-за того, что строка Regex неожиданно ведет себя как вертикальная строка. Чтобы заставить это поведение корректно, все символы в строке регулярного выражения должны быть экранированы вручную без использования escape-перехода.

Т.е. Регулярное выражение должно выполняться с помощью ручных экранов:

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

который переводится в ",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))"

При использовании вербабитной строки @",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))" она ведет себя следующим образом: вы можете отлаживать регулярное выражение:

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

Итак, я рекомендую решение harpo, но следите за этой маленькой ошибкой!

Я включил в CsvReader несколько необязательных отказоустойчивых уведомлений, если эта ошибка возникает (если у вас есть предварительно известное количество столбцов):

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

Это может быть введено через конструктор:

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

Ответ 11

Если вы заинтересованы в более учебном упражнении по анализу файлов вообще (используя CSV в качестве примера), вы можете проверить эту статью Джулиан Бакналл. Мне нравится статья, потому что она ломает дело в гораздо меньшие проблемы, которые намного менее непреодолимы. Сначала вы создаете грамматику, и как только у вас будет хорошая грамматика, это относительно простой и методичный процесс преобразования грамматики в код.

В статье используется С#, а внизу находится ссылка внизу.

Ответ 12

Если вам кажется, что вы изобретаете колесо, для вас может работать следующее:

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

Ответ 13

В Европе эта проблема должна быть поставлена ​​раньше, чем этот вопрос. В Европе мы используем запятую для десятичной точки. См. Следующие цифры:

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

Таким образом, невозможно использовать разделитель запятой для файлов CSV. По этой причине CSV файлы в Европе разделяются точкой с запятой (;).

Такие программы, как Microsoft Excel, могут считывать файлы с точкой с запятой и переключаться с разделителя. Вы могли бы даже использовать вкладку (\t) в качестве разделителя. См. этот ответ от пользователя "Ужин" .

Ответ 14

Существует популярная библиотека для обработки данных этого типа, FileHelpers

Ответ 15

Как это касается общих практик, давайте начнем с правил большого пальца:

  • Не используйте CSV, используйте XML с библиотекой для чтения и записи XML файла.

  • Если вы должны использовать CSV. Сделайте это правильно и используйте бесплатную библиотеку для анализа и хранения файлов CSV.

Чтобы оправдать 1), большинство парсеров CSV не кодируются, поэтому, если вы не имеете дело с US-ASCII, вы просите о проблемах. Например, Excel 2002 хранит CSV в локальной кодировке без каких-либо примечаний о кодировании. Стандарт CSV широко не принят:(. С другой стороны, стандарт xml хорошо принят, и он отлично справляется с кодировками.

Чтобы оправдать 2), существует множество парсеров csv для почти всех языков, поэтому нет необходимости изобретать колесо, даже если решения выглядят довольно просто.

Чтобы назвать несколько:

Действительно, нет необходимости реализовывать это вручную, если вы не собираетесь разбирать его на встроенном устройстве.

Ответ 16

Вы можете прочитать файл csv следующим образом.

это использует расщепления и заботится о пространствах.

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

Ответ 17

Я думаю, что самым простым решением этой проблемы является заставить клиента открыть csv в excel, а затем ctrl + r, чтобы заменить все запятые на любой идентификатор, который вы хотите. Это очень просто для клиента и требует только одного изменения в вашем коде, чтобы прочитать разделитель по вашему выбору.

Ответ 18

Во-первых, позвольте спросить себя: "Почему мы чувствуем необходимость обрабатывать запятые по-разному для файлов CSV?"

Для меня ответ: "Потому что, когда я экспортирую данные в CSV файл, запятые в поле исчезают, и мое поле разделяется на несколько полей, где запятые появляются в исходных данных". (Это потому, что запятая является разделителем полей CSV.)

В зависимости от вашей ситуации полуколоны могут также использоваться в качестве разделителей полей CSV.

Учитывая мои требования, я могу использовать символ, например одиночный кавычек с низким значением 9, который выглядит как запятая.

Итак, вот как вы можете это сделать в 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
}

Второй символ запятой в функции Replace равен десятичному значению 8218.

Помните, что если у вас есть клиенты, у которых могут быть только текстовые считыватели ascii, этот символ decima 8218 не будет выглядеть как запятая. Если это ваш случай, я бы рекомендовал окружить поле запятой (или точкой с запятой) с двойными кавычками в RFC 4128: https://tools.ietf.org/html/rfc4180

Ответ 19

Я обычно URL-кодирую поля, которые могут содержать любые запятые или любые специальные символы. И затем декодируйте его, когда он используется/отображается на любом визуальном носителе.

(запятые становятся% 2C)

Каждый язык должен иметь методы для URL-кодирования и декодирования строк.

например, в java

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

Я знаю, что это очень общее решение, и это может быть не идеально для ситуации, когда пользователь хочет просматривать содержимое файла csv вручную.

Ответ 20

Я обычно делаю это в своих подпрограммах обработки файлов CSV. Предположим, что переменная 'line' - это одна строка в CSV файле, и все значения столбцов заключены в двойные кавычки. После выполнения следующих двух строк вы получите столбцы CSV в коллекции "values".

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

Ответ 21

Самое простое решение, которое я нашел, это тот, который использует LibreOffice:

  • Заменить все литералы " на "
  • Поместите двойные кавычки вокруг строки

Вы также можете использовать тот, который использует Excel:

  • Заменить все литералы " на ""
  • Поместите двойные кавычки вокруг строки

Обратите внимание, что другим людям рекомендуется сделать только шаг 2 выше, но это не работает с строками, в которых за " следует ,, например, в CSV, где вы хотите иметь один столбец со строкой hello",world, как CSV читал бы:

"hello",world"

Что интерпретируется как строка с двумя столбцами: hello и world"

Ответ 22

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

Ответ 23

Для разделения полей используйте символ табуляции (\ t).