导航

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

上一篇:使用ExcelReport导出Excel

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

概述

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

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

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

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

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

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

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

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

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

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

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

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

源码解析

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

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

1)从Export类开始吧!

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

这便引出了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传过来的“元素格式化器”集合。

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

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

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

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);

            }

        }

    }

}

源码下载:

ExcelReport第二篇:ExcelReport源码解析的更多相关文章

  1. Java中常用的七个阻塞队列第二篇DelayQueue源码介绍

    Java中常用的七个阻塞队列第二篇DelayQueue源码介绍 通过前面两篇文章,我们对队列有了了解及已经认识了常用阻塞队列中的三个了.本篇我们继续介绍剩下的几个队列. 本文主要内容:通过源码学习De ...

  2. 第二章 ConcurrentHashMap源码解析

    注:在看这篇文章之前,如果对HashMap的层不清楚的话,建议先去看看HashMap源码解析. http://www.cnblogs.com/java-zhao/p/5106189.html 1.对于 ...

  3. 第二章 ArrayList源码解析

    一.对于ArrayList需要掌握的七点内容 ArrayList的创建:即构造器 往ArrayList中添加对象:即add(E)方法 获取ArrayList中的单个对象:即get(int index) ...

  4. 第二章 Google guava cache源码解析1--构建缓存器

    1.guava cache 当下最常用最简单的本地缓存 线程安全的本地缓存 类似于ConcurrentHashMap(或者说成就是一个ConcurrentHashMap,只是在其上多添加了一些功能) ...

  5. Shiro源码解析-Session篇

    上一篇Shiro源码解析-登录篇中提到了在登录验证成功后有对session的处理,但未详细分析,本文对此部分源码详细分析下. 1. 分析切入点:DefaultSecurityManger的login方 ...

  6. [源码解析] PyTorch 分布式(8) -------- DistributedDataParallel之论文篇

    [源码解析] PyTorch 分布式(8) -------- DistributedDataParallel之论文篇 目录 [源码解析] PyTorch 分布式(8) -------- Distrib ...

  7. [源码解析] PyTorch分布式优化器(1)----基石篇

    [源码解析] PyTorch分布式优化器(1)----基石篇 目录 [源码解析] PyTorch分布式优化器(1)----基石篇 0x00 摘要 0x01 从问题出发 1.1 示例 1.2 问题点 0 ...

  8. [源码解析] TensorFlow 分布式 DistributedStrategy 之基础篇

    [源码解析] TensorFlow 分布式 DistributedStrategy 之基础篇 目录 [源码解析] TensorFlow 分布式 DistributedStrategy 之基础篇 1. ...

  9. 【MyBatis源码解析】MyBatis一二级缓存

    MyBatis缓存 我们知道,频繁的数据库操作是非常耗费性能的(主要是因为对于DB而言,数据是持久化在磁盘中的,因此查询操作需要通过IO,IO操作速度相比内存操作速度慢了好几个量级),尤其是对于一些相 ...

  10. ArrayList源码解析(二)

    欢迎转载,转载烦请注明出处,谢谢. https://www.cnblogs.com/sx-wuyj/p/11177257.html 自己学习ArrayList源码的一些心得记录. 继续上一篇,Arra ...

随机推荐

  1. linux u-boot跟踪方法总结

    拿到一块板子,其中很重要的一项就是看电路图还有Datasheet. 这个真的很重要,首先你要知道cpu的架构是什么,armv7?arvmv5?还是其他的,哪个公司的芯片?是freescale 还是TI ...

  2. Delphi结构体的扩展,可以自动初始化,反初始化,自定义拷贝函数.

    转载:http://www.raysoftware.cn/?p=518&utm_source=tuicool 恭贺Delphi XE7诞生,Delphi XE7在编译器内部集成了我之前所实现的 ...

  3. 【原创】ReFlux细说

    ReFlux细说 Flux作为一种应用架构(application architecture)或是设计模式(pattern),阐述的是单向数据流(a unidirectional data flow) ...

  4. java对txt文件内容追加

    package com.test; import java.io.FileOutputStream; /** * 对txt文件在文本追加内容 * @author Wdnncey * */ public ...

  5. NDK学习4: Eclipse HelloWorld

    NDK学习4: Eclipse HelloWorld 1.配置Eclipse NDK环境  Window->preferences->android->ndk   2.新建Andro ...

  6. Yii中的错误及异常处理

    Yii中的错误及异常处理 Yii已经默认已经在CApplication上实现了异常和错误的接管,这是通过php的set_exception_handler, set_error_handler实现的. ...

  7. C# webservice 编写、发布、调用

    采用的工具VS2010生成工程 1. 生成webservice工程:建 ASP.NET 空WEB 应用程序. 2. 在建好的ASP.NET 空WEB应用程序中新建项“web 服务”. 完成上述内容工程 ...

  8. ccf559c

    题意:给出一个矩阵棋盘,大小不超过10^5.上面有n个非法点(n<=2000)不可以踩,从左上角开始走到右下角,每次只能向下或者向右移动.问有多少种走法.结果对10^9+7取模. 分析: 组合数 ...

  9. Light OJ 1032

    数位dp,许多数位dp需要统计某种模式(子串)出现的数量,这种题通常需要在递归参数中加入高位已经出现过的模式的数量. #include <cstdio> #include <cstr ...

  10. angular.js初探

    2015年7月27日 22:26:35 星期一 用在我论坛里的小栗子: 先列出来一级回帖, 点击帖子前边的"查看回复"按钮无刷新的去请求该帖子的所有回复 首先要引用js文件, 我这 ...