사용자 도구

사이트 도구


kb:cppcsvparser

C++ CSV Parser

  • std::string 및 std::vector를 이용해 구현한 소스다.
  • Visual C++ 8.0 기준으로 작성했다.
    • 벡터 쪽의 at() 함수만 수정하면 다른 플랫폼에서도 별 문제없이 컴파일되리라 생각하는데, 어떨지는 모르겠다.
  • 여러 라인에 걸친 컬럼 값도 제대로 파싱한다.

소스

CsvFile.h
#ifndef __CSVFILE_H__
#define __CSVFILE_H__
 
#include <string>
#include <vector>
 
#if _MSC_VER
    #include <hash_map>
#else
    #include <map>
#endif
 
////////////////////////////////////////////////////////////////////////////////
/// \class cCsvAlias
/// \brief CSV 파일을 수정했을 때 발생하는 인덱스 문제를 줄이기 위한 
/// 별명 객체.
///
/// 예를 들어 0번 컬럼이 A에 관한 내용을 포함하고, 1번 컬럼이 B에 관한 내용을 
/// 포함하고 있었는데...
///
/// <pre>
/// int a = row.AsInt(0);
/// int b = row.AsInt(1);
/// </pre>
///
/// 그 사이에 C에 관한 내용을 포함하는 컬럼이 끼어든 경우, 하드코딩되어 있는 
/// 1번을 찾아서 고쳐야 하는데, 상당히 에러가 발생하기 쉬운 작업이다. 
///
/// <pre>
/// int a = row.AsInt(0);
/// int c = row.AsInt(1);
/// int b = row.AsInt(2); <-- 이 부분을 일일이 신경써야 한다.
/// </pre>
/// 
/// 이 부분을 문자열로 처리하면 유지보수에 들어가는 수고를 약간이나마 줄일 수 
/// 있다.
////////////////////////////////////////////////////////////////////////////////
 
class cCsvAlias
{
private:
#if _MSC_VER
    typedef stdext::hash_map<std::string, size_t> NAME2INDEX_MAP;
    typedef stdext::hash_map<size_t, std::string> INDEX2NAME_MAP;
#else
    typedef std::map<std::string, size_t> NAME2INDEX_MAP;
    typedef std::map<size_t, std::string> INDEX2NAME_MAP;
#endif
 
    NAME2INDEX_MAP m_Name2Index;  ///< 셀 인덱스 대신으로 사용하기 위한 이름들
    INDEX2NAME_MAP m_Index2Name;  ///< 잘못된 alias를 검사하기 위한 추가적인 맵
 
 
public:
    /// \brief 생성자
    cCsvAlias() {} 
 
    /// \brief 소멸자
    virtual ~cCsvAlias() {}
 
 
public:
    /// \brief 셀을 액세스할 때, 숫자 대신 사용할 이름을 등록한다.
    void AddAlias(const char* name, size_t index);
 
    /// \brief 모든 데이터를 삭제한다.
    void Destroy();
 
    /// \brief 숫자 인덱스를 이름으로 변환한다.
    const char* operator [] (size_t index) const;
 
    /// \brief 이름을 숫자 인덱스로 변환한다.
    size_t operator [] (const char* name) const;
 
 
private:
    /// \brief 복사 생성자 금지
    cCsvAlias(const cCsvAlias&) {}
 
