利用NPOI將EXCEL轉換成HTML的C#實現


領導說想做一個網頁打印功能,而且模板可以自定義,我考慮了三個方案,一是打印插件,二是在線 html 編輯器,三是 excel 模板,領導建議用的是打印插件的形式,我研究了一下,一個是需要下載安裝,二個是模板定義其實也相當不方便,所以我想采用后兩種,而在線  html 編輯器的話,直接畫出來的並不真的是所見即所得,打印效果肯定需要不停的去調整,而直接 html 代碼呢,對客戶的要求又比較高(不可否認,很多客戶都不知道 html 是什么玩意兒),所以最后選擇了 excel 形式,搜了一下 npoi 官網,發現一個 java 版的 html 導出,於是辛苦了一下,把它改造成了 c# 的,在此過種中發現Java版本的沒有處理合並單元格,且字體相對較大,我對此進行了一點改進,另外發現了兩個NPOI的BUG,因為時間關系,也就沒有去弄NPOI的源碼了,等我有空了再來解決這兩個BUG吧, 代碼注釋不多,需要看注釋的直接去看 java版本的即可。

貼上效果圖:

 

using NPOI.HSSF.UserModel;
using NPOI.POIFS.FileSystem;
using NPOI.SS.Format;
using NPOI.SS.UserModel;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;

namespace ExcelUtility
{
    public class EXCELTOHTML 
    {
        private IWorkbook wb = null;

        private const String DEFAULTS_CLASS = "excelDefaults";
        private const String COL_HEAD_CLASS = "colHeader";
        private const String ROW_HEAD_CLASS = "rowHeader";

        private const int IDX_TABLE_WIDTH = -2;
        private const int IDX_HEADER_COL_WIDTH = -1;


        private int firstColumn;
        private int endColumn;

        private bool gotBounds;

        private List<KeyValuePair<HorizontalAlignment, string>> HALIGN = new List<KeyValuePair<HorizontalAlignment, string>>() {
            new KeyValuePair<HorizontalAlignment, string>(HorizontalAlignment.Left, "left"),
            new KeyValuePair<HorizontalAlignment, string>(HorizontalAlignment.Center, "center"),
            new KeyValuePair<HorizontalAlignment, string>(HorizontalAlignment.Right, "right"),
            new KeyValuePair<HorizontalAlignment, string>(HorizontalAlignment.Fill, "left"),
            new KeyValuePair<HorizontalAlignment, string>(HorizontalAlignment.Justify, "left"),
            new KeyValuePair<HorizontalAlignment, string>(HorizontalAlignment.CenterSelection, "center"),
            new KeyValuePair<HorizontalAlignment, string>(HorizontalAlignment.General, "left")
        };

        private List<KeyValuePair<VerticalAlignment, string>> VALIGN = new List<KeyValuePair<VerticalAlignment, string>>() {
            new KeyValuePair<VerticalAlignment, string>(VerticalAlignment.Bottom, "bottom"),
            new KeyValuePair<VerticalAlignment, string>(VerticalAlignment.Center, "middle"),
            new KeyValuePair<VerticalAlignment, string>(VerticalAlignment.Top, "top")
        };

        private List<KeyValuePair<BorderStyle, string>> BORDER = new List<KeyValuePair<BorderStyle, string>>() {
            new KeyValuePair<BorderStyle, string>(BorderStyle.DashDot, "dashed 1pt"),
            new KeyValuePair<BorderStyle, string>(BorderStyle.DashDotDot, "dashed 1pt"),
            new KeyValuePair<BorderStyle, string>(BorderStyle.Dashed, "dashed 1pt"),
            new KeyValuePair<BorderStyle, string>(BorderStyle.Dotted, "dotted 1pt"),
            new KeyValuePair<BorderStyle, string>(BorderStyle.Double, "double 3pt"),
            new KeyValuePair<BorderStyle, string>(BorderStyle.Hair, "dashed 1px"),
            new KeyValuePair<BorderStyle, string>(BorderStyle.Medium, "solid 2pt"),
            new KeyValuePair<BorderStyle, string>(BorderStyle.MediumDashDot, "dashed 2pt"),
            new KeyValuePair<BorderStyle, string>(BorderStyle.MediumDashDotDot, "dashed 2pt"),
            new KeyValuePair<BorderStyle, string>(BorderStyle.MediumDashed, "dashed 2pt"),
            new KeyValuePair<BorderStyle, string>(BorderStyle.None, "none"),
            new KeyValuePair<BorderStyle, string>(BorderStyle.SlantedDashDot, "dashed 2pt"),
            new KeyValuePair<BorderStyle, string>(BorderStyle.Thick, "solid 3pt"),
            new KeyValuePair<BorderStyle, string>(BorderStyle.Thin, "solid 1pt")
        };

