EasyExcelによるExcelセルスタイルの高度なカスタマイズ

EasyExcelライブラリを使用すると、Excelファイルのヘッダーとコンテンツの両方に対して、きめ細やかなセルスタイル設定が可能です。ここでは、アノテーションベースのアプローチと、プログラムによる柔軟なスタイル定義戦略について解説します。

アノテーションを用いたスタイル設定

EasyExcelでは、クラスやフィールドに特定のJavアノテーションを付与することで、セルのスタイルを宣言的に設定できます。主要なアノテーションは以下の通りです。

@CellContentStyle

このアノテーションは、データセル(ヘッダー以外のセル)の書式を設定するために使用され、クラスまたはフィールドに適用できます。設定可能なプロパティは以下の通りです。

  1. dataFormat: 日付や数値の書式を指定します。
  2. hidden: セルを非表示にするかどうかを決定します。
  3. locked: セルをロックし、編集不可にするかを設定します。
  4. quotePrefix: セルの前に「'」記号を追加し、数値や数式を文字列として扱います。
  5. horizontalAlignment: テキストの水平方向の配置(左寄せ、中央揃え、右寄せなど)を設定します。
  6. wrapped: セル内のテキストを自動的に折り返すかを指定します。trueに設定すると、複数行に表示されてすべての内容が見えるようになります。
  7. verticalAlignment: テキストの垂直方向の配置(上寄せ、中央揃え、下寄せなど)を設定します。
  8. rotation: セル内のテキストの回転角度を設定します。Excel 2003では-90°〜90°、Excel 2007以降では0°〜180°の範囲です。
  9. indent: セル内のテキストのインデント(字下げ)のスペース数を設定します。
  10. borderLeft, borderRight, borderTop, borderBottom: 各辺の罫線スタイルを設定します。
  11. leftBorderColor, rightBorderColor, topBorderColor, bottomBorderColor: 各辺の罫線色を設定します。
  12. fillPatternType: セルの塗りつぶしパターンを設定します。
  13. fillBackgroundColor: セルの背景色を設定します。
  14. fillForegroundColor: セルの前景色(塗りつぶしの色)を設定します。
  15. shrinkToFit: セルの内容に合わせてテキストを自動的に縮小するかを設定します。

@CellContentFontStyle

データセル内のテキストのフォント書式を設定するアノテーションで、@CellContentStyleと同様にクラスまたはフィールドに適用できます。設定可能なプロパティは以下の通りです。

  1. fontName: フォントの名前(例: "メイリオ"、"游ゴシック")を指定します。
  2. fontHeightInPoints: フォントの高さをポイント単位で設定します。
  3. italic: テキストを斜体にするかを設定します。
  4. strikeout: テキストに取り消し線を引くかを設定します。
  5. color: フォントの色を設定します。
  6. typeOffset: 上付き文字や下付き文字などのオフセットを設定します。
  7. underline: テキストに下線を引くかを設定します。
  8. bold: テキストを太字にするかを設定します。
  9. charset: フォントの文字セットを設定します。

@HeaderStyle

表のヘッダーセル(見出し)のスタイルを設定するアノテーションで、@CellContentStyleとほぼ同様のプロパティを持ち、クラスまたはフィールドに適用可能です。

プロパティリストは@CellContentStyleと同様です。

@HeaderFontStyle

ヘッダーセル内のテキストのフォント書式をカスタマイズするアノテーションです。@CellContentFontStyleとほぼ同様のプロパティを持ち、クラスまたはフィールドに適用可能です。

プロパティリストは@CellContentFontStyleと同様です。

プログラマブルなスタイル戦略

より複雑なスタイル要件や動的なスタイル設定には、プログラムでスタイルを定義する戦略(Strategy)クラスを使用します。

AbstractCustomStyleHandler

この抽象クラスは、カスタムスタイルを適用するための基本となるハンドラを提供します。setHeadCellStyleとsetContentCellStyleメソッドをオーバーライドすることで、ヘッダーおよびデータセルのスタイルを独自に定義できます。

import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;
import com.alibaba.excel.write.metadata.head.Head;
import org.apache.poi.ss.usermodel.Cell;

public abstract class AbstractCustomStyleHandler implements CellWriteHandler {

    @Override
    public int order() {
        // スタイル処理の順序を定義
        return com.alibaba.excel.constant.OrderConstant.DEFINE_STYLE;
    }