    /// \brief 대입 연산자 금지
    const cCsvAlias& operator = (const cCsvAlias&) { return *this; }
};
 
 
////////////////////////////////////////////////////////////////////////////////
/// \class cCsvRow 
/// \brief CSV 파일의 한 행을 캡슐화한 클래스
///
/// CSV의 기본 포맷은 엑셀에서 보이는 하나의 셀을 ',' 문자로 구분한 것이다.
/// 하지만, 셀 안에 특수 문자로 쓰이는 ',' 문자나 '"' 문자가 들어갈 경우, 
/// 모양이 약간 이상하게 변한다. 다음은 그 변화의 예이다.
/// 
/// <pre>
/// 엑셀에서 보이는 모양 | 실제 CSV 파일에 들어가있는 모양
/// ---------------------+----------------------------------------------------
/// ItemPrice            | ItemPrice
/// Item,Price           | "Item,Price"
/// Item"Price           | "Item""Price"
/// "ItemPrice"          | """ItemPrice"""
/// "Item,Price"         | """Item,Price"""
/// Item",Price          | "Item"",Price"
/// </pre>
/// 
/// 이 예로서 다음과 같은 사항을 알 수 있다.
/// - 셀 내부에 ',' 또는 '"' 문자가 들어갈 경우, 셀 좌우에 '"' 문자가 생긴다.
/// - 셀 내부의 '"' 문자는 2개로 치환된다.
///
/// \sa cCsvFile
////////////////////////////////////////////////////////////////////////////////
 
class cCsvRow : public std::vector<std::string>
{
public:
    /// \brief 기본 생성자
    cCsvRow() {}
 
    /// \brief 소멸자
    ~cCsvRow() {}
 
 
public:
    /// \brief 해당 셀의 데이터를 int 형으로 반환한다.
    int AsInt(size_t index) const { return atoi(at(index).c_str()); }
 
    /// \brief 해당 셀의 데이터를 double 형으로 반환한다.
    double AsDouble(size_t index) const { return atof(at(index).c_str()); }
 
    /// \brief 해당 셀의 데이터를 문자열로 반환한다.
    const char* AsString(size_t index) const { return at(index).c_str(); }
 
    /// \brief 해당하는 이름의 셀 데이터를 int 형으로 반환한다.
    int AsInt(const char* name, const cCsvAlias& alias) const {
        return atoi( at(alias[name]).c_str() ); 
    }
 
    /// \brief 해당하는 이름의 셀 데이터를 int 형으로 반환한다.
    double AsDouble(const char* name, const cCsvAlias& alias) const {
        return atof( at(alias[name]).c_str() ); 
    }
 
    /// \brief 해당하는 이름의 셀 데이터를 문자열로 반환한다.
    const char* AsString(const char* name, const cCsvAlias& alias) const { 
        return at(alias[name]).c_str(); 
    }
 
 
private:
    /// \brief 복사 생성자 금지
    cCsvRow(const cCsvRow&) {}
 
    /// \brief 대입 연산자 금지
    const cCsvRow& operator = (const cCsvRow&) { return *this; }
};
 
 
////////////////////////////////////////////////////////////////////////////////
/// \class cCsvFile
/// \brief CSV(Comma Seperated Values) 파일을 read/write하기 위한 클래스
///
/// <b>sample</b>
/// <pre>
/// cCsvFile file;
///
/// cCsvRow row1, row2, row3;
/// row1.push_back("ItemPrice");
/// row1.push_back("Item,Price");
/// row1.push_back("Item\"Price");
///
/// row2.reserve(3);
/// row2[0] = "\"ItemPrice\"";
/// row2[1] = "\"Item,Price\"";
/// row2[2] = "Item\",Price\"";
///
/// row3 = "\"ItemPrice\"\"Item,Price\"Item\",Price\"";
///
/// file.add(row1);
/// file.add(row2);
/// file.add(row3);
/// file.save("test.csv", false);
/// </pre>
///
/// \todo 파일에서만 읽어들일 것이 아니라, 메모리 소스로부터 읽는 함수도 
/// 있어야 할 듯 하다.
////////////////////////////////////////////////////////////////////////////////
 
class cCsvFile
{
private:
    typedef std::vector<cCsvRow*> ROWS;
 
    ROWS m_Rows; ///< 행 컬렉션
 
 
public:
    /// \brief 생성자
    cCsvFile() {}
 
    /// \brief 소멸자
    virtual ~cCsvFile() { Destroy(); }
 
 
public:
    /// \brief 지정된 이름의 CSV 파일을 로드한다.
    bool Load(const char* fileName, const char seperator=',', const char quote='"');
 
    /// \brief 가지고 있는 내용을 CSV 파일에다 저장한다.
    bool Save(const char* fileName, bool append=false, char seperator=',', char quote='"') const;
 
