Open XML SDK 備忘録

おもにExcelファイル (.xlsx) を扱います。

P-SPACE
就学奨励費システム

マイナンバー対応 特別支援教育 就学奨励費 支給業務支援システム

詳細...

P-SPACE
就学奨励費ソフトウェア

特別支援教育 就学奨励費 支給業務支援ソフトウェア

詳細...

開発室
(備忘録そのほか)

システム開発にまつわる(かもしれない)調査・実験結果など

詳細...

まずはセルデータから - その4

前回の残件である「セルに文字列をセットする」方法について説明します。

手順としては(おさらいになりますが)以下のようになります。

  1. SharedStringパーツからセットしたい文字列を検索して、そのインデックス番号(n番目のsi要素)を取得する。
    (このときセットした文字列をもつsi要素が存在しなければ新たにsi要素を追加し、そのインデックス番号を取得する。)
  2. 取得したインデックスを、文字列をセットしたいc要素の値として指定する

文字列をセットするためには、前回のプログラムの57行目を以下のように書き換えます。

cell.DataType = CellValues.SharedString;
cell.CellValue = new CellValue(InsertSharedStringItem("abc", bookPart.SharedStringTablePart).ToString());

ここでInsertSharedStringItemの定義はhttps://msdn.microsoft.com/ja-jp/library/office/gg278314.aspx#code-snippet-2にあります。(以下引用します)

// Given text and a SharedStringTablePart, creates a SharedStringItem with the specified text 
// and inserts it into the SharedStringTablePart. If the item already exists, returns its index.
private static int InsertSharedStringItem(string text, SharedStringTablePart shareStringPart)
{
    // If the part does not contain a SharedStringTable, create one.
    if (shareStringPart.SharedStringTable == null)
    {
        shareStringPart.SharedStringTable = new SharedStringTable();
    }

    int i = 0;

    // Iterate through all the items in the SharedStringTable. If the text already exists, return its index.
    foreach (SharedStringItem item in shareStringPart.SharedStringTable.Elements<SharedStringItem>())
    {
        if (item.InnerText == text)
        {
            return i;
        }

        i++;
    }

    // The text does not exist in the part. Create the SharedStringItem and return its index.
    shareStringPart.SharedStringTable.AppendChild(new SharedStringItem(new DocumentFormat.OpenXml.Spreadsheet.Text(text)));
    shareStringPart.SharedStringTable.Save();

    return i;
}

InsertSharedStringItemは先の手順2を行い、そのインデックスを返します。
プログラムを実行するとセルA6に文字列"abc"が入力されます。

実用的な側面から

...と、ここまでで説明を終わっているのがMSDNの上記ページです。

しかし上記のInsertSharedStringItemの実装はあくまでサンプルコードとしての実装であって、これをそのまま実際のアプリケーションで使おうとするといろいろと問題があります。(問題というには大げさかもしれませんが...)

フリガナの扱い

先ほどは文字"abc"をセットしましたが、代わりに"東京特許許可局"とするとどうなるでしょうか?

...意図したとおりにセルA6に"東京特許許可局"が入っています。しかしProductivity Toolでworksheetパーツの中身を覗いてみると以下のようになっています。

同じ"東京特許許可局"にもかかわらず、別のインデックスが振られています。

別のインデックスが割り振られる理由は「フリガナ(ルビ)の存在」によります。SharedStringパーツを見てみると、以下のようになっています。

インデックスが1(2番目)のsi要素にはフリガナ「トウキョウトッキョキョカキョク」があります。それゆえ、InsertSharedStringItemの定義の16行目のitem.InnerTextの値は「東京特許許可局トウキョウトッキョキョカキョク」となってしまい、「東京特許許可局」と一致しません。その結果、新たなsi要素(3番目のsi要素 / インデックス2)が作成されてしまいます。

フリガナのことだけを考えるならば、InsertSharedStringItemの定義の16行目を以下のように書き換えることでセルA2, A6ともに同じインデックスを割り振らせることができます。

if (item.Text != null && item.Text.InnerText == text) {

文字の一部にセットされた書式の扱い

InsertSharedStringItemの定義の16行目を上記のように書き換えることで、フリガナの問題は解決されますが、また別の問題が生じます。

セルA2の内容を「東京特許許可局」のように、一部の書式を変更します。そしてInsertSharedStringItemの定義の16行目を上記のように書き換えてプログラムを実行すると、やはり新たなsi要素を作ってしまいます。

...原因については述べるまでもないでしょう。

実装するアプリケーションの用途によっては、これはこれで期待どおりの振る舞いかもしれません。

文字の一部にセットされた書式はsi要素の中に定義されるので、こういった書式についてもコントロールしたいならば、InsertSharedStringItemの引数を工夫する必要があります。

書式を引数で渡す...というのは限界がありますので、セルにセットする「文字列」を引き渡すのではなくsi要素(DocumentFormat.OpenXml.Spreadsheet.SharedStringItem)を引き渡すようにするとうまくいきます。(引き渡すべきSharedStringItemインスタンスをInsertSharedStringItemの呼び出し側で準備する手間が増えますが...)