转载连接:http://www.fengfly.com/plus/view-197341-1.html
最近做一个小的报表系统,功能本身没什么。最后客户要求一个打印功能,所谓打印,就是按照页面上报表的样子,一模一样的为其生成Excel文件。
再也不想为了构造结构一样的Excel表格而再次考虑繁琐数据逻辑了!于是乎冒出了这样的一个想法:我要是能获得页面上的报表table,那么只要分析其结构,不就可以构造出相应的Excel表格来吗?
思来想去,觉得这应该是一条可以走的通的路,于是便着手寻找实现的办法。终于,发现WebRequest和WebResponse,其分别代表一个Web请求和服务器基于请求的回应,而这个回应包含服务器返回的数据流,从数据流便可获取想要的报表table。
整个Excel生成功能的思路如下图所示:
涉及WebRequest、WebResponse的核心代码如下:
1
WebRequest wr=
WebRequest.Create(psUrl);
2
HttpWebRequest oRequest
=
wr
as
HttpWebRequest;
3
if
(
null
==
oRequest)
return
null
;
4
5
HttpWebResponse oResponse ;
=
(HttpWebResponse) oRequest.GetResponse();
6
string
sResponseContent
=
string
.Empty;
7
if
(oResponse.StatusCode
==
HttpStatusCode.OK)
8
{
9
using
(StreamReader sr
=
new
StreamReader(oResponse.GetResponseStream(),Encoding.UTF8))
10
{
11
sResponseContent
=
sr.ReadToEnd();
//
sResponseContent,保存了服务器返回的数据流字符串内容
12
sr.Close();
13
}
14
}
15
oResponse.Close();
特别需要说明的是,在从数据流向字符串转化的过程中,其内会残留许多\r\t之类的转义字符,我们需要将其剔除。
1
sResponseContent
=
Regex.Replace (sResponseContent,
"
[\n\r\t]
"
,
""
);
本篇至此,下篇将着重介绍table到Excel cell的转化过程。
在上一篇文章中,已经解决了如何获取html table结构的问题。在本篇文章中,我们着力于table结构的解析。
Html table的结构我们大家都很熟悉,那么在另一端如何构造一个结构,让Excel可以很好的接受和处理呢?
直观的看,一个完整Excel的内容是由位于各个单元格(Cell)中的内容组合而成的。而每个单元格(Cell)都有相应X、Y坐标来标示其位置。也就是说,一个Excel文件实质上就是许多Cell构成的集合,每个Cell用坐标属性确定位置,用内容属性存储内容。
基于此,我设计了最基本的Cell结构:
◆ X坐标
◆ Y坐标
◆ 合并列情况
◆ 合并行情况
◆ 内容
构成Excel的最基本的结构已经确定,下一步摆在我们面前的就是将html table转化为Excel Cell集合。
Html table中的每个td节点对应一个Excel单元格,其内容不必说,行、列的合并情况也自可由td的rowspan、colspan属性得出,转化的关键点就在于由table的tr td结构定位Excel单元格位置,即X、Y坐标。
Y坐标容易确定,即td所在tr的行数。至于一td的X坐标,其要受到两方面因素的影响:与该td同处一tr,但位于其之前(反映在表格视觉上即其左侧td)td的占位情况和该td所在tr的之前tr中某些td的跨行情况。
基于此种考虑,定位td的X坐标需经过两个过程的推导:用于处理左侧td占位影响的横向推导(Horizontal Deduction)和处理之前行跨行td影响的纵向推导(Vertical Deduction)。
以下图所示table为例,展示两次推导过程。
横向推导(Horizontal Deduction) 一次横向推导(Horizontal Deduction)限定在一tr范围内。整个过程基于递归的原理,递归模型如下:
核心代码为:
1
private
int
HorizontalDeduction(HtmlNode phnTd)
2
{
3
HtmlNode hnPreviousSibling
=
phnTd.PreviousSibling;
4
while
(hnPreviousSibling
!=
null
&&
hnPreviousSibling.Name
!=
phnTd.Name)
5
{
6
hnPreviousSibling
=
hnPreviousSibling.PreviousSibling;
7
}
8
9
if
(hnPreviousSibling
!=
null
)
10
{
11
int
nColSpan
=
hnPreviousSibling.GetAttributeValue(
"
colspan
"
,
1
);
12
return
HorizontalDeduction(hnPreviousSibling)
+
nColSpan;
13
}
14
15
return
0
;
16
}
经过横向推导,各td的X、Y坐标如下图所示:
纵向推导(Vertical Deduction) 一次纵向推导的过程可以描述为(当前推导td用A表示):
找到A之前的行tr中与A具有相同X坐标的td节点B
if (B.rowspan>(A.Y-B.Y))
{
X+=B.colspan,即A的X坐标向后推B.colspan的位置
同时,与A同处一tr但在其后边的td节点均应向后推B.colspan个位移
}
对td节点A反复执行这样的一个过程,直到确定A无需再次移动。
纵向推导核心代码为:
1
bool
bActedPush
=
false
;
2
3
do
4
{
5
int
nComparedItemIndex
=
-
1
;
6
for
(
int
j
=
i
-
1
; j
>=
0
; j
--
)
7
{
8
if
(plstCells[j]._nStartX
==
oCurrentCell._nStartX)
9
{
10
nComparedItemIndex
=
j;
11
break
;
12
}
13
}
14
15
if
(nComparedItemIndex
>=
0
)
16
{
17
if
(plstCells[nComparedItemIndex]._nRowSpan
>
(oCurrentCell._nStartY
-
plstCells[nComparedItemIndex]._nStartY))
18
{
19
oCurrentCell._nStartX
+=
plstCells[nComparedItemIndex]._nColSpan;
20
21
bActedPush
=
true
;
22
23
for
(
int
k
=
i
+
1
; k
<
plstCells.Count; k
++
)
24
{
25
if
(plstCells[k]._nStartY
==
oCurrentCell._nStartY)
26
{
27
plstCells[k]._nStartX
+=
plstCells[nComparedItemIndex]._nColSpan;
28
}
29
}
30
}
31
else
32
{
33
bActedPush
=
false
;
34
}
35
}
36
else
37
{
38
bActedPush
=
false
;
39
}
40
}
41
while
(bActedPush);
以示例table中的four td为例,其经过纵向推导过程后的坐标位置情况如下图:
关于示例代码的几点说明:
1、 在示例代码中,我通过一个Config文件对生成的Excel的文件名、其内报表的内容和位置做了一些控制。基本内容如下:
<
ExcelDocument
>
<
BaseInfo
>
<
FileName
>
Sample Excel File
FileName
>
<
SheetCount
>
1
SheetCount
>
BaseInfo
>
<
Tables
>
<
ExcelTable
>
<
TableName
>
示例表一
TableName
>
<
WhichSheet
>
1
WhichSheet
>
<
StartX
>
2
StartX
>
<
StartY
>
2
StartY
>
<
Source
>
Sample_Page_1.aspx
Source
>
ExcelTable
>
Tables
>
ExcelDocument
>
2、 在解析html的过程中,我使用了HtmlAgilityPack解析工具。感兴趣的朋友可以研究一下。地址是这里:http://htmlagilitypack.codeplex.com/。
3、 HtmlAgilityPack解析html的过程中,其将html标签之间的空隙也会看成是一个节点,为内容为空字符串的文本节点,这点大家应注意。
4、 该示例代码基本上是一个完整的功能,并且和系统中其他模块耦合度很小。有类似需求的朋友可以拿来直接用。