    /// \brief 모든 데이터를 메모리에서 삭제한다.
    void Destroy();
 
    /// \brief 해당하는 인덱스의 행을 반환한다.
    cCsvRow* operator [] (size_t index);
 
    /// \brief 해당하는 인덱스의 행을 반환한다.
    const cCsvRow* operator [] (size_t index) const;
 
    /// \brief 행의 갯수를 반환한다.
    size_t GetRowCount() const { return m_Rows.size(); }
 
 
private:
    /// \brief 복사 생성자 금지
    cCsvFile(const cCsvFile&) {}
 
    /// \brief 대입 연산자 금지
    const cCsvFile& operator = (const cCsvFile&) { return *this; }
};
 
 
////////////////////////////////////////////////////////////////////////////////
/// \class cCsvTable
/// \brief CSV 파일을 이용해 테이블 데이터를 로드하는 경우가 많은데, 이 클래스는 
/// 그 작업을 좀 더 쉽게 하기 위해 만든 유틸리티 클래스다.
///
/// CSV 파일을 로드하는 경우, 숫자를 이용해 셀을 액세스해야 하는데, CSV 
/// 파일의 포맷이 바뀌는 경우, 이 숫자들을 변경해줘야한다. 이 작업이 꽤 
/// 신경 집중을 요구하는 데다가, 에러가 발생하기 쉽다. 그러므로 숫자로 
/// 액세스하기보다는 문자열로 액세스하는 것이 약간 느리지만 낫다고 할 수 있다.
///
/// <b>sample</b>
/// <pre>
/// cCsvTable table;
///
/// table.alias(0, "ItemClass");
/// table.alias(1, "ItemType");
///
/// if (table.load("test.csv"))
/// {
///     while (table.next())
///     {
///         std::string item_class = table.AsString("ItemClass");
///         int         item_type  = table.AsInt("ItemType"); 
///     }
/// }
/// </pre>
////////////////////////////////////////////////////////////////////////////////
 
class cCsvTable
{
private:
    cCsvFile  m_File;   ///< CSV 파일 객체
    cCsvAlias m_Alias;  ///< 문자열을 셀 인덱스로 변환하기 위한 객체
    int       m_CurRow; ///< 현재 횡단 중인 행 번호
 
 
public:
    /// \brief 생성자
    cCsvTable();
 
    /// \brief 소멸자
    virtual ~cCsvTable();
 
 
public:
    /// \brief 지정된 이름의 CSV 파일을 로드한다.
    bool Load(const char* fileName, const char seperator=',', const char quote='"');
 
    /// \brief 셀을 액세스할 때, 숫자 대신 사용할 이름을 등록한다.
    void AddAlias(const char* name, size_t index) { m_Alias.AddAlias(name, index); }
 
    /// \brief 다음 행으로 넘어간다.
    bool Next();
 
    /// \brief 현재 행의 셀 숫자를 반환한다.
    size_t ColCount() const;
 
    /// \brief 인덱스를 이용해 int 형으로 셀값을 반환한다.
    int AsInt(size_t index) const;
 
    /// \brief 인덱스를 이용해 double 형으로 셀값을 반환한다.
    double AsDouble(size_t index) const;
 
    /// \brief 인덱스를 이용해 std::string 형으로 셀값을 반환한다.
    const char* AsString(size_t index) const;
 
    /// \brief 셀 이름을 이용해 int 형으로 셀값을 반환한다.
    int AsInt(const char* name) const { return AsInt(m_Alias[name]); }
 
    /// \brief 셀 이름을 이용해 double 형으로 셀값을 반환한다.
    double AsDouble(const char* name) const { return AsDouble(m_Alias[name]); }
 
    /// \brief 셀 이름을 이용해 std::string 형으로 셀값을 반환한다.
    const char* AsString(const char* name) const { return AsString(m_Alias[name]); }
 
    /// \brief alias를 포함해 모든 데이터를 삭제한다.
    void Destroy();
 
 
private:
    /// \brief 현재 행을 반환한다.
    const cCsvRow* const CurRow() const;
 
