python实现excel公式格式化的示例代码


Posted in Python onDecember 23, 2020

之前跟一些小伙伴有个讨论:

python实现excel公式格式化的示例代码

大概就是很多跟数据打交道的朋友都面对过很复杂的excel公式,有时嵌套层数特别多,肉眼观看很容易蒙圈。
有了这样的需求,我就有了解决问题的想法,说干就干,于是一个比较牛逼的excel公式格式化的工具就出现了。

效果体验

先看看效果吧:

=IF(C11>100%*C4,IF(C11<=200%*C4,C11*50%-C4*15%,C11*60%-C4*35%),IF(C11<=C4*50%,C11*30%,C11*40%-C4*5%))

的格式化结果是:

=IF(
 C11>100%*C4,
 IF(
  C11<=200%*C4,
  C11*50%-C4*15%,
  C11*60%-C4*35%
 ),
 IF(
  C11<=C4*50%,
  C11*30%,
  C11*40%-C4*5%
 )
)

python实现excel公式格式化的示例代码

(SMA(MAX(CLOSE-DELAY(CLOSE,1),0),12,1)/SMA(ABS(CLOSE-DELAY(CLOSE,1)),12,1)*100-MIN(SMA(MAX(CLOSE-DELAY(
CLOSE,1),0),12,1)/SMA(ABS(CLOSE-DELAY(CLOSE,1)),12,1)*100,12))/(MAX(SMA(MAX(CLOSE-DELAY(CLOSE,1),0),12,
1)/SMA(ABS(CLOSE-DELAY(CLOSE,1)),12,1)*100,12)-MIN(SMA(MAX(CLOSE-DELAY(CLOSE,1),0),12,1)/SMA(ABS(
CLOSE-DELAY(CLOSE,1)),12,1)*100,12))

的格式化结果为:

(
 SMA(MAX(CLOSE-DELAY(CLOSE,1),0),12,1)
 /
 SMA(ABS(CLOSE-DELAY(CLOSE,1)),12,1)
 *
 100-MIN(
  SMA(MAX(CLOSE-DELAY(CLOSE,1),0),12,1)
  /
  SMA(ABS(CLOSE-DELAY(CLOSE,1)),12,1)*100,
  12
 )
)
/
(
 MAX(
  SMA(MAX(CLOSE-DELAY(CLOSE,1),0),12,1)
  /
  SMA(ABS(CLOSE-DELAY(CLOSE,1)),12,1)*100,
  12
 )
 -
 MIN(
  SMA(MAX(CLOSE-DELAY(CLOSE,1),0),12,1)
  /
  SMA(ABS(CLOSE-DELAY(CLOSE,1)),12,1)*100,
  12
 )
)
=IF(ROW()>COLUMN(),"",IF(ROW()=COLUMN(),$B15,ROUNDDOWN($B15*INDIRECT(SUBSTITUTE(ADDRESS(1,3+COLUMN()-ROW(),
4),1,"")&56),0)))

的格式化结果为:

=IF(
 ROW()>COLUMN(),
 "",
 IF(
  ROW()=COLUMN(),
  $B15,
  ROUNDDOWN(
   $B15*INDIRECT(
    SUBSTITUTE(ADDRESS(1,3+COLUMN()-ROW(), 4),1,"")
    &
    56
   ),
   0
  )
 )
)

python实现excel公式格式化的示例代码

(文末有体验网址)

不过接下来,将公布这套格式化程序的完整代码和开发思想,有技术能力的小伙伴可以考虑改进该代码。

完整代码

__author__ = 'xiaoxiaoming'

from collections import deque
import re


class Node:
  def __init__(self, parent=None, tab_size=0):
    self.parent = parent
    self.tab_size = tab_size
    self.data = []

  def is_single_node(self):
    for e in self.data:
      if not isinstance(e, str):
        return False
    return True

  def get_single_text(self):
    return "".join(self.data)


