python 生成復雜表格,自動分頁等功能
解決將Python中的樹形目錄數據轉換為Word表格,并生成帶有合并單元格的檢測報告的問題。首先,要解決“tree目錄數據”和“Word表格互換”,指將樹形結構的數據轉換成Word表格,并在轉換過程中合并相應的單元格以反映層次結構。此外,還有自動分頁和固定表頭的問題,現在可能希望生成的報告能夠自動處理這些格式問題。
首先,我需要明確樹形目錄數據的結構。通常,樹形結構有父子節點關系,比如文件夾結構,每個節點可能有名稱、類型、大小、修改日期等屬性。希望將這些層次結構在Word表格中展示,通過合并單元格來表示父子關系,例如,一個父節點占據一行,其子節點在下方縮進顯示,可能需要合并某些列來體現層級。
在Python中,處理樹形結構通常使用遞歸。對于每個節點,可能需要確定其在表格中的行位置,并根據層級決定合并哪些單元格。例如,根節點可能需要合并所有列的一行,子節點可能只在第一列顯示名稱,后面的列合并或留空,或者根據具體需求調整。
生成Word文檔需要使用python-docx庫。該庫允許創建表格、設置樣式、合并單元格等操作。關鍵點是如何遍歷樹形數據并動態生成表格行,同時處理合并單元格的邏輯。可能需要為每個節點計算其在表格中的行數和列數,特別是當子節點存在時,父節點可能需要跨多行合并。
以下是試驗代碼
from docx import Document
from docx.shared import Pt, Cm
from docx.enum.table import WD_TABLE_ALIGNMENT
from docx.oxml.shared import OxmlElement, qn
import randomclass TreeNode:def __init__(self, name, depth=0, is_file=False, size=0, parent=None):self.name = nameself.depth = depthself.is_file = is_fileself.size = f"{size} KB" if is_file else ""self.parent = parentself.children = []self.start_row = 0self.end_row = 0self.col_span = 1 # 新增橫向合并跨度class EnhancedDirectoryReport:def __init__(self, filename):self.doc = Document()self.filename = filenameself._setup_document()self.table = Noneself.current_row = 0self.column_map = ['一級目錄', '二級目錄', '三級目錄', '文件名', '路徑', '大小']self.current_table = Noneself.current_page_rows = 0self.max_page_rows = 35 # 根據實際內容調整每頁行數self.active_directory = {} # 記錄當前活躍的目錄層級def _setup_document(self):section = self.doc.sections[0]margins = {'left': 2, 'right': 2, 'top': 2.5, 'bottom': 2.5}for attr, cm_val in margins.items():setattr(section, f"{attr}_margin", Cm(cm_val))style = self.doc.styles['Normal']style.font.name = '微軟雅黑'style.font.size = Pt(10)def _create_new_page(self):"""創建新頁面并初始化表格"""if self.current_table is not None:self.doc.add_page_break()self.current_table = self.doc.add_table(rows=0, cols=6)self.current_table.style = 'Table Grid'widths = [Cm(3.5), Cm(3.5), Cm(3.5), Cm(4), Cm(6), Cm(2.5)]for idx, w in enumerate(widths):self.current_table.columns[idx].width = wself._create_table_header()print('建表頭后',self.current_row,self.current_page_rows)self.current_page_rows = 1 # 表頭占1行# 重新應用活躍目錄self._reapply_active_directory()def _reapply_active_directory(self):"""在新頁重新應用當前活躍目錄"""for depth in [1, 2, 3]:if depth in self.active_directory:node = self.active_directory[depth]self._add_directory_row(node, depth)def _add_directory_row(self, node, depth):"""添加目錄行并更新活躍狀態"""row = self.current_table.add_row()cells = row.cells# 填充目錄信息cells[depth - 1].text = node.namecells[depth - 1].paragraphs[0].alignment = WD_TABLE_ALIGNMENT.LEFT# 設置跨列合并if depth == 1:cells[0].merge(cells[5])elif depth == 2:cells[1].merge(cells[5])elif depth == 3:cells[2].merge(cells[5])# 更新活躍目錄self.active_directory[depth] = nodeself.current_page_rows += 1def _check_page_break(self):"""檢查是否需要分頁"""if self.current_page_rows >= self.max_page_rows:self._create_new_page()print('分頁')def _add_file_row(self, node):"""添加文件行"""self._check_page_break()row = self.current_table.add_row()cells = row.cells# 填充文件信息cells[3].text = node.namecells[4].text = self._get_full_path(node)cells[5].text = node.size# 繼承活躍目錄for depth in [1, 2, 3]:if depth in self.active_directory:cells[depth - 1].text = self.active_directory[depth].namecells[depth - 1].paragraphs[0].alignment = WD_TABLE_ALIGNMENT.CENTERself.current_page_rows += 1def _get_full_path(self, node):path = []current = node.parentwhile current and current.depth > 0:path.insert(0, current.name)current = current.parentreturn '/' + '/'.join(path)def process_structure(self, root):"""處理目錄結構"""self._create_new_page()stack = [(root, False)] # (node, visited)while stack:node, visited = stack.pop()if visited:# 后序遍歷處理合并if not node.is_file:self._update_active_directory(node)continueif node.is_file:self._add_file_row(node)else:# 前序遍歷添加目錄self._check_page_break()self._add_directory_row(node, node.depth)stack.append((node, True))# 逆向添加子節點以保持順序for child in reversed(node.children):stack.append((child, False))self.doc.save(self.filename)def _update_active_directory(self, node):"""更新活躍目錄狀態"""# 清除子目錄狀態for depth in list(self.active_directory.keys()):if depth > node.depth:del self.active_directory[depth]def _create_table_header(self):header = self.table.add_row()for idx, text in enumerate(self.column_map):cell = header.cells[idx]cell.text = textcell.paragraphs[0].runs[0].font.bold = Truecell.paragraphs[0].alignment = WD_TABLE_ALIGNMENT.CENTERself._set_cell_color(cell, 'A3D3D3')tr = header._trtrPr = tr.get_or_add_trPr()tblHeader = OxmlElement('w:tblHeader')tblHeader.set(qn('w:val'), "true")trPr.append(tblHeader)print(self.current_row)self.current_row += 1def _set_cell_color(self, cell, hex_color):shading = OxmlElement('w:shd')shading.set(qn('w:fill'), hex_color)cell._tc.get_or_add_tcPr().append(shading)def _smart_merge(self, node):"""智能合并策略核心方法"""# 垂直合并處理if node.depth <= 3 and not node.is_file:self._vertical_merge(node)# 橫向合并處理if node.depth == 1 and not any(not c.is_file for c in node.children):self._horizontal_merge(node, 1, 3) # 一級目錄合并到文件名列if node.depth == 2 and not any(not c.is_file for c in node.children):self._horizontal_merge(node, 2, 3) # 二級目錄合并到文件名列def _horizontal_merge(self, node, start_col, end_col):"""安全橫向合并方法"""for row_idx in range(node.start_row, node.end_row):# 獲取需要合并的單元格print('nc ', row_idx, start_col, end_col)start_cell = self.table.cell(row_idx, start_col)end_cell = self.table.cell(row_idx, end_col)print(row_idx, start_col, end_col)print('開結',start_cell, end_cell)# 檢查是否已經被合并if start_cell._element is end_cell._element:print('已合并過')continueelse:start_cell.merge(end_cell)def _vertical_merge(self, node):"""垂直方向合并"""if node.start_row >= node.end_row:returndepth_col_map = {1: 0, 2: 1, 3: 2}col_idx = depth_col_map.get(node.depth)if col_idx is not None:try:start_cell = self.table.cell(node.start_row, col_idx)end_cell = self.table.cell(node.end_row - 1, col_idx)start_cell.merge(end_cell)start_cell.text = node.nameexcept IndexError as e:print(f"垂直合并失敗:{node.name}")raise edef _fill_row_data(self, node):"""填充數據并設置合并策略"""row = self.table.add_row()cells = row.cells# 文件信息if node.is_file:cells[3].text = node.namecells[4].text = self._get_full_path(node)cells[5].text = node.size# else:# # 設置目錄層級# for d in range(1, 4):# print(d, cells[d])# print(node.name, node.depth)# if node.depth == d:# cells[d - 1].text = node.name# # if d < 3:# # cells[d].merge(cells[d])# 設置樣式for cell in cells:cell.vertical_alignment = WD_TABLE_ALIGNMENT.CENTERself.current_row += 1return row# def _get_full_path(self, node):# path = []# current = node.parent# while current and current.depth > 0:# path.insert(0, current.name)# current = current.parent# return '/' + '/'.join(path) + ('' if node.is_file else f'/{node.name}')def _process_node(self, node):node.start_row = self.current_row#增限制,如為凈空不加行if node.depth > 1 and node.is_file:self._fill_row_data(node)for child in node.children:self._process_node(child)node.end_row = self.current_rowself._smart_merge(node)def generate_report(self, root):self.table = self.doc.add_table(rows=0, cols=6)self.table.style = 'Table Grid'widths = [Cm(3.5), Cm(3.5), Cm(3.5), Cm(4), Cm(6), Cm(2.5)]for idx, w in enumerate(widths):self.table.columns[idx].width = w# self._create_table_header()self._create_new_page()self._process_node(root)print(self.doc.tables)self.doc.save(self.filename)# 測試數據生成器
class TestDataGenerator:@staticmethoddef create_large_structure():root = TreeNode("ROOT", depth=0)# 一級目錄(10個)for i in range(1, 11):dir1 = TreeNode(f"一級目錄_{i}", depth=1, parent=root)root.children.append(dir1)# 30%概率沒有子目錄if random.random() < 0.3:# 直接添加文件for j in range(random.randint(2, 5)):file = TreeNode(f"文件_{i}-{j}.docx", depth=4,is_file=True,size=random.randint(100, 5000),parent=dir1)dir1.children.append(file)continue# 二級目錄(每個一級目錄3-5個)for j in range(random.randint(3, 5)):dir2 = TreeNode(f"二級目錄_{i}-{j}", depth=2, parent=dir1)dir1.children.append(dir2)# 50%概率沒有三級目錄if random.random() < 0.5:# 直接添加文件for k in range(random.randint(3, 6)):file = TreeNode(f"文件_{i}-{j}-{k}.xlsx", depth=4,is_file=True,size=random.randint(100, 5000),parent=dir2)dir2.children.append(file)continue# 三級目錄(每個二級目錄2-4個)for k in range(random.randint(2, 4)):dir3 = TreeNode(f"三級目錄_{i}-{j}-{k}", depth=3, parent=dir2)dir2.children.append(dir3)# 添加文件for m in range(random.randint(3, 8)):file = TreeNode(f"文件_{i}-{j}-{k}-{m}.pptx", depth=4,is_file=True,size=random.randint(100, 5000),parent=dir3)dir3.children.append(file)return rootif __name__ == '__main__':# 生成測試數據data_generator = TestDataGenerator()root_node = data_generator.create_large_structure()# 生成報告report = EnhancedDirectoryReport("上下左右目錄2.docx")report.generate_report(root_node)
效果如圖所示: