NPOI で xlsx を出力すると、なぜか下線が引かれてしまう

NPOI 2.2.1 で 2007 形式の Excel ファイル (.xlsx) を出力した際、なぜか文字にすべて下線が引かれてしまうため調べてみた結果、以下のような問題を発見しました。(PR 済み)

  • XSSFFont の下線情報の設定処理がおかしい
  • FontUnderline.NONE が内部値がおかしい

この合わせ技によりバグが発生します。

まず XSSFFont.cs の該当部実装がこちら。

internal void SetUnderline(FontUnderlineType underline)
{
    if (underline == FontUnderlineType.None && _ctFont.sizeOfUArray() > 0)
    {
        _ctFont.SetUArray(null);
    }
    else
    {
        CT_UnderlineProperty ctUnderline = _ctFont.sizeOfUArray() == 0 ? _ctFont.AddNewU() : _ctFont.GetUArray(0);
        ST_UnderlineValues val = (ST_UnderlineValues)FontUnderline.ValueOf(underline).Value;
        ctUnderline.val = val;
    }
}

新しい値が None で、かつ内部で既に下線の値を保持している場合、内部に保持している下線情報をクリアします。

そして、そうではない場合は既に内部に下線の値を保持している場合はその情報を、保持していない場合は新しい下線情報を作成した上で、指定された値を保持します。

この情報は出力結果となる xslx 内にある styles.xml に u 要素の出力に反映され、値が設定されていない場合 (_ctFont.sizeOfUArray() == 0 の時) は u 要素が出力されず、値が存在する場合はその値が出力されます。

出力される値は FontUnderlineType.cs にある None の値 となりますが、こうなっています。

public static readonly FontUnderline NONE = new FontUnderline(5);

これを踏まえた上で、下線を設定しない状況で明示的に下線なしの指定をしてみます。

var font = ((XSSFWorkbook)workbook).CreateFont();
font.Underline = FontUnderlineType.None;

この状況の場合、font の内部に保持している下線の情報が存在しており、ここに None の値がセットされていることになります。このため、u 要素が出力されます。

これで出力すると styles.xml に <u val="5"/> が出力されますが、Excel 上で見ると下線が引かれてしまうのです。

そして……。

var font = ((XSSFWorkbook)workbook).CreateFont();
font.Underline = FontUnderlineType.None;
font.Underline = FontUnderlineType.None;

SetUnderline() のコードを見た上であれば、これにより何が起きるかは想像できると思います。

そう、この場合は 2 回目の値の設定により、内部の下線情報がクリアされます。

このため、styles.xml に u 要素が出力されません。

つまり、下線なしにしたい場合は None を「設定しない」か、偶数回設定したらいいことになります。そんな馬鹿な!

さて、ここで FontUnderline.cs を少しだけいじってみます。

まず NONE の値を 5 から 0 に。

public static readonly FontUnderline NONE = new FontUnderline(0);

次に、静的コンストラクタの処理をほんのちょっとだけ変更。

static FontUnderline()
{
    if (_table == null)
    {
        _table = new FontUnderline[5];
        _table[0] = FontUnderline.NONE;
        _table[1] = FontUnderline.SINGLE;
        _table[2] = FontUnderline.DOUBLE;
        _table[3] = FontUnderline.SINGLE_ACCOUNTING;
        _table[4] = FontUnderline.DOUBLE_ACCOUNTING;
    }
}

(_table = new FontUnderline[6]; を [5] に、_table[5] だった NONE を [0] に移動しています)

この状態で、先と同じように一度だけ指定して出力してみます。

var font = ((XSSFWorkbook)workbook).CreateFont();
font.Underline = FontUnderlineType.None;

この場合、u 要素は <u val="none"/> と出力されます。あれ?(笑)

Office Open XML の仕様である ECMA-376 の Part 4 にこの辺りの定義が書かれていますが、スプレッドシートのフォントスタイルにおける u 要素の値は以下のようになっています。

sml_ST_UnderlineValues = 
  string "single" 
  | string "double" 
  | string "singleAccounting" 
  | string "doubleAccounting" 
  | string "none" 

そう、"5" はおかしくて "none" が正しいのです。そして、この場合 (当然) 下線は引かれません。(そもそも 0 にすると適切に "none" を出すって……)