def split_text_blocks(excel_func_text):
  """
  将excel公式字符串,按照一定的规则切割成数组
  :param excel_func_text: 被切割的excel公式字符串
  :return: 切割后的结果
  """
  excel_func_text = excel_func_text.replace('\n', '').replace('\r', '')
  excel_func_text = re.sub(" +", " ", excel_func_text)
  lines = []
  i, j = 0, 0
  while j < len(excel_func_text):
    c = excel_func_text[j]
    if (c == '(' and excel_func_text[j + 1] != ')') or c == ',':
      lines.append(excel_func_text[i:j + 1])
      i = j = j + 1
    elif c == ')' and excel_func_text[j - 1] != '(':
      if i < j:
        lines.append(excel_func_text[i:j])
        i = j # 起始文件块置于)处
      # 以下代码查找,如果中间不包含(或),则将)和,之间的文本块加入到划分结果
      k = excel_func_text.find(",", j + 1)
      l = excel_func_text.find("(", j + 1, k)
      m = excel_func_text.find(")", j + 1, k)
      if k != -1 and l == -1 and m == -1:
        lines.append(excel_func_text[i:k + 1])
        i = j = k + 1
      elif j + 1 < len(excel_func_text) and excel_func_text[j + 1] != ')':
        lines.append(")")
        lines.append(excel_func_text[j + 1])
        i = j = j + 2
      else:
        lines.append(")")
        i = j = j + 1
    elif c == '"':
      j = excel_func_text.find('"', j + 1) + 1
    else:
      j += 1
  return lines


blank_char_count = 2


def combine_node(root, text_max_length=60, max_combine_layer=3):
  """
  合并最内层的只有纯文本子节点的节点为单个文本节点
  :param root: 被合并的节点
  :param text_max_length: 合并后的文本长度不超过该参数,则应用该合并替换原节点
  :param max_combine_layer: 最大合并层数
  :return:
  """
  for _ in range(max_combine_layer):
    no_change = True
    stack = deque([root])
    while stack:
      node = stack.pop()
      tmp = {}
      for i, e in enumerate(node.data):
        if isinstance(e, Node):
          if e.is_single_node():
            single_text = e.get_single_text()
            if len(single_text) < text_max_length:
              tmp[i] = single_text
          else:
            stack.append(e)
      for i, e in tmp.items():
        node.data[i] = e
      if len(tmp) != 0:
        no_change = False
    if no_change:
      break


def node_next_line(node):
  for i, e in enumerate(node.data):
    if isinstance(e, str):
      if i == 0 or i == len(node.data) - 1:
        tab = node.tab_size - 1
      else:
        tab = node.tab_size
      yield f"{' ' * blank_char_count * tab}{e}"
    else:
      yield from node_next_line(e)
      

def excel_func_format(excel_func_text, blank_count=2, combine_single_node=True, text_max_length=60,
           max_combine_layer=3):
  """
  将excel公式格式化成比较容易阅读的格式
  :param excel_func_text: 被格式化的excel公式字符串
  :param blank_count: 最终显示的格式化字符串的1个tab用几个空格表示
  :param combine_single_node: 是否合并纯文本节点,该参数设置为True后面的参数才生效
  :param text_max_length: 合并后的文本长度不超过该参数,则应用该合并替换原节点
  :param max_combine_layer: 最大合并层数
  :return: 格式化后的字符串
  """
  global blank_char_count
  blank_char_count = blank_count
  blocks = split_text_blocks(excel_func_text)
  # print("\n".join(blocks))
  # print('-----------拆分结果-----------')
  tab_size = 0
  node = root = Node()
  for block in blocks:
    if block.endswith("("):
      tab_size += 1
      child_node = Node(node, tab_size)
      node.data.append(child_node)
      node = child_node
      node.data.append(block)
    elif block.startswith(")"):
      tab_size -= 1
      node.data.append(block)
      node = node.parent
    else:
      node.data.append(block)
  if combine_single_node:
    combine_node(root, text_max_length, max_combine_layer)
  result = [line for line in node_next_line(root)]
  return "\n".join(result)

