C++에서 엑셀파일 처리하기

C++에서 엑셀파일을 다루는 법을 찾아보았다.

2012년도에 엑셀 파일을 처리할 방법을 찾다가 https://blog.dongbumkim.com/archives/754 이런 글을 썼었는데 이제는 그보다 더 나은 방법을 찾을 수 있었다.

예전에 작업하던 소스에서는 ODBC를 이용하여 연결했었는데 왠일인지 이 방법으로는 제대로 연결되지 않았다. 당시에는 잘 작동하던 소스였는데…

찾아보니 윈도우 버전이 올라가며 제대로 작동하지 않는듯하다.(확실히 끝까지 찾아보지는 못했다.) ADO 등의 다른 방법으로 연결하는 것을 안내하고 있었다.

웹서핑을 하며 엑셀 파일을 다룰 수 있는 라이브러리를 찾아보다가 스택오버플로우에서 xlnt 라는 라이브러리를 추천해줘서 한번 테스트해보았다.

https://github.com/tfussell/xlnt

무려 크로스플랫폼의 C++14에 맞춘 xlsx 파일 처리 가능한 라이브러리라고 설명이 되어있다.

처음에는 잘 작동하는듯했으나… 오 잘되네 하고 본격적인 테스트코드를 만들다보니 버그가 있었다. 시트를 하나 더 추가해서 2개의 시트를 만들고 컬럼을 추가하면 에러가 떴다. 엑셀 파일을 아무리 봐도 문제가 없어 검색해봤더니 이미 버그리포팅이 되어있던 이슈. 나도 가서 한마디 거들었다.

https://github.com/tfussell/xlnt/issues/330#issuecomment-440589168

xlnt로 하루 넘는 시간을 날리고 다시 검색해보니 ExcelFormat 라이브러리를 추천했다. 이것으로 작업을 성공적으로 했다는 댓글도 있어서 이걸로 결정.

https://www.codeproject.com/Articles/42504/ExcelFormat-Library

사용해보니 다른 외부 컴포넌트 없이 바로 사용 가능했다.

단점은,

  • xlsx는 지원하지 않고 xls 파일만 지원한다.
  • 데이터를 가져올 각 셀마다 데이터타입을 정확하게 지정해줘야한다.
  • 그런데 숫자만 들어있는 셀은 전부다 double 형으로 데이터를 가져온다. int로 캐스팅해야함.
    for (int k = 0; k < sheet->GetTotalRows(); ++k)
    {
        for (int j = 0; j < sheet->GetTotalCols(); ++j)
        {
            ExcelFormat::BasicExcelCell* cell = sheet->Cell(k, j);
            switch (cell->Type())
            {
            case ExcelFormat::BasicExcelCell::INT:
                std::cout << "INT:" << cell->GetInteger() << std::endl;
                break;
            case ExcelFormat::BasicExcelCell::DOUBLE:
                std::cout << "DOUBLE:" << static_cast<int>(cell->GetDouble()) << std::endl;
                break;
            case ExcelFormat::BasicExcelCell::STRING:
                std::cout << "STRING:" << cell->GetString() << std::endl;
                break;
            case ExcelFormat::BasicExcelCell::WSTRING:
                std::cout << "WSTRING:" << cell->GetWString() << std::endl;
                break;
            case ExcelFormat::BasicExcelCell::UNDEFINED:
            case ExcelFormat::BasicExcelCell::FORMULA:
            default:
                break;
            }
        }
    }
  • 암호 걸린 엑셀파일은 처리 불가능하다.

테스트 코드를 열심히 작성해봤더니 시트가 여러개일때도, 데이터가 꽤 많을 때에도 데이터 처리가 무난했다.

사용했던 테스트 코드의 일부.

class DataTableMgr
{
private:
	ExcelFormat::BasicExcel xls;
};

bool DataTableMgr::LoadWeaponBombTable(void)
{
    ExcelFormat::BasicExcelWorksheet* sheet = nullptr;
    
    for (int i = 0; i < xls.GetTotalWorkSheets(); ++i)
    {
        sheet = xls.GetWorksheet(i);
        if (nullptr == sheet)
            continue;

        if (!strcmp(WEAPONBOM_SHEETNAME, sheet->GetAnsiSheetName()))
        {
            sheet = xls.GetWorksheet(i);
            break;
        }
    }

    if (nullptr == sheet)
        return false;

    // 첫행은 컬럼 인덱스이므로 항상 패쓰
    for (int i = 1; i < sheet->GetTotalRows(); ++i)
    {
        DATATABLE_WEAPONBOMB weapon_bomb;
        weapon_bomb.index = static_cast<int>(sheet->Cell(i, 0)->GetDouble());
        weapon_bomb.id = static_cast<int>(sheet->Cell(i, 1)->GetDouble());
        weapon_bomb.id_5pack = static_cast<int>(sheet->Cell(i, 2)->GetDouble());
        weapon_bomb.id_11pack = static_cast<int>(sheet->Cell(i, 3)->GetDouble());
        weapon_bomb.itemid = static_cast<int>(sheet->Cell(i, 4)->GetDouble());

        weapon_bomb_map.insert(DATATABLE_WEAPONBOMB_MAP::value_type(weapon_bomb.index, weapon_bomb));
    }

    std::cout << "WeaponBomb count : " << weapon_bomb_map.size() << std::endl;

    return true;
}

코드 자체는 잘 작동했지만, static 데이터를 엑셀 파일로 관리하는 것에 대해 위험함이 제기 되어 이 프로젝트는 무산되었다. ㅎㅎㅎ 나중에 언젠가는 쓸 일이 있겠지.