ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java] CSV 를 Excel 로 변환하는 Java 코드 예제
    Programming Language/Java 2023. 2. 8. 16:52
    반응형

    최근 java 를 이용해서 .csv 파일을 Excel 로 변환 해야하는 일이 있었다.

    그런데 단순히 csv 파일을 excel 의 하나의 sheet 로 변환하는 예제나 라이브러리는 몇 개 있었는데,
    하나의 엑셀에 특정 컬럼들을 특정 시트에 작성하는 형태의 라이브러리 는 잘 나오지 않아서 개발 한 것을 적어둔다.

    1. 라이브러리 사용법

    - 라이브러리 사용법에 대한 README.md

    1.1. settings.xml 설정

    <server>
        <id>github</id>
        <username>${github 유저명}</username>
        <password>${github 토큰}</password>
    </server>

    1.1. 디펜던시 설정

    <project>
        <dependencies>
            <dependency>
                <groupId>pe.fwani.convert</groupId>
                <artifactId>csv-to-excel</artifactId>
                <version>0.0.1-java11-SNAPSHOT</version>
            </dependency>
        </dependencies>
    
        <repositories>
            <repository>
                <id>github-fwani-releases</id>
                <url>https://github.com/fwani/fwani-maven-repo/raw/main/releases</url>
                <releases>
                    <enabled>true</enabled>
                    <updatePolicy>always</updatePolicy>
                </releases>
            </repository>
            <repository>
                <id>github-fwani-snapshots</id>
                <url>https://github.com/fwani/fwani-maven-repo/raw/main/snapshots</url>
                <snapshots>
                    <enabled>true</enabled>
                    <updatePolicy>always</updatePolicy>
                </snapshots>
            </repository>
        </repositories>
    </project>

    1.2. 코드에서 사용 예제

    import pe.fwani.convert.CsvToExcel;
    
    class Example {
        public static void main(String[] args) {
            var convertor = new CsvToExcel("origin.csv", "output.xlsx");
            convertor.setWorkbookType(WorkbookType.SXSSF);
            convertor.convert(List.of(
                    new Pair<>("시트1", List.of("col1", "col2")),
                    new Pair<>("시트2", List.of("col1", "col3", "col4"))
            ));
        }
    }


    2. 코드 설명

    2.1. 디펜던시

    먼저 csv 와 excel 을 다루기 위해 commons-csvapache-poi 라이브러리를 설정 해준다.

    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-csv</artifactId>
        <version>1.9.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>${poi.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>${poi.version}</version>
    </dependency>

    2.2. 1개의 row 를 시트에 추가하는 코드

    - csv 를 파싱한 List<String> 형태의 데이터를 sheet 에 추가하는 코드 이다.
    - sheet 에 index 를 이용해 row 를 생성하고, 각 데이터를 row 에 생성한 cell 에 넣는다.
    - 빈 데이터 ("") 인 경우 cell 을 생성하지 않도록 해서, 메모리 사용량을 줄이려고 했다. (실제로 체크는 못했다ㅜㅜ)
    - dropAllNoneRow: true 일 경우 모든 데이터가 빈값("") 이면 엑셀의 row 를 생성 하지 않는다.

    private boolean addRow(Sheet sheet, int rowIndex, List<String> data, boolean dropAllNoneRow) {
        if (dropAllNoneRow && data.stream().allMatch(x -> x.equals(""))) {
            return false;
        }
        if (data.stream().allMatch(x -> x.equals(""))) {
            return true;
        }
        var row = sheet.createRow(rowIndex);
        var idx = new AtomicInteger();
        data.forEach(x -> {
            var cellId = idx.getAndIncrement();
            if (x.equals(""))
                return;
            row.createCell(cellId).setCellValue(x);
        });
        return true;
    }

    2.3. 모든 데이터를 엑셀 시트로 변환 코드

    - commons-csv 의 CSVParser 를 이용하여 InputStream 을 분석한다.
    - Workbook 을 생성하고, sheet 하나를 생성한다.
    - Microsoft Excel 의 최대 row 개수인 1,048,576 개를 넘어가는 데이터는 버리고, Stream 으로 읽은 row 를 excel 에 넣는다.

     private void convertAll(String sheetName, boolean dropAllRow) throws IOException {
        try (var wb = getWorkbook(workbookType);
             var fileInputStream = new FileInputStream(source);
             var reader = new InputStreamReader(fileInputStream, StandardCharsets.UTF_8);
             var writer = new FileOutputStream(destination)
        ) {
            var parser = csvFormat.parse(reader);
            var sheet = wb.createSheet(sheetName);
            int nowRowIdx = 0;
            for (var record : parser) {
                if (nowRowIdx == MAX_ROWS_OF_SHEET) {
                    break;
                }
                addRow(sheet, nowRowIdx++, record.stream().toList(), dropAllRow);
            }
            wb.write(writer);
        }
    }

    2.4. 각 시트별로 선택한 컬럼만 변환 하는 코드

    - header 부분
    - csv 첫 번째 라인을 header 로 처리한다.
    - sheetNameAndColumnNamesPairs 를 순환하며, 각 sheet 를 생성하고, 시트별 선택된 컬럼의 인덱스를 찾아낸다.
    - 헤더 라인을 sheet 0번 row 로 추가한다.
    - row 부분
    - 각 시트에 해당하는 index 를 이용하여 row 를 추가한다.
    - 각 시트별로 Microsoft Excel 의 최대 row 개수인 1,048,576 개를 넘어가는 데이터는 버린다.

    private void convertEachSheetWithColumns(List<Pair<String, List<String>>> sheetNameAndColumnNamesPairs,
                                             boolean dropAllRow
    ) throws IOException {
        try (var wb = getWorkbook(workbookType);
             var fileInputStream = new FileInputStream(source);
             var reader = new InputStreamReader(fileInputStream, StandardCharsets.UTF_8);
             var writer = new FileOutputStream(destination)
        ) {
            List<Pair<Sheet, List<Integer>>> sheetAndIndexes = new ArrayList<>();
            List<Integer> rowIdxList = new ArrayList<>();
    
            var parser = csvFormat.parse(reader);
            boolean isHeader = true;
            for (var record : parser) {
                if (isHeader) {  // header
                    isHeader = false;
                    var header = record.toList().stream()
                            .map(this::getCaseSensitiveString)
                            .toList();
                    for (var sheetNameAndColumnNames : sheetNameAndColumnNamesPairs) {
                        var sheet = wb.createSheet(sheetNameAndColumnNames.getKey());
                        var selectedIndexes = sheetNameAndColumnNames.getValue()
                                .stream().map(x -> header.indexOf(getCaseSensitiveString(x)))
                                .filter(x -> x >= 0)
                                .toList();
                        addRow(sheet, 0, selectedIndexes.stream().map(header::get).toList(), dropAllRow);
                        sheetAndIndexes.add(new Pair<>(sheet, selectedIndexes));
                        rowIdxList.add(1);
                    }
                } else {  // row
                    if (rowIdxList.stream().allMatch(x -> x == MAX_ROWS_OF_SHEET || x == -1)) {
                        break;
                    }
                    for (var i = 0; i < sheetAndIndexes.size(); i++) {
                        var rowIdx = rowIdxList.get(i);
                        if (rowIdx == MAX_ROWS_OF_SHEET) {
                            continue;
                        }
                        var pair = sheetAndIndexes.get(i);
                        if (addRow(pair.getKey(), rowIdx, pair.getValue().stream().map(record::get).toList(), dropAllRow)) {
                            rowIdxList.set(i, rowIdx + 1);
                        }
                    }
                }
            }
            wb.write(writer);
        }
    }

    3. 결과 예제

    - 아래 왼쪽의 csv 파일을 오른쪽 엑셀로 변환
    - 조건 : sheet1 - 번호/이름 컬럼, sheet2 - 학년/이름 컬럼을 저장

    csv -&gt; excel 변환 결과 사진

    4. 전체 코드

    - 전체 코드는 csv-to-exce-java 에서 확인 할 수 있다.

    반응형

    댓글

Designed by Tistory.