1. 程式人生 > 程式設計 >python實現excel公式格式化的示例程式碼

python實現excel公式格式化的示例程式碼

之前跟一些小夥伴有個討論:

image-20201214091536248

大概就是很多跟資料打交道的朋友都面對過很複雜的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*60%-C4*35%
 ),IF(
  C11<=C4*50%,C11*40%-C4*5%
 )
)

image-20201214165452189

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

的格式化結果為:

(
 SMA(MAX(CLOSE-DELAY(CLOSE,1)
 /
 SMA(ABS(CLOSE-DELAY(CLOSE,1)
 *
 100-MIN(
  SMA(MAX(CLOSE-DELAY(CLOSE,1)
  /
  SMA(ABS(CLOSE-DELAY(CLOSE,12
 )
)
/
(
 MAX(
  SMA(MAX(CLOSE-DELAY(CLOSE,12
 )
 -
 MIN(
  SMA(MAX(CLOSE-DELAY(CLOSE,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(),ROUNDDOWN(
   $B15*INDIRECT(
    SUBSTITUTE(ADDRESS(1,"")
    &
    56
   ),0
  )
 )
)

image-20201214165926821

(文末有體驗網址)

不過接下來,將公佈這套格式化程式的完整程式碼和開發思想,有技術能力的小夥伴可以考慮改進該程式碼。

完整程式碼

__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(")",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,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(),0)))

文字分塊切分

def split_text_blocks(excel_func_text):
  """
  將excel公式字串,按照一定的規則切割成陣列
  :param excel_func_text: 被切割的excel公式字串
  :return: 切割後的結果
  """
  excel_func_text = excel_func_text.replace('\n',j + 1) + 1
    else:
      j += 1
  return lines

s = """=IF(ROW()>COLUMN(),0))) """

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

的執行結果為:

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

這端程式碼首先替換掉所有的換行符,將多個空格替換為單個空格,然後將左右括號和逗號作為切分點進行切分。

但存在一些特殊情況,例如ROW()和COLUMN()括號內部沒有任何內容,所有這種括號應該作為普通字元處理,另外被""包含的字串可能包含括號,也應該作為普通字元。

構建多叉樹層次結構

設計資料結構:

class Node:
  def __init__(self,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)如下:

image-20201214180114188

遍歷列印這顆多叉樹

def node_next_line(node):
  for i,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(),ROUNDDOWN(
   $B15*INDIRECT(
    SUBSTITUTE(
     ADDRESS(
      1,4
     ),""
    )
    &
    56
   ),0
  )
 )
)

合併最內層的節點

顯然將最內層的node5節點合併一下閱讀性更好:

image-20201214181546248

首先給資料結構增加判斷是否為純文字節點的方法:

class Node:
  def __init__(self,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,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(),ROUNDDOWN(
   $B15*INDIRECT(
    SUBSTITUTE(
     ADDRESS(1,0
  )
 )
)

合併二次:

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

結果:

=IF(
 ROW()>COLUMN(),0
  )
 )
)

合併三次:

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

結果:

=IF(
 ROW()>COLUMN(),ROUNDDOWN(
   $B15*INDIRECT(SUBSTITUTE(ADDRESS(1,0
  )
 )
)

合併三次後的記憶體情況:

image-20201214182511540

體驗網址

http://xiaoxiaoming.xyz:8088/excel

不保證永久有效。

到此這篇關於python實現excel公式格式化的示例程式碼的文章就介紹到這了,更多相關python excel公式格式化內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!