CSVファイルの解析環境構築（その４：条件付き書式）

## はじめに
前回までの記事でCSVファイルの並び替えやウィンドウ枠の固定など、データ解析をするときに良く設定する手順を自動化できた。
実際にデータ解析をしていると、値がある条件の時にセルや文字の装飾などして解析しやすくすることが多く、ある程度は条件付き書式でいくつかルールを設けて解析しやすくしている。
前回までに作成したコードを拡張して、解析するデータに自動で条件付き書式を設定できるようにした。

## 今回やってみたこと
* 条件付き書式を自動設定できるようにした

## 今後のレベルアップ項目
* いまのところ思い浮かばない...

## 追加した機能
* 解析データに条件付き書式を設定して、セルの背景を変更できるようにした

## 実装
### ファイル構成
```dir:フォルダ構成
+---csv2xlsx
    result_code.py　　　 # 共通の戻り値定義
    command_line.py　　　# コマンドライン解析用
    const_excel.py　　　 # Excel VBAの定数定義
    simple_excel.py　　　# 簡易Excel制御クラス
    format_condition.py　# 条件付き書式の設定クラス
    main.py　　　        # メイン処理
```

### サンプルコード
<details><summary>全コード</summary>

#### result_code.py : 共通の戻り値定義
ソースコード間で戻り値を共通化するために作成。

```python:result_code.py
from enum import IntEnum

class ResultCode(IntEnum):
    """ 戻り値 """
    SUCCESS = 0
    ERROR = -1
```

#### command_line.py : コマンドライン解析用
コマンドラインの解析処理をメイン処理と分離するため別ファイルで処理した。
いつも似たようなコード書いているので、作成するアプリごとに編集して使いまわせればと思っています。

```python:command_line.py
import os
import argparse
from result_code import ResultCode

# カレントディレクトリ・ファイルパス
APP_PATH = os.getcwd()
FILE_PATH = os.path.dirname(__file__)


class CommandParam():
    """ コマンドパラメータクラス """
    path_csvfile: str
    path_output: str


def check_commandline(param: CommandParam) -> ResultCode:
    """
    コマンドラインチェック

    Args:
        param (class)       : (out)コマンドパラメータ

    returns:
        ResultCode.SUCCESS  : 正常
        ResultCode.ERROR    : 引数エラー
    """
    result = ResultCode.SUCCESS

    # コマンドライン パラメータ
    parser = argparse.ArgumentParser(
        description='Aout WinMerge Tool', add_help=True)
    parser.add_argument('arg1', help='CSVファイルのパス')
    parser.add_argument('arg2', help='出力先ファイル名＆パス')
    args = parser.parse_args()

    # パスチェック
    if not os.path.isfile(args.arg1):
        print(f'{args.arg1} が見つかりません')
        result = ResultCode.ERROR

    # コマンドラインパラメータの保存
    if result == ResultCode.SUCCESS:
        param.path_csvfile = args.arg1
        param.path_output = args.arg2

    return result
```

