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 데이터를 엑셀 파일로 관리하는 것에 대해 위험함이 제기 되어 이 프로젝트는 무산되었다. ㅎㅎㅎ 나중에 언젠가는 쓸 일이 있겠지.

C#에서 엑셀 문서 저장하기

ClosedXML_small2C#으로 사내용으로 쓸 프로그램을 만들다가 엑셀 파일로 저장해야할 일이 생겼다.

인터넷을 찾아헤메다가 결국은 내가 사용하게 된 방법에 대해 정리한다.

찾아본 끝에 내린 결로은 OpenXML 기술을 이용하는 방식으로 택했다. .xlsx라는 가장 최신의 엑셀 포맷이고 MS오피스 뿐만 아니라 오픈오피스에서도 잘 지원한다. OpenXML이 무엇인가에 대해서는 구체적으로 나도 잘 모르겠다. 혹시 관심 있는 사람들은 위키 같은 곳을 찾아봄이 좋을듯하다. 난 엑셀 파일 읽고 쓰기가 필요한 것이지 엑셀 포맷 그 자체에는 별로 관심도 없거니와 알 필요성도 적어서(물론 알면 좋겠지만) 일단은 OpenXML에 대해 잠깐만 웹서핑을 해본 후 사용하기로 결정했다. OpenXML에 대해 더 알고 싶다면 다음의 페이지를 방문해보자.

OpenXML을 사용하기 위해서 ClosedXML 이라는 라이브러리를 이용할 것이고 http://closedxml.codeplex.com 를 참조하면 된다.

내 환경은 Visual Studio 2012 이며 C# Winform .NET 3.5 였는데 이 기능을 이용하기 위해서는 .NET 4.0 으로 프로젝트 설정을 변경해야했다. 4.0 아래 버전에서는 호환이 안되기 때문에 오류가 난다.

다운로드 받은 후 압축을 풀어보면 몇개의 DLL파일과 XML 파일 등으로 이루어져 있으며 C#을 사용했던 사람이라면 별로 어려움 없이 사용할 수 있으리라 본다.

예제코드는 http://closedxml.codeplex.com/wikipage?title=Showcase&referringTitle=Home 에 설명되어 있고 이것을 참조하면서 만들면 별 무리가 없다.

엑셀 파일을 작성하는데 필요한 기초적인 기능은 충분히 제공된다. 결과적으로는 제대로 선택한 것 같고 별 어려움 없이 엑셀 문서를 작성할 수 있었다.

하나 주의할 사항이라면,

엑셀 파일로 작성할 내용이 많아지면 속도가 꽤나 느려질 수 있다. 특히 다수의 셀에 색상을 입힌다거나 Style 속성을 바꾸는 작업 등은 속도를 굉장히 느려지게 할 수 있으니 주의해야한다. 다수의 셀에 특정 Style을 적용하고 싶다면 for 루프를 돌면서 각 셀마다 Style을 지정하지 말고

ws.Range("B" + index.ToString() + ":D" + index.ToString()).Style.Font.Bold = true;
ws.Range("B" + index.ToString() + ":D" + index.ToString()).Style.Font.FontColor = XLColor.White;
ws.Range("B" + index.ToString() + ":D" + index.ToString()).Style.Fill.BackgroundColor = XLColor.Black;

와 같은 코드를 사용해서 한번에 잡아주는 것이 속도 향상에 도움이 된다.

예제페이지처럼 RangeTable을 잡고 한번에 적용해주면 더 속도향상이 되리라 생각하지만 이렇게 하기에는 너무 귀찮아져서 일단은 난 행단위로 처리했다.

(이 ClosedXML 라이브러리를 이용했다면 프로그램 배포시 ClosedXML.DLL 파일도 같이 배포해야하니 이것 역시 주의하자.))

개발이 모두 끝나고서 알게된 것이지만 ClosedXML은 .NET 4.0용과 .NET 3.5용이 있었다. 이런 젠쟝… ( http://closedxml.codeplex.com/releases/view/96561 )

MFC에서 데이터를 엑셀 파일로 저장하는 방법

2018년 11월 30일 추가.

아래의 내용보다 더 좋은 엑셀파일 처리 라이브러리를 찾았다.

https://blog.dongbumkim.com/archives/5258

 

MFC에서 자료를 엑셀파일로 저장하는 방법을 찾아보았다.

여러가지 방법들이 많이 있었는데 그중에 가장 괜찮은 방법이어서 나중에 잊어버리지 않도록 여기에 써둔다.

아래 내용을 이용하기 위한 파일은 XLAutomation 이다.

혹시나 이 글을 볼 사람들이 있을까봐 아래의 코드는 짬식(wingyui)님 블로그의 내용(http://blog.naver.com/wingyui?Redirect=Log&logNo=30070208599)이며 이 코드 및 내용에 대한 모든 권리는 짬식(wingyui)님에게 있다는 것을 밝혀둔다.

int i=1,j=1,k=1;
 int columnNum=0;
 char temp[10];
 CString m_strFileName;

 CXLEzAutomation XL(FALSE); // FALSE: 처리 과정을 화면에 보이지 않는다

 m_strFileName = "Student Quiz Results";   // 저장할 파일 Name

 // column 데이터
 XL.SetCellValue(++columnNum, 1, "Login ID");
 XL.SetCellValue(++columnNum, 1, "Student ID");
 XL.SetCellValue(++columnNum, 1, "Name");

 //column 에 Quiz 번호 만큼 추가
 for(i=1;i<=m_iQuizNum;i++){
  _snprintf(temp, 10, "Quiz %d", i);
  XL.SetCellValue(++columnNum, 1 ,temp);
 }

 //학생수와 Quiz 수만큼 리스트 컨트롤에서 엑셀로 가져옴.
 for(k=1; k<=m_iStudentNum; k++)
  for(j=1; j<=columnNum; j++)
   XL.SetCellValue(j, k+1, m_ctrlScroeList.GetItemText(k-1, j));

 CFileDialog cFDlg(false,"xlsx",m_strFileName+".xlsx", OFN_HIDEREADONLY |
  OFN_OVERWRITEPROMPT | OFN_NOCHANGEDIR, "xlsx 파일 (*.xlsx)|*.xlsx|", NULL );

 if(cFDlg.DoModal() == IDOK)
  XL.SaveFileAs(cFDlg.GetPathName());

 XL.ReleaseExcel();

코드는 그다지 어렵지 않다. 하나 중요한 점은 Column과 Row 번호를 지정할 때는 0부터 시작하는 것이 아니라 1부터 시작한다는 것. 프로그래밍 할때 모든 루프는 다 0부터 시작하다보니 어색하게 느껴진다. 반드시 1부터 넣을 것.

또 하나 중요한 점은 파일을 저장할 때 확장명을 xlsx로 해야 잘 열린다. xls로 해도 저장이 되긴 하는데 MS Excel 2010으로 열려고 하니 형식이 이상하다는 경고가 한번씩 떴다. xlsx로 저장하니 오류 없이 잘 열린다.