사용자 도구

사이트 도구


kb:cppcsvparser

차이

문서의 선택한 두 판 사이의 차이를 보여줍니다.

차이 보기로 링크

kb:cppcsvparser [2014/11/07 17:37] (현재)
줄 1: 줄 1:
 +====== C++ CSV Parser ======
 +  * std::string 및 std::​vector를 이용해 구현한 소스다.
 +  * Visual C++ 8.0 기준으로 작성했다.
 +    * 벡터 쪽의 at() 함수만 수정하면 다른 플랫폼에서도 별 문제없이 컴파일되리라 생각하는데,​ 어떨지는 모르겠다.
 +  * 여러 라인에 걸친 컬럼 값도 제대로 파싱한다.
 +
 +
 +====== 소스 ======
 +<file cpp 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__
 +</​file>​
 +
 +<file cpp 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];​
 +}
 +</​file>​
 +
 +====== 샘플 ======
 +<code cpp>
 +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;
 +}
 +</​code>​
 +
 +----
 +  * see also [[CsvFileProcessing]]
  
kb/cppcsvparser.txt · 마지막으로 수정됨: 2014/11/07 17:37 (바깥 편집)