ということで、この点を 0 にすると (とりあえず 2007 形式では) 適切な u 要素が出力されるようになります。

しかし、そもそも <u val="none"/> って、出す必要ないよね? という話があります。指定しなければ下線引かれないのですし。なのでこんな感じでいいのでは。

internal void SetUnderline(FontUnderlineType underline)
{
    if (underline == FontUnderlineType.None)
    {
        _ctFont.SetUArray(null);
    }
    else
    {
        CT_UnderlineProperty ctUnderline = _ctFont.sizeOfUArray() == 0 ? _ctFont.AddNewU() : _ctFont.GetUArray(0);
        ST_UnderlineValues val = (ST_UnderlineValues)FontUnderline.ValueOf(underline).Value;
        ctUnderline.val = val;
    }
}

(if の条件を None の場合のみに変更)

……ということで NPOI にこんな感じで PR だしたものの、POI だとどうなっているのだろうって確認してみたのですが、詳しく追いかけてないけど、XSSFFont.javaFontUnderline.java もまったく同じような……。

コーディング規約あれこれ

コーディング規約は、コードの読みやすさを統一する上でとても重要なものです。自然言語で言えば、ですます調で揃えるのか、であるだ調で揃えるのかといった点もあれば、送り仮名を本則に揃えるのかといった点など、文章の規律を揃えるという点として極めて重要なポイントとなります。

こうした点は、しっかりと統一されていないと、同じ言語で記述されていても統一感のない、極めて読み辛い文章となってしまうのは、言うまでもありません。

コーディング規約にはネット上には様々な資料が存在しており、とても参考になる情報が多々存在します。しかし、そういった資料があるにも関わらず、結果として微妙に残念な規約は割と実在します。

では、どういった点を気にして「ここは良い」「ここは不要」といった点を考えたらいいのでしょうか。

……という辺りで、今までに見たり経験したりしたコーディング規約で、それなりに気になった点とかをかるーくまとめてみました。

まぁ、固有の言語に依存する部分は必要に応じて省くか書き換えることだけはやろうね、位しか考えてません。

固有の言語に依存する部分は必要に応じて省くか書き換える

Java ベースの規約で良くある点として、== ではなく .equals() を利用して比較する点があります。

これはなぜでしょうか。

Java の場合、== で比較した場合は同じオブジェクトの参照であるかどうかを確認します。

C 的に言えばこういう感じですね。

const char* required_string = "abc";

int compare_required_string(char* p) {
    return p == required_string;
}

本来求めている話で言うと、多分こっちが正解です。

const char* required_string = "abc";

int compare_required_string(char* p) {
    return strcmp(required_string, p) == 0;
}

値として required_string と一致しているか、つまり strcmp() を呼び出して 0 が取れれば true、0 以外であれば false という結果を期待していると想定されます。

Java の場合、required_string.equals(p) で比較した場合に、値が一致したら true、一致しないなら false となりますが、C 言語であれば == の場合は当然アドレスが一致したら true となりますし、Java でも required_string == p は、そういう意味となります。

これらを理解していない人向けに「Java では比較に .equals() を使え」というのは、「大抵の場合に求めている結果になる」という点では合っていても、こうした説明を省いて「.equals() を使え」と言うのは、いろんな意味で誤っています。

さらに、これらを「そのまま適当に翻訳して .NET Framework のコーディング規約に適用する」と、様々に誤ったこととなります。

例えば C# でこのようなコードを記述したとします。

private const String requiredString = "abc";

public bool compareRequiredString(String p) {
    return requiredString == p;
}

この場合、C# (や VB.NET、つまり .NET Framework) の場合は文字列比較となるため、requiredString と p の、つまり文字列が一致するかどうかを比較します。つまり、Java における .equals() 相当となります。

これと同じイメージで、.NET Framework で用意されている Object.Equals() が同等のメソッドであると思って利用すると、実は期待している結果と正反対の結果をもたらします。

.NET Framework では Object.Equals() は、値型の場合は値としての等価性を、参照型の場合は参照型としての等価性をテストします。結果として、String 等の場合は参照型としてテストされるため、.ReferenceEquals()、つまりポインターが一致しているかどうかの比較となってしまいます。

まさに、Java における「==.equals()」で比較した場合とまったく逆の結果となってしまいます。

