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 相关文章推荐
从零学Python之入门(三)序列
May 25 Python
在Django的视图(View)外使用Session的方法
Jul 23 Python
Django应用程序中如何发送电子邮件详解
Feb 04 Python
对numpy 数组和矩阵的乘法的进一步理解
Apr 04 Python
django之对FileField字段的upload_to的设定方法
Jul 28 Python
Python箱型图处理离群点的例子
Dec 09 Python
python ffmpeg任意提取视频帧的方法
Feb 21 Python
Pycharm plot独立窗口显示的操作
Dec 11 Python
python中封包建立过程实例
Feb 18 Python
Python编程根据字典列表相同键的值进行合并
Oct 05 Python
Python3的进程和线程你了解吗
Mar 16 Python
Python爬虫 简单介绍一下Xpath及使用
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 header()函数使用说明
2008/07/10 PHP
php 判断字符串中是否包含html标签
2014/02/17 PHP
jquery中的 $(&quot;#jb51&quot;)与document.getElementById(&quot;jb51&quot;) 的区别
2011/07/26 Javascript
javascript 判断中文字符长度的函数代码
2012/08/27 Javascript
表头固定(利用jquery实现原理介绍)
2012/11/08 Javascript
一个可拖拽列宽表格实例演示
2012/11/26 Javascript
Jquery为a标签的href赋值实现代码
2013/05/03 Javascript
Js操作树节点自动折叠展开的几种方法
2014/05/05 Javascript
Angular中的Promise对象($q介绍)
2015/03/03 Javascript
JavaScript 闭包机制详解及实例代码
2016/10/10 Javascript
vue.js声明式渲染和条件与循环基础知识
2017/07/31 Javascript
Vue0.1的过滤代码如何添加到Vue2.0直接使用
2017/08/23 Javascript
Nuxt升级2.0.0时出现的问题(小结)
2018/10/08 Javascript
Vue+Element实现动态生成新表单并添加验证功能
2019/05/23 Javascript
vue.js 2.0实现简单分页效果
2019/07/29 Javascript
深入解析微信小程序开发中遇到的几个小问题
2020/07/11 Javascript
Vue实现菜单切换功能
2020/11/08 Javascript
[01:00:53]2018DOTA2亚洲邀请赛3月29日 小组赛B组 iG VS Secret
2018/03/30 DOTA
使用IPython下的Net-SNMP来管理类UNIX系统的教程
2015/04/15 Python
Pycharm 操作Django Model的简单运用方法
2018/05/23 Python
python图形工具turtle绘制国际象棋棋盘
2019/05/23 Python
使用Python画股票的K线图的方法步骤
2019/06/28 Python
Python中的引用和拷贝实例解析
2019/11/14 Python
Python实现一个优先级队列的方法
2020/07/31 Python
通过代码简单了解django model序列化作用
2020/11/12 Python
如何使用python-opencv批量生成带噪点噪线的数字验证码
2020/12/21 Python
Python 转移文件至云对象存储的方法
2021/02/07 Python
英国领先的珍珠首饰品牌:Orchira
2016/09/11 全球购物
Ryderwear澳洲官网:澳大利亚高端健身训练装备品牌
2018/09/18 全球购物
数字漫画:comiXology
2020/06/13 全球购物
高二学年自我鉴定范文(2篇)
2014/09/26 职场文书
房产协议书范本2014
2014/09/30 职场文书
党员干部廉政承诺书
2015/04/28 职场文书
2015年高中班主任工作总结
2015/04/30 职场文书
Python+tkinter实现高清图片保存
2022/03/13 Python
spring boot实现文件上传
2022/08/14 Java/Android