• ExcelReport第二篇:ExcelReport源码解析


    导航

    目   录:基于NPOI的报表引擎——ExcelReport

    上一篇:使用ExcelReport导出Excel

    下一篇:扩展元素格式化器

    概述

    针对上一篇随笔收到的反馈,在展开对ExcelReport源码解析之前,我认为把编写该组件时的想法分享给大家是有必要的。

    编写该组件时,思考如下:

    1)要实现样式、格式与数据的彻底分离。

    为什么要将样式、格式与数据分离呢?恩,你不妨想一想在生成报表时,那些是变的而那些又是不变的。我的结论是:变的是数据。

    有了这个想法,很自然的想到用模板去承载不变的部分(常量内容的样式、格式、数据及变量内容的样式、格式),在程序中控制变的部分(变量内容的数据)。

    这里以上一篇中的例子标识:

    image

    变量内容已使用粉红色边框标出,其余为常量内容。好了,相信“内容的数据”大家都知道那个是那个的。下面截图,内容的样式和格式。

    image

    image

    现在我们回到上篇中使用的模板,相信你应该知道它承载那些东西了。

    image

    啰嗦了这么多,总结一下样式、格式与数据分离的好处:它让我们编写程序时关注更少(只需关心“变量内容的数据”)。

    2)关注“变量内容的数据”填充到模板的最小单元(我们把这些称之为元素格式化器),然后利用合成模式(Composite)搞定整个文档的数据填充。

    为什么要抽象一个“元素格式化器”的概念呢?我们看数据源,我们有可能要将某个类型的数据填充到某个单元格、也可能将一个集合填充到多行、有可能将一张图片填充到某个位置、也有可能就将某个字符串合并到某个单元格的内容中......如此种种。那么它们有什么共同点呢?它们都是填充“变量内容的数据”到模板的。

    3)外部调用,统一从一个处理入口处理。

    源码解析

    有了上面的背景,这张UML想必不难理解了。

    ExcelReport

    当然,如果你还是觉得复杂, 没关系。我会先介绍一下这里面几个重点关系。

    1)从Export类开始吧!

    这是一个静态类,非常简单。只有两个静态方法:ExportToLocal()、ExportToWeb()分别将生成的文件导出到本地和Web。这多半是废话了,下面是重点:

    image

    这便引出了SheetFormatterContainer类,SheetFormatterContainer类是何许也?

    • SheetFormatterContainer是一个存储“格式化一个Sheet用到的元素格式化器集合”的容器。

    说到这,顺便说下:ElementFormatter类和SheetFormatterContext类。

    • ElementFormatter:元素格式化器。
    • SheetFormatterContext:Sheet格式化上下文。

    回到Export:

    public static byte[] ExportToBuffer(string templateFile, params SheetFormatterContainer[] containers)
    {
        var workbook = LoadTemplateWorkbook(templateFile);
        foreach (var container in containers)
        {
            var sheet = workbook.GetSheet(container.SheetName);
            var context = new SheetFormatterContext(sheet, container.Formatters);
            context.Format();
        }
        return workbook.SaveToBuffer();
    }

    如上代码,在执行导出的过程中,将每一个SheetFormatterContainer对象转换成了SheetFormatterContext对象。然后SheetFormatterContext对象调用自身的Format()方法格式化Sheet。

    public void Format()
    {
        if (null == Sheet || null == Formatters)
        {
            return;
        }
        foreach (var formatter in Formatters)
        {
            formatter.Format(this);
        }
    }

    Formatters就是从SheetFormatterContainer传过来的“元素格式化器”集合。

    image

    抽象的“元素格式化器”:

    image

    至此,ExcelReport组件的核心部分已经介绍完成了,下面附上代码(如下代码仅供参考,了解ExcelReport组件最新动态,请到GitHub下载最新源码)。

    2)附--(ExcelReport1.0源码)

    image

    SheetFormatterContainer.cs

    /*
     类:SheetFormatterContainer
     描述:Sheet中元素的格式化器集合
     编 码 人:韩兆新 日期:2015年01月17日
     修改记录:
    
    
    
    */
     
    using System.Collections.Generic;
     
    namespace ExcelReport
    {
        public class SheetFormatterContainer
        {
            #region 成员字段及属性
     
            private string sheetName;
     
            public string SheetName
            {
                get { return sheetName; }
            }
     
            private IEnumerable<ElementFormatter> formatters;
     
            public IEnumerable<ElementFormatter> Formatters
            {
                get { return formatters; }
            }
     
            #endregion 成员字段及属性
     
            #region 构造函数
     
            public SheetFormatterContainer(string sheetName, IEnumerable<ElementFormatter> formatters)
            {
                this.sheetName = sheetName;
                this.formatters = formatters;
            }
     
            #endregion 构造函数
        }
    }

    SheetFormatterContext.cs

    /*
     类:SheetFormatterContext
     描述:Sheet格式化的上下文
     编 码 人:韩兆新 日期:2015年01月17日
     修改记录:
    
    
    
    */
     
    using System.Collections.Generic;
    using NPOI.SS.UserModel;
     
    namespace ExcelReport
    {
        public class SheetFormatterContext
        {
            #region 成员字段及属性
     
            private int _increaseRowsCount = 0;
     
            public ISheet Sheet { get; set; }
     
            public IEnumerable<ElementFormatter> Formatters { get; set; }
     
            #endregion 成员字段及属性
     
            #region 构造函数
     
            public SheetFormatterContext()
            {
            }
     
            public SheetFormatterContext(ISheet sheet, IEnumerable<ElementFormatter> formatters)
            {
                this.Sheet = sheet;
                this.Formatters = formatters;
            }
     
            #endregion 构造函数
     
            #region 获取指定行当前行标
     
            /// <summary>
            /// 获取指定行当前行标
            /// </summary>
            /// <param name="rowIndexInTemplate">指定行在模板中的行标</param>
            /// <returns>当前行标</returns>
            public int GetCurrentRowIndex(int rowIndexInTemplate)
            {
                return rowIndexInTemplate + _increaseRowsCount;
            }
     
            #endregion 获取指定行当前行标
     
            #region 在指定行后插入一行(并将指定行作为模板复制样式)
     
            /// <summary>
            /// 在指定行后插入一行(并将指定行作为模板复制样式)
            /// </summary>
            /// <param name="templateRowIndex">模板行在模板中的行标</param>
            public void InsertEmptyRow(int templateRowIndex)
            {
                var templateRow = Sheet.GetRow(GetCurrentRowIndex(templateRowIndex));
                var insertRowIndex = GetCurrentRowIndex(templateRowIndex + 1);
                if (insertRowIndex < Sheet.LastRowNum)
                {
                    Sheet.ShiftRows(insertRowIndex, Sheet.LastRowNum, 1, true, false);
                }
                var newRow = Sheet.CreateRow(GetCurrentRowIndex(templateRowIndex + 1));
                _increaseRowsCount++;
                foreach (var cell in templateRow.Cells)
                {
                    newRow.CreateCell(cell.ColumnIndex).CellStyle = cell.CellStyle;
                }
            }
     
            #endregion 在指定行后插入一行(并将指定行作为模板复制样式)
     
            #region 清除指定行单元格内容
     
            /// <summary>
            /// 清除指定行单元格内容
            /// </summary>
            /// <param name="rowIndex">指定行在模板中的行标</param>
            public void ClearRowContent(int rowIndex)
            {
                var row = Sheet.GetRow(GetCurrentRowIndex(rowIndex));
                foreach (var cell in row.Cells)
                {
                    cell.SetCellValue(string.Empty);
                }
            }
     
            #endregion 清除指定行单元格内容
     
            #region 删除指定行
     
            /// <summary>
            /// 删除指定行
            /// </summary>
            /// <param name="rowIndex">指定行在模板中的行标</param>
            public void RemoveRow(int rowIndex)
            {
                var row = Sheet.GetRow(GetCurrentRowIndex(rowIndex));
                Sheet.RemoveRow(row);
            }
     
            #endregion 删除指定行
     
            #region 格式化Sheet
     
            /// <summary>
            /// 格式化Sheet
            /// </summary>
            public void Format()
            {
                if (null == Sheet || null == Formatters)
                {
                    return;
                }
                foreach (var formatter in Formatters)
                {
                    formatter.Format(this);
                }
            }
     
            #endregion 格式化Sheet
        }
    }

    ElementFormatter.cs

    /*
     类:ElementFormatter
     描述:(元素)格式化器(抽象)
     编 码 人:韩兆新 日期:2015年01月17日
     修改记录:
    
    
    
    */
     
    using System;
    using NPOI.SS.UserModel;
     
    namespace ExcelReport
    {
        public abstract class ElementFormatter
        {
            #region 设置单元格值
     
            protected virtual void SetCellValue(ICell cell, object value)
            {
                if (null == cell)
                {
                    return;
                }
                if (null == value)
                {
                    cell.SetCellValue(string.Empty);
                }
                else
                {
                    var valueTypeCode = Type.GetTypeCode(value.GetType());
     
                    switch (valueTypeCode)
                    {
                        case TypeCode.String:   //字符串类型
                            cell.SetCellValue(Convert.ToString(value));
                            break;
     
                        case TypeCode.DateTime: //日期类型
                            cell.SetCellValue(Convert.ToDateTime(value));
                            break;
     
                        case TypeCode.Boolean:  //布尔型
                            cell.SetCellValue(Convert.ToBoolean(value));
                            break;
     
                        case TypeCode.Int16:    //整型
                        case TypeCode.Int32:
                        case TypeCode.Int64:
                        case TypeCode.Byte:
                        case TypeCode.Single:   //浮点型
                        case TypeCode.Double:
                        case TypeCode.UInt16:   //无符号整型
                        case TypeCode.UInt32:
                        case TypeCode.UInt64:
                            cell.SetCellValue(Convert.ToDouble(value));
                            break;
     
                        default:
                            cell.SetCellValue(string.Empty);
                            break;
                    }
                }
            }
     
            #endregion 设置单元格值
     
            #region 格式化操作
     
            public abstract void Format(SheetFormatterContext context);
     
            #endregion 格式化操作
        }
    }

    CellFormatter.cs

    /*
     类:CellFormatter
     描述:单元格(元素)格式化器
     编 码 人:韩兆新 日期:2015年01月17日
     修改记录:
    
    
    
    */
     
    using System.Drawing;
     
    namespace ExcelReport
    {
        public class CellFormatter : ElementFormatter
        {
            #region 成员字段及属性
     
            private Point _cellPoint;
            private object _value;
     
            #endregion 成员字段及属性
     
            #region 构造函数
     
            public CellFormatter(Point cellPoint, object value)
            {
                _cellPoint = cellPoint;
                _value = value;
            }
     
            public CellFormatter(int rowIndex, int columnIndex, object value)
            {
                _cellPoint = new Point(rowIndex, columnIndex);
                _value = value;
            }
     
            #endregion 构造函数
     
            #region 格式化操作
     
            public override void Format(SheetFormatterContext context)
            {
                var rowIndex = context.GetCurrentRowIndex(_cellPoint.X);
                var row = context.Sheet.GetRow(rowIndex);
                if (null == row)
                {
                    row = context.Sheet.CreateRow(rowIndex);
                }
                var cell = row.GetCell(_cellPoint.Y);
                if (null == cell)
                {
                    cell = row.CreateCell(_cellPoint.Y);
                }
                SetCellValue(cell, _value);
            }
     
            #endregion 格式化操作
        }
    }

    TableFormatter.cs

    /*
     类:TableFormatter
     描述:表格(元素)格式化器
     编 码 人:韩兆新 日期:2015年01月17日
     修改记录:
    
    
    
    */
     
    using System;
    using System.Collections.Generic;
     
    namespace ExcelReport
    {
        public class TableFormatter<TSource> : ElementFormatter
        {
            #region 成员字段
     
            private int _templateRowIndex;
            private IEnumerable<TSource> _dataSource;
            private List<TableColumnInfo<TSource>> _columnInfoList;
     
            #endregion 成员字段
     
            #region 构造函数
     
            public TableFormatter(int templateRowIndex, IEnumerable<TSource> dataSource, params TableColumnInfo<TSource>[] columnInfos)
            {
                _templateRowIndex = templateRowIndex;
                _dataSource = dataSource;
                _columnInfoList = new List<TableColumnInfo<TSource>>();
                if (null != columnInfos && columnInfos.Length > 0)
                {
                    _columnInfoList.AddRange(columnInfos);
                }
            }
     
            #endregion 构造函数
     
            #region 格式化操作
     
            public override void Format(SheetFormatterContext context)
            {
                context.ClearRowContent(_templateRowIndex); //清除模板行单元格内容
                if (null == _columnInfoList || _columnInfoList.Count <= 0 || null == _dataSource)
                {
                    return;
                }
                foreach (TSource rowSource in _dataSource)
                {
                    var row = context.Sheet.GetRow(context.GetCurrentRowIndex(_templateRowIndex));
                    foreach (TableColumnInfo<TSource> colInfo in _columnInfoList)
                    {
                        var cell = row.GetCell(colInfo.ColumnIndex);
                        SetCellValue(cell, colInfo.DgSetValue(rowSource));
                    }
                    context.InsertEmptyRow(_templateRowIndex);  //追加空行
                }
                context.RemoveRow(_templateRowIndex);   //删除空行
            }
     
            #endregion 格式化操作
     
            #region 添加列信息
     
            public void AddColumnInfo(TableColumnInfo<TSource> columnInfo)
            {
                _columnInfoList.Add(columnInfo);
            }
     
            public void AddColumnInfo(int columnIndex, Func<TSource, object> dgSetValue)
            {
                _columnInfoList.Add(new TableColumnInfo<TSource>(columnIndex, dgSetValue));
            }
     
            #endregion 添加列信息
        }
    }

    ExportHelper.cs

    /*
     类:ExportHelper
     描述:导出助手类
     编 码 人:韩兆新 日期:2015年01月17日
     修改记录:
    
    
    
    */
     
    using System.IO;
    using NPOI.SS.UserModel;
     
    namespace ExcelReport
    {
        internal static class ExportHelper
        {
            #region 加载模板,获取IWorkbook对象
     
            private static IWorkbook LoadTemplateWorkbook(string templateFile)
            {
                using (var fileStream = new FileStream(templateFile, FileMode.Open, FileAccess.Read)) //读入excel模板
                {
                    return WorkbookFactory.Create(fileStream);
                }
            }
     
            #endregion 加载模板,获取IWorkbook对象
     
            #region 将IWorkBook对象转换成二进制文件流
     
            private static byte[] SaveToBuffer(this IWorkbook workbook)
            {
                using (var ms = new MemoryStream())
                {
                    workbook.Write(ms);
                    ms.Flush();
                    ms.Position = 0;
                    return ms.GetBuffer();
                }
            }
     
            #endregion 将IWorkBook对象转换成二进制文件流
     
            #region 导出格式化处理后的文件到二进制文件流
     
            public static byte[] ExportToBuffer(string templateFile, params SheetFormatterContainer[] containers)
            {
                var workbook = LoadTemplateWorkbook(templateFile);
                foreach (var container in containers)
                {
                    var sheet = workbook.GetSheet(container.SheetName);
                    var context = new SheetFormatterContext(sheet, container.Formatters);
                    context.Format();
                }
                return workbook.SaveToBuffer();
            }
     
            #endregion 导出格式化处理后的文件到二进制文件流
        }
    }

    Export.cs

    /*
     类:Export
     描述:导出
     编 码 人:韩兆新 日期:2015年01月17日
     修改记录:
    
    
    
    */
     
    using System;
    using System.IO;
    using System.Web;
     
    namespace ExcelReport
    {
        public static class Export
        {
            #region 导出到本地
     
            public static void ExportToLocal(string templateFile, string targetFile, params SheetFormatterContainer[] containers)
            {
                #region 参数验证
     
                if (string.IsNullOrWhiteSpace(templateFile))
                {
                    throw new ArgumentNullException("templateFile");
                }
                if (string.IsNullOrWhiteSpace(targetFile))
                {
                    throw new ArgumentNullException("targetFile");
                }
                if (!File.Exists(templateFile))
                {
                    throw new FileNotFoundException(templateFile + " 文件不存在!");
                }
     
                #endregion 参数验证
     
                using (FileStream fs = File.OpenWrite(targetFile))
                {
                    var buffer = ExportHelper.ExportToBuffer(templateFile, containers);
                    fs.Write(buffer, 0, buffer.Length);
                    fs.Flush();
                }
            }
     
            #endregion 导出到本地
     
            #region 导出到Web
     
            public static void ExportToWeb(string templateFile, string targetFile, params SheetFormatterContainer[] containers)
            {
                #region 参数验证
     
                if (string.IsNullOrWhiteSpace(templateFile))
                {
                    throw new ArgumentNullException("templateFile");
                }
                if (string.IsNullOrWhiteSpace(targetFile))
                {
                    throw new ArgumentNullException("targetFile");
                }
                if (!File.Exists(templateFile))
                {
                    throw new FileNotFoundException(templateFile + " 文件不存在!");
                }
     
                #endregion 参数验证
     
                HttpContext.Current.Response.ContentType = "application/vnd.ms-excel";
                HttpContext.Current.Response.AppendHeader("Content-Disposition", "attachment;filename=" + HttpUtility.UrlEncode(targetFile, System.Text.Encoding.UTF8));
                HttpContext.Current.Response.BinaryWrite(ExportHelper.ExportToBuffer(templateFile, containers));
                HttpContext.Current.Response.End();
            }
     
            #endregion 导出到Web
        }
    }

    Parameter.cs

    /*
     类:Parameter
     描述:参数信息
     编 码 人:韩兆新 日期:2015年01月17日
     修改记录:
    
    
    
    */
     
    using System.Drawing;
     
    namespace ExcelReport
    {
        public class Parameter
        {
            public Parameter()
            {
            }
     
            public Parameter(string sheetName, string parameterName, Point cellPoint)
            {
                this.SheetName = sheetName;
                this.ParameterName = parameterName;
                this.CellPoint = cellPoint;
            }
     
            public string SheetName { set; get; }
     
            public string ParameterName { set; get; }
     
            public Point CellPoint { set; get; }
        }
    }

    ParameterCollection.cs

    /*
     类:ParameterCollection
     描述:模板中参数信息的集合
     编 码 人:韩兆新 日期:2015年01月17日
     修改记录:
    
    
    
    */
     
    using System.Collections.Generic;
    using System.Drawing;
    using System.IO;
    using System.Xml.Serialization;
     
    namespace ExcelReport
    {
        public class ParameterCollection
        {
            protected List<Parameter> parameterList = new List<Parameter>();
     
            public Point this[string sheetName, string parameterName]
            {
                get
                {
                    foreach (Parameter parameter in parameterList)
                    {
                        if (parameter.SheetName.Equals(sheetName) && parameter.ParameterName.Equals(parameterName))
                        {
                            return parameter.CellPoint;
                        }
                    }
                    return new Point();
                }
                set
                {
                    bool isExist = false;
                    foreach (Parameter parameter in parameterList)
                    {
                        if (parameter.SheetName.Equals(sheetName) && parameter.ParameterName.Equals(parameterName))
                        {
                            isExist = true;
                            parameter.CellPoint = value;
                        }
                    }
                    if (!isExist)
                    {
                        parameterList.Add(new Parameter(sheetName, parameterName, value));
                    }
                }
            }
     
            public void Load(string xmlPath)
            {
                string fileName = xmlPath;
                if (File.Exists(fileName))
                {
                    XmlSerializer xmlSerializer = new XmlSerializer(parameterList.GetType());
                    using (Stream reader = new FileStream(fileName, FileMode.Open, FileAccess.Read))
                    {
                        parameterList = xmlSerializer.Deserialize(reader) as List<Parameter>;
                    }
                }
                else
                {
                    parameterList = new List<Parameter>();
                }
            }
     
            public void Save(string xmlPath)
            {
                string fileName = xmlPath;
                FileInfo fileInfo = new FileInfo(fileName);
                DirectoryInfo directoryInfo = fileInfo.Directory;
                if (!directoryInfo.Exists)
                {
                    directoryInfo.Create();
                }
                XmlSerializer xmlSerializer = new XmlSerializer(parameterList.GetType());
                using (Stream writer = new FileStream(fileName, FileMode.Create, FileAccess.Write))
                {
                    xmlSerializer.Serialize(writer, parameterList);
                }
            }
        }
    }

    源码下载:

    image

  • 相关阅读:
    Django模型层之ORM
    bzoj1037 [ZJOI2008]生日聚会
    bzoj4423 [AMPPZ2013]Bytehattan
    bzoj1018 [SHOI2008]堵塞的交通
    关于弦图一些问题的解法
    bzoj1006 [HNOI2008]神奇的国度
    bzoj2561 最小生成树
    bzoj3720 Gty的妹子树
    bzoj3489 A simple rmq problem
    bzoj4066 简单题
  • 原文地址:https://www.cnblogs.com/hanzhaoxin/p/4240398.html
Copyright © 2020-2023  润新知