前陣子曾排除過一枚EPPlus處理ReportViewer匯出xlsx的Bug,繼續深入才發現事情遠比想像複雜: 表格式報表經ReportViewer匯出成Excel檔,透過EPPlus處理存檔後,用Excel開啟又再次爆出xl/styles.xml及xl/worksheets/sheet1.xml損壞訊息,經修復可讀取,但已原本的格式、顏色設定盡失。


圖1 ReportViewer匯出的原始Excel檔,實驗目標是將"A1"儲存格改成"已修改"


圖2 EPPlus處理後發生錯誤,樣式遺失

試著追進Source Code,發現問題埋得頗深。由於EPPlus在存檔時會重新以自己的角度詮釋及重整Open XML文件,即使我們只改一個欄位,XML也會經過一番重構;當原始內容偏複雜,就會產出不符合Excel或Open XML標準的結果。評估要修正這些問題將涉及大規模核心邏輯翻寫,工程不小,有違想利用元件簡化開發的初衷。還在Alpha階段的NPOI 2.0已能支援xlsx,決定也讓它上場試試,考驗一下能否正確處理ReportViewer的匯出檔。


圖3 NPOI存檔後也發生錯誤,遺失樣式設定

可惜,NPOI也闖關失敗,修復訊息中出現很嚇人的"災難性的失敗",且修改結果也未出現。

最後,只能考慮相形之下較難用的Open XML SDK(目前已經出到2.5,但還是只像為硬綁綁的XML套了層絨布套,坐了屁股照樣會疼),由於它基本上是用維護XML文件的角度進行操作,不會重構翻寫XML,破壞結構風險低很多。而它也是本次測試唯一修改成功且Excel開啟正常的範例。


圖4 Open XML SDK 2.5修改結果

附上程式碼供參考:

static void Main(string[] args)
{
    string src = @"d:\temp\source.xlsx";
 
    //NOPI
    IWorkbook workbook = new XSSFWorkbook(src);
    ISheet sheet = workbook.GetSheetAt(0);
    sheet.GetRow(0).GetCell(0).SetCellValue("已修改");
    FileStream sw = File.Create(@"d:\temp\npoi.xlsx");
    workbook.Write(sw);
    sw.Close();
 
    //EPPlus
    using (ExcelPackage p = new ExcelPackage(new FileInfo(src)))
    {
        var sht = p.Workbook.Worksheets.First();
        sht.Cells[1, 1].Value = "已修改";
        p.SaveAs(new FileInfo(@"d:\temp\epplus.xlsx"));
    }
 
    //OpenXML SDK 2.5
    //REF: http://msdn.microsoft.com/en-us/library/office/cc850837.aspx
    string dst = src.Replace(Path.GetFileName(src), "sdk.xlsx");
    File.Copy(src, dst, true);
    using (var shtDoc = SpreadsheetDocument.Open(dst, true))
    {
        var sht = shtDoc.WorkbookPart.Workbook.Descendants<Sheet>().First();
        var shtPart = shtDoc.WorkbookPart.GetPartById(sht.Id) as WorksheetPart;
        var cell = shtPart.Worksheet.Descendants<Row>().First()
                    .Descendants<Cell>().First();
        cell.RemoveAllChildren();
        //REF: InlineString http://bit.ly/ZpUf18
        var ins = new InlineString();
        ins.AppendChild(new Text("已修改"));
        cell.AppendChild(ins);
        cell.DataType = 
            new DocumentFormat.OpenXml.EnumValue<CellValues>(
                CellValues.InlineString);
        //shtPart.Worksheet.Save();
        shtDoc.WorkbookPart.Workbook.Save();
        shtDoc.Close();
    }
}

【2012-12-28補充】新選擇: 令人驚豔的Excel程式庫 - ClosedXML


Comments

Be the first to post a comment

Post a comment