    /// \brief 복사 생성자 금지
    cCsvTable(const cCsvTable&) {}
 
    /// \brief 대입 연산자 금지
    const cCsvTable& operator = (const cCsvTable&) { return *this; }
};
 
#endif //__CSVFILE_H__
CsvFile.cpp
#include "CsvFile.h"
#include <fstream>
 
#ifndef Assert
    #include <assert.h>
    #define Assert assert
    #define LogToFile (void)(0);
#endif
 
namespace
{
    /// 파싱용 state 열거값
    enum ParseState
    {
        STATE_NORMAL = 0, ///< 일반 상태
        STATE_QUOTE       ///< 따옴표 뒤의 상태
    };
 
    /// 문자열 좌우의 공백을 제거해서 반환한다.
    std::string Trim(std::string str)
    {
        str = str.erase(str.find_last_not_of(" \t\r\n") + 1);
        str = str.erase(0, str.find_first_not_of(" \t\r\n"));
        return str;
    }
 
    /// \brief 주어진 문장에 있는 알파벳을 모두 소문자로 바꾼다.
    std::string Lower(std::string original)
    {
        std::transform(original.begin(), original.end(), original.begin(), tolower);
        return original;
    }
}
 
////////////////////////////////////////////////////////////////////////////////
/// \brief 셀을 액세스할 때, 숫자 대신 사용할 이름을 등록한다.
/// \param name 셀 이름
/// \param index 셀 인덱스
////////////////////////////////////////////////////////////////////////////////
void cCsvAlias::AddAlias(const char* name, size_t index)
{
    std::string converted(Lower(name));
 
    Assert(m_Name2Index.find(converted) == m_Name2Index.end());
    Assert(m_Index2Name.find(index) == m_Index2Name.end());
 
    m_Name2Index.insert(NAME2INDEX_MAP::value_type(converted, index));
    m_Index2Name.insert(INDEX2NAME_MAP::value_type(index, name));
}
 
////////////////////////////////////////////////////////////////////////////////
/// \brief 모든 데이터를 삭제한다.
////////////////////////////////////////////////////////////////////////////////
void cCsvAlias::Destroy()
{
    m_Name2Index.clear();
    m_Index2Name.clear();
}
 
////////////////////////////////////////////////////////////////////////////////
/// \brief 숫자 인덱스를 이름으로 변환한다.
/// \param index 숫자 인덱스
/// \return const char* 이름
////////////////////////////////////////////////////////////////////////////////
const char* cCsvAlias::operator [] (size_t index) const
{
    INDEX2NAME_MAP::const_iterator itr(m_Index2Name.find(index));
    if (itr == m_Index2Name.end())
    {
        LogToFile(NULL, "cannot find suitable conversion for %d", index);
        Assert(false && "cannot find suitable conversion");
        return NULL;
    }
 
    return itr->second.c_str();
}
 
////////////////////////////////////////////////////////////////////////////////
/// \brief 이름을 숫자 인덱스로 변환한다.
/// \param name 이름
/// \return size_t 숫자 인덱스
////////////////////////////////////////////////////////////////////////////////
size_t cCsvAlias::operator [] (const char* name) const
{
    NAME2INDEX_MAP::const_iterator itr(m_Name2Index.find(Lower(name)));
    if (itr == m_Name2Index.end())
    {
        LogToFile(NULL, "cannot find suitable conversion for %s", name);
        Assert(false && "cannot find suitable conversion");
        return 0;
    }
 
    return itr->second;
}
 
