上文提到,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。
经过两次修正,得到如下程序
python
import os
import zipfile
import shutil
import xml.etree.ElementTree as ET
from pathlib import Path
def 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 = None
for root, dirs, files in os.walk(temp_dir):
for file in files:
if file == "sheet1.xml":
sheet_path = os.path.join(root, file)
break
if sheet_path:
break
if 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的r
version0 = 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的r
version1 = 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的r
version2 = 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 None
def 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=6
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 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 = i
for child in elem:
indent(child, level + 1)
if not elem.tail or not elem.tail.strip():
elem.tail = i
else:
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i
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()
执行情况如下
bash
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的r
wps1.xlsx - 有span, 无row的r, 无col的r
wps2.xlsx - 有span, 有row的r, 无col的r
文件保存在: /par/4
ls -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缓冲区边读边写,不全部存在内存。
他给出的程序如下
python
import os
import re
import zipfile
import shutil
from pathlib import Path
def 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:
break
buffer.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 text
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):
# 替换为修改后的文件
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 None
def 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那样读写兼顾真的要复杂很多。