将HTML表格内容导出到Excel - 服务器端






4.83/5 (5投票s)
获取表格的HTML结构并导出到真正的Excel文件 - 服务器端处理。
引言
任何报告解决方案的一个非常普遍的需求是,一旦用户在浏览器中以 HTML 格式查看了报告,就允许用户将报告内容导出为 Excel 或 PDF。有一些 Nuget 包可以很好地处理 PDF(例如 wkhtmltopdf
),但我从未找到一个像样的 Excel 导出工具。市面上有一些 jQuery 库声称可以完成这项工作,但对于任何客户端工具,我最大的问题是用户尝试打开下载的 Excel 文件时收到的“文件格式”警告。
背景
我决定编写一些 C# 代码,它接受一个包含表格 HTML 代码的字符串,最终将生成为 Excel 文件供用户下载。用户只需在其浏览器中单击导出按钮,即可将已渲染的 HTML 报告以真正的 xlsx 文件格式下载到用户的浏览器窗口中。
我们将使用 NPOI v2.3.0,它作为 nuget 包在 Visual Studio 中可用。网址是 http://npoi.codeplex.com/。这是著名的 Apache POI 库的 .NET 实现,用于读取和写入 MS Office 文档。
我们将使用 System.Xml.Linq
(XElement
及其他相关类)在服务器端(C#)解析 HTML 表格结构。
每个 Excel 单元格将由一个名为 ExcelCellMeta
的 POCO 类表示,该类具有 5 个 public
属性。
public class ExcelCellMeta
{
public string Content { get; set; }
public string StyleName { get; set; }
public int ColumnIndex { get; set; }
public int ColSpan { get; set; }
public int RowSpan { get; set; }
}
- Content 代表 Excel 单元格的内容。
StyleName
是代码中定义的 Excel 样式对象名称。目前我只有有限数量的 Excel 样式。如果未指定,我们的引擎将应用默认样式,我在代码中将其命名为“Content
”。ColumnIndex
是 Excel 单元格的起始索引。如果一个 Excel 单元格跨越两个 Excel 列 C 和 D,则其索引为 2(对应于 C,即单元格的起始列)。ColSpan
是一个单元格跨越的列数。RowSpan
是一个单元格跨越的行数。
整个 Excel 工作表由一个名为 ExcelMeta
的 POCO 类表示,该类具有两个 public
属性。
public class ExcelMeta
{
public List<List<ExcelCellMeta>> Meta { get; set; }
public double[] ColumnWidths { get; set; }
}
- 名为 Meta 的属性是
ExcelCellMeta
的列表。内部列表代表一个 Excel 行(ExcelCellMeta
对象的集合);外部列表代表一组行或一个 Excel 工作表。 ColumnWidths
是一个数组,用于设置每个 Excel 列的宽度。它应包含与您需要的 Excel 列数完全相同的元素。大多数情况下,您会希望将这些宽度保持在 1.0 到 2.0 之间,因为在代码中,我将把这些宽度乘以 5120。这只是我的偏好。
为了使用此工具,您当然可以在代码中设置 ExcelMeta
对象,完全无需设置 HTML 表格结构并将其解析为 ExcelMeta
对象,但这正是我着手要做的事情。
还有另一个类,名为 ExcelHelper
。这个类负责将 HTML 表格结构解析为 ExcelMeta
并生成 Excel 文件。它有 5 个样式对象
public ICellStyle heading1 { get; set; }
public ICellStyle heading2 { get; set; }
public ICellStyle rowHead { get; set; }
public ICellStyle columnHead { get; set; }
public ICellStyle content { get; set; }
和两个方法
public ExcelMeta GetExcelMeta(string theTableHtml);
public byte[] GetExcelDocument(ExcelMeta excelMeta);
您将调用第一个方法将 HTML 表格结构转换为 ExcelMeta
对象,然后将其传递给第二个方法以获取字节数组形式的 Excel 文档。
Using the Code
为了装饰表格的 HTML,我们需要使用一些自定义 HTML 属性。
“table
”标签必须“属性化”为 'data-xls-columns
' 和 'data-xls-column-widths
',如下所示
<table class="table table-bordered" data-xls-columns="5"
data-xls-column-widths="1,1.5,1.5,1,1">
在这里,我们告诉我们的 ExcelMeta
解析器,我们希望在 Excel 工作表中包含 5 列,并且第 2 列和第 3 列的宽度应该是其他列的 1.5 倍。
如果希望从 Excel 工作表中排除某个表格行,则“tr
”可以属性化为 'data-xls-exclude="True"
'。
<tr data-xls-exclude="True">
“td
”可以属性化为 rowspan
、colspan
、data-xls-col-index
和 data-xls-class
。rowspan
和 colspan
的作用与它们在 HTML 中的作用相同,是我们使用的唯一两个标准的 HTML 属性。data-xls-col-index
用于指示 Excel 单元格的起始列索引。A 列对应 0,B 列对应 1,C 列对应 2,依此类推。data-xls-class
的值必须是我们在 ExcelHelper
类中定义的 5 个 ICellStyle
对象之一,即 heading1
、heading2
、rowHead
、columnHead
或 content
。
<tr>
<td colspan=2 rowspan=2 data-xls-col-index="0"
data-xls-class="rowHead" class="rowHead"> A 2x2 cell </td>
<td colspan=3 data-xls-col-index="2" > A 1X3 cell </td>
</tr>
<tr>
<td colspan=3 data-xls-col-index="2" >A 1X3 cell </td>
</tr>
您注意到第二行中的第一列是如何被“data-xls-col-index
”设置为 2
(C 列)的吗?这是因为第一行中的 2x2 单元格在第一行和第二行中都占据了“data-xls-col-index
”0 和 1(A 和 B)。
一个示例 HTML 表格可能如下所示
<table class="table table-bordered" data-xls-columns="5"
data-xls-column-widths="1,1.5,1.5,1,1">
<tbody><tr data-xls-exclude="True">
<td colspan="5" style="text-align:right;">
<a id="exportLink" href="#" class="btn btn-default">
<span class="glyphicon glyphicon-th"></span> Export to Excel</a></td>
</tr>
<tr>
<td colspan="5" data-xls-col-index="0"
data-xls-class="heading1" class="heading1">Searching criteria</td>
</tr>
<tr>
<td colspan="2" data-xls-col-index="0" data-xls-class="rowHead"
class="rowHead">Search Param 1</td>
<td colspan="3" data-xls-col-index="2">8821650318629</td>
</tr>
<tr>
<td colspan="2" data-xls-col-index="0" data-xls-class="rowHead"
class="rowHead">Period</td>
<td colspan="3" data-xls-col-index="2">04/04/2017 - 03/05/2017</td>
</tr>
<tr>
<td colspan="2" rowspan="2" data-xls-col-index="0"
data-xls-class="rowHead" class="rowHead">Search Param 2</td>
<td colspan="3" data-xls-col-index="2">Call Details</td>
</tr>
<tr>
<td colspan="3" data-xls-col-index="2">GPS Location Information</td>
</tr>
<tr>
<td colspan="2" rowspan="2" data-xls-col-index="0"
data-xls-class="rowHead" class="rowHead">Search Param 3</td>
<td colspan="3" data-xls-col-index="2">MS - ORIGINATING</td>
</tr>
<tr>
<td colspan="3" data-xls-col-index="2">MS - TERMINATING</td>
</tr>
<tr>
<td colspan="5" data-xls-col-index="0"
data-xls-class="heading1" class="heading1">Summary Report</td>
</tr>
<tr>
<td colspan="5" data-xls-col-index="0"
data-xls-class="heading2" class="heading2">Originating Call Details</td>
</tr>
<tr>
<td data-xls-col-index="0" data-xls-class="columnHead"
class="columnHead">Number calling </td>
<td data-xls-col-index="1" data-xls-class="columnHead"
class="columnHead">Number called</td>
<td data-xls-col-index="2" data-xls-class="columnHead"
class="columnHead">Date(YYYY/MM/DD HH:MM:SS) </td>
<td data-xls-col-index="3" data-xls-class="columnHead"
class="columnHead">Call Duration(sec) </td>
<td data-xls-col-index="4" data-xls-class="columnHead"
class="columnHead">Country </td>
</tr>
<tr>
<td data-xls-col-index="0">8821650318629 </td>
<td data-xls-col-index="1">393662555630 </td>
<td data-xls-col-index="2">2017/05/02 16:31:17 </td>
<td data-xls-col-index="3">0 </td>
<td data-xls-col-index="4">Mediterranean Sea </td>
</tr>
<tr>
<td data-xls-col-index="0">8821650318629 </td>
<td data-xls-col-index="1">393662555630 </td>
<td data-xls-col-index="2">2017/05/02 03:40:30 </td>
<td data-xls-col-index="3">0 </td>
<td data-xls-col-index="4">Italy </td>
</tr>
<tr>
<td colspan="5" data-xls-col-index="0"
data-xls-class="heading2" class="heading2">Terminating Call Details</td>
</tr>
<tr>
<td data-xls-col-index="0" data-xls-class="columnHead"
class="columnHead">Number calling </td>
<td data-xls-col-index="1" data-xls-class="columnHead"
class="columnHead">Number called </td>
<td data-xls-col-index="2" data-xls-class="columnHead"
class="columnHead">Date(YYYY/MM/DD HH:MM:SS) </td>
<td data-xls-col-index="3" data-xls-class="columnHead"
class="columnHead">Call Duration(sec) </td>
<td data-xls-col-index="4" data-xls-class="columnHead"
class="columnHead">Country </td>
</tr>
<tr>
<td data-xls-col-index="0" colspan="5" class="NoRecord">No Record found.</td>
</tr>
<tr>
<td data-xls-col-index="0" colspan="5"
data-xls-class="heading2" class="heading2">GPS Location Information </td>
</tr>
<tr>
<td data-xls-col-index="0" data-xls-class="columnHead"
class="columnHead">IMEI </td>
<td data-xls-col-index="1" data-xls-class="columnHead"
class="columnHead">Date(YYYY/MM/DD HH:MM:SS) </td>
<td data-xls-col-index="2" data-xls-class="columnHead"
class="columnHead">Country </td>
<td data-xls-col-index="3" data-xls-class="columnHead"
class="columnHead">Latitude </td>
<td data-xls-col-index="4" data-xls-class="columnHead"
class="columnHead">Longitude </td>
</tr>
<tr>
<td data-xls-col-index="0">3560130010789500 </td>
<td data-xls-col-index="1">2017/05/03 16:02:51 </td>
<td data-xls-col-index="2">ITALY </td>
<td data-xls-col-index="3">45.220586 </td>
<td data-xls-col-index="4">12.282395 </td>
</tr>
</tbody></table>
在我的 MVC 应用程序中,我使用 class
来方便地将 HTML 代码从 View
提交到 Controller
。
public class ExcelModel
{
[AllowHtml]
public string Data { get; set; }
}
/
您可以使用此 jQuery 来提交 HTML
function exportToExcel(id)
{
$('#Data').remove();
$(id).attr("action","@Url.Action("ExportExcel")");
tableHtml = $(id).html();
$(id).append("<input id="Data" name="Data" type="hidden" />");
$('#Data').val(tableHtml);
$(id).submit();
}
$(document).ready(function () {
$("#exportLink").click(function () { exportToExcel("#exportTable");});
});
用于将 Excel 文件提供给视图的 MVC 控制器方法如下所示
public ActionResult ExportExcel(ExcelModel excelModel)
{
ExcelHelper excelHelper= new ExcelHelper();
ExcelMeta meta = excelHelper.GetExcelMeta(excelModel.Data);
byte[] content = excelHelper.GetExcelDocument(meta);
FileContentResult result = new FileContentResult
(content, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
{
FileDownloadName = "CARS.xlsx"
};
return result;
}
您可以在此处下载 VS2017 解决方案。
历史
- 2017 年 5 月 9 日:初始版本