2019独角兽企业重金招聘Python工程师标准>>>

导航

目   录:基于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);
}
}
}
}

源码下载:

下载地址:https://github.com/hanzhaoxin/ExcelReport

转载于:https://my.oschina.net/hanzhaoxin/blog/373277

ExcelReport第二篇:ExcelReport源码解析相关推荐

  1. Android源码解析(一)动画篇-- Animator属性动画系统

    Android源码解析-动画篇 Android源码解析(一)动画篇-- Animator属性动画系统 Android源码解析(二)动画篇-- ObjectAnimator Android在3.0版本中 ...

  2. dubbo源码解析-集群容错架构设计

    前言 本来是想把整个dubbo源码解析一次性弄完,再做成一个系列来发布的,但是正巧最近有位好朋友要去杭州面试,就和我交流了一下.本着对dubbo源码略有心得的心态,在交流过程中也发表了个人的一些粗劣的 ...

  3. clickhouse原理解析与开发实战 pdf_重识SSM,“超高频面试点+源码解析+实战PDF”,一次性干掉全拿走...

    重识SSM,"超高频面试点"+"源码解析"+"实战PDF",一次性干掉全拿走!! 01 超高频面试点知识篇 1.1 Spring超高频面试点 ...

  4. 前端单页路由《stateman》源码解析

    <stateman>是波神的一个超级轻量的单页路由,拜读之后写写自己的小总结. stateman的github地址 github.com/leeluolee/s- 简单使用 以下文章全部以 ...

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

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

  6. 2022-10-24 ClickHouse 源码解析-查询引擎经典理论

    ClickHouse 源码解析: 综述 ClickHouse 源码解析: MergeTree Write-Path ClickHouse 源码解析: MergeTree Read-Path Click ...

  7. Dubbo源码解析 —— Router

    作者:肥朝 原文地址:http://www.jianshu.com/p/278e782eef85 友情提示:欢迎关注公众号[芋道源码].????关注后,拉你进[源码圈]微信群和[肥朝]搞基嗨皮. 友情 ...

  8. android输入法01:SoftKeyboard源码解析01

      本文主要介绍android自带输入法实例SoftKeyboard的源码,共分为两篇:第一篇为SoftKeyboard框架概述,第二篇为源码注释. 1.IMF简介 一个IMF结构中包含三个主要的部分 ...

  9. Laravel源码解析之从入口开始

    前言 提升能力的方法并非使用更多工具,而是解刨自己所使用的工具.今天我们从Laravel启动的第一步开始讲起. 入口文件 laravel是单入口框架,所有请求必将经过index.php define( ...

  10. spring boot2.x设置session有效时间_Spring 源码解析 Scopes 之 Request 、Session 、Application...

    (给ImportNew加星标,提高Java技能) 转自:开源中国,作者:麦克斯 链接:my.oschina.net/wang5v/blog/3017934 Request.Session.Applic ...

最新文章

  1. 《神经架构搜索NAS》最新进展综述,25页pdf
  2. 用node.js启动mock.js
  3. Android技术点增长 - 收藏集 - 掘金
  4. 3D目标检测2021
  5. SAP 限制出货数量小于销售订单数量
  6. php 实验室管理系统,生物信息实验室管理系统-Metalims安装
  7. 调用webservice超时问题的解决
  8. 解决ansible报错“msg“: “Failed to import docker-py - cannot import name __version__.
  9. 11 父子组件数据关系与状态提升
  10. xp修改时间同步服务器地址,xp时间同步服务器地址更换
  11. Linux下Tomcat设置自动启动
  12. java 用户控件_C#自定义控件VS用户控件
  13. xml学习总结(四)
  14. plsql 快捷键设置
  15. Ardour:专业的数字音频任务站
  16. FileReader读取本地文件
  17. 【原创·总结】影响sql查询性能的因素
  18. tableau学生版注册流程
  19. 用matlab解系统框图,控制系统框图(请教matlab中怎么画控制系统流程框图?)
  20. linux播放csf文件

热门文章

  1. 解决js动态改变dom元素属性后页面及时渲染问题
  2. 【开发环境】 irun(ncverilog)无法dump fsdb波形问题解决方法
  3. VS2012 无法启动IIS Express Web服务器的解决方案
  4. pycharm安装第三方包问题解决
  5. Java基础-异常-throws Exception-抛声明
  6. 异常:java.lang.NoSuchMethodError: org.apache.poi.ss.usermodel.Workbook.getCellStyleAt
  7. centos7执行 wget命令: command not found的两种解决方法
  8. 建立.NET Core控制台应用程序以输出EXE?
  9. 如何检查对象是否为数组?
  10. 如何设置Win11账户密码有效期?Win11账户密码使用期限设置教程