#### const_excel.py：Excel VBAの定数定義
Excel制御で使用する定数を定義する。下記のサイトを参考に必要な定義のみ抜き出しました。（地味に面倒な作業なので、とても助かりました）
・[Python pywin32(win32com) Excel 操作備忘録](https://qiita.com/feo52/items/150745ae0cc17cb5c866)

```python:const_excel.py
# region XlFileFormat 定義：ファイル保存フォーマット
xlCSV = 6
xlHtml = 44
xlWorkbookDefault = 51
xlOpenXMLWorkbook = 51
xlOpenXMLWorkbookMacroEnabled = 52
xlWorkbookNormal = -4143
xlCurrentPlatformText = -4158
# endregion

# region XlYesNoGuess 定義：Yes/No
xlGuess = 0
xlYes = 1
xlNo = 2
# endregion

# region XlSortOrder 定義：ソート順
xlAscending = 1
xlDescending = 2
xlManual = -4135
# endregion

# region XlSortOrientation 定義：ソート方向（列・行）
xlSortColumns = 1
xlSortRows = 2
# endregion

# region XlSortMethod 定義：ソート条件：ふりがなの使用・不使用
xlPinYin = 1
xlStroke = 2
# endregion

# region XlDeleteShiftDirection 列挙型：削除後にセルをシフトするかを指定
#        XlInsertShiftDirection 列挙型：挿入時にセルをシフトする方向を指
xlShiftUp = -4162                       # セルは上にシフトします
xlShiftToLeft = -4159                   # セルは左にシフトします
xlShiftDown = -4121                     # セルを挿入後、下に伸ばす
xlShiftToRight = -4161                  # セルを挿入後、右に伸ばす
# endregion

# region XlFormatConditionType 列挙型：条件付き書式を設定
xlAboveAverageCondition = 12            # 平均以上の条件
xlBlanksCondition = 10	                # 空白の条件
xlCellValue = 1                         # セルの値
xlColorScale = 3                        # カラー スケール
xlDataBar = 4                           # DataBar
xlErrorsCondition = 16                  # エラー条件
xlExpression = 2                        # Expression
xlIconSet = 6                           # アイコン セット
xlNoBlanksCondition = 13                # 空白の条件なし
xlNoErrorsCondition = 17                # エラー条件なし
xlTextString = 9                        # テキスト文字列
xlTimePeriod = 11                       # 期間
xlTop10 = 5                             # 上から 10 個の値
xlUniqueValues = 8                      # 一意の値
# endregion

# region Constants 列挙型：Microsoft Excel で使用されるグローバル定数
xlAbove = 0
xlBelow = 1
xlSolid = 1
xlFirst = 0
xlLast = 1
xlLastCell = 11
xlTopToBottom = 1
xlLeftToRight = 2
xlGeneral = 1
xlAutomatic = -4105
xlFormats = -4122
xlNone = -4142
xlCenter = -4108
xlDistributed = -4117
xlJustify = -4130
xlBottom = -4107
xlLeft = -4131
xlRight = -4152
xlTop = -4160
xlRTL = -5004
xlLTR = -5003
xlContext = -5002
# endregion

# region XlThemeColor
xlThemeColorAccent1 = 5                 # Accent1
xlThemeColorAccent2 = 6                 # Accent2
xlThemeColorAccent3 = 7                 # Accent3
xlThemeColorAccent4 = 8                 # Accent4
xlThemeColorAccent5 = 9                 # Accent5
xlThemeColorAccent6 = 10                # Accent6
xlThemeColorDark1 = 1                   # Dark1
xlThemeColorDark2 = 3                   # Dark2
xlThemeColorFollowedHyperlink = 12      # 表示済みのハイパーリンク
xlThemeColorHyperlink = 11              # ハイパーリンク
xlThemeColorLight1 = 2                  # Light1
xlThemeColorLight2 = 4                  # Light2
# endregion


def rgb(col_r: int,
        col_g: int,
        col_b: int) -> int:
    return (col_b * 256 * 256 + col_g * 256 + col_r)


xlColBlack = rgb(0, 0, 0)
xlColWhite = rgb(255, 255, 255)
xlColRed = rgb(255, 0, 0)
xlColGreen = rgb(0, 255, 0)
xlColBlue = rgb(0, 0, 255)

xlColGainsboro = rgb(220, 220, 220)
xlColLightGray = rgb(211, 211, 211)
xlColSilver = rgb(192, 192, 192)
xlColDarkGray = rgb(169, 169, 169)
xlColGray = rgb(128, 128, 128)
```

色の定義がちょっと少ない。
下記のページを参考によく使う色を増やしていければよいと思います。
・[Webで使えるカラーネームと、そのカラーコード・RGB値一覧](https://www.webcreatorbox.com/blog/color-name)

#### simple_excel.py：簡易Excel制御クラス
Excel制御する処理をクラス化。
今回は、条件付き書式を設定するメソッドを追加した。

```python:simple_excel.py
import os
import win32com.client
import const_excel as xl
from result_code import ResultCode
from format_condition import Font, Interior

# カレントディレクトリ・ファイルパス
APP_PATH = os.getcwd()
FILE_PATH = os.path.dirname(__file__)

# 定数定義
# region デフォルト定義
DEFAULT_VISIBLE = True                      # Excel表示
DEFAULT_FORCED_WRITE = False                # 上書き保存の強制有無
# endregion


class SimpleExcel:
    """
    Excel制御クラス
    """

    # 定数定義
    # region パラメータ定数
    AUTO_FIT = -1                           # 行・高さの自動調整
    MAX_USED_COLUMNS = -1                   # 使用している列の最大値
    MAX_USED_ROWS = -1                      # 使用している行の最大値
    NOTHING = 0                             # 指定しない
    # endregion

    # region コンストラクタ
    def __init__(self,
                 visibled: bool = DEFAULT_VISIBLE):
        """
        コンストラクタ
        """
        self.__xlapp = None
        self.__visibled = visibled
    # endregion

    # region デストラクタ
    def __del__(self):
        """
        デストラクタ
        """
        try:
            # ワークブックを閉じる
            if self.__wbook is not None:
                self.__wbook.Close(SaveChanges=False)

            # Excelを閉じる
            if self.__xlapp is not None:
                self.__xlapp.Quit()
        except Exception as e:
            # すでにExcel終了済みならエラー無視する
            if type(e).__name__ != 'com_error':
                print(f"終了処理に失敗しました\n{e}")
    # endregion

    # region プロパティ
    # region Excel表示On/Off
    @property
    def visibled(self) -> bool:
        """
        Excel表示On/Off
        """
        return self.__visibled

    @visibled.setter
    def visibled(self,
                 value: bool):
        """
        Excel表示On/Off

        Args:
            value               : (in) True = 表示On
        """
        self.__visibled = value
        if self.__xlapp is not None:
            self.__xlapp.Visible = value
    # endregion

    # region アクティブシートで使用されている列数
    @property
    def get_count_columns(self) -> int:
        """
        アクティブシートで使用されている列数
        """
        result = self.__wsheet.UsedRange.Columns(
            self.__wsheet.UsedRange.Columns.Count).Column
        return result
    # endregion

    # region アクティブシートで使用されている行数
    @property
    def get_count_rows(self) -> int:
        """
        アクティブシートで使用されている行数
        """
        result = self.__wsheet.UsedRange.Rows(
            self.__wsheet.UsedRange.Rows.Count).Row
        return result
    # endregion
    # endregion

    # region 公開メソッド
    # region Excelでファイルを開く
    def open(self,
             file_path: str) -> ResultCode:
        """
        Excelでファイルを開く

        Args:
            file_path           : (in) 読込むファイル名&パス

        returns:
            ResultCode.SUCCESS  : 正常
            ResultCode.ERROR    : オープンエラー
        """
        result = ResultCode.SUCCESS

        if not os.path.isfile(file_path):
            print(f"{file_path} が見つかりません")
            result = ResultCode.ERROR
        else:
            try:
                # Excelを起動してファイルを開く
                if self.__xlapp is None:
                    self.__xlapp = win32com.client.Dispatch(
                        'Excel.Application')
                    self.__xlapp.Visible = self.__visibled

                self.__wbook = self.__xlapp.Workbooks.Open(file_path)
                self.select_sheet(1)

            except Exception as e:
                print(f"ファイルオープンに失敗しました\n{e}")
                result = ResultCode.ERROR

        return result
    # endregion

    # region ワークブックを閉じる
    def close(self):
        """
        ワークブックを閉じる

        Args:
            なし

        returns:
            ResultCode.SUCCESS  : 正常
            ResultCode.ERROR    : クローズエラー（未定義）
        """
        result = ResultCode.SUCCESS

        if self.__wbook is not None:
            self.__wbook.Close(SaveChanges=False)

        return result
    # endregion

    # region 名前を付けて保存
    def save_as(self,
                file_path: str,
                format: xl = xl.xlOpenXMLWorkbook,
                forced_write: bool = DEFAULT_FORCED_WRITE) -> ResultCode:
        """
        名前を付けて保存

        Args:
            file_path           : (in) 保存するファイル名&パス
            format              : (in) 保存形式
            forced_write        : (in) True=強制的に上書きする

        returns:
            ResultCode.SUCCESS  : 正常
            ResultCode.ERROR    : 保存エラー
        """
        result = ResultCode.SUCCESS

        # Excelファイル(xlsx)形式で保存する
        if self.__wsheet is not None:
            try:
                if forced_write:
                    # 警告メッセージを非表示にする（強制的に上書きする）
                    self.__xlapp.DisplayAlerts = False

                # 名前を付けて保存する
                self.__wsheet.SaveAs(file_path, FileFormat=format)

                if forced_write:
                    # 警告メッセージ表示を元に戻す
                    self.__xlapp.DisplayAlerts = True

            except Exception as e:
                print(f"ファイルの書き込みに失敗しました\n{e}")
                result = ResultCode.ERROR

        return result
    # endregion

    # region セル選択（移動）
    def select_cell(self,
                    row: int,
                    column: int) -> ResultCode:
        """
        セル選択（移動）

        Args:
            row                 : (in) 行番号
            column              : (in) 列番号

        returns:
            ResultCode.SUCCESS  : 正常
            ResultCode.ERROR    : エラー（未定義）
        """
        result = ResultCode.SUCCESS

        range_cell = self.conv_r1c1_to_a1(row, column)
        self.__wsheet.Range(range_cell).Select()

        return result
    # endregion

    # region シートを選択する
    def select_sheet(self,
                     name: any) -> any:
        """
        シートを選択する

        Args:
            name                : (in) シート名 or シート番号

        returns:
            シートオブジェクト
        """
        # シート名またはシート番号での指定
        self.__wsheet = self.__wbook.Worksheets(name)
        return self.__wsheet
    # endregion

    # region 列の並び替えをする
    def sort_column(self,
                    key1_row: int,
                    order1: int = xl.xlAscending) -> ResultCode:
        """
        列の並び替えをする

        Args:
            key1_row            : (in) 並べ替えのキーになる行番号（１～）
            order1              : (in) 並べ替え順序

        returns:
            ResultCode.SUCCESS  : 正常
            ResultCode.ERROR    : 並べ替えエラー（未定義）
        """
        result = ResultCode.SUCCESS

        # 並び替えキーのカラム文字作成(行指定）
        col_str = self.conv_r1c1_to_a1(key1_row, 1)
        range_key = self.__wsheet.Range(col_str)

        # 並び替え範囲のカラム文字作成(シートの使用領域全体）
        range_sort = self.covn_range_r1c1_to_a1(
            1, 1, self.get_count_rows, self.get_count_columns)

        # シートの並び替え
        self.__wsheet.Sort.SortFields.Clear
        self.__wsheet.Sort.SortFields.Add(Key=range_key, Order=order1)
        self.__wsheet.Sort.SetRange(self.__wsheet.Range(range_sort))
        self.__wsheet.Sort.Header = xl.xlYes
        self.__wsheet.Sort.Orientation = xl.xlSortRows
        self.__wsheet.Sort.MatchCase = False
        self.__wsheet.Sort.SortMethod = xl.xlPinYin
        self.__wsheet.Sort.Apply()

        return result
    # endregion

    # region カラム文字変換
    # region 列番号 → A1形式のカラム文字に変換
    def conv_colindex_to_addr(self,
                              column: int) -> str:
        """
        列番号 → A1形式のカラム文字に変換

        Args:
            column              : (in) 列番号

        returns:
            A1形式のカラム文字
        """
        return (self.__wsheet.Cells(1, column).Address).split("$")[1]
    # endregion

    # region R1C1指定 → A1形式に変換
    def conv_r1c1_to_a1(self,
                        row: int,
                        column: int) -> str:
        """
        R1C1指定 → A1形式に変換

        Args:
            row                 : (in) 行番号
            column              : (in) 列番号

        returns:
            A1形式
        """
        if row == self.NOTHING:
            # 列指定
            result = f'{self.conv_colindex_to_addr(column)}'
        elif column == self.NOTHING:
            # 行指定
            result = f'{row}'
        else:
            # 行＆列指定
            result = f'{self.conv_colindex_to_addr(column)}{row}'

        return result
    # endregion

    # region R1C1指定 → A1形式の範囲文字に変換
    def covn_range_r1c1_to_a1(self,
                              row1: int,
                              column1: int,
                              row2: int,
                              column2: int) -> str:
        """
        R1C1指定 → A1形式の範囲文字に変換

        Args:
            row1                : (in) 行番号(左上)
            column1             : (in) 列番号(左上)
            row2                : (in) 行番号(右下)
            column2             : (in) 列番号(右下)

        returns:
            A1形式の範囲文字
        """
        # パラメータ指定の場合、変換する
        row1 = self.__conv_param_rows(row1)
        row2 = self.__conv_param_rows(row2)
        column1 = self.__conv_param_columns(column1)
        column2 = self.__conv_param_columns(column2)

        return \
            f'{self.conv_r1c1_to_a1(row1, column1)}:' \
            f'{self.conv_r1c1_to_a1(row2, column2)}'
    # endregion
    # endregion

    # region シートをコピーする
    def sheet_copy(self,
                   sheet_base: any,
                   sheet_after: any) -> ResultCode:
        """
        シートをコピーする

        Args:
            sheet_base          : (in) コピー元のシート
            sheet_after         : (in) コピー先のシート名（またはシート番号）

        returns:
            ResultCode.SUCCESS  : 正常
            ResultCode.ERROR    : コピーエラー（未定義）
        """
        result = ResultCode.SUCCESS

        # シートのコピー
        # ※Beforeを省略すると、Afterも無効になるのか新規ブックが作成される
        sheet_base.Copy(
            Before=None, After=self.__wbook.Worksheets(sheet_after))

        return result
    # endregion

    # region ウィンドウ枠を固定する
    def freeze_window(self,
                      row: int,
                      column: int) -> ResultCode:
        """
        ウィンドウ枠を固定する

        Args:
            row                 : (in) 固定する行番号（NOTHING=指定しない）
            column              : (in) 固定する列番号（NOTHING=指定しない）

        returns:
            ResultCode.SUCCESS  : 正常
            ResultCode.ERROR    : コピーエラー（未定義）
        """
        result = ResultCode.SUCCESS

        # ウィンドウ枠を固定する
        self.__wsheet.Activate()
        range_freeze = self.__wsheet.Range(
            self.covn_range_r1c1_to_a1(row, column, row, column))
        range_freeze.Select()
        self.__wsheet.Parent.Windows(1).FreezePanes = True

        return result
    # endregion

    # region オートフィルタをセットする
    def set_auto_filter(self,
                        row: int) -> ResultCode:
        """
        オートフィルタをセットする

        Args:
            row                 : (in) 定義する行番号

        returns:
            ResultCode.SUCCESS  : 正常
            ResultCode.ERROR    : エラー（未定義）
        """
        result = ResultCode.SUCCESS

        self.__wsheet.Activate()
        range_filter = self.__wsheet.Range(
            self.covn_range_r1c1_to_a1(
                row, 1, self.get_count_rows, self.get_count_columns))
        range_filter.AutoFilter()

        return result
    # endregion

    # region 列の幅を変更する
    def change_column_size(self,
                           size: float,
                           column1: int,
                           column2: int):
        """
        列の幅を変更する

        Args:
            size                : (in) 列の幅
            column1             : (in) 列番号(開始)
            column2             : (in) 列番号(終了)

        returns:
            ResultCode.SUCCESS  : 正常
            ResultCode.ERROR    : エラー（未定義）
        """
        result = ResultCode.SUCCESS

        self.__wsheet.Activate()
        range_width = self.__wsheet.Range(
            self.covn_range_r1c1_to_a1(
                self.NOTHING, column1, self.NOTHING, column2))

        if size == self.AUTO_FIT:
            range_width.EntireColumn.AutoFit()
        else:
            range_width.ColumnWidth = size

        return result
    # endregion

    # region 行の高さを変更する
    def change_row_size(self,
                        size: float,
                        row1: int,
                        row2: int):
        """
        行の高さを変更する

        Args:
            size                : (in) 行の高さ
            row1                : (in) 行番号(開始)
            row2                : (in) 行番号(終了)

        returns:
            ResultCode.SUCCESS  : 正常
            ResultCode.ERROR    : エラー（未定義）
        """
        result = ResultCode.SUCCESS

        self.__wsheet.Activate()
        range_height = self.__wsheet.Range(
            self.covn_range_r1c1_to_a1(row1, self.NOTHING, row2, self.NOTHING))

        if size == self.AUTO_FIT:
            range_height.EntireRow.AutoFit()
        else:
            range_height.RowHeight = size

        return result
    # endregion

    # region 条件付き書式設定
    def set_format_condition(self,
                             range: str,
                             formula: str,
                             setting_font: Font,
                             setting_interior: Interior):
        """
        条件付き書式設定
        """
        result = ResultCode.SUCCESS

        self.__wsheet.Activate()

        settings = range.FormatConditions.Add(
            Type=xl.xlExpression, Operator=None, Formula1=formula)

        settings.Font.Bold = setting_font.bold
        settings.Font.Italic = setting_font.italic
        settings.Font.Color = setting_font.color
        settings.Interior.Color = setting_interior.color

        return result
    # endregion
    # endregion

    # region 非公開メソッド
    # region 行番号のパラメータ変換
    def __conv_param_rows(self,
                          value) -> int:
        """
        行番号のパラメータ変換

        Args:
            value               : (in) 行番号orパラメータ指定

        returns:
            行番号
        """
        result = value
        if value == self.MAX_USED_ROWS:
            result = self.get_count_rows
        return result
    # endregion

    # region 列番号のパラメータ変換
    def __conv_param_columns(self,
                             value) -> int:
        """
        列番号のパラメータ変換

        Args:
            value               : (in) 列番号orパラメータ指定

        returns:
            列番号
        """
        result = value
        if value == self.MAX_USED_COLUMNS:
            result = self.get_count_columns
        return result
    # endregion
    # endregion
```

#### format_condition.py：条件付き書式の設定クラス
条件付き書式の設定により、変更するセルの背景やフォント情報を設定するためのクラスを集めたファイル。今のところ、フォントとセルの背景くらいで困らないので、必要に応じて拡張していけば良いと思っています。

```python:format_condition.py
import const_excel as xl

# 定数定義
# region デフォルト定義
DEFAULT_FONT_COLOR = xl.xlColBlack          # フォントカラー
DEFAULT_FONT_SIZE = 11                      # フォントサイズ
DEFAULT_FONT_BOLD = False                   # フォント太字
DEFAULT_FONT_ITALIC = False                 # フォント太斜体
DEFAULT_INTERIOR_COLOR = xl.xlColWhite      # セル背景色
# endregion


class Font:
    """
    条件付き書式 - フォント設定クラス
    """

    def __init__(self):
        self.__color = DEFAULT_FONT_COLOR
        self.__size = DEFAULT_FONT_SIZE
        self.__bold = DEFAULT_FONT_BOLD
        self.__italic = DEFAULT_FONT_ITALIC

    # region プロパティ
    # region フォントカラー
    @property
    def color(self):
        """
        フォントカラー
        """
        return self.__color

    @color.setter
    def color(self, value: int):
        """
        フォントカラー

        Args:
            value               : (in) フォントカラー(RGB値)
        """
        self.__color = value
    # endregion

    # region フォントサイズ
    @property
    def size(self):
        """
        フォントサイズ
        """
        return self.__size

    @size.setter
    def size(self, value: bool):
        """
        フォントサイズ

        Args:
            value               : (in) フォントサイズ(ポイント)
        """
        self.__size = value
    # endregion

    # region フォント太字
    @property
    def bold(self):
        """
        フォント太字
        """
        return self.__bold

    @bold.setter
    def bold(self, value: bool):
        """
        フォント太字

        Args:
            value               : (in) True=フォント太字
        """
        self.__bold = value
    # endregion

    # region フォント斜体
    @property
    def italic(self):
        """
        フォント斜体
        """
        return self.__bold

    @italic.setter
    def italic(self, value: bool):
        """
        フォント斜体

        Args:
            value               : (in) True=フォント斜体
        """
        self.__italic = value
    # endregion
    # endregion


class Interior:
    """
    条件付き書式 - セル背景設定クラス
    """

    def __init__(self):
        self.__color = xl.rgb(255, 255, 255)

    # region プロパティ
    # region 背景カラー
    @property
    def color(self):
        """
        背景カラー
        """
        return self.__color

    @color.setter
    def color(self, value: int):
        """
        背景カラー

        Args:
            value               : (in) 背景カラー(RGB値)
        """
        self.__color = value
    # endregion
    # endregion
```

#### main.py：メイン処理
```python:main.py
import os
import command_line
from result_code import ResultCode
from command_line import CommandParam
from simple_excel import SimpleExcel, xl
from format_condition import Font, Interior

# カレントディレクトリ・ファイルパス
APP_PATH = os.getcwd()
FILE_PATH = os.path.dirname(__file__)

# 定数定義
EXCEL_BASE_FILE = 'test_base.xlsx'
EXCEL_BASE_SHEET_NAME = 'データ名一覧'
EXCEL_CELL_MACRO_NUMBER = f'=IF(ISERROR(VLOOKUP(A$3, ' \
    f'{EXCEL_BASE_SHEET_NAME}!$A:$C, 3, FALSE)), "", ' \
    f'VLOOKUP(A$3, {EXCEL_BASE_SHEET_NAME}!$A:$C, 3, FALSE))'
EXCEL_CELL_MACRO_NAME = f'=IF(ISERROR(VLOOKUP(A$3, ' \
    f'{EXCEL_BASE_SHEET_NAME}!$A:$C, 2, FALSE)), "", ' \
    f'VLOOKUP(A$3, {EXCEL_BASE_SHEET_NAME}!$A:$C, 2, FALSE))'
EXCEL_COLUMN_SIZE = 12.0


def main():
    """
    CSVファイルを読込み、1行目をキーに並べ替えをして
    Excel形式(xlsx)でファイル保存するサンプル
    """
    # コマンドラインチェック
    param = CommandParam()
    ret = command_line.check_commandline(param)

    # CSVファイルを開く
    excel_csv = None
    if ret == ResultCode.SUCCESS:
        # インスタンス生成してファイルを開く
        excel_csv = SimpleExcel()
        ret = excel_csv.open(param.path_csvfile)

    # ベースのExcelファイルを開く
    excel_base = None
    if ret == ResultCode.SUCCESS:
        # インスタンス生成してファイルを開く
        path_base = os.path.join(APP_PATH, EXCEL_BASE_FILE)
        excel_base = SimpleExcel()
        ret = excel_base.open(path_base)

        # ベースファイルのシートをコピーする
        if ret == ResultCode.SUCCESS:
            wsheet_base = excel_base.select_sheet(EXCEL_BASE_SHEET_NAME)
            excel_csv.sheet_copy(wsheet_base, 1)

            # ベースファイルを閉じる
            excel_base.close()

    if ret == ResultCode.SUCCESS:
        # 先頭から2行分の空行を挿入する
        sheet_csv = excel_csv.select_sheet(1)
        sheet_csv.Rows(1).Insert(Shift=xl.xlShiftDown)
        sheet_csv.Rows(1).Insert(Shift=xl.xlShiftDown)

        # ソート順とデータ名のセルマクロを書き込む
        sheet_csv.Range("A1").Value = EXCEL_CELL_MACRO_NUMBER
        sheet_csv.Range("A2").Value = EXCEL_CELL_MACRO_NAME

        # 上記のマクロを横方向へコピーする
        range_dest = sheet_csv.Range(
            excel_csv.covn_range_r1c1_to_a1(
                1, 2, 2, excel_csv.MAX_USED_COLUMNS))
        sheet_csv.Range("A1:A2").Copy(Destination=range_dest)

        # セルの文字を縮小して全体表示を設定する
        range_data_name = sheet_csv.Range(
            excel_csv.covn_range_r1c1_to_a1(
                2, 1, 2, excel_csv.MAX_USED_COLUMNS))
        range_data_name.ShrinkToFit = True

    # 1行目をキーに列の並び替えをする
    if ret == ResultCode.SUCCESS:
        ret = excel_csv.sort_column(1, xl.xlDescending)

    # 列の幅を変更する
    if ret == ResultCode.SUCCESS:
        ret = excel_csv.change_column_size(
            EXCEL_COLUMN_SIZE, 1, excel_csv.MAX_USED_COLUMNS)

    # 4行目でウィンドウ枠を固定する
    if ret == ResultCode.SUCCESS:
        ret = excel_csv.freeze_window(4, excel_csv.NOTHING)

    # 3行目にオートフィルタを追加する
    if ret == ResultCode.SUCCESS:
        ret = excel_csv.set_auto_filter(3)

        excel_csv.select_cell(1, 1)

    if ret == ResultCode.SUCCESS:
        # 条件付き書式設定のフォント、背景色を設定
        setting_font = Font()
        setting_font.color = xl.xlColBlack
        setting_interior = Interior()
        setting_interior.color = xl.xlColLightGray

        # 条件付き書式設定
        range_f = sheet_csv.Range("A1:F13")
        excel_csv.set_format_condition(
            range_f, '=$B1=1', setting_font, setting_interior)

    # Excelファイル(xlsx)形式で保存する
    if ret == ResultCode.SUCCESS:
        ret = excel_csv.save_as(param.path_output, forced_write=False)
        if ret == ResultCode.SUCCESS:
            print(f'{param.path_output} に保存しました')

    # 終了処理
    if excel_base is not None:
        del excel_base

    if excel_csv is not None:
        del excel_csv


if __name__ == "__main__":
    main()
```
</details>


### サンプルコードの詳細
#### 条件付き書式設定

```python:main.py
from format_condition import Font, Interior
...

# 条件付き書式設定のフォント、背景色を設定
setting_font = Font()                                         # (※１）
setting_font.color = xl.xlColBlack
setting_interior = Interior()                                 # (※２）
setting_interior.color = xl.xlColLightGray

# 条件付き書式設定
range_f = sheet_csv.Range("A1:F13")                           # (※３）
excel_csv.set_format_condition(
    range_f, '=$B1=1', setting_font, setting_interior)        # (※４）

...
```

 (※１） 条件成立時に変更するフォント情報を設定する
 (※２） 条件成立時に変更するセル情報を設定する。現状は背景色のみ
 (※３） 条件付き書式設定を定義するセルの範囲を生成する
 (※４） ※１～３の設定と合わせて、条件式を引数に渡すことで条件付き書式がシートに書き出される

 ![条件付き書式設定後.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/683161/03d0b495-2341-4aef-9b92-009dab74117c.png)

#### 実処理部

```python:simple_excel.py
...

# region 条件付き書式設定
def set_format_condition(self,
                         range: str,
                         formula: str,
                         setting_font: Font,
                         setting_interior: Interior):
    """
    条件付き書式設定
    """
    result = ResultCode.SUCCESS

    self.__wsheet.Activate()

    # ※第２引数の「Operator=None」は省略できない...
    settings = range.FormatConditions.Add(
        Type=xl.xlExpression, Operator=None, Formula1=formula)

    settings.Font.Bold = setting_font.bold
    settings.Font.Italic = setting_font.italic
    settings.Font.Color = setting_font.color
    settings.Interior.Color = setting_interior.color

    return result
# endregion

...
```

FormatConditions.Add()メソッドで条件付き書式を登録しているが、例のごとく、「数式を指定する」(Type=xl.xlExpression)場合に省略可能な Operatorを定義しないと例外が発生していた。VBAのメソッドを使用する場合、引数の途中は省略できない仕様なのかもしれないと理解しました。