win32com专题:提纲序号生成解决方案

2023年有一部分时间在与win32comdoc/docx打交道,主要完成文章改写、文章生成方面的项目。其中,对提纲序号的生成的需求一直存在,经过一段时间的积累和完善,形成了一套自用的提纲序号生成工具,用到现在感觉也还可以,本次复盘就再盘一盘它。

序号生成的特点

序号具有多样性,如一、1.1(一)1、1.等,还具有连续性和等级性。不用多说,接触文文章的人都能理解。这些纷杂的特点堆在一起导致刚开始两眼一抹黑,干脆只用使用穷举解决:直接把序号枚举到50级,因为目前所接触到的文章提纲序号顶天了也才没见过超过30的,经过实践,最终使用生成器解决了各种情形下提纲序号生成的问题。

形成的解决方案(生成器)

中文序号

能生成一、二、三、至九十九,或带括号的,如(一)

CHINESE_MAP = {1: '一', 2: '二', 3: '三', 4: '四', 5: '五', 6: '六', 
                       7: '七', 8: '八', 9: '九', 10: '十'}

CHINESE_BTS_MAP = {1: '(一)', 2: '(二)', 3: '(三)', 4: '(四)', 5: '(五)', 6: '(六)', 
                       7: '(七)', 8: '(八)', 9: '(九)', 10: '(十)'}

# 中文标题序号
def chinese_numerals_generator(mark=True):
    start=1
    while True:
        if start <= 10:
            base = CHINESE_MAP[start]
            yield base if not mark else f'{base}、'
            start += 1
        else:
            # 倍数, 余数
            multiple, remainder = divmod(start, 10)
            # 10的倍数
            if remainder == 0:
                base = f'{CHINESE_MAP[multiple]}十'
                yield base if not mark else f'{base}、'
            # 10的倍数 + 余数
            else:
                base = f'{CHINESE_MAP[multiple]}{CHINESE_MAP[remainder]}'
                yield base if not mark else f'{base}、'
            start += 1

# 中文标题序号带括号
def chinese_numerals_with_bts():
    start = 1
    while True:

        if start <= 10:
            yield CHINESE_BTS_MAP[start]
            start += 1
        else:
            # 倍数, 余数
            multiple, remainder = divmod(start, 10)
            # 10的倍数
            if remainder == 0:
                yield f'({CHINESE_MAP[multiple]}十)'
            # 10的倍数 + 余数
            else:
                yield f'({CHINESE_MAP[multiple]}{CHINESE_MAP[remainder]})'
            start += 1

数字 + 符号

生成的类型为:1 1. 1、,使用参数控制符号类型

# 阿拉伯数字序号
def numerals_generator(mark_type=2):
    """
    @param mark_type: 1.序号加、 2. 序号加. 3.仅序号
    """
    if mark_type > 3:
        raise ValueError('mark_type must be 1 to 3')
    
    _map = {
        1: '、',
        2: '.',
        3: None
    }
    start = 1
    type_ = _map.get(mark_type, None)

    while True:
        yield start if not mark_type else f'{start}{type_}'
        start += 1

数字 + 括号

生成的类型为:(1) (1),前者为英文括号,后者为中文括号

 阿拉伯数字带括号序号 英文括号
def numerals_with_enbts_generator(start=1):
    while True:
        yield f"({start})"
        start += 1

# 阿拉伯数字带括号序号 中文括号
def numerals_with_cnbts_generator(start=1):
    while True:
        yield f"({start})"
        start += 1

当然,这两者还可以合二为一,不过一直感觉语义明确比节省代码的优先度要高一些。

二级标题类型的数字

生成的类型为:1.1,参数控制第一位数字

# 多数字序号 二级标题 如 1.1
def multiple_numerals2_generator(start=1):
    count=1
    while True:
        yield f"{start}.{count}"
        count += 1
三级标题类型的数字