////////////////////////////////////////////////////////////////////////////////
/// \brief 지정된 이름의 CSV 파일을 로드한다.
/// \param fileName CSV 파일 이름
/// \param seperator 필드 분리자로 사용할 글자. 기본값은 ','이다.
/// \param quote 따옴표로 사용할 글자. 기본값은 '"'이다.
/// \return bool 무사히 로드했다면 true, 아니라면 false
////////////////////////////////////////////////////////////////////////////////
bool cCsvFile::Load(const char* fileName, const char seperator, const char quote)
{
    Assert(seperator != quote);
 
    std::ifstream file(fileName, std::ios::in);
    if (!file) return false;
 
    Destroy(); // 기존의 데이터를 삭제
 
    cCsvRow* row = NULL;
    ParseState state = STATE_NORMAL;
    std::string token = "";
    char buf[2048+1] = {0,};
 
    while (file.good())
    {
        file.getline(buf, 2048);
        buf[sizeof(buf)-1] = 0;
 
        std::string line(Trim(buf));
        if (line.empty() || (state == STATE_NORMAL && line[0] == '#')) continue;
 
        std::string text  = std::string(line) + "  "; // 파싱 lookahead 때문에 붙여준다.
        size_t cur = 0;
 
        while (cur < text.size())
        {
            // 현재 모드가 QUOTE 모드일 때,
            if (state == STATE_QUOTE)
            {
                // '"' 문자의 종류는 두 가지이다.
                // 1. 셀 내부에 특수 문자가 있을 경우 이를 알리는 셀 좌우의 것
                // 2. 셀 내부의 '"' 문자가 '"' 2개로 치환된 것
                // 이 중 첫번째 경우의 좌측에 있는 것은 CSV 파일이 정상적이라면, 
                // 무조건 STATE_NORMAL에 걸리게 되어있다.
                // 그러므로 여기서 걸리는 것은 1번의 우측 경우나, 2번 경우 뿐이다.
                // 2번의 경우에는 무조건 '"' 문자가 2개씩 나타난다. 하지만 1번의
                // 우측 경우에는 아니다. 이를 바탕으로 해서 코드를 짜면...
                if (text[cur] == quote)
                {
                    // 다음 문자가 '"' 문자라면, 즉 연속된 '"' 문자라면 
                    // 이는 셀 내부의 '"' 문자가 치환된 것이다.
                    if (text[cur+1] == quote)
                    {
                        token += quote;
                        ++cur;
                    }
                    // 다음 문자가 '"' 문자가 아니라면 
                    // 현재의 '"'문자는 셀의 끝을 알리는 문자라고 할 수 있다.
                    else
                    {
                        state = STATE_NORMAL;
                    }
                }
                else
                {
                    token += text[cur];
                }
            }
            // 현재 모드가 NORMAL 모드일 때,
            else if (state == STATE_NORMAL)
            {
                if (row == NULL)
                    row = new cCsvRow();
 
                // ',' 문자를 만났다면 셀의 끝의 의미한다.
                // 토큰으로서 셀 리스트에다가 집어넣고, 토큰을 초기화한다.
                if (text[cur] == seperator)
                {
                    row->push_back(token);
                    token.clear();
                }
                // '"' 문자를 만났다면, QUOTE 모드로 전환한다.
                else if (text[cur] == quote)
                {
                    state = STATE_QUOTE;
                }
                // 다른 일반 문자라면 현재 토큰에다가 덧붙인다.
                else
                {
                    token += text[cur];
                }
            }
 
            ++cur;
        }
 
        // 마지막 셀은 끝에 ',' 문자가 없기 때문에 여기서 추가해줘야한다.
        // 단, 처음에 파싱 lookahead 때문에 붙인 스페이스 문자 두 개를 뗀다.
        if (state == STATE_NORMAL)
        {
            Assert(row != NULL);
            row->push_back(token.substr(0, token.size()-2));
            m_Rows.push_back(row);
            token.clear();
            row = NULL;
        }
        else
        {
            token = token.substr(0, token.size()-2) + "\r\n";
        }
    }
 
    return true;
}
 
