feat: excel
This commit is contained in:
0
nc_http/core/excel/__init__.py
Normal file
0
nc_http/core/excel/__init__.py
Normal file
9
nc_http/core/excel/constants.py
Normal file
9
nc_http/core/excel/constants.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
EXCEL_MIME_TYPE = [
|
||||||
|
'application/excel',
|
||||||
|
'application/vnd.ms-excel',
|
||||||
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||||
|
]
|
||||||
|
EXCEL_SUFFIX = [
|
||||||
|
'.xls',
|
||||||
|
'.xlsx',
|
||||||
|
]
|
25
nc_http/core/excel/excel_format.py
Normal file
25
nc_http/core/excel/excel_format.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
class ExcelFormat:
|
||||||
|
|
||||||
|
title = {
|
||||||
|
'font': '黑体', 'font_size': 18, 'bold': True, 'align': 'center', 'valign': 'vcenter',
|
||||||
|
}
|
||||||
|
remark = {
|
||||||
|
'font': '黑体', 'font_size': 8, 'bold': False, 'align': 'right', 'valign': 'vcenter',
|
||||||
|
'bottom': 1, 'top_color': 'white', 'top': 1,
|
||||||
|
}
|
||||||
|
header = {
|
||||||
|
'font': '宋体', 'font_size': 8, 'bold': True, 'align': 'center', 'valign': 'vcenter',
|
||||||
|
'border': 1, 'text_wrap': True,
|
||||||
|
}
|
||||||
|
body = {
|
||||||
|
'font': '宋体', 'font_size': 6, 'bold': False, 'align': 'center', 'valign': 'vcenter',
|
||||||
|
'border': 1, 'text_wrap': True,
|
||||||
|
}
|
||||||
|
big_header = {
|
||||||
|
'font': '宋体', 'font_size': 10, 'bold': False, 'align': 'left', 'valign': 'vcenter',
|
||||||
|
'border': True, 'text_wrap': False,
|
||||||
|
}
|
||||||
|
big_body = {
|
||||||
|
'font': '宋体', 'font_size': 10, 'bold': False, 'align': 'left', 'valign': 'vcenter',
|
||||||
|
'border': False, 'text_wrap': False,
|
||||||
|
}
|
55
nc_http/core/excel/excel_reader.py
Normal file
55
nc_http/core/excel/excel_reader.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
import xlrd
|
||||||
|
from werkzeug.datastructures import FileStorage
|
||||||
|
|
||||||
|
from nc_http.core.excel.constants import EXCEL_SUFFIX
|
||||||
|
from nc_http.core.excel.exceptions import InvalidSuffixError
|
||||||
|
|
||||||
|
|
||||||
|
class ExcelReader(object):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def read_excel(file_src=None, row_index=0, file_contents=None, sheet_name=None):
|
||||||
|
if file_src:
|
||||||
|
data = xlrd.open_workbook(file_src)
|
||||||
|
elif file_contents:
|
||||||
|
data = xlrd.open_workbook(file_contents=file_contents)
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
sheets = data.sheets()
|
||||||
|
table = sheets[0]
|
||||||
|
# 根据 sheet 名来选择 sheet(可选)
|
||||||
|
if sheet_name:
|
||||||
|
for sheet in sheets:
|
||||||
|
if sheet.name == sheet_name:
|
||||||
|
table = sheet
|
||||||
|
|
||||||
|
nrows = table.nrows
|
||||||
|
rows = []
|
||||||
|
for rownum in range(row_index, nrows):
|
||||||
|
row = table.row_values(rownum)
|
||||||
|
rows.append(row)
|
||||||
|
return rows
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def read(file_handler):
|
||||||
|
if isinstance(file_handler, FileStorage):
|
||||||
|
filename = file_handler.filename.strip(r'"')
|
||||||
|
suffix = os.path.splitext(filename)[-1]
|
||||||
|
if suffix not in EXCEL_SUFFIX:
|
||||||
|
raise InvalidSuffixError
|
||||||
|
result = ExcelReader.read_excel(file_contents=file_handler.read())
|
||||||
|
else:
|
||||||
|
filename = str(file_handler) if not hasattr(file_handler, 'name') else file_handler.name
|
||||||
|
suffix = os.path.splitext(filename)[-1]
|
||||||
|
if suffix not in EXCEL_SUFFIX:
|
||||||
|
raise InvalidSuffixError
|
||||||
|
result = ExcelReader.read_excel(filename)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
s = ExcelReader.read(open(r'D:\test.xlsx'))
|
||||||
|
print(s)
|
109
nc_http/core/excel/excel_writer.py
Normal file
109
nc_http/core/excel/excel_writer.py
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
from xlsxwriter import Workbook
|
||||||
|
|
||||||
|
from nc_http.core.excel.excel_format import ExcelFormat
|
||||||
|
|
||||||
|
|
||||||
|
class ExcelWriter(object):
|
||||||
|
title_format_setting = ExcelFormat.title
|
||||||
|
remark_format_setting = ExcelFormat.remark
|
||||||
|
header_format_setting = ExcelFormat.big_header
|
||||||
|
body_format_setting = ExcelFormat.big_body
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def pack_excel(cls, data=None, **options):
|
||||||
|
output = BytesIO()
|
||||||
|
workbook = Workbook(output, {'in_memory': True})
|
||||||
|
cls.write(workbook, data, **options)
|
||||||
|
workbook.close()
|
||||||
|
output.seek(0)
|
||||||
|
return output
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_excel(cls, file, data=None, **options):
|
||||||
|
data = data or []
|
||||||
|
workbook = Workbook(file)
|
||||||
|
cls.write(workbook, data, **options)
|
||||||
|
workbook.close()
|
||||||
|
return file
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def write(cls, workbook, data, headers=None, merges=None, row_stretches=None, col_stretches=None,
|
||||||
|
paper=None, style_formats=None):
|
||||||
|
data = data or []
|
||||||
|
headers = headers or []
|
||||||
|
if headers:
|
||||||
|
data = headers + data
|
||||||
|
if not data:
|
||||||
|
return
|
||||||
|
|
||||||
|
worksheet = workbook.add_worksheet()
|
||||||
|
worksheet.center_horizontally()
|
||||||
|
worksheet.set_margins(left=0.2, right=0.2, top=0.4, bottom=0.2)
|
||||||
|
# worksheet.set_column('A:Z', 20)
|
||||||
|
# worksheet.set_default_row(16)
|
||||||
|
if paper:
|
||||||
|
worksheet.set_paper(paper)
|
||||||
|
|
||||||
|
style_formats = style_formats or {}
|
||||||
|
|
||||||
|
header_format = style_formats.get('header') or cls.header_format_setting
|
||||||
|
body_format = style_formats.get('body') or cls.body_format_setting
|
||||||
|
|
||||||
|
col_style = {}
|
||||||
|
if col_stretches:
|
||||||
|
'''
|
||||||
|
col_stretches = [
|
||||||
|
[0, 0, 25, {'text_wrap': True}],
|
||||||
|
[1, 1, 10],
|
||||||
|
]
|
||||||
|
'''
|
||||||
|
for stretch in col_stretches:
|
||||||
|
if len(stretch) != 4:
|
||||||
|
continue
|
||||||
|
if stretch[0] == stretch[1]:
|
||||||
|
col_style[stretch[0]] = stretch[3]
|
||||||
|
elif stretch[0] < stretch[1]:
|
||||||
|
for col in range(stretch[0], stretch[1] + 1):
|
||||||
|
col_style[col] = stretch[3]
|
||||||
|
|
||||||
|
for row_num in range(len(data)):
|
||||||
|
row = data[row_num]
|
||||||
|
if isinstance(row, list):
|
||||||
|
for col_num in range(len(row)):
|
||||||
|
column = row[col_num]
|
||||||
|
if headers and row_num < len(headers):
|
||||||
|
worksheet.write(row_num, col_num, column, workbook.add_format(header_format.copy()))
|
||||||
|
else:
|
||||||
|
# 单元格样式与列样式合并
|
||||||
|
_body_format = body_format.copy()
|
||||||
|
if col_style:
|
||||||
|
_col_style = col_style.get(col_num, {})
|
||||||
|
_body_format.update(_col_style)
|
||||||
|
worksheet.write(row_num, col_num, column, workbook.add_format(_body_format))
|
||||||
|
# 单元格合并
|
||||||
|
if merges:
|
||||||
|
for merge in merges:
|
||||||
|
if len(merge) > 5:
|
||||||
|
format_item = workbook.add_format(merge[5])
|
||||||
|
else:
|
||||||
|
format_item = workbook.add_format(header_format)
|
||||||
|
worksheet.merge_range(*merge[:5], cell_format=format_item)
|
||||||
|
# 行高指定
|
||||||
|
if row_stretches:
|
||||||
|
for stretch in row_stretches:
|
||||||
|
worksheet.set_row(*stretch)
|
||||||
|
# 列宽指定
|
||||||
|
if col_stretches:
|
||||||
|
for stretch in col_stretches:
|
||||||
|
stretch = stretch.copy()
|
||||||
|
if len(stretch) >= 4:
|
||||||
|
stretch[3] = workbook.add_format(stretch[3])
|
||||||
|
worksheet.set_column(*stretch)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
ExcelWriter.create_excel(r'E:\test.xlsx', [[1, 2, 2, 2], [1, 2, 2, 2], [1, 2, 2, 2], [1, 2, 2, 2]])
|
||||||
|
|
||||||
|
ExcelWriter.pack_excel([[1, 2, 2, 2], [1, 2, 2, 2], [1, 2, 2, 2], [1, 2, 2, 2]])
|
6
nc_http/core/excel/exceptions.py
Normal file
6
nc_http/core/excel/exceptions.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
class InvalidFileError(Exception):
|
||||||
|
"""无效的 excel 文件"""
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidSuffixError(InvalidFileError):
|
||||||
|
"""无效的后缀名"""
|
19
nc_http/tests/excel.py
Normal file
19
nc_http/tests/excel.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import unittest
|
||||||
|
|
||||||
|
from nc_http.core.excel.excel_reader import ExcelReader
|
||||||
|
from nc_http.core.excel.excel_writer import ExcelWriter
|
||||||
|
|
||||||
|
|
||||||
|
class ExcelTestCase(unittest.TestCase):
|
||||||
|
filename = r'D:\test.xlsx'
|
||||||
|
|
||||||
|
def test_writer(self):
|
||||||
|
ExcelWriter.create_excel(self.filename, [[1, 2, 2, 2], [1, 2, 2, 2], [1, 2, 2, 2], [1, 2, 2, 2]])
|
||||||
|
ExcelWriter.pack_excel([[1, 2, 2, 2], [1, 2, 2, 2], [1, 2, 2, 2], [1, 2, 2, 2]])
|
||||||
|
|
||||||
|
def test_reader(self):
|
||||||
|
ExcelReader.read(open(self.filename))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
Reference in New Issue
Block a user