处理流程浅析

下面都以如下公式作为示例:

=IF(ROW()>COLUMN(),"",IF(ROW()=COLUMN(),$B15,ROUNDDOWN($B15*INDIRECT(SUBSTITUTE(ADDRESS(1,3+COLUMN()-ROW(),
4),1,"")&56),0)))

文本分块切分

def split_text_blocks(excel_func_text):
  """
  将excel公式字符串,按照一定的规则切割成数组
  :param excel_func_text: 被切割的excel公式字符串
  :return: 切割后的结果
  """
  excel_func_text = excel_func_text.replace('\n', '').replace('\r', '')
  excel_func_text = re.sub(" +", " ", excel_func_text)
  lines = []
  i, j = 0, 0
  while j < len(excel_func_text):
    c = excel_func_text[j]
    if (c == '(' and excel_func_text[j + 1] != ')') or c == ',':
      lines.append(excel_func_text[i:j + 1])
      i = j = j + 1
    elif c == ')' and excel_func_text[j - 1] != '(':
      if i < j:
        lines.append(excel_func_text[i:j])
        i = j # 起始文件块置于)处
      # 以下代码查找,如果中间不包含(或),则将)和,之间的文本块加入到划分结果
      k = excel_func_text.find(",", j + 1)
      l = excel_func_text.find("(", j + 1, k)
      m = excel_func_text.find(")", j + 1, k)
      if k != -1 and l == -1 and m == -1:
        lines.append(excel_func_text[i:k + 1])
        i = j = k + 1
      elif j + 1 < len(excel_func_text) and excel_func_text[j + 1] != ')':
        lines.append(")")
        lines.append(excel_func_text[j + 1])
        i = j = j + 2
      else:
        lines.append(")")
        i = j = j + 1
    elif c == '"':
      j = excel_func_text.find('"', j + 1) + 1
    else:
      j += 1
  return lines

s = """=IF(ROW()>COLUMN(),"",IF(ROW()=COLUMN(),$B15,ROUNDDOWN($B15*INDIRECT(SUBSTITUTE(ADDRESS(1,3+COLUMN()-ROW(),
    4),1,"")&56),0))) """

blocks = split_text_blocks(s)
for block in blocks:
  print(block)

的运行结果为:

=IF(
ROW()>COLUMN(),
"",
IF(
ROW()=COLUMN(),
$B15,
ROUNDDOWN(
$B15*INDIRECT(
SUBSTITUTE(
ADDRESS(
1,
3+COLUMN()-ROW(),
 4
),
1,
""
)
&
56
),
0
)
)
)

这端代码首先替换掉所有的换行符,将多个空格替换为单个空格,然后将左右括号和逗号作为切分点进行切分。

但存在一些特殊情况,例如ROW()和COLUMN()括号内部没有任何内容,所有这种括号应该作为普通字符处理,另外被""包含的字符串可能包含括号,也应该作为普通字符。

构建多叉树层次结构

设计数据结构:

class Node:
  def __init__(self, parent=None, tab_size=0):
    self.parent = parent
    self.tab_size = tab_size
    self.data = []

parent存储父节点的指针,tab_size存储当前节点的层级,data存储当前节点的所有数据。

构建代码:

tab_size = 0
node = root = Node()
for block in blocks:
  if block.endswith("("):
    tab_size += 1
    child_node = Node(node, tab_size)
    node.data.append(child_node)
    node = child_node
    node.data.append(block)
  elif block.startswith(")"):
    tab_size -= 1
    node.data.append(block)
    node = node.parent
  else:
    node.data.append(block)

构建完毕后,这段数据在内存中的结构(仅展示data)如下:

python实现excel公式格式化的示例代码

遍历打印这颗多叉树

