2021-07-15

博客介绍了如何使用Python的openpyxl库将Excel转换为HTML,同时处理合并单元格和隐藏行的问题。作者首先创建了一个HTML模板,然后遍历Excel表格,检查行是否隐藏并处理合并单元格。在遇到隐藏行时,选择跳过或调整合并单元格的rowspan。最终,作者遇到了在不同浏览器中样式显示不一致的问题,特别是隐藏行在Safari中的表现。博客还分享了相关的CSS样式代码。
摘要由CSDN通过智能技术生成

目录

最近有个需求,把每天自动生成的Excel 报告通过邮件发送的时候,在正文里也要展示出来。emmm…

这就只能使用exchangelib的HTMLBody来发送正文了。

接下来就上网狗哥一下excel转html,看到有人写过excel转html的方法,但是没有合并单元格和隐藏行的情况,没D用。既然没现成的,那就自己写一个出来吧,搞起来!

运行环境

  1. MacOS Big Sur
  2. Python 3.7.3rc1

第三方库

  1. openpyxl

准备工作

写好一份HTML模版,主要写好固定的表头和各种样式,放到一个html文件里读取,或者直接放到py文件中。

干货

这里主要是写tbody部分

准备了个函数

用来每读取一次单元格,合并一次html string。

table_html = '<tbody>'

def join_html(html_: str):
    global table_html
    table_html = '\n'.join((table_html, html_))
    return table_html

带隐藏行的版本

from openpyxl import load_workbook
from openpyxl.worksheet.cell_range import MultiCellRange, CellRange
# 加载Excel文件,读取sheet
wb = load_workbook(EXCEL_FP)
sht = wb[SHT_NAME]

# 获取合并单元格的dict数据
a: MultiCellRange = sht.merged_cells
merged_celld = {i.coord.split(':')[0]: i for i in a.ranges}
# 遍历每一行, row_ : int
for row_ in range(3, sht.max_row):
    # 如果当前行隐藏,则set hidden_row_
    hidden_row_ = True if sht.row_dimensions[row_].hidden else False
    join_html(''.join(('<tr', ' class="hide"' if hidden_row_ else '', f' ln={row_}', '>')))
    # 遍历当前行每一格, col_ : 'A'
    for col_ in map(chr, range(65, 65 + sht.max_column)):
        # 开始写行内容
        # A1 B1
        coord_ = f"{col_}{row_}"
        v_ = sht[coord_].value
        # N列有空格子,把None转化成空str,防止误会
        if col_ == 'N' and v_ is None:
            v_ = ''
        # EIM列数据是百分比格式
        elif col_ in 'EIM' and v_ is not None:
            v_ = f'{v_*100:.2f}%'
        # 这里用not None,因为会有int 0
        if v_ is not None:
            # 如果有值,则查看是否是合并单元格, 如果是合并的单元格,其值为None,并且已经被span,不再需要处理
            is_merged: CellRange = merged_celld.get(coord_)
            # 如果是合并单元格,则获取合并单元格长度和宽度
            if is_merged:
                merged_size_c = is_merged.size.get('columns')
                merged_size_r = is_merged.size.get('rows')
            else:
                # 初始化变量为0
                merged_size_c = 0
                merged_size_r = 0
            td_ = ''.join(('<td',
                           f' rowspan="{merged_size_r}"' if merged_size_r else '',
                           f' colspan="{merged_size_c}"' if merged_size_c else '',
                           f'>{v_}</td>'
                           ))
            join_html(td_)
    else:
        # 遍历完该行后
        join_html('</tr>')
# 遍历完所有行后,tbody收尾
table_html = join_html('</tbody>')

这个时候的table_html已经是想要的html版本表格了。
但是这个时候发现了个问题,我是在Google Chrome中调试的css样式,给想要隐藏的行添加了样式,给内容特别多的单元格设置了对齐(默认是居中)和溢出滑动。

.hide {
    visibility: collapse
}
.fitem {
    text-align: left;
    vertical-align: top;
    overflow: scroll;
}