    @Override
    public void afterCellDispose(CellWriteHandlerContext context) {
        if (context.getHead() == null) {
            return; // ヘッダー情報がない場合は処理しない
        }
        if (context.getHead()) {
            applyHeaderStyle(context); // ヘッダーセルにスタイルを適用
        } else {
            applyContentStyle(context); // データセルにスタイルを適用
        }
    }

    /**
     * ヘッダーセルにスタイルを適用します。
     */
    protected void applyHeaderStyle(CellWriteHandlerContext context) {
        // 派生クラスで具体的な実装を提供する必要があります
        applyHeaderStyle(context.getCell(), context.getHeadData(), context.getRelativeRowIndex());
    }

    /**
     * ヘッダーセルにスタイルを適用する抽象メソッド。
     * 派生クラスでオーバーライドしてカスタムスタイルを適用します。
     */
    protected void applyHeaderStyle(Cell cell, Head head, Integer relativeRowIndex) {
        throw new UnsupportedOperationException("カスタムスタイルを使用するには、applyHeaderStyle メソッドをオーバーライドする必要があります。");
    }

    /**
     * データセルにスタイルを適用します。
     */
    protected void applyContentStyle(CellWriteHandlerContext context) {
        // 派生クラスで具体的な実装を提供する必要があります
        applyContentStyle(context.getCell(), context.getHeadData(), context.getRelativeRowIndex());
    }

    /**
     * データセルにスタイルを適用する抽象メソッド。
     * 派生クラスでオーバーライドしてカスタムスタイルを適用します。
     */
    protected void applyContentStyle(Cell cell, Head head, Integer relativeRowIndex) {
        throw new UnsupportedOperationException("カスタムスタイルを使用するには、applyContentStyle メソッドをオーバーライドする必要があります。");
    }
}

ColumnStyleStrategy

AbstractCustomStyleHandlerを継承したColumnStyleStrategyは、ヘッダーとコンテンツのスタイルを柔軟に設定できる便利な実装です。この戦略では、WriteCellStyleオブジェクトを事前に設定し、エクスポート時にハンドラとして登録することで、罫線、背景色、配置、フォント、文字サイズなど、幅広いスタイル要件に対応できます。

import com.alibaba.excel.util.ListUtils;
import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;
import com.alibaba.excel.write.metadata.style.WriteCellData;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import org.springframework.util.CollectionUtils;

import java.util.List;

@Getter
@Setter
@EqualsAndHashCode(callSuper = true)
public class ColumnStyleStrategy extends AbstractCustomStyleHandler {

    private WriteCellStyle headerCellSettings;
    private List<WriteCellStyle> dataCellSettingsList;

    public ColumnStyleStrategy() {
    }

    public ColumnStyleStrategy(WriteCellStyle headerStyle, List<WriteCellStyle> contentStyles) {
        this.headerCellSettings = headerStyle;
        this.dataCellSettingsList = contentStyles;
    }

    public ColumnStyleStrategy(WriteCellStyle headerStyle, WriteCellStyle defaultContentStyle) {
        this.headerCellSettings = headerStyle;
        if (defaultContentStyle != null) {
            this.dataCellSettingsList = ListUtils.newArrayList(defaultContentStyle);
        }
    }

    /**
     * ヘッダーセルにスタイルを適用します。
     */
    @Override
    protected void applyHeaderStyle(CellWriteHandlerContext context) {
        if (shouldSkipProcessing(context) || headerCellSettings == null) {
            return;
        }
        WriteCellData<?> currentCellData = context.getFirstCellData();
        WriteCellStyle.merge(headerCellSettings, currentCellData.getOrCreateStyle());
    }

    /**
     * データセルにスタイルを適用します。
     */
    @Override
    protected void applyContentStyle(CellWriteHandlerContext context) {
        if (shouldSkipProcessing(context) || CollectionUtils.isEmpty(dataCellSettingsList)) {
            return;
        }
        WriteCellData<?> currentCellData = context.getFirstCellData();
        int styleCount = dataCellSettingsList.size();

        // 相対行インデックスが null または 0以下の場合(最初のデータ行)は最初のスタイルを適用
        // それ以外は、相対行インデックスを基にスタイルを循環適用
        if (context.getRelativeRowIndex() == null || context.getRelativeRowIndex() <= 0 || styleCount == 0) {
            WriteCellStyle.merge(dataCellSettingsList.get(0), currentCellData.getOrCreateStyle());
        } else {
            // ヘッダー行を考慮し、データ行の0インデックスに合わせて調整
            WriteCellStyle.merge(
                dataCellSettingsList.get((context.getRelativeRowIndex() - 1) % styleCount),
                currentCellData.getOrCreateStyle());
        }
    }