def node_next_line(node):
  for i, e in enumerate(node.data):
    if isinstance(e, str):
      if i == 0 or i == len(node.data) - 1:
        tab = node.tab_size - 1
      else:
        tab = node.tab_size
      yield f"{' ' * 2 * tab}{e}"
    else:
      yield from node_next_line(e)
      
result = [line for line in node_next_line(root)]
print("\n".join(result))

结果:

=IF(
 ROW()>COLUMN(),
 "",
 IF(
  ROW()=COLUMN(),
  $B15,
  ROUNDDOWN(
   $B15*INDIRECT(
    SUBSTITUTE(
     ADDRESS(
      1,
      3+COLUMN()-ROW(),
       4
     ),
     1,
     ""
    )
    &
    56
   ),
   0
  )
 )
)

合并最内层的节点

显然将最内层的node5节点合并一下阅读性更好:

python实现excel公式格式化的示例代码

首先给数据结构增加判断是否为纯文本节点的方法:

class Node:
  def __init__(self, parent=None, tab_size=0):
    self.parent = parent
    self.tab_size = tab_size
    self.data = []

  def is_single_node(self):
    for e in self.data:
      if not isinstance(e, str):
        return False
    return True

  def get_single_text(self):
    return "".join(self.data)

下面是合并纯文本节点的实现,max_combine_layer决定了合并的最大次数,如果合并后长度超过text_max_length参数,则不应用这次合并:

from collections import deque

def combine_node(root, text_max_length=60, max_combine_layer=3):
  """
  合并最内层的只有纯文本子节点的节点为单个文本节点
  :param root: 被合并的节点
  :param text_max_length: 合并后的文本长度不超过该参数,则应用该合并替换原节点
  :param max_combine_layer: 最大合并层数
  :return:
  """
  for _ in range(max_combine_layer):
    no_change = True
    stack = deque([root])
    while stack:
      node = stack.pop()
      tmp = {}
      for i, e in enumerate(node.data):
        if isinstance(e, Node):
          if e.is_single_node():
            single_text = e.get_single_text()
            if len(single_text) < text_max_length:
              tmp[i] = single_text
          else:
            stack.append(e)
      for i, e in tmp.items():
        node.data[i] = e
      if len(tmp) != 0:
        no_change = False
    if no_change:
      break

合并一次:

combine_node(root, 100, 1)
result = [line for line in node_next_line(root)]
print("\n".join(result))

结果:

=IF(
 ROW()>COLUMN(),
 "",
 IF(
  ROW()=COLUMN(),
  $B15,
  ROUNDDOWN(
   $B15*INDIRECT(
    SUBSTITUTE(
     ADDRESS(1,3+COLUMN()-ROW(), 4),
     1,
     ""
    )
    &
    56
   ),
   0
  )
 )
)

合并二次:

combine_node(root, 100, 2)
result = [line for line in node_next_line(root)]
print("\n".join(result))

结果:

=IF(
 ROW()>COLUMN(),
 "",
 IF(
  ROW()=COLUMN(),
  $B15,
  ROUNDDOWN(
   $B15*INDIRECT(
    SUBSTITUTE(ADDRESS(1,3+COLUMN()-ROW(), 4),1,"")
    &
    56
   ),
   0
  )
 )
)

合并三次:

combine_node(root, 100, 3)
result = [line for line in node_next_line(root)]
print("\n".join(result))

结果:

=IF(
 ROW()>COLUMN(),
 "",
 IF(
  ROW()=COLUMN(),
  $B15,
  ROUNDDOWN(
   $B15*INDIRECT(SUBSTITUTE(ADDRESS(1,3+COLUMN()-ROW(), 4),1,"")&56),
   0
  )
 )
)

合并三次后的内存情况:

python实现excel公式格式化的示例代码

体验网址

http://xiaoxiaoming.xyz:8088/excel

不保证永久有效。

