Как я могу читать и анализировать файлы CSV в C ++? - PullRequest
230 голосов
/ 13 июля 2009

Мне нужно загрузить и использовать данные файла CSV в C ++. На данный момент это может быть просто парсер, разделенный запятыми (т.е. не беспокойтесь о экранировании новых строк и запятых). Основная потребность - построчный анализатор, который будет возвращать вектор для следующей строки каждый раз, когда вызывается метод.

Я нашел эту статью, которая выглядит довольно многообещающе: http://www.boost.org/doc/libs/1_35_0/libs/spirit/example/fundamental/list_parser.cpp

Я никогда не использовал Boost's Spirit, но готов попробовать. Но только если нет более простого решения, я пропускаю.

Ответы [ 33 ]

1 голос
/ 26 июня 2013

Это старая ветка, но она все еще находится в верхней части результатов поиска, поэтому я добавляю свое решение, используя std :: stringstream и простой метод замены строк, который нашел здесь Yves Baumes.

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

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

#include <string>
#include <sstream>
#include <fstream>

void StringReplace(std::string& str, const std::string& oldStr, const std::string& newStr)
// code by  Yves Baumes
// http://stackoverflow.com/questions/1494399/how-do-i-search-find-and-replace-in-a-standard-string
{
  size_t pos = 0;
  while((pos = str.find(oldStr, pos)) != std::string::npos)
  {
     str.replace(pos, oldStr.length(), newStr);
     pos += newStr.length();
  }
}

void LoadCSV(std::string &filename) {
   std::ifstream stream(filename);
   std::string in_line;
   std::string Field;
   std::string Chan;
   int ChanType;
   double Scale;
   int Import;
   while (std::getline(stream, in_line)) {
      StringReplace(in_line, ",", " ");
      std::stringstream line(in_line);
      line >> Field >> Chan >> ChanType >> Scale >> Import;
      if (Field.substr(0,2)!="//") {
         // do your stuff 
         // this is CBuilder code for demonstration, sorry
         ShowMessage((String)Field.c_str() + "\n" + Chan.c_str() + "\n" + IntToStr(ChanType) + "\n" +FloatToStr(Scale) + "\n" +IntToStr(Import));
      }
   }
}
0 голосов
/ 20 декабря 2018

У меня есть более быстрое решение, изначально предназначенное для этого вопроса:

Как вытащить определенную часть из разных строк?

Но он был закрыт, очевидно. Я не собираюсь выбрасывать это, хотя:

#include <iostream>
#include <string>
#include <regex>

std::string text = "\"4,\"\"3\"\",\"\"Mon May 11 03:17:40 UTC 2009\"\",\"\"kindle2\"\",\"\"tpryan\"\",\"\"TEXT HERE\"\"\";;;;";

int main()
{
    std::regex r("(\".*\")(\".*\")(\".*\")(\".*\")(\".*\")(\".*\")(\".*\")(\".*\")(\".*\")(\".*\")");
    std::smatch m;
    std::regex_search(text, m, r);
    std::cout<<"FOUND: "<<m[9]<<std::endl;

    return 0;
}

Просто выберите из списка совпадений, какой вы хотите, по индексу. Регекс это блаженство.

0 голосов
/ 19 декабря 2018

Если вы используете Visual Studio / MFC, следующее решение может облегчить вашу жизнь. Он поддерживает как Unicode, так и MBCS, имеет комментарии, не имеет зависимостей, кроме CString, и работает достаточно хорошо для меня. Он не поддерживает разрывы строк, встроенные в строку в кавычках, но мне все равно, если в этом случае это не приведет к сбою, а это не так.

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

class CParseCSV {
public:
// Construction
    CParseCSV(const CString& sLine);

// Attributes
    bool    GetString(CString& sDest);

protected:
    CString m_sLine;    // line to extract tokens from
    int     m_nLen;     // line length in characters
    int     m_iPos;     // index of current position
};

CParseCSV::CParseCSV(const CString& sLine) : m_sLine(sLine)
{
    m_nLen = m_sLine.GetLength();
    m_iPos = 0;
}

bool CParseCSV::GetString(CString& sDest)
{
    if (m_iPos < 0 || m_iPos > m_nLen)  // if position out of range
        return false;
    if (m_iPos == m_nLen) { // if at end of string
        sDest.Empty();  // return empty token
        m_iPos = -1;    // really done now
        return true;
    }
    if (m_sLine[m_iPos] == '\"') {  // if current char is double quote
        m_iPos++;   // advance to next char
        int iTokenStart = m_iPos;
        bool    bHasEmbeddedQuotes = false;
        while (m_iPos < m_nLen) {   // while more chars to parse
            if (m_sLine[m_iPos] == '\"') {  // if current char is double quote
                // if next char exists and is also double quote
                if (m_iPos < m_nLen - 1 && m_sLine[m_iPos + 1] == '\"') {
                    // found pair of consecutive double quotes
                    bHasEmbeddedQuotes = true;  // request conversion
                    m_iPos++;   // skip first quote in pair
                } else  // next char doesn't exist or is normal
                    break;  // found closing quote; exit loop
            }
            m_iPos++;   // advance to next char
        }
        sDest = m_sLine.Mid(iTokenStart, m_iPos - iTokenStart);
        if (bHasEmbeddedQuotes) // if string contains embedded quote pairs
            sDest.Replace(_T("\"\""), _T("\""));    // convert pairs to singles
        m_iPos += 2;    // skip closing quote and trailing delimiter if any
    } else if (m_sLine[m_iPos] == ',') {    // else if char is comma
        sDest.Empty();  // return empty token
        m_iPos++;   // advance to next char
    } else {    // else get next comma-delimited token
        sDest = m_sLine.Tokenize(_T(","), m_iPos);
    }
    return true;
}

// calling code should look something like this:

    CStdioFile  fIn(pszPath, CFile::modeRead);
    CString sLine, sToken;
    while (fIn.ReadString(sLine)) { // for each line of input file
        if (!sLine.IsEmpty()) { // ignore blank lines
            CParseCSV   csv(sLine);
            while (csv.GetString(sToken)) {
                // do something with sToken here
            }
        }
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...