效果很好,就和Excel表中的效果一模一样。不料拿到Safari中,隐藏的行如果有被合并的单元格,这一行就只会隐藏当前行被合并单元格之外的单元格,并且留白,并不上移与上一行对接。这不是Excel展示的效果。css了解不多,所以我就考虑隐藏的行就不要了,于是有了下面改动👇

自动过滤掉hide行

from openpyxl import load_workbook
from openpyxl.worksheet.cell_range import MultiCellRange, CellRange
# 加载Excel文件,读取sheet
wb = load_workbook(EXCEL_FP)
sht = wb[SHT_NAME]

# 获取合并单元格的dict数据
a: MultiCellRange = sht.merged_cells
merged_celld = {i.coord.split(':')[0]: i for i in a.ranges}
# 遍历每一行, row_ : int
for row_ in range(3, sht.max_row):
    # 如果当前行隐藏,则set hidden_row_
    hidden_row_ = True if sht.row_dimensions[row_].hidden else False
    if hidden_row_:
        continue
    join_html(''.join(('<tr', f' ln={row_}', '>')))
    # 遍历当前行每一格, col_ : 'A'
    for col_ in map(chr, range(65, 65 + sht.max_column)):
        # 开始写行内容
        # A1 B1
        coord_ = f"{col_}{row_}"
        v_ = sht[coord_].value
        # N列有空格子,把None转化成空str,防止误会
        if col_ == 'N' and v_ is None:
            v_ = ''
        # EIM列数据是百分比格式
        elif col_ in 'EIM' and v_ is not None:
            v_ = f'{v_*100:.2f}%'
        # 这里用not None,因为会有int 0
        if v_ is not None:
            # 如果有值,则查看是否是合并单元格, 如果是合并的单元格,其值为None,并且已经被span,不再需要处理
            is_merged: CellRange = merged_celld.get(coord_)
            # 如果是合并单元格,则获取合并单元格长度和宽度
            if is_merged:
                merged_size_c = is_merged.size.get('columns')
                merged_size_r = is_merged.size.get('rows')
                # 这里需要获取到合并单元格的最后一行行号
                merged_bottom = is_merged.max_row
                for cell_r in range(row_, merged_bottom+1):
                	# 然后开始过滤合并的行,发现一行隐藏,rowspan-1,最后得出的是正确的rowspan
                    if sht.row_dimensions[cell_r].hidden:
                        merged_size_r -= 1
            else:
                # 初始化变量为0
                merged_size_c = 0
                merged_size_r = 0
            td_ = ''.join(('<td',
                           f' rowspan="{merged_size_r}"' if merged_size_r else '',
                           f' colspan="{merged_size_c}"' if merged_size_c else '',
                           f'>{v_}</td>'
                           ))
            join_html(td_)
    else:
        # 遍历完该行后
        join_html('</tr>')
# 遍历完所有行后,tbody收尾
table_html = join_html('</tbody>')

最后看起来大致是想要的效果了,但是还是有个小问题:
我原本是设置了表头列宽,内容特别多的表格会以该列宽进行撑高,把所有内容都显示出来。这个时候overflow: auto/scroll/hidden都不管用了,因为它没溢出😭。
经过n次排除法找问题,发现只有当前行下有隐藏行,那么这些行所共有的合并单元格内容才会溢出,尝试改变td的display属性,可以溢出,但是只要display不是table-cell,整个表格就塌的像迈阿密的公寓一样。

这是我用的一些样式:

table {
            table-layout: fixed;
            border-collapse: collapse;
        }

        thead {
            height: 21px;
            color: white;
            font-size: 12.0px;
            font-weight: 900;
            text-align: center;
            vertical-align: middle;
            background: #595959;
        }

        td {
            word-break: break-all;
            word-wrap: break-word;
            border: 1px solid black
        }

        .hide {
            visibility: collapse
        }

        .fitem {
            text-align: left;
            vertical-align: top;
            overflow: scroll;
        }

如果有前端玩的比较好的,可以尝试帮忙改改样式,可以让表格不塌,内容也能溢出。不胜感激。

声明

本文版权所属 @大耳朵图图画画 如需非商业性转载,请保留署名。如需商业性转载出版,请直接和我联系。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值