Top / .NET備忘録 / 99.小ネタ / 14.NPOI の不具合

NPOI(2.2.1.0) を使って xlsx ファイルを作成してみたときに不具合を発見したのでメモ。

XSSFRichTextString の ApplyFont がどうもおかしいようです。

Imports System.IO
Imports NPOI.SS.UserModel
Imports NPOI.XSSF.UserModel

Module Module1

    Sub Main()
        Dim book As New XSSFWorkbook()
        Dim sheet As ISheet = book.CreateSheet()
        Dim cell As ICell = sheet.CreateRow(0).CreateCell(0)
        Dim helper As ICreationHelper = book.GetCreationHelper()
        Dim rtx As IRichTextString = helper.CreateRichTextString("ABCDEF")
        Dim font1 As IFont = CreateFont(book, IndexedColors.Red)
        Dim font2 As IFont = CreateFont(book, IndexedColors.Blue)
        Dim font3 As IFont = CreateFont(book, IndexedColors.Green)
        rtx.ApplyFont(0, 2, font1)
        rtx.ApplyFont(2, 4, font2)
        rtx.ApplyFont(4, 6, font3)
        cell.SetCellValue(rtx)
        Using fs As New FileStream("test.xlsx", FileMode.Create)
            book.Write(fs)
        End Using
        Process.Start("test.xlsx")
    End Sub

    Function CreateFont(ByVal book As IWorkbook, ByVal color As IndexedColors)
        Dim fnt As IFont = book.CreateFont()
        fnt.Color = color.Index
        Return fnt
    End Function

End Module

このようにしてリッチテキストを作成すると

ABCDEF

こんな具合になります。
HSSFWorkbook の場合は問題ないのですが・・・

ところが、順番を変えて

        rtx.ApplyFont(4, 6, font3)
        rtx.ApplyFont(2, 4, font2)
        rtx.ApplyFont(0, 2, font1)

とすると期待通り

ABCDEF

となります。

本家のソース(poi-3.16-beta2)) を見てみると

    void applyFont(TreeMap<Integer, CTRPrElt> formats, int startIndex, int endIndex, CTRPrElt fmt) {
            // delete format runs that fit between startIndex and endIndex
            // runs intersecting startIndex and endIndex remain
            int runStartIdx = 0;
            for (Iterator<Integer> it = formats.keySet().iterator(); it.hasNext();) {
                int runEndIdx = it.next();
                if (runStartIdx >= startIndex && runEndIdx < endIndex) {
                   it.remove();
                }
                runStartIdx = runEndIdx;
            }

            if(startIndex > 0 && !formats.containsKey(startIndex)) {
                // If there's a format that starts later in the string, make it start now
                for(Map.Entry<Integer, CTRPrElt> entry : formats.entrySet()) {
                   if(entry.getKey() > startIndex) {
                      formats.put(startIndex, entry.getValue());
                      break;
                   }
                }
            }
            formats.put(endIndex, fmt);

            // assure that the range [startIndex, endIndex] consists if a single run
            // there can be two or three runs depending whether startIndex or endIndex
            // intersected existing format runs
            SortedMap<Integer, CTRPrElt> sub = formats.subMap(startIndex, endIndex);
            while(sub.size() > 1) sub.remove(sub.lastKey());
        }

NPOI は

        internal void ApplyFont(SortedDictionary<int, CT_RPrElt> formats, int startIndex, int endIndex, CT_RPrElt fmt) 
        {
            
            // delete format runs that fit between startIndex and endIndex
            // runs intersecting startIndex and endIndex remain
            //int runStartIdx = 0;
            List<int> toRemoveKeys=new List<int>();
            for (SortedDictionary<int, CT_RPrElt>.KeyCollection.Enumerator it = formats.Keys.GetEnumerator(); it.MoveNext(); )
            {
                int runIdx = it.Current;
                if (runIdx >= startIndex && runIdx < endIndex)
                {
                    toRemoveKeys.Add(runIdx);
                }
            }
            foreach (int key in toRemoveKeys)
            {
                formats.Remove(key);
            }
            if (startIndex > 0 && !formats.ContainsKey(startIndex))
            {
                // If there's a format that starts later in the string, make it start now
                foreach (KeyValuePair<int, CT_RPrElt> entry in formats)
                {
                    if (entry.Key > startIndex)
                    {
                        formats[startIndex] = entry.Value;
                        break;
                    }
                }
            }
            formats[endIndex] = fmt;

            // assure that the range [startIndex, endIndex] consists if a single run
            // there can be two or three runs depending whether startIndex or endIndex
            // intersected existing format runs
            //SortedMap<int, CT_RPrElt> sub = formats.subMap(startIndex, endIndex);
            //while(sub.size() > 1) sub.remove(sub.lastKey());       
        }

移植に失敗してるっぽいです。

            List<int> toRemoveKeys=new List<int>();
            for (SortedDictionary<int, CT_RPrElt>.KeyCollection.Enumerator it = formats.Keys.GetEnumerator(); it.MoveNext(); )
            {
                int runIdx = it.Current;
                if (runIdx >= startIndex && runIdx < endIndex)
                {
                    toRemoveKeys.Add(runIdx);
                }
            }

この部分を

            int runStartIdx = 0;
            List<int> toRemoveKeys=new List<int>();
            for (SortedDictionary<int, CT_RPrElt>.KeyCollection.Enumerator it = formats.Keys.GetEnumerator(); it.MoveNext(); )
            {
                int runEndIdx = it.Current;
                if (runStartIdx >= startIndex && runEndIdx < endIndex) 
                {
                    toRemoveKeys.Add(runEndIdx);
                }
                runStartIdx = runEndIdx;
            }

のようにすればいいっぽい。




トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   最終更新のRSS
Last-modified: 2017-02-15 (水) 02:00:24 (132d)