[进阶实践] 表格生成描述

        本篇文章我们来练习使用openpyxl完成一个小需求,打造一个简单的低代码工具。在数据报表或日常工作中,我们经常会遇到收集表格中的数据生成报告的需求,如果是日报,每天都要从繁复庞大的表格中誊写数据,一不留心就出错了,能不能写一个程序可以输入表格输出对应的描述呢?当然是可以的,但是有很多类似的需求,每次都要重新修改一次程序吗?答案是否定的,此时我们就要定义一套自己的操作语言来灵活应对变化的需求。

        看完本篇文章你将获得 成果见这里 的一个低代码工具,文本设计的是一种最简单的程式,不需要按照编译器的步骤来设计,定义一行是一个操作语句,只有常量定义语句和两个操作符,没有函数。

1. 读取代码并执行

        需要注意的是 windows的txt编码可能是 utf-8 也有可能是 utf-8 BOM,utf-8 BOM在文件头会有一个不可见字符,如果用utf-8的方式去读utf-8 BOM文件就会出问题(显示的时候没问题,因为不可见字符输出时也不可见,但是程序中进行比较就会出问题),所以此处统一使用utf-8 BOM的方式读,即使读utf-8文件也不会出问题。handle_line是解析每行语句的函数。

program_path = sys.argv[1].lstrip()
with open(program_path,'r',encoding='utf_8_sig') as f:
    line = f.readline()
    while line:
        handle_line(line)
        line = f.readline()

2. 处理语句

这个层级的函数主要负责从一行语句中解析出操作符和参数,调用对应的函数(此处可以考虑将所有语句封装起来,耦合度会更低一些,该功能较简单,笔者暂未单独封装,只将复杂逻辑封装为单独函数)


def handle_line(line:str):
    line = line.lstrip()
    if(len(line) == 0 or line[0] == "#" or line[0] == '\n'):
        return
    if(line.find("@FROM") != -1):
        k,v = line.split("@FROM")
        k = k.strip()
        v = v.strip()
        # print(k+":"+v)
        if(v.find("@USING") != -1):
            table, sheet = v.split("@USING")
            table = table.strip()
            sheet = sheet.strip()
            v = BaseExcel(table, data_only = True,  sheet=sheet)
        # print(k)
        context[k] = v
    elif line.find("@IS") != -1:
        k,v = line.split("@IS")
        k = k.strip()
        v = v.strip()
        context[k] = v
    elif(line.find("@TO") != -1):
        operation,target = line.split("@TO")
        operation = operation.strip()
        target = target.strip()
        # print(line)
        if line.find("@INJECT") != -1:
            table, template = operation.split("@INJECT")
            table = table.strip()
            template = template.strip()
            res = gen_desc(context[template],context[table])
        else:
            _,template = operation.split("@AUTOFILL")
            template = template.strip()
            res = gen_desc(context[template])
        with open(target, 'a') as f:
            f.write(res)

3. 生成描述 

        到了本文的核心部分,如何将模板中插的数据桩(【位置-格式】)标记转化为数据,本文设计了以下方式:

  1. 使用【和】将模板分成几段,放入列表
  2. 遍历段,将文字符合数据桩格式的段替换为数据(利用table.get_cell函数)
  3. 将段拼接起来,得到结果
def get_value(notation:str, table:BaseExcel):
    note_list = notation.split('-')
    # print(note_list)
    if len(note_list) == 2:
        position, data_type = note_list
    else:
        table, position, data_type = note_list
        table = context[table]
    return table.get_cell(position, data_type)

def gen_desc(template:str, table:BaseExcel=None):
    template = re.split(r'[【】]',template)
    for i in range(len(template)):
        if re.match(".*-?([A-Z]+[0-9]+)-(D|F[0-9]+|P[0-9]+|C)", template[i]):
            template[i] = get_value(template[i], table)
        else:
            pass
    return "".join(template)  

4. 简单的Excel操作类

        该类提供与excel相关的所有操作,也是常量中实际存储的数据格式,由于该处需求较为简单,本类只实现了所需功能。

import openpyxl
from utils import nomalize
class BaseExcel():
    def __init__(self, path, data_only=True, sheet=None):
        self.path = path
        self.sheet = sheet
        self.workbook = openpyxl.load_workbook(path, data_only=data_only)
		
    def using(self,sheet:str):
        self.sheet = self.workbook[sheet]
    def update_cell(self, sheet:str, position:str, value:any):
        self.workbook[sheet][position] = value
    def get_cell_with_sheet(self, sheet:str, position:str, format:str=None):
        value = self.workbook[sheet][position]
        value = nomalize(value)
        return value
    def get_cell(self, position:str, format:str=None):
        value = self.workbook[self.sheet][position]
        value = value.value
        value = nomalize(value, format)
        return value

5. 数据格式化

        该部分代码很简单,但是也是核心需求,报表是一种展示,其展示的数据很有可能与表格中的形式不同,能够显式的指定数据是保证程序可用的关键一步,可见办公自动化看似简单的需求里面也包含很多需要注意的点,需要深入理解业务才能设计出真正能够提高效率的办公自动化软件。

def nomalize(value:any, format:str):
    if value is None:
        raise Exception("请检查数据单元格位置,读取到空值")
    if format is None:return value
    if format == 'D':
        return str(int(round(value,0)))
    if format[0] == 'F':
        format = int(format[1:])
        return str(round(value, format))
    if format[0] == 'P':
        format = int(format[1:])
        return str(round(value*100, format))+"%"
    else:
        return value

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值