Excel歪门邪道(1) — 用C#解决Excel修复文件后批注被删除的问题

今天是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文件不支持后续的一些功能,例如切片器、一些后来更新的公式等等,甚至是条件格式、表格样式也会发生变化1,其次是表格范围,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,因为是刚学不久,又抄了别人不少的实现逻辑,代码的可读性就不用苛求了,能跑就行

using System;
using System.Text;
using System.Windows.Forms;
using System.IO.Compression;
using System.IO;
using CZip = ICSharpCode.SharpZipLib.Zip;

private void Button1_Click(object sender, EventArgs e)
{
    int i = 0;
    OpenFileDialog path = new OpenFileDialog();
    //文件类型过滤
    path.Filter = @"待修复Excel表格|*.xlsx";
    //展开文件选择对话框
    DialogResult result = path.ShowDialog();
    if (result == DialogResult.OK)
    {
        //获取导出文件夹目录
        string expath = path.FileName + @"_Fix";
        if(Directory.Exists(expath))
        {
            DialogResult fg= MessageBox.Show("导出数据已存在!是否覆盖?覆盖后将失去所有已导出数据!", "警告", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
            if (fg == DialogResult.Yes)//确认删除
            {
                //对重复文件夹进行删除操作
                DirectoryInfo dl = new DirectoryInfo(expath);
                dl.Delete(true);
                DialogResult qr= MessageBox.Show("删除命令已执行,是否继续?", "提示", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
                if (qr == DialogResult.Yes)
                {
                    //继续执行命令
                }
                else
                {
                    if (qr == DialogResult.No)//删除文件夹后不继续
                    {
                        i = 1;
                    }
                }
            }
            else
            {
                if (fg == DialogResult.No)//确认不删除
                {
                    i = 1;
                }
            }
        }
        //强制用户确认后才进行解压操作
        if (i == 0)
        {
            //获取打开文件的路径(全路径,含文件名)
            int fnlength = path.ToString().LastIndexOf("FileName");
            string sFile = path.ToString().Substring(fnlength + 9, path.ToString().Length - fnlength - 9);
            //获取打开文件所在目录,传递至sourcepath
            string sourcepath = Path.GetDirectoryName(sFile);

            //开始解压文件
            ZipFile.ExtractToDirectory(path.FileName, expath);
            //跳转至xl文件夹下
            string copath = expath + "\\xl\\";
            //获取xl文件夹下所有带有comments字符的xml文件路径
            string[] pathFile = Directory.GetFiles(copath, "comments*.xml", SearchOption.TopDirectoryOnly);
            //将中间文件命名为cobak.xml
            string strcon = copath + @"cobak.xml";
            //暂时将替换容器内容清空
            string con = "";
            foreach (string str in pathFile)
            {
                StreamReader reader = new StreamReader(str, Encoding.UTF8);
                //读取至文件尾
                con = reader.ReadToEnd();
                //替换shapeId="0"为空值
                con = con.Replace(@"shapeId=""0""", "");
                //替换Excel2013的不正确的XML代码块
                con = con.Replace(@"</commment></commentList></comments>", "");

                //开始以UTF-8编码开始写操作
                StreamWriter writer = new StreamWriter(strcon, false, Encoding.UTF8);
                //写入已替换的内容
                writer.Write(con);
                //释放写入缓冲区,暂时关闭写入器
                writer.Flush();
                writer.Close();
                reader.Close();

                //将写入文件复制为原文件,删除写入文件
                File.Copy(strcon, str, true);
                File.Delete(strcon);
            }
            //到此替换写入操作已完成

            //开始修改文件扩展名
            string nFile = Path.ChangeExtension(sFile, ".zip");
            FileInfo fi = new FileInfo(sFile);
            fi.MoveTo(nFile);

            //开始Czip操作
            CZip.ZipFile zip = new CZip.ZipFile(nFile);
            zip.BeginUpdate();
            //获取每个comments.xml文件的目录
            foreach (string str in pathFile)
            {
                string filename = Path.GetFileName(str);
                zip.Add(str,@"/xl/" + filename);

            }
            zip.CommitUpdate();
            //关闭Czip
            zip.Close();
            //将文件扩展名改回xlsx
            string endFile =Path.ChangeExtension(nFile, ".xlsx");
            //删除临时目录
            fi.CopyTo(endFile, true);
            DirectoryInfo dl = new DirectoryInfo(expath);
            dl.Delete(true);
            //删除临时文件
            File.Delete(nFile);


            System.Diagnostics.Process.Start("explorer.exe", sourcepath);
        }
    }

 

项目在Github仓库的release下载地址:

https://github.com/InfinityEx/Excel-Comments-Fixer/releases/tag/Debug

 

勘误清单:

1.2024/09/20 | 关于xls文件特性的表述:描述不准确,不兼容选项是否包含PQ编辑器没有详细测试过

posted @ 2024-09-18 13:57  InfinityEx  阅读(27)  评论(0编辑  收藏  举报