////////////////////////////////////////////////////////////////////////////////
/// \brief 가지고 있는 내용을 CSV 파일에다 저장한다.
/// \param fileName CSV 파일 이름
/// \param append true일 경우, 기존의 파일에다 덧붙인다. false인 경우에는 
/// 기존의 파일 내용을 삭제하고, 새로 쓴다.
/// \param seperator 필드 분리자로 사용할 글자. 기본값은 ','이다.
/// \param quote 따옴표로 사용할 글자. 기본값은 '"'이다.
/// \return bool 무사히 저장했다면 true, 에러가 생긴 경우에는 false
////////////////////////////////////////////////////////////////////////////////
bool cCsvFile::Save(const char* fileName, bool append, char seperator, char quote) const
{
    Assert(seperator != quote);
 
    // 출력 모드에 따라 파일을 적당한 플래그로 생성한다.
    std::ofstream file;
    if (append) { file.open(fileName, std::ios::out | std::ios::app); }
    else { file.open(fileName, std::ios::out | std::ios::trunc); }
 
    // 파일을 열지 못했다면, false를 리턴한다.
    if (!file) return false;
 
    char special_chars[5] = { seperator, quote, '\r', '\n', 0 };
    char quote_escape_string[3] = { quote, quote, 0 };
 
    // 모든 행을 횡단하면서...
    for (size_t i=0; i<m_Rows.size(); i++)
    {
        const cCsvRow& row = *((*this)[i]);
 
        std::string line;
 
        // 행 안의 모든 토큰을 횡단하면서...
        for (size_t j=0; j<row.size(); j++)
        {
            const std::string& token = row[j];
 
            // 일반적인('"' 또는 ','를 포함하지 않은) 
            // 토큰이라면 그냥 저장하면 된다.
            if (token.find_first_of(special_chars) == std::string::npos)
            {
                line += token;
            }
            // 특수문자를 포함한 토큰이라면 문자열 좌우에 '"'를 붙여주고,
            // 문자열 내부의 '"'를 두 개로 만들어줘야한다.
            else
            {
                line += quote;
 
                for (size_t k=0; k<token.size(); k++)
                {
                    if (token[k] == quote) line += quote_escape_string;
                    else line += token[k];
                }
 
                line += quote;
            }
 
            // 마지막 셀이 아니라면 ','를 토큰의 뒤에다 붙여줘야한다.
            if (j != row.size() - 1) { line += seperator; }
        }
 
        // 라인을 출력한다.
        file << line << std::endl;
    }
 
    return true;
}
 
////////////////////////////////////////////////////////////////////////////////
/// \brief 모든 데이터를 메모리에서 삭제한다.
////////////////////////////////////////////////////////////////////////////////
void cCsvFile::Destroy()
{
    for (ROWS::iterator itr(m_Rows.begin()); itr != m_Rows.end(); ++itr)
        delete *itr;
 
    m_Rows.clear();
}
 
////////////////////////////////////////////////////////////////////////////////
/// \brief 해당하는 인덱스의 행을 반환한다.
/// \param index 인덱스
/// \return cCsvRow* 해당 행
////////////////////////////////////////////////////////////////////////////////
cCsvRow* cCsvFile::operator [] (size_t index)
{
    Assert(index < m_Rows.size());
    return m_Rows[index];
}
 
////////////////////////////////////////////////////////////////////////////////
/// \brief 해당하는 인덱스의 행을 반환한다.
/// \param index 인덱스
/// \return const cCsvRow* 해당 행
////////////////////////////////////////////////////////////////////////////////
const cCsvRow* cCsvFile::operator [] (size_t index) const
{
    Assert(index < m_Rows.size());
    return m_Rows[index];
}
 
////////////////////////////////////////////////////////////////////////////////
/// \brief 생성자
////////////////////////////////////////////////////////////////////////////////
cCsvTable::cCsvTable()
: m_CurRow(-1)
{
}
 
////////////////////////////////////////////////////////////////////////////////
/// \brief 소멸자
////////////////////////////////////////////////////////////////////////////////
cCsvTable::~cCsvTable()
{
}
 
////////////////////////////////////////////////////////////////////////////////
/// \brief 지정된 이름의 CSV 파일을 로드한다.
/// \param fileName CSV 파일 이름
/// \param seperator 필드 분리자로 사용할 글자. 기본값은 ','이다.
/// \param quote 따옴표로 사용할 글자. 기본값은 '"'이다.
/// \return bool 무사히 로드했다면 true, 아니라면 false
////////////////////////////////////////////////////////////////////////////////
bool cCsvTable::Load(const char* fileName, const char seperator, const char quote)
{
    Destroy();
    return m_File.Load(fileName, seperator, quote);
}
 