        public EXCELTOHTML(IWorkbook wb)
        {
            this.wb = wb;
        }

        public EXCELTOHTML(string path)
        {
            using (var inputfs = new FileStream(path, FileMode.Open, FileAccess.Read))
            {
                NPOIFSFileSystem fs = new NPOIFSFileSystem(inputfs);
                this.wb = new HSSFWorkbook(fs.Root, true);
            }
        }

        public string ToHtml(int sheetIndex = 0, bool completeHtmls = true, bool needTitle = true)
        {
            return ToHtml(wb.GetSheetName(sheetIndex), completeHtmls, needTitle);
        }

        public string ToHtml(string sheetName, bool completeHtmls = true, bool needTitle = true)
        {
            StringBuilder sbRet = new StringBuilder();

            if (completeHtmls)
            {
                sbRet.Append("<?xml version=\"1.0\" encoding=\"iso-8859-1\" ?>\n");
                sbRet.Append("<html>\n");
                sbRet.Append("<head>\n");
            }
            sbRet.Append(GetInlineStyle());
            if (completeHtmls)
            {
                sbRet.Append("</head>\n");
                sbRet.Append("<body>\n");
            }
            sbRet.Append(GetSheets(sheetName, needTitle));
            if (completeHtmls)
            {
                sbRet.Append("</body>\n");
                sbRet.Append("</html>\n");
            }

            return sbRet.ToString();
        }

        private string GetSheets(string sheetName, bool needTitle)
        {
            StringBuilder sbRet = new StringBuilder();

            ISheet sheet = wb.GetSheet(sheetName);
            sbRet.Append(GetSheet(sheet, needTitle));

            return sbRet.ToString();
        }

        private string GetSheet(ISheet sheet, bool needTitle)
        {
            StringBuilder sbRet = new StringBuilder();

            List<KeyValuePair<int, int>> widths = computeWidths(sheet);
            int tableWidth = widths.Where(o => o.Key == IDX_TABLE_WIDTH).First().Value;
            sbRet.Append(string.Format("<table class={0} cellspacing=\"0\" cellpadding=\"0\" style=\"width:{1}px;\">\n", DEFAULTS_CLASS, tableWidth));
            sbRet.Append(GetCols(widths, needTitle));
            sbRet.Append(GetSheetContent(sheet, needTitle));
            sbRet.Append("</table>\n");

            return sbRet.ToString();
        }

        private string GetColumnHeads()
        {
            StringBuilder sbRet = new StringBuilder();

            sbRet.Append(string.Format("<thead>\n"));
            sbRet.Append(string.Format("  <tr class={0}>\n", COL_HEAD_CLASS));
            sbRet.Append(string.Format("    <th class={0}>◊</th>\n", COL_HEAD_CLASS));
            //noinspection UnusedDeclaration
            for (int i = firstColumn; i < endColumn; i++)
            {
                StringBuilder colName = new StringBuilder();

                int cnum = i;
                do
                {
                    colName.Insert(0, (char)('A' + cnum % 26));
                    cnum /= 26;
                } while (cnum > 0);

                sbRet.Append(string.Format("    <th class={0}>{1}</th>\n", COL_HEAD_CLASS, colName));
            }
            sbRet.Append("  </tr>\n");
            sbRet.Append("</thead>\n");

            return sbRet.ToString();
        }

