DeepSeek辅助编写的将xlsx格式文件中sheet1.xml按需分别保留或去掉标签的程序
上文提到,xlsx格式文件中有一些标签属性可选,为了考察不同标签存在时,对程序读取解析性能的影响,需要一个程序来对已有的文件做加工。
交给DeepSeek的提示词如下
wps生成的xlsx中sheet1.xml 标签中有spans="1:cols"标签和r="n"标签(其中cols是总列数,n是数字行号),标签中有r="Bn"其中B是xlsx表格字母列号,n是数字行号)等标签,编写python程序,将sheet.xml中的上述标签分别保留或去掉,形成:
0.无span,无row的r,无col的r
1.有span,无row的r,无col的r
2.有span,有row的r,无col的r
分别输出,保存在子目录0、1、2的同名文件中
例如原始的xml,27340.884122563463474,第0种的要求27340.884122563463474也就是,第1种是27340.884122563463474,第2种是27340.884122563463474
请增加功能,把位于0、1、2目录的sheet1.xml文件替换到xlsx文件解压后的xl/worksheets目录下,xlsx文件中其他目录和文件保持不变,重新压缩生成新的xlsx文件,分别命名为wps0.xlsx,wps1.xlsx,wps2.xlsx。
经过两次修正,得到如下程序
import os
import zipfile
import shutil
import xml.etree.ElementTree as ET
from pathlib import Pathdef process_sheet_xml(input_xlsx_path, output_base_dir):"""处理xlsx文件中的sheet1.xml,生成3种不同标签组合的版本,并重新打包为xlsx文件Args:input_xlsx_path: 输入的xlsx文件路径output_base_dir: 输出文件的基础目录"""# 创建输出目录for i in range(3):os.makedirs(os.path.join(output_base_dir, str(i)), exist_ok=True)# 临时解压目录temp_dir = "temp_extract"os.makedirs(temp_dir, exist_ok=True)try:# 解压xlsx文件with zipfile.ZipFile(input_xlsx_path, 'r') as zip_ref:zip_ref.extractall(temp_dir)# 查找sheet1.xml文件sheet_path = Nonefor root, dirs, files in os.walk(temp_dir):for file in files:if file == "sheet1.xml":sheet_path = os.path.join(root, file)breakif sheet_path:breakif not sheet_path:print("未找到sheet1.xml文件")return# 读取原始XML内容with open(sheet_path, 'r', encoding='utf-8') as f:original_xml = f.read()# 解析XML文件tree = ET.parse(sheet_path)root = tree.getroot()# 处理命名空间namespaces = {'ss': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'}ET.register_namespace('', namespaces['ss'])# 版本0: 无span, 无row的r, 无col的rversion0 = ET.ElementTree(ET.fromstring(original_xml))for row in version0.findall('.//{http://schemas.openxmlformats.org/spreadsheetml/2006/main}row'):# 移除spans属性if 'spans' in row.attrib:del row.attrib['spans']# 移除r属性if 'r' in row.attrib:del row.attrib['r']# 移除c元素的r属性for cell in row.findall('.//{http://schemas.openxmlformats.org/spreadsheetml/2006/main}c'):if 'r' in cell.attrib:del cell.attrib['r']# 版本1: 有span, 无row的r, 无col的rversion1 = ET.ElementTree(ET.fromstring(original_xml))for row in version1.findall('.//{http://schemas.openxmlformats.org/spreadsheetml/2006/main}row'):# 保留spans属性# 移除r属性if 'r' in row.attrib:del row.attrib['r']# 移除c元素的r属性for cell in row.findall('.//{http://schemas.openxmlformats.org/spreadsheetml/2006/main}c'):if 'r' in cell.attrib:del cell.attrib['r']# 版本2: 有span, 有row的r, 无col的rversion2 = ET.ElementTree(ET.fromstring(original_xml))for row in version2.findall('.//{http://schemas.openxmlformats.org/spreadsheetml/2006/main}row'):# 保留spans属性# 保留r属性# 只移除c元素的r属性for cell in row.findall('.//{http://schemas.openxmlformats.org/spreadsheetml/2006/main}c'):if 'r' in cell.attrib:del cell.attrib['r']# 保存三个版本的sheet1.xml到输出目录output_files = [os.path.join(output_base_dir, "0", "sheet1.xml"),os.path.join(output_base_dir, "1", "sheet1.xml"),os.path.join(output_base_dir, "2", "sheet1.xml")]versions = [version0, version1, version2]for i, (version, output_file) in enumerate(zip(versions, output_files)):# 美化XML格式#indent(version.getroot())# 写入文件version.write(output_file, encoding='utf-8', xml_declaration=True)print(f"版本 {i} 的sheet1.xml已保存到: {output_file}")# 为每个版本创建新的xlsx文件create_new_xlsx_files(temp_dir, output_base_dir)finally:# 清理临时文件if os.path.exists(temp_dir):shutil.rmtree(temp_dir)def create_new_xlsx_files(temp_dir, output_base_dir):"""为每个版本创建新的xlsx文件"""for version in range(3):# 创建版本特定的临时目录version_temp_dir = os.path.join(output_base_dir, f"temp_version_{version}")if os.path.exists(version_temp_dir):shutil.rmtree(version_temp_dir)shutil.copytree(temp_dir, version_temp_dir)# 替换sheet1.xml文件modified_sheet_path = os.path.join(output_base_dir, str(version), "sheet1.xml")target_sheet_path = find_sheet1_in_dir(version_temp_dir)if target_sheet_path and os.path.exists(modified_sheet_path):# 备份原始文件#backup_path = target_sheet_path + ".backup"#shutil.copy2(target_sheet_path, backup_path)# 替换为修改后的文件shutil.copy2(modified_sheet_path, target_sheet_path)print(f"版本 {version}: 已替换 {target_sheet_path}")# 创建新的xlsx文件output_xlsx = os.path.join(output_base_dir, f"wps{version}.xlsx")create_zip_from_directory(version_temp_dir, output_xlsx)print(f"版本 {version}: 已创建 {output_xlsx}")# 清理临时目录shutil.rmtree(version_temp_dir)def find_sheet1_in_dir(directory):"""在目录中查找sheet1.xml文件"""for root, dirs, files in os.walk(directory):for file in files:if file == "sheet1.xml":return os.path.join(root, file)return Nonedef create_zip_from_directory(directory, output_zip):"""将目录内容压缩为xlsx文件"""# 确保输出文件不存在if os.path.exists(output_zip):os.remove(output_zip)# 创建zip文件with zipfile.ZipFile(output_zip, 'w', zipfile.ZIP_DEFLATED) as zipf: # , compresslevel=6for root, dirs, files in os.walk(directory):for file in files:file_path = os.path.join(root, file)# 计算在zip中的相对路径arcname = os.path.relpath(file_path, directory)zipf.write(file_path, arcname)def indent(elem, level=0):"""美化XML格式,添加缩进"""i = "\n" + level * " "if len(elem):if not elem.text or not elem.text.strip():elem.text = i + " "if not elem.tail or not elem.tail.strip():elem.tail = ifor child in elem:indent(child, level + 1)if not elem.tail or not elem.tail.strip():elem.tail = ielse:if level and (not elem.tail or not elem.tail.strip()):elem.tail = idef main():# 使用示例input_file = "4/wps_h0.xlsx" # 替换为你的xlsx文件路径output_dir = "4" # 输出目录# 确保输出目录存在os.makedirs(output_dir, exist_ok=True)process_sheet_xml(input_file, output_dir)print("处理完成!")print(f"生成的文件:")print(f" wps0.xlsx - 无span, 无row的r, 无col的r")print(f" wps1.xlsx - 有span, 无row的r, 无col的r") print(f" wps2.xlsx - 有span, 有row的r, 无col的r")print(f"文件保存在: {os.path.abspath(output_dir)}")if __name__ == "__main__":main()
执行情况如下
python3 stripxml3.py 4/wps_h0.xlsx 4
版本 0 的sheet1.xml已保存到: 4/0/sheet1.xml
版本 1 的sheet1.xml已保存到: 4/1/sheet1.xml
版本 2 的sheet1.xml已保存到: 4/2/sheet1.xml
版本 0: 已替换 4/temp_version_0/xl/worksheets/sheet1.xml
版本 0: 已创建 4/wps0.xlsx
版本 1: 已替换 4/temp_version_1/xl/worksheets/sheet1.xml
版本 1: 已创建 4/wps1.xlsx
版本 2: 已替换 4/temp_version_2/xl/worksheets/sheet1.xml
版本 2: 已创建 4/wps2.xlsx
处理完成!
生成的文件:wps0.xlsx - 无span, 无row的r, 无col的rwps1.xlsx - 有span, 无row的r, 无col的rwps2.xlsx - 有span, 有row的r, 无col的r
文件保存在: /par/4ls -l 4/wps*xlsx
-rw-r--r-- 1 root root 6839896 Aug 21 11:10 4/wps0.xlsx
-rw-r--r-- 1 root root 6839896 Aug 21 11:10 4/wps1.xlsx
-rw-r--r-- 1 root root 6854333 Aug 21 11:10 4/wps2.xlsx
-rw-r--r-- 1 root root 9315700 Aug 21 10:28 4/wps_h0.xlsx
程序功能正确,但处理大文件太耗内存,我处理一个4万行256列的数据,内存不足,使用了交换文件,还是无法完成。于是继续提示:
请把它改成用正则表达式替换,只处理和标签左侧部分,改写后写入xml, 其余的标签按原文件复制,用一个64KB缓冲区边读边写,不全部存在内存。
他给出的程序如下
import os
import re
import zipfile
import shutil
from pathlib import Pathdef process_sheet_xml(input_xlsx_path, output_base_dir):"""使用正则表达式和流式处理处理xlsx文件中的sheet1.xml"""# 创建输出目录for i in range(3):os.makedirs(os.path.join(output_base_dir, str(i)), exist_ok=True)# 临时解压目录temp_dir = "temp_extract"os.makedirs(temp_dir, exist_ok=True)try:# 解压xlsx文件with zipfile.ZipFile(input_xlsx_path, 'r') as zip_ref:zip_ref.extractall(temp_dir)# 查找sheet1.xml文件sheet_path = find_sheet1_in_dir(temp_dir)if not sheet_path:print("未找到sheet1.xml文件")return# 使用正则表达式处理三个版本process_with_regex(sheet_path, output_base_dir)# 为每个版本创建新的xlsx文件create_new_xlsx_files(temp_dir, output_base_dir)finally:# 清理临时文件if os.path.exists(temp_dir):shutil.rmtree(temp_dir)def process_with_regex(sheet_path, output_base_dir):"""使用正则表达式处理sheet1.xml文件"""# 定义正则表达式模式row_pattern = re.compile(r'<row\s+([^>]*)>')cell_pattern = re.compile(r'<c\s+([^>]*)>')# 输出文件路径output_files = [os.path.join(output_base_dir, "0", "sheet1.xml"),os.path.join(output_base_dir, "1", "sheet1.xml"),os.path.join(output_base_dir, "2", "sheet1.xml")]# 缓冲区大小buffer_size = 65536 # 64KB# 处理三个版本for version, output_file in enumerate(output_files):process_single_version(sheet_path, output_file, version, row_pattern, cell_pattern, buffer_size)print(f"版本 {version} 已处理完成: {output_file}")def process_single_version(input_path, output_path, version, row_pattern, cell_pattern, buffer_size):"""处理单个版本的sheet1.xml"""with open(input_path, 'r', encoding='utf-8') as infile, \open(output_path, 'w', encoding='utf-8') as outfile:buffer = []while True:# 读取一块数据chunk = infile.read(buffer_size)if not chunk:breakbuffer.append(chunk)current_text = ''.join(buffer)# 处理当前文本块processed_text = process_text_chunk(current_text, version, row_pattern, cell_pattern)# 写入处理后的文本outfile.write(processed_text)# 清空缓冲区,保留最后部分以避免截断标签buffer = [current_text[-100:]] # 保留最后100字符避免标签截断# 处理最后剩余的部分if buffer:final_text = ''.join(buffer)processed_final = process_text_chunk(final_text, version, row_pattern, cell_pattern)outfile.write(processed_final)def process_text_chunk(text, version, row_pattern, cell_pattern):"""处理文本块中的row和cell标签"""# 处理row标签def process_row(match):attrs = match.group(1)if version == 0:# 版本0: 移除所有属性return '<row>'elif version == 1:# 版本1: 只保留spans属性spans_match = re.search(r'spans="[^"]*"', attrs)if spans_match:return f'<row {spans_match.group(0)}>'else:return '<row>'else: # version == 2# 版本2: 保留spans和r属性spans_match = re.search(r'spans="[^"]*"', attrs)r_match = re.search(r'r="\d+"', attrs)kept_attrs = []if spans_match:kept_attrs.append(spans_match.group(0))if r_match:kept_attrs.append(r_match.group(0))if kept_attrs:return f'<row {" ".join(kept_attrs)}>'else:return '<row>'# 处理cell标签def process_cell(match):# 所有版本都移除cell的r属性attrs = match.group(1)# 移除r属性attrs = re.sub(r'r="[^"]*"', '', attrs)attrs = attrs.strip()if attrs:return f'<c {attrs}>'else:return '<c>'# 应用替换text = row_pattern.sub(process_row, text)text = cell_pattern.sub(process_cell, text)return textdef create_new_xlsx_files(temp_dir, output_base_dir):"""为每个版本创建新的xlsx文件"""for version in range(3):# 创建版本特定的临时目录version_temp_dir = os.path.join(output_base_dir, f"temp_version_{version}")if os.path.exists(version_temp_dir):shutil.rmtree(version_temp_dir)shutil.copytree(temp_dir, version_temp_dir)# 替换sheet1.xml文件modified_sheet_path = os.path.join(output_base_dir, str(version), "sheet1.xml")target_sheet_path = find_sheet1_in_dir(version_temp_dir)if target_sheet_path and os.path.exists(modified_sheet_path):# 替换为修改后的文件shutil.copy2(modified_sheet_path, target_sheet_path)print(f"版本 {version}: 已替换 {target_sheet_path}")# 创建新的xlsx文件output_xlsx = os.path.join(output_base_dir, f"wps{version}.xlsx")create_zip_from_directory(version_temp_dir, output_xlsx)print(f"版本 {version}: 已创建 {output_xlsx}")# 清理临时目录shutil.rmtree(version_temp_dir)def find_sheet1_in_dir(directory):"""在目录中查找sheet1.xml文件"""for root, dirs, files in os.walk(directory):for file in files:if file == "sheet1.xml":return os.path.join(root, file)return Nonedef create_zip_from_directory(directory, output_zip):"""将目录内容压缩为xlsx文件"""# 确保输出文件不存在if os.path.exists(output_zip):os.remove(output_zip)# 创建zip文件with zipfile.ZipFile(output_zip, 'w', zipfile.ZIP_DEFLATED) as zipf:for root, dirs, files in os.walk(directory):for file in files:file_path = os.path.join(root, file)# 计算在zip中的相对路径arcname = os.path.relpath(file_path, directory)zipf.write(file_path, arcname)def main():# 使用示例input_file = "4/wps_h0.xlsx" # 替换为你的xlsx文件路径output_dir = "4" # 输出目录# 确保输出目录存在os.makedirs(output_dir, exist_ok=True)process_sheet_xml(input_file, output_dir)print("处理完成!")print(f"生成的文件:")print(f" wps0.xlsx - 无span, 无row的r, 无col的r")print(f" wps1.xlsx - 有span, 无row的r, 无col的r") print(f" wps2.xlsx - 有span, 有row的r, 无col的r")print(f"文件保存在: {os.path.abspath(output_dir)}")if __name__ == "__main__":main()
这个正则版本不知为何处理小文件正确,大文件就格式错误,本来是大文件想用它,只好放弃。
这也说明,市面上的第三方处理xlsx程序为什么大都是读写分离的,比如calamine只读,libxlsxwrite只写,因为像Excel和WPS那样读写兼顾真的要复杂很多。