生成的类型为:1.1.1,参数控制第一和第二位数字

# 多数字序号 三级标题 如 1.1.1
def multiple_numerals3_generator(start=1, middle=1):
    count=1
    while True:
        yield f"{start}.{middle}.{count}"
        count += 1

使用方法

使用非常简单,先调用函数,再调用next方法即可逐个获取序号

demo = chinese_numerals_generator()
next(demo)

进一步优化

后来对该方法进行了优化,统一了入口,只暴露一个函数outline_seq_maker

def outline_seq_maker(
        type: Literal=['CHINESE', 'CHINESE_BTS', 'PURE_NUM', 'NUM_CN_BTS', 'NUM_EN_BTS', 'MULTI_NUM2', 'MULTI_NUM3'], 
        start=1, 
        middle=1, 
        mark=True,
        mark_type=2.
    ) -> Generator:

    _map = {
        'CHINESE': partial(chinese_numerals_generator, mark=mark),
        'CHINESE_BTS': chinese_numerals_with_bts,
        'PURE_NUM': partial(numerals_generator, mark_type=mark_type),
        'NUM_CN_BTS': partial(numerals_with_cnbts_generator, start=start),
        'NUM_EN_BTS': partial(numerals_with_enbts_generator, start=start),
        'MULTI_NUM2': partial(multiple_numerals2_generator, start=start),
        'MULTI_NUM3': partial(multiple_numerals3_generator, start=start, middle=middle),
    }
    return _map[type]()

不管使用哪种序号生成器,调用outline_seq_maker即可获得,通过参数type来映射不同的序号生成器,考虑到它们入参的不同,使用偏函数partial一下,_map[type]()就能实现函数的调用,而无需考虑参数不同的情形。使用方法:

g = outline_seq_maker('CHINESE')
next(g)

最终的方案

再后来,又有了新的需求,需要获取某个序号生成器到底i生成到哪里了,所以又增加了类的包装,使用类属性记住序号生成器的状态:

class OutlineSeqMaker:

    def __init__(
            self,
            type: Literal['CHINESE', 'CHINESE_BTS', 'PURE_NUM', 'NUM_CN_BTS', 'NUM_EN_BTS', 'MULTI_NUM2', 'MULTI_NUM3'],
            start=1,
            middle=1,
            mark=True,
            mark_type=2,
        ):
            self.type_ = type
            self.start = start
            self.middle = middle
            self.mark = mark
            self.mark_type = mark_type
            self.generator: Generator = None
            self.__current_level = 0
            self.init()

    def init(self) -> Generator:
        _map = {
            'CHINESE': partial(chinese_numerals_generator, mark=self.mark),
            'CHINESE_BTS': chinese_numerals_with_bts,
            'PURE_NUM': partial(numerals_generator, mark_type=self.mark_type),
            'NUM_CN_BTS': partial(numerals_with_cnbts_generator, start=self.start),
            'NUM_EN_BTS': partial(numerals_with_enbts_generator, start=self.start),
            'MULTI_NUM2': partial(multiple_numerals2_generator, start=self.start),
            'MULTI_NUM3': partial(multiple_numerals3_generator, start=self.start, middle=self.middle),
        }
        self.generator =  _map[self.type_]()

    @property
    def current_level(self):
        return self.__current_level
    
    def next(self):
        self.__current_level += 1
        return next(self.generator)
    
    def destroy(self):
        del self

在类里,增加了next方法、current_level属性方法,使用方法:

# 带括号的中文序号
g = OutlineSeqMaker(type='CHINESE_BTS', mark_type=1)
# 获得一个序号
g.next()
# 获取当前序号的位置
g.current_level
# 不需要时主动销毁该序号生成器
g.destroy()

对于解决方案,个人始终遵循简单易用,好维护,易扩展的宗旨,这套方法也不是很完善,仅能解决目前我所遇到的问题,边积累边优化吧。

  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值