到此这篇关于python实现excel公式格式化的示例代码的文章就介绍到这了,更多相关python excel公式格式化内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
使用70行Python代码实现一个递归下降解析器的教程
Apr 17 Python
Python的Twisted框架上手前所必须了解的异步编程思想
May 25 Python
python数据清洗系列之字符串处理详解
Feb 12 Python
利用python编写一个图片主色转换的脚本
Dec 07 Python
如何优雅地改进Django中的模板碎片缓存详解
Jul 04 Python
python 判断参数为Nonetype类型或空的实例
Oct 30 Python
Python socket模块实现的udp通信功能示例
Apr 10 Python
Pytorch之parameters的使用
Dec 31 Python
完美解决keras 读取多个hdf5文件进行训练的问题
Jul 01 Python
Python Web项目Cherrypy使用方法镜像
Nov 05 Python
用Python selenium实现淘宝抢单机器人
Jun 18 Python
Python内置类型集合set和frozenset的使用详解
Apr 26 Python
python 基于opencv实现图像增强
Dec 23 #Python
python接口自动化框架实战
Dec 23 #Python
pycharm远程连接服务器并配置python interpreter的方法
Dec 23 #Python
python实现发送QQ邮件(可加附件)
Dec 23 #Python
如何通过安装HomeBrew来安装Python3
Dec 23 #Python
python实现定时发送邮件到指定邮箱
Dec 23 #Python
python实现定时发送邮件
Dec 23 #Python
You might like
php中几种常见安全设置详解
2010/04/06 PHP
php将session放入memcached的设置方法
2014/02/14 PHP
smarty中常用方法实例总结
2015/08/07 PHP
Mootools 1.2 手风琴(Accordion)教程
2009/09/15 Javascript
JS实现网页上随滚动条滚动的层效果代码
2015/11/04 Javascript
轻松实现javascript数据双向绑定
2015/11/11 Javascript
jQuery中trigger()与bind()用法分析
2015/12/18 Javascript
浅谈javascript中的call、apply、bind
2016/03/06 Javascript
AngularJs  E2E Testing 详解
2016/09/02 Javascript
实例讲解JavaScript中call、apply、bind方法的异同
2016/09/13 Javascript
D3.js实现折线图的方法详解
2016/09/21 Javascript
AngularJS辅助库browserTrigger用法示例
2016/11/03 Javascript
js 实现获取name 相同的页面元素并循环遍历的方法
2017/02/14 Javascript
Webpack打包慢问题的完美解决方法
2017/03/16 Javascript
IScroll5实现下拉刷新上拉加载的功能实例
2017/08/11 Javascript
AngularJS使用ui-route实现多层嵌套路由的示例
2018/01/10 Javascript
Vue.js组件高级特性实例详解
2018/12/24 Javascript
vue.js多页面开发环境搭建过程
2019/04/24 Javascript
微信小程序调用微信支付接口的实现方法
2019/04/29 Javascript
NodeJs crypto加密制作token的实现代码
2019/11/15 NodeJs
Python中使用语句导入模块或包的机制研究
2015/03/30 Python
解决Python中字符串和数字拼接报错的方法
2016/10/23 Python
python 将字符串中的数字相加求和的实现
2019/07/18 Python
Python爬虫学习之翻译小程序
2019/07/30 Python
浅析PyTorch中nn.Linear的使用
2019/08/18 Python
Python如何输出警告信息
2020/07/30 Python
美国领先的汽车轮胎和轮毂供应商:TireBuyer
2016/07/21 全球购物
波兰家具和室内装饰品购物网站:Vivre
2018/04/10 全球购物
速卖通欧盟:Aliexpress EU
2020/08/19 全球购物
直接有效的自我评价
2014/01/11 职场文书
标准自荐信范文
2014/01/29 职场文书
反腐倡廉演讲稿
2014/05/22 职场文书
乡镇干部个人对照检查材料(群众路线)
2014/09/26 职场文书
暂停营业通知
2015/04/25 职场文书
一年级下册数学教学反思
2016/02/16 职场文书
德生BCL3000抢先使用感受和评价
2022/04/07 无线电