////////////////////////////////////////////////////////////////////////////////
/// \brief 다음 행으로 넘어간다.
/// \return bool 다음 행으로 무사히 넘어간 경우 true를 반환하고, 더 이상
/// 넘어갈 행이 존재하지 않는 경우에는 false를 반환한다.
////////////////////////////////////////////////////////////////////////////////
bool cCsvTable::Next()
{
    // 20억번 정도 호출하면 오버플로가 일어날텐데...괜찮겠지?
    return ++m_CurRow < (int)m_File.GetRowCount() ? true : false;
}
 
////////////////////////////////////////////////////////////////////////////////
/// \brief 현재 행의 셀 숫자를 반환한다.
/// \return size_t 현재 행의 셀 숫자
////////////////////////////////////////////////////////////////////////////////
size_t cCsvTable::ColCount() const
{
    return CurRow()->size();
}
 
////////////////////////////////////////////////////////////////////////////////
/// \brief 인덱스를 이용해 int 형으로 셀 값을 반환한다.
/// \param index 셀 인덱스
/// \return int 셀 값
////////////////////////////////////////////////////////////////////////////////
int cCsvTable::AsInt(size_t index) const
{
    const cCsvRow* const row = CurRow();
    Assert(row);
    Assert(index < row->size());
    return row->AsInt(index);
}
 
////////////////////////////////////////////////////////////////////////////////
/// \brief 인덱스를 이용해 double 형으로 셀 값을 반환한다.
/// \param index 셀 인덱스
/// \return double 셀 값
////////////////////////////////////////////////////////////////////////////////
double cCsvTable::AsDouble(size_t index) const
{
    const cCsvRow* const row = CurRow();
    Assert(row);
    Assert(index < row->size());
    return row->AsDouble(index);
}
 
////////////////////////////////////////////////////////////////////////////////
/// \brief 인덱스를 이용해 std::string 형으로 셀 값을 반환한다.
/// \param index 셀 인덱스
/// \return const char* 셀 값
////////////////////////////////////////////////////////////////////////////////
const char* cCsvTable::AsString(size_t index) const
{
    const cCsvRow* const row = CurRow();
    Assert(row);
    Assert(index < row->size());
    return row->AsString(index);
}
 
////////////////////////////////////////////////////////////////////////////////
/// \brief alias를 포함해 모든 데이터를 삭제한다.
////////////////////////////////////////////////////////////////////////////////
void cCsvTable::Destroy()
{
    m_File.Destroy();
    m_Alias.Destroy();
    m_CurRow = -1;
}
 
////////////////////////////////////////////////////////////////////////////////
/// \brief 현재 행을 반환한다.
/// \return const cCsvRow* 액세스가 가능한 현재 행이 존재하는 경우에는 그 행의
/// 포인터를 반환하고, 더 이상 액세스 가능한 행이 없는 경우에는 NULL을 
/// 반환한다.
////////////////////////////////////////////////////////////////////////////////
const cCsvRow* const cCsvTable::CurRow() const
{
    if (m_CurRow < 0)
    {
        Assert(false && "call Next() first!");
        return NULL;
    }
    else if (m_CurRow >= (int)m_File.GetRowCount())
    {
        Assert(false && "no more rows!");
        return NULL;
    }
 
    return m_File[m_CurRow];
}

샘플

int main()
{
    cCsvTable table;
 
    if (!table.Load("file.csv"))
        return -1;
 
    table.AddAlias("First",     0);
    table.AddAlias("Second",    1);
    table.AddAlias("Third",     2);
 
    while (table.Next())
    {
        int first = table.AsInt("First");
        int second = table.AsInt("Second");
        std::string third = table.AsString("Third"));
    }
 
    return 0;
}

kb/cppcsvparser.txt · 마지막으로 수정됨: 2014/11/07 17:37 (바깥 편집)