    /**
     * セルデータが空の場合に処理をスキップするかどうかを判断します。
     */
    private boolean shouldSkipProcessing(CellWriteHandlerContext context) {
        return context.getFirstCellData() == null;
    }
}

DefaultExportStyle

EasyExcelは、ファイルエクスポート時にデフォルトでDefaultExportStyleクラスを使用してヘッダーとデータセルのスタイルを設定します。このデフォルトスタイルは、ヘッダーにグレーの背景色を適用します。

import com.alibaba.excel.constant.OrderConstant;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.VerticalAlignment;

public class DefaultExportStyle extends ColumnStyleStrategy {

    @Override
    public int order() {
        return OrderConstant.DEFAULT_DEFINE_STYLE;
    }

    public DefaultExportStyle() {
        super();
        WriteCellStyle defaultHeaderStyle = new WriteCellStyle();
        defaultHeaderStyle.setWrapped(true);
        defaultHeaderStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        defaultHeaderStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        defaultHeaderStyle.setLocked(true);
        defaultHeaderStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
        defaultHeaderStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
        defaultHeaderStyle.setBorderTop(BorderStyle.THIN);
        defaultHeaderStyle.setBorderBottom(BorderStyle.THIN);
        defaultHeaderStyle.setBorderLeft(BorderStyle.THIN);
        defaultHeaderStyle.setBorderRight(BorderStyle.THIN);

        WriteFont headerFont = new WriteFont();
        headerFont.setFontName("メイリオ"); // 例としてフォント名を変更
        headerFont.setFontHeightInPoints((short)12); // 例としてフォントサイズを12ptに設定
        headerFont.setBold(true);
        defaultHeaderStyle.setWriteFont(headerFont);

        setHeaderCellSettings(defaultHeaderStyle);
    }
}

DefaultExportStyleによるデフォルトのヘッダーの灰色背景を無効にしたい場合、以下のメソッドを使用します。

public void exampleExportWithoutDefaultStyle() {
    // データ生成メソッドを仮定
    List<ItemRecord> data = new ArrayList<>();
    // ... サンプルデータをdataに追加 ...

    EasyExcel.write("no_default_style_export.xlsx", ItemRecord.class)
            .useDefaultStyle(false) // デフォルトスタイルを無効化
            .sheet("データシート")
            .doWrite(data);
}

注意: useDefaultStyle(false)メソッドは、必ずsheet()メソッドの前に配置する必要があります。これより後に配置すると、ExcelWriterクラスの構築時に値が取得できず、デフォルトスタイルが適用されてしまう可能性があります。

カスタムスタイルユーティリティクラス

スタイル設定を再利用可能な形で管理するために、ユーティリティクラスを作成することが推奨されます。これにより、複数のエクスポート処理で一貫したスタイルを簡単に適用できます。

import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.VerticalAlignment;

/**
 * EasyExcelのカスタムスタイルを構築するためのユーティリティクラス
 */
public class ExcelStyleBuilder {

    /**
     * ヘッダーセルのスタイルを構築します。
     */
    public static WriteCellStyle buildHeaderStyle() {
        WriteCellStyle headerStyle = new WriteCellStyle();

        // 背景色とパターンを設定
        headerStyle.setFillForegroundColor(IndexedColors.PALE_BLUE.getIndex()); // 背景色を変更
        headerStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);

        // フォント設定
        WriteFont headerFont = new WriteFont();
        headerFont.setFontName("游ゴシック"); // フォント名を変更
        headerFont.setFontHeightInPoints((short) 13); // フォントサイズを変更
        headerFont.setBold(true); // 太字
        headerStyle.setWriteFont(headerFont);

        // 罫線設定
        headerStyle.setBorderBottom(BorderStyle.MEDIUM); // 罫線スタイルをMEDIUMに変更
        headerStyle.setBottomBorderColor(IndexedColors.BLACK.getIndex());
        headerStyle.setBorderLeft(BorderStyle.MEDIUM);
        headerStyle.setLeftBorderColor(IndexedColors.BLACK.getIndex());
        headerStyle.setBorderRight(BorderStyle.MEDIUM);
        headerStyle.setRightBorderColor(IndexedColors.BLACK.getIndex());
        headerStyle.setBorderTop(BorderStyle.MEDIUM);
        headerStyle.setTopBorderColor(IndexedColors.BLACK.getIndex());