        private string GetSheetContent(ISheet sheet, bool needTitle)
        {
            StringBuilder sbRet = new StringBuilder();

            if (needTitle)
            {
                sbRet.Append(GetColumnHeads());
            }

            sbRet.Append(string.Format("<tbody>\n"));
            IEnumerator rows = sheet.GetRowEnumerator();
            while (rows.MoveNext())
            {
                IRow row = (IRow)rows.Current;

                sbRet.Append(string.Format("  <tr>\n"));
                if (needTitle)
                {
                    sbRet.Append(string.Format("    <td class={0}>{1}</td>\n", ROW_HEAD_CLASS, row.RowNum + 1));
                }

                StringBuilder sbTemp = new StringBuilder();
                int mergeCnt = 0;
                ICell preCell = null;
                ICell cell = null;

                for (int i = firstColumn; i < endColumn; i++)
                {
                    String content = " ";
                    String attrs = "";
                    ICellStyle style = null;
                    bool isMerge = false;

                    if (i >= row.FirstCellNum && i < row.LastCellNum)
                    {
                        cell = row.GetCell(i);
                        if (cell != null)
                        {
                            isMerge = cell.IsMergedCell;
                            style = cell.CellStyle;
                            attrs = tagStyle(cell, style);
                            //Set the value that is rendered for the cell
                            //also applies the format
                            MyCellFormat cf = MyCellFormat.GetInstance(style.GetDataFormatString());
                            CellFormatResult result = cf.Apply(cell);
                            content = result.Text; //never null
                            if (string.IsNullOrEmpty(content))
                            {
                                content = " ";
                            }
                        }
                    }

                    if (isMerge == true && content == " ")
                    {
                        /*
                         * 因為 NPOI 返回的 cell 沒有 mergeCnt 屬性,只有一個 IsMergedCell 屬性
                         * 如果有5個單元格,后面四個單元格合並成一個大單元格
                         * 它返回的其實還是5個單元格,IsMergedCell 分別是: false,true,true,true,true
                         * 上頭這種情況還算好,我們好歹還能猜到后面四個單元格是合並單元格
                         * 
                         * 但是如果第一個單獨,后面四個每兩個合並呢?
                         * TMD返回的還是5個單元格,IsMergedCell 仍然是: false,true,true,true,true
                         * 所以這里是有問題的,我沒法知道后面的四個單元格是四個合並成一個呢,還是兩個兩個的分別合並
                         * 這個是沒辦法的,除非從NPOI的源代碼里頭去解決這個問題,介於上班呢,要求的是出結果,所以公司是
                         * 不太會允許我去干這種投入產出比較差的事情的,所以這個問題我采用了一個成本比較低的辦法來繞開
                         * 
                         * 辦法就是我們在定義模板的時候,可以通過為每一個合並單元格添加內容來避免。
                         * 比如說 cell1(內容), cell2,cell3(內容), cell4,cell5(內容)
                         * 這樣的話我就能知道 cell1 IsMergedCell = false 是一個獨立的單元格
                         * cell2, cell3, cell4, cell5 的 IsMergedCell 雖然都是 true, 但是因為 cell4 這個位置有內容了,
                         * 那我就曉得 cell2 和 cell3 是合並的, cell4 和 cell5 也是合並的。
                         * 
                         * 當然這里還會有個小小的問題,如果 cell4, cell5 里頭是一個會被替換掉的內容,也即 $[字段] 這樣的東西
                         * 如果實際的內容為 null 那么 cell4, cell5 合並單元格的內容也就是 null 了,這又回到了之前的問題了, 
                         * 所以此處要求定義模板的時候 $[內容] 后面加一個空格,這樣在生成 html 的時候,其實是不影響打印效果的。
                         * 也即 “$[] ”注意雙引號里頭的 “]”后頭有個空格
                         */
                        if (mergeCnt == 1 && preCell != null && preCell.IsMergedCell == false)
                        {
                            sbTemp.Append(string.Format("    <td class={0} {1}{3}>{2}</td>\n", styleName(style), attrs, content, (isMerge) ? " colspan=\"1\"" : ""));
                        } else {
                            mergeCnt++;
                        }
                    } else {
                        sbTemp.Replace("colspan=\"1\"", string.Format("colspan=\"{0}\"", mergeCnt));
                        mergeCnt = 1;
                        sbTemp.Append(string.Format("    <td class={0} {1}{3}>{2}</td>\n", styleName(style), attrs, content, (isMerge) ? " colspan=\"1\"" : ""));
                    }
                    preCell = cell;
                }
                sbRet.Append(sbTemp.Replace("colspan=\"1\"", string.Format("colspan=\"{0}\"", mergeCnt)).ToString());


                sbRet.Append(string.Format("  </tr>\n"));
            }
            sbRet.Append(string.Format("</tbody>\n"));


            return sbRet.ToString();
        }

        private String tagStyle(ICell cell, ICellStyle style)
        {
            if (style.Alignment == HorizontalAlignment.General)
            {
                switch (ultimateCellType(cell))
                {
                    case CellType.String:
                        return "style=\"text-align: left;\"";
                    case CellType.Boolean:
                    case CellType.Error:
                        return "style=\"text-align: center;\"";
                    case CellType.Numeric:
                    default:
                        // "right" is the default
                        break;
                }
            }
            return "";
        }

        private static CellType ultimateCellType(ICell c)
        {
            CellType type = c.CellType;
            if (type == CellType.Formula)
            {
                type = c.CachedFormulaResultType;
            }
            return type;
        }

