首页 > 编程语言 >Excel歪门邪道(1) — 用C#解决Excel修复文件后批注被删除的问题

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

时间:2024-09-18 13:50:55浏览次数:1  
标签:批注 string 文件 C# Excel xlsx xls

今天是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

相关文章

  • 2024年一站式解决用termux安装matplotlib,pandas,numy和scipy问题
    用Python玩数据的技术人员都知道这几个库的重要性,话不多说,直接开始! termux版本:0.119.0-bate1​​​​   1.安装numpynumpy是Python的一种开源的科学计算库现在安装的是最新版本 1.26.5它是安装这几个库中最简单的,只需键入:pkgupdate&&pkgupgrade  #养......
  • android 14.0 Launcher3定制folder文件夹16宫格实现二
    1.概述在14.0的系统产品rom定制化开发中,对于Launcher3的定制功能也是不少的,比如在Launcher3中添加默认文件夹,把默认的app添加的文件夹里面,其他的app然后按顺序排序。在文件夹布局就是默认的16宫格布局,接下来分析下相关源码来实现功能2.Launcher3定制化之修改添加的默认文件......
  • Docker拉取镜像报错:failed to register layer: exit status 22: unpigz: abort: zlib
    今天在甲方的服务器上升级Docker容器时拉取镜像报错failedtoregisterlayer:exitstatus22:unpigz:abort:zlibversionlessthan1.2.3,很纳闷明明在阿里云上测试的时候没有这个问题来着,遂开始查资料发现是pigz的bug,将其升级即可重写拉取镜像运行操作步骤:安装新版本c......
  • java class
    cstdioimportjava.util.Scanner;classRead{//ilikeC++getchar()foreverQwQ!!!//idon'tknowwhyjavascannerdonothavethatQAQ!!!staticScannersc;staticStringbuff;staticintbufP;Read(){sc=new......
  • 解决ZBLOG网站运行中出现"JavaScript加载失败"问题
    当Z-Blog网站运行中出现“JavaScript加载失败”的问题时,可能的原因有几个方面,包括但不限于插件冲突、HTTPS配置问题、CDN问题等。以下是针对这些问题的一些解决方案:1.插件冲突问题描述:某些插件可能与Z-Blog核心或其他插件存在冲突,导致JavaScript无法正常加载。解决......
  • java-CC1 链条审计
    java-CC1链条审计CC1是CommonsCollections1的简称,它是ApacheCommonsCollections库中的一个已知的反序列化利用链。而这个库也是java中比较通用的库。在java语言里面有执行系统命令的Runtime类像php中的eval()、system()、exec()、shell_exec()、assert()、passt......
  • C++信奥老师解一本通题 1164:digit函数
    ​【题目描述】在程序中定义一函数digit(n,k),它能分离出整数n从右边数第k个数字。【输入】正整数n和k。【输出】一个数字。【输入样例】318593【输出样例】8#include<iostream>usingnamespacestd;intdigit(longlongn,intk){ if(k==1) returnn%10......
  • 打卡信奥刷题(773)用Scratch图形化工具信P5737[普及组/提高组] 【深基7.例3】闰年展示
    【深基7.例3】闰年展示题目描述输入x,yx,yx,y,输出[......
  • LeeCode打卡第二十八天
    LeeCode打卡第二十八天第一题:路径总和II(LeeCode第437题):给定一个二叉树的根节点root,和一个整数targetSum,求该二叉树里节点值之和等于targetSum的路径的数目。路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。解法......
  • zblog网站打开出现syntax error, unexpected '['解决办法
    当Z-Blog网站打开时出现 syntaxerror,unexpected'[' 的错误,这通常是PHP语法错误导致的。以下是详细的排查和解决步骤:常见原因及解决办法1.代码语法错误问题描述:PHP代码中存在语法错误。解决方法:找到报错的文件和行号。检查该行代码是否存在语法错误,如括号不匹......