        // 配置と折り返し設定
        headerStyle.setWrapped(true); // 自動折り返し
        headerStyle.setHorizontalAlignment(HorizontalAlignment.CENTER); // 水平中央揃え
        headerStyle.setVerticalAlignment(VerticalAlignment.CENTER); // 垂直中央揃え
        headerStyle.setShrinkToFit(true); // テキストを自動的に縮小

        return headerStyle;
    }

    /**
     * データセルのスタイルを構築します。
     */
    public static WriteCellStyle buildContentStyle() {
        WriteCellStyle contentStyle = new WriteCellStyle();

        // 背景色とパターンを設定
        contentStyle.setFillForegroundColor(IndexedColors.LIGHT_YELLOW.getIndex()); // 背景色を変更
        contentStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);

        // フォント設定
        WriteFont contentFont = new WriteFont();
        contentFont.setFontHeightInPoints((short) 11); // フォントサイズを変更
        contentFont.setFontName("游ゴシック"); // フォント名を変更
        contentStyle.setWriteFont(contentFont);

        // 罫線設定
        contentStyle.setBorderBottom(BorderStyle.THIN);
        contentStyle.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); // 罫線色を変更
        contentStyle.setBorderLeft(BorderStyle.THIN);
        contentStyle.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
        contentStyle.setBorderRight(BorderStyle.THIN);
        contentStyle.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
        contentStyle.setBorderTop(BorderStyle.THIN);
        contentStyle.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());

        // 配置と折り返し設定
        contentStyle.setHorizontalAlignment(HorizontalAlignment.LEFT); // 水平左寄せに変更
        contentStyle.setVerticalAlignment(VerticalAlignment.TOP); // 垂直上寄せに変更
        contentStyle.setWrapped(false); // 自動折り返しを無効に
        contentStyle.setShrinkToFit(false); // 自動縮小を無効に

        return contentStyle;
    }
}

Excelエクスポートの例

上記のカスタムスタイルユーティリティクラスとColumnStyleStrategyを使用して、データをExcelファイルにエクスポートする例を示します。まず、エクスポートするデータのモデルクラスを定義します。

import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;

import java.util.Date;

@Data
public class ItemRecord {

    @ExcelProperty(value = "商品ID")
    private Integer productId;

    @ExcelProperty(value = "商品名")
    private String name;

    @ExcelProperty(value = "単価")
    private Double unitPrice;

    @ExcelProperty(value = "在庫数")
    private Integer stockQuantity;

    @ExcelProperty(value = "最終更新")
    private Date lastModified;
}

次に、Spring Bootコントローラでこのモデルとカスタムスタイルを適用してExcelをエクスポートするメソッドの例です。

import com.alibaba.excel.EasyExcel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import jakarta.servlet.http.HttpServletResponse; // Jakarta EE 9以降の場合
// import javax.servlet.http.HttpServletResponse; // Java EE 8以前の場合

import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

@RestController
public class ExcelExportController {

    @GetMapping("/export-items")
    public void exportItemData(HttpServletResponse response) {
        try {
            response.setContentType("application/vnd.ms-excel");
            response.setCharacterEncoding("utf-8");
            // ファイル名のエンコード
            String fileName = URLEncoder.encode("商品データ_カスタムスタイル", "UTF-8").replaceAll("\\+", "%20");
            response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx"); // .xlsx形式を推奨

            List<ItemRecord> dataToExport = new ArrayList<>();
            
            // サンプルデータ1
            ItemRecord record1 = new ItemRecord();
            record1.setProductId(101);
            record1.setName("ノートPC Z-Pro");
            record1.setUnitPrice(1200.00);
            record1.setStockQuantity(25);
            record1.setLastModified(new Date());
            dataToExport.add(record1);

            // サンプルデータ2
            ItemRecord record2 = new ItemRecord();
            record2.setProductId(102);
            record2.setName("ワイヤレスキーボード K-200");
            record2.setUnitPrice(75.50);
            record2.setStockQuantity(150);
            record2.setLastModified(new Date());
            dataToExport.add(record2);

            EasyExcel.write(response.getOutputStream(), ItemRecord.class)
                    .sheet("製品リスト")
                    .registerWriteHandler(new ColumnStyleStrategy(
                            ExcelStyleBuilder.buildHeaderStyle(),
                            ExcelStyleBuilder.buildContentStyle())) // カスタムスタイルを登録
                    .doWrite(dataToExport);

        } catch (Exception e) {
            e.printStackTrace();
            // エラー処理を適切に追加
        }
    }
}

タグ: EasyExcel Java Excel Spreadsheet Apache POI

9月30日 08:48 投稿