この辺りをすべて踏まえた上で、分かっていて C# や VB.NET 向けに「== (VB.NET では =) ではなく .Equals() を利用して比較しろ」という「コーディング規約」であるなら構わないのですが、通常「.Equals() を使え」なんていうこと自体が、まずありえません。

こうした「それぞれの言語や環境ごとに違う」部分を何も考えずに、ただ漫然と「Java 向けのコーディング規約で .equals() を使うように書かれていたので、.NET 向けのコーディング規約も、似たような名前の .Equals() を使うようにした」というようなことは、本当に危険ですし、してはいけません。

他言語などのコーディング規約にはこういった差異がありうるため、何も考えずに適当に置き換えるようなことは規約は必要に応じて省くか、明示的に異なることを記述する必要があります。

固有の言語特有の文化の受け入れ方針を確定する

企業文化や普段採用しているポリシーと、採用した言語の文化が異なる場合というのも存在します。

例えば、Ruby の場合は条件が否定式となる場合、基本的には if/unless を使い分ける事が望まれます。

しかし、elsif がかかる場面などでは、最初の条件判断が unless の場合、続く elsif 文では条件判断のコンテキストが否定から肯定に変わるため、コードの読みやすさが落ちるという点があります。これは特に複数の条件が絡むと起きやすくなります。

例えば bool 値を持つ flag_aflag_b がある場合で考えてみます。

以下が例 1 です。

if !flag_a
    target_process1
elsif flag_b
    target_process2
else
    target_process_3
end

そして、以下が例 2 です。

unless flag_a
    target_process1
elsif flag_b
    target_process2
else
    target_process3
end

個別の部分部分で見ると例 2 の方が分かりやすいのは確かですが、一連の条件式で見ると、私には例 1 の方が読みやすいと感じます。これは条件判断式のコンテキストが常に if、つまり「肯定式」で統一されているからです。

さて、これはどちらが正しいのでしょうか。

はっきり言って、これに答えはないでしょう。

まして、これを規約に含めようとすると、単独の (elsifelse を含まない) 条件式にも影響を及ぼす可能性があります。

私の場合、個人的には unless を使っていい状況を以下のように決めています。

  • コントラクト部分である (パラメーターのチェックや戻り値の適切性のテスト)
  • 条件式が一つだけ (複数のパラメーターを確認しない)
  • unless を使った方が分かりやすい

大きくフローに影響しないということ、unless 自体が否定を表すため、コンテキストが分かりにくくなる二重否定を避けること、分かりやすくならないなら if でいい、という形です。

コントラクト部分であっても、同じパラメーターを連続して ififunless といった形でテストすると評価コンテキストが変わって分かりにくくなりますので、if なら if で、unless なら unless で揃えるようにします。

言語の表現能力に依存するか、明示的にするのか

これは Java や C# などのように if 文のテストが bool 値とならない言語 (C 言語や Perl、Ruby など) を対象としています。

例えば変数 a (または $a) の値が 0 である場合を考えてみます。

C 言語の場合は以下のようになります。

int a = 0;

if (a) {
    not_executed();
} else {
    executed();
}

Perl の場合は以下のようになります。

my $a = 0;

if ($a) {
    not_executed;
} else {
    executed;
}

Ruby の場合は以下のようになります。

a = 0;

if (a) {
    executed;
} else {
    not_executed;
}

Ruby は bool 値ではない場合値の有無をベースに条件式の結果を決定するため、C 言語や Perl と異なる結果となります。

では、C 言語と Perl が同じような結果を返すのかと言えば、そんなことはありません。

以下のような場合、C 言語では true となります。

const char* p = "0";

if (p) {
    executed();
} else {
    not_executed();
}

対して、このパターンでは Perl は false となります。

my $p = "0";

if ($p) {
    not_executed();
} else {
    executed();
}

さて、何が正しくて、何が正しくないのでしょうか。

これらは、採用している言語ごとの特性を踏まえて最小の記述で求める結果を得るのか、コードの可読性を重視して、たとえ冗長になっても条件を明示するのかといった、組織やターゲットのポリシーによって決定されるものだと思います。

私の場合は、富豪メソッドが基本なので、文法はできるだけ言語に沿うようにするものの、(多少コストが増えても) 条件は常に明示する形を基準にしています。