        private string GetCols(List<KeyValuePair<int, int>> widths, bool needTitle)
        {
            StringBuilder sbRet = new StringBuilder();

            if (needTitle)
            {
                int headerColWidth = widths.Where(o => o.Key == IDX_HEADER_COL_WIDTH).First().Value;
                sbRet.Append(string.Format("<col style=\"width:{0}px\"/>\n", headerColWidth));
            }
            for (int i = firstColumn; i < endColumn; i++)
            {
                int colWidth = widths.Where(o => o.Key == i).First().Value;
                sbRet.Append(string.Format("<col style=\"width:{0}px;\"/>\n", colWidth));
            }

            return sbRet.ToString();
        }

        private List<KeyValuePair<int, int>> computeWidths(ISheet sheet)
        {
            List<KeyValuePair<int, int>> ret = new List<KeyValuePair<int, int>>();
            int tableWidth = 0;

            ensureColumnBounds(sheet);

            // compute width of the header column
            int lastRowNum = sheet.LastRowNum;
            int headerCharCount = lastRowNum.ToString().Length;
            int headerColWidth = widthToPixels((headerCharCount + 1) * 256);
            ret.Add(new KeyValuePair<int, int>(IDX_HEADER_COL_WIDTH, headerColWidth));
            tableWidth += headerColWidth;

            for (int i = firstColumn; i < endColumn; i++)
            {
                int colWidth = widthToPixels(sheet.GetColumnWidth(i));
                ret.Add(new KeyValuePair<int, int>(i, colWidth));
                tableWidth += colWidth;
            }

            ret.Add(new KeyValuePair<int, int>(IDX_TABLE_WIDTH, tableWidth));
            return ret;
        }

        private int widthToPixels(double widthUnits)
        {
            return (int)(Math.Round(widthUnits * 9 / 256));
        }

        private void ensureColumnBounds(ISheet sheet)
        {
            if (gotBounds) return;

            IEnumerator iter = sheet.GetRowEnumerator();
            if (iter.MoveNext()) firstColumn = 0;
            else firstColumn = int.MaxValue;

            endColumn = 0;
            iter.Reset();
            while (iter.MoveNext())
            {
                IRow row = (IRow)iter.Current;
                short firstCell = row.FirstCellNum;
                if (firstCell >= 0)
                {
                    firstColumn = Math.Min(firstColumn, firstCell);
                    endColumn = Math.Max(endColumn, row.LastCellNum);
                }
            }
            gotBounds = true;
        }

        private string GetInlineStyle()
        {
            StringBuilder sbRet = new StringBuilder();

            sbRet.Append("<style type=\"text/css\">\n");
            sbRet.Append(GetStyles());
            sbRet.Append("</style>\n");

            return sbRet.ToString();
        }

        private string GetStyles()
        {
            StringBuilder sbRet = new StringBuilder();

            HashSet<ICellStyle> seen = new HashSet<ICellStyle>();
            for (int i = 0; i < wb.NumberOfSheets; i++)
            {
                ISheet sheet = wb.GetSheetAt(i);
                IEnumerator rows = sheet.GetRowEnumerator();
                while (rows.MoveNext())
                {
                    IRow row = (IRow)rows.Current;
                    foreach (ICell cell in row)
                    {
                        ICellStyle style = cell.CellStyle;
                        if (!seen.Contains(style))
                        {
                            sbRet.Append(GetStyle(style));
                            seen.Add(style);
                        }
                    }
                }
            }

            return sbRet.ToString();
        }

        private string GetStyle(ICellStyle style)
        {
            StringBuilder sbRet = new StringBuilder();

            sbRet.Append(string.Format(".{0} .{1} {{\n", DEFAULTS_CLASS, styleName(style)));
            sbRet.Append(styleContents(style));
            sbRet.Append("}\n");

            return sbRet.ToString();
        }

        private string styleContents(ICellStyle style)
        {
            StringBuilder sbRet = new StringBuilder();

            sbRet.Append(styleOut("text-align", style.Alignment));
            sbRet.Append(styleOut("vertical-align", style.VerticalAlignment));
            sbRet.Append(fontStyle(style));
            sbRet.Append(borderStyles(style));
            sbRet.Append(colorStyles(style));

            return sbRet.ToString();
        }

        private string colorStyles(ICellStyle style)
        {
            StringBuilder sbRet = new StringBuilder();

            //sbRet.Append("還未實現!");

            return sbRet.ToString();
        }

