替换 excel 文件中的无效 XML 字符并将其写回磁盘导致在 MS Excel 中打开文件时出现文件损坏错误

Replacing Invalid XML characters from an excel file and writing it back to disk causes file is corrupted error on opening in MS Excel

问题的一些背景知识:

我们有一个 ASP.NET MVC5 应用程序,我们在其中使用 FlexMonster 在网格中显示数据。数据源是一个存储过程,它将所有数据带入 UI 网格,一旦用户单击导出按钮,它就会将报告导出到 Excel。但是,在某些情况下,导出到 excel 会失败。 部分数据存在一些无效字符,未按建议修复来源possible/feasiblehere

我目前的做法:

EPPlus 库无法初始化工作簿,因为输入 excel 文件包含一些无效的 XML 字符。我发现该文件已转储,其中包含一些无效字符。我研究了可能的方法。

首先,我在excel文件中找出了有问题的字符。我首先尝试使用 Notepad++ 手动将无效字符替换为空白 space,EPPlus 可以成功读取文件。

现在使用其他 SO 线程 here and here 中给出的方法,我替换了所有可能出现的无效字符。我现在正在使用

XmlConvert.IsXmlChar

找出有问题的 XML 字符并替换为空白 space 的方法。

我创建了一个示例程序,我正在尝试处理有问题的 excel sheet。

//in main method 
String readFile = File.ReadAllText(filePath);
string content = RemoveInvalidXmlChars(readFile);
File.WriteAllText(filePath, content);

//removal of invalid characters
        static string RemoveInvalidXmlChars(string inputText)  
        {
            StringBuilder withoutInvalidXmlCharsBuilder = new StringBuilder();
            int firstOccurenceOfRealData = inputText.IndexOf("<t>");
            int lastOccurenceOfRealData = inputText.LastIndexOf("</t>");

            if (firstOccurenceOfRealData < 0 ||
                lastOccurenceOfRealData < 0 ||
                firstOccurenceOfRealData > lastOccurenceOfRealData)
                return inputText;

            withoutInvalidXmlCharsBuilder.Append(inputText.Substring(0, firstOccurenceOfRealData)); 
            int remaining = lastOccurenceOfRealData - firstOccurenceOfRealData;
            string textToCheckFor = inputText.Substring(firstOccurenceOfRealData, remaining); 

            foreach (char c in textToCheckFor)
            {
                withoutInvalidXmlCharsBuilder.Append((XmlConvert.IsXmlChar(c)) ? c : ' ');
            }
      withoutInvalidXmlCharsBuilder.Append(inputText.Substring(lastOccurenceOfRealData));

            return withoutInvalidXmlCharsBuilder.ToString();

        }

如果我使用 notepad++ 手动替换有问题的字符,则文件在 MSExcel 中打开 很好。上面提到的代码成功地替换了相同的无效字符并将内容写回到文件中。但是,当我尝试使用 MS Excel 打开 excel 文件时,它抛出一个错误,指出文件可能已损坏并且没有显示任何内容 (下面的快照).此外,以下代码

var excelPackage = new ExcelPackage(new FileInfo(filePath));

在我通过 Notepad++ 更新的文件上,抛出以下异常

"CRC error: the file being extracted appears to be corrupted. Expected 0x7478AABE, Actual 0xE9191E00"}

我的问题:

  1. 我这样修改内容的方法正确吗?
  2. 如果是,我如何将更新后的字符串写入 Excel 文件?
  3. 如果我的方法是错误的,我该如何继续摆脱无效的 XML 字符?

打开文件时显示错误(没有无效的 XML 字符):

首先弹出

当我点击是时

提前致谢!

根据您最后的评论,它听起来确实像一个二进制(推测为 XLSX)文件。要确认,请使用 7zip 打开 FlexMonster 创建的文件。如果它正常打开并且您在文件夹中看到一堆 XML 文件,则它是 XLSX。

在这种情况下,二进制文件上的 search/replace 听起来是个很糟糕的主意。它可能适用于 XML 部分,但也可能会替换其他部分中的合法字符。我认为更好的方法是按照@PanagiotisKanavos 的建议并使用 ZipArchive。但是你必须按照正确的顺序重建它,否则 Excel 会抱怨。与这里的做法类似 ,您可以这样做:

public static void ReplaceXmlString(this ZipArchive xlsxZip, FileInfo outFile, string oldString, string newstring)
{
    using (var outStream = outFile.Open(FileMode.Create, FileAccess.ReadWrite))
    using (var copiedzip = new ZipArchive(outStream, ZipArchiveMode.Update))
    {
        //Go though each file in the zip one by one and copy over to the new file - entries need to be in order
        foreach (var entry in xlsxZip.Entries)
        {
            var newentry = copiedzip.CreateEntry(entry.FullName);
            var newstream = newentry.Open();
            var orgstream = entry.Open();

            //Copy non-xml files over
            if (!entry.Name.EndsWith(".xml"))
            {
                orgstream.CopyTo(newstream);
            }
            else
            {
                //Load the xml document to manipulate
                var xdoc = new XmlDocument();
                xdoc.Load(orgstream);

                var xml = xdoc.OuterXml.Replace(oldString, newstring);
                xdoc = new XmlDocument();
                xdoc.LoadXml(xml);

                xdoc.Save(newstream);
            }

            orgstream.Close();
            newstream.Flush();
            newstream.Close();
        }
    }
}

这样使用时:

[TestMethod]
public void ReplaceXmlTest()
{
    var datatable = new DataTable("tblData");
    datatable.Columns.AddRange(new[]
    {
        new DataColumn("Col1", typeof (int)),
        new DataColumn("Col2", typeof (int)),
        new DataColumn("Col3", typeof (string))
    });

    for (var i = 0; i < 10; i++)
    {
        var row = datatable.NewRow();
        row[0] = i;
        row[1] = i * 10;
        row[2] = i % 2 == 0 ? "ABCD" : "AXCD";
        datatable.Rows.Add(row);
    }

    using (var pck = new ExcelPackage())
    {
        var workbook = pck.Workbook;
        var worksheet = workbook.Worksheets.Add("source");

        worksheet.Cells.LoadFromDataTable(datatable, true);
        worksheet.Tables.Add(worksheet.Cells["A1:C11"], "Table1");

        //Now similulate the copy/open of the excel file into a zip archive
        using (var orginalzip = new ZipArchive(new MemoryStream(pck.GetAsByteArray()), ZipArchiveMode.Read))
        {
            var fi = new FileInfo(@"c:\temp\ReplaceXmlTest.xlsx");
            if (fi.Exists)
                fi.Delete();

            orginalzip.ReplaceXmlString(fi, "AXCD", "REPLACED!!");
        }
    }
}

给出这个:

请记住,这完全是蛮力。您可以采取任何措施使文件过滤器更智能,而不是简单地执行所有 xml 个文件,这将是一件非常好的事情。如果问题出在 SharedString.xml 文件中或工作表文件夹中的 xml 文件中,则可能将其限制在 SharedString.xml 文件中。在不了解更多数据的情况下很难说。