今天是2024年9月18日,中秋节刚过,狠下心来决定把原来发在CSDN的文章全部搬过来,倒不是因为文章被CSDN拿去白嫖或者是担心CSDN倒闭,而是觉得自己过去的29年生活自己活的像个小丑,如今已到而立之年被社会彻底淘汰已成定局,因此在离开这个世界之前,我还是决定把过往的一些没什么用的经验重新用文字记录在博客园,哪天我真的死了,我起码在园子发过一点稍微有用的东西,至于有多少用,就很难讲了,毕竟有了AI以后,AI势必会接管一切,但是学习知识的目的却从未变过,因为当你有互联网时你能用AI,等到你成单机了的时候,你连百度都用不了了,一些很简单的问题你也束手无策的时候,那才是AI战胜人类真正完成闭环。
本篇是Excel歪门邪道第一篇,问题要追溯到我第一份工作时遇到的问题,当然,文章本身并无意义,因为现在普遍用WPS的情况下,批注的问题已经被WPS消灭了,本文探讨的是问题出现的原因以及用Winform C#程序完成该操作的思路
首先批注被删的表现形式:其一是常见于Excel2007打开别人发的Excel工作簿文件时,显示文件有错误,修复文件后显示已删除批注,然后别人写的批注全都没了;其二是xlsx文件都有该问题,但是xls文件安然无恙
这里就要提到两个问题:第一为什么批注会被删,第二为什么xls文件不会有这个报错
批注被删的主要原因是Excel2007无法识别批注,具体点就是Excel2007程序无法正常识别之后版本的一些表格外的对象(object)包括但不限于:批注、图形、数据验证等等,这和微软OpenXML标准也有关系,从Excel2007以后,微软开始用了基于OpenXML的一中新的文件格式:xlsx,同样的Word的叫docx,PowerPoint叫pptx,三者其实都是基于一个技术的产物,然而微软在迭代Office用的OpenXML标准的时候并没有向后兼容的问题,这就为早期版本的Office套件不兼容标准埋下了祸根。
按照Excelhome论坛用户jaffedream在论坛回帖中的说法,Excel2007开始识别批注异常的问题要追溯到批注在XML文件中的记录方式,在Excel2007以后的版本中,comments*.xml文件中对批注记录多了一个ShapeId="0"的节点,这就导致Excel2007等版本中,解析器在解析XML文件时无法识别到这个节点,因此会提示用户文件有错误,询问是否修复。但是这里肯定是不能点确定去修复的,因为一旦让Excel修复,由于Excel还是不认识这个节点,最后为了文件能被正常读取,还是会直接删掉整个批注在的项,等于没有修复,关键的信息还是丢失了。
至于xls文件为什么不会有这个报错,因为xls本来就是为了兼容97-2003期间的标准,这个标准已经定型不会发生任何变化了,也因为这个原因,xls兼容性很强,很难出现文件异常的问题,但这并不代表xls就是万用解,首先xls文件不支持后续的一些功能,例如切片器、PQ编辑器等,其次是表格范围,xls的列最大只能支持到256列,行最大只能支持到65536行,而xlsx的这两项分别最大支持16384列和1048576行,这也是为什么xls文件中如果引用xlsx文件的列直接用类似$A:$A之类的方法引用时,会提示无效引用,该文件版本包含的公式中不能引用范围外区域的问题,然后是宏的问题,xlsx默认是不允许宏(marco)存在的,如果要允许使用宏必须另存为xlsm文件,而xls是允许嵌入宏代码的,而这并不安全,因为如果你的电脑默认是允许宏启动的(虽然微软知道宏不安全默认给关了),如果xls被加入了恶意的宏代码,那么随着文件打开就会自动运行宏病毒,但你说xlsx不允许宏存在是不是就万事大吉了呢?未必——因为CSDN上的有人已经发现了瞒天过海的方法,这个有机会再说。
所以讲了一大通废话,关键要修复这个问题就几步(甚至可以自己手动改过来):
1.解压缩文件结构,把comments文件解压出来
2.删除comments文件中所有的ShapeId="0"
3.把修改过的comments文件重新打回压缩包(至于为什么不能新建一个压缩包把所有文件直接打进去,因为Excel工作簿和压缩包打包方式不一样,这样做Excel程序识别不了)
下面是实现的代码,解压缩模块用到了SharpZip,因为是刚学不久,又抄了别人不少的实现逻辑,代码的可读性就不用苛求了,能跑就行
1 using System; 2 using System.Text; 3 using System.Windows.Forms; 4 using System.IO.Compression; 5 using System.IO; 6 using CZip = ICSharpCode.SharpZipLib.Zip; 7 8 private void Button1_Click(object sender, EventArgs e) 9 { 10 int i = 0; 11 OpenFileDialog path = new OpenFileDialog(); 12 //文件类型过滤 13 path.Filter = @"待修复Excel表格|*.xlsx"; 14 //展开文件选择对话框 15 DialogResult result = path.ShowDialog(); 16 if (result == DialogResult.OK) 17 { 18 //获取导出文件夹目录 19 string expath = path.FileName + @"_Fix"; 20 if(Directory.Exists(expath)) 21 { 22 DialogResult fg= MessageBox.Show("导出数据已存在!是否覆盖?覆盖后将失去所有已导出数据!", "警告", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); 23 if (fg == DialogResult.Yes)//确认删除 24 { 25 //对重复文件夹进行删除操作 26 DirectoryInfo dl = new DirectoryInfo(expath); 27 dl.Delete(true); 28 DialogResult qr= MessageBox.Show("删除命令已执行,是否继续?", "提示", MessageBoxButtons.YesNo, MessageBoxIcon.Question); 29 if (qr == DialogResult.Yes) 30 { 31 //继续执行命令 32 } 33 else 34 { 35 if (qr == DialogResult.No)//删除文件夹后不继续 36 { 37 i = 1; 38 } 39 } 40 } 41 else 42 { 43 if (fg == DialogResult.No)//确认不删除 44 { 45 i = 1; 46 } 47 } 48 } 49 //强制用户确认后才进行解压操作 50 if (i == 0) 51 { 52 //获取打开文件的路径(全路径,含文件名) 53 int fnlength = path.ToString().LastIndexOf("FileName"); 54 string sFile = path.ToString().Substring(fnlength + 9, path.ToString().Length - fnlength - 9); 55 //获取打开文件所在目录,传递至sourcepath 56 string sourcepath = Path.GetDirectoryName(sFile); 57 58 //开始解压文件 59 ZipFile.ExtractToDirectory(path.FileName, expath); 60 //跳转至xl文件夹下 61 string copath = expath + "\\xl\\"; 62 //获取xl文件夹下所有带有comments字符的xml文件路径 63 string[] pathFile = Directory.GetFiles(copath, "comments*.xml", SearchOption.TopDirectoryOnly); 64 //将中间文件命名为cobak.xml 65 string strcon = copath + @"cobak.xml"; 66 //暂时将替换容器内容清空 67 string con = ""; 68 foreach (string str in pathFile) 69 { 70 StreamReader reader = new StreamReader(str, Encoding.UTF8); 71 //读取至文件尾 72 con = reader.ReadToEnd(); 73 //替换shapeId="0"为空值 74 con = con.Replace(@"shapeId=""0""", ""); 75 //替换Excel2013的不正确的XML代码块 76 con = con.Replace(@"</commment></commentList></comments>", ""); 77 78 //开始以UTF-8编码开始写操作 79 StreamWriter writer = new StreamWriter(strcon, false, Encoding.UTF8); 80 //写入已替换的内容 81 writer.Write(con); 82 //释放写入缓冲区,暂时关闭写入器 83 writer.Flush(); 84 writer.Close(); 85 reader.Close(); 86 87 //将写入文件复制为原文件,删除写入文件 88 File.Copy(strcon, str, true); 89 File.Delete(strcon); 90 } 91 //到此替换写入操作已完成 92 93 //开始修改文件扩展名 94 string nFile = Path.ChangeExtension(sFile, ".zip"); 95 FileInfo fi = new FileInfo(sFile); 96 fi.MoveTo(nFile); 97 98 //开始Czip操作 99 CZip.ZipFile zip = new CZip.ZipFile(nFile); 100 zip.BeginUpdate(); 101 //获取每个comments.xml文件的目录 102 foreach (string str in pathFile) 103 { 104 string filename = Path.GetFileName(str); 105 zip.Add(str,@"/xl/" + filename); 106 107 } 108 zip.CommitUpdate(); 109 //关闭Czip 110 zip.Close(); 111 //将文件扩展名改回xlsx 112 string endFile =Path.ChangeExtension(nFile, ".xlsx"); 113 //删除临时目录 114 fi.CopyTo(endFile, true); 115 DirectoryInfo dl = new DirectoryInfo(expath); 116 dl.Delete(true); 117 //删除临时文件 118 File.Delete(nFile); 119 120 121 System.Diagnostics.Process.Start("explorer.exe", sourcepath); 122 } 123 }
项目在Github仓库的release下载地址:
https://github.com/InfinityEx/Excel-Comments-Fixer/releases/tag/Debug
标签:批注,string,文件,C#,Excel,xlsx,xls From: https://www.cnblogs.com/FEAGLESTUDIO/p/18418362