        private string borderStyles(ICellStyle style)
        {
            StringBuilder sbRet = new StringBuilder();

            sbRet.Append(styleOut("border-left", style.BorderLeft));
            /*
             * NPOI有BUG,合並單元格的 border-right 永遠都是 None
             * 我們可以通過設置合並單元格后邊那個單元格的左邊框的解決
             * 如果當前合並單元格已經合並到最后一列了,我們就只能再加一列了,為了不影響打印效果
             * 這最后加的這一列在設置好左邊框后,需要把寬度設置得很小,比如說0.1這樣
             */
            sbRet.Append(styleOut("border-right", style.BorderRight));
            sbRet.Append(styleOut("border-top", style.BorderTop));
            sbRet.Append(styleOut("border-bottom", style.BorderBottom));

            return sbRet.ToString();
        }

        private string fontStyle(ICellStyle style)
        {
            StringBuilder sbRet = new StringBuilder();

            IFont font = style.GetFont(wb);

            if (font.Boldweight == 0)
            {
                sbRet.Append("  font-weight: bold;\n");
            }
            if (font.IsItalic)
            {
                sbRet.Append("  font-style: italic;\n");
            }

            double fontheight = font.FontHeight / 10 - 10;
            if (fontheight == 9)
            {
                //fix for stupid ol Windows
                fontheight = 10;
            }
            sbRet.Append(string.Format("  font-size: {0}pt;\n", fontheight));

            return sbRet.ToString();
        }

        private string styleOut(string k, HorizontalAlignment p)
        {
            return k + ":" + HALIGN.Where(o => o.Key == p).First().Value + ";\n";
        }
        private string styleOut(string k, VerticalAlignment p)
        {
            return k + ":" + VALIGN.Where(o => o.Key == p).First().Value + ";\n";
        }
        private string styleOut(string k, BorderStyle p)
        {
            return k + ":" + BORDER.Where(o => o.Key == p).First().Value + ";\n";
        }

        private string styleName(ICellStyle style)
        {
            if (style == null)
            {
                style = wb.GetCellStyleAt((short)0);
            }
            StringBuilder sb = new StringBuilder();
            sb.Append(string.Format("style_{0}", style.Index));
            return sb.ToString();
        }
    }
}

  javascript部分:

 

最后NPOI在處理日期的時候,還有一個BUG

using NPOI.SS.UserModel;
using NPOI.Util;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using NPOI.SS.Format;

namespace ExcelUtility
{
    /// <summary>
    /// 這個東西是為了解決 NPOI CellFormat 的BUG而存在的。
    /// 它在讀取 日期格式 的時候有時候會報錯。
    /// </summary>
    public class MyCellFormat
    {
        private CellFormat cellformat = null;

        private MyCellFormat(string format) {
            this.cellformat = CellFormat.GetInstance(format);
        }

        public static MyCellFormat GetInstance(string format) {
            return new MyCellFormat(format);
        }

        public CellFormatResult Apply(ICell cell)
        {
            try
            {
                return cellformat.Apply(cell);
            }
            catch (Exception)
            {
                var formatStr = cell.CellStyle.GetDataFormatString();
                var mc = new Regex(@"(yy|M|d|H|s|ms)").Match(formatStr); 
                /*
                 * 目前全部不能正常轉換的日期格式都轉換成 yyyy - MM - dd 的形式
                 * 比如說:【[$-F800]dddd\,\ mmmm\ dd\,\ yyyy】這個格式
                 * 稍微 google 了下( https://msdn.microsoft.com/en-us/library/dd318693(VS.85).aspx)
                 * 這個字符串 0x0800 表示 [System default locale language]
                 * 因時間關系,只能干完手頭的活之后再慢慢研究了。
                 */
                if (mc.Success)
                {
                    return CellFormat.GetInstance("yyyy-MM-dd").Apply(cell);
                }
                else return cellformat.Apply(cell.ToString() + "<!-- This is the bug of NPOI, Maybe you should modify the file which name is \"MyCellFormat.cs\" -->");
            }
        }

        public CellFormatResult Apply(Object v)
        {
            return cellformat.Apply(v);
        }
    }
}

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



猜您在找 C# 利用NPOI 實現Excel轉html C# 實現HTML轉換成圖片的方法 C# Excel轉換成Json工具(含源碼) C#將HTML表格 html文件轉換成excel C#實現文檔轉換成PDF C#實現集合轉換成json格式數據 python實現excel轉換成pdf python實現excel轉換成pdf
 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM
轉換成DataTable NPOI操作excel--以及組件轉換成Pdf