OpenXML编写器使用使图形不可读

时间:2016-07-06 14:53:49

标签: c# openxml openxml-sdk

由于我正在编写相当大的xlsx文件,因此我按照本页推荐使用OpenXmlReaderOpenXmlWriter

https://blogs.msdn.microsoft.com/brian_jones/2010/06/22/writing-large-excel-files-with-the-open-xml-sdk/

我唯一要做的就是更改现有单元格中的公式,并确保丢弃它们的值,以便在Excel打开文件时重新计算它。

以下是我使用的功能:

    public void Save(Stream Input, Stream Output)
    {
        Input.Position = 0;
        if (Input != Output)
            Input.CopyTo(Output);

        using (SpreadsheetDocument document = SpreadsheetDocument.Open(Output, true))
        {
            WorkbookPart wbPart = document.WorkbookPart;

            // force recalculation as we change formulas
            wbPart.Workbook.CalculationProperties.ForceFullCalculation = true;
            wbPart.Workbook.CalculationProperties.FullCalculationOnLoad = true;

            // store the worksheet parts in a separate list because the loop below
            // adds and removes elements inside wbPart.WorksheetParts
            List<WorksheetPart> originalWsParts = new List<WorksheetPart>();
            foreach (WorksheetPart inputWsPart in wbPart.WorksheetParts)
                originalWsParts.Add(inputWsPart);

            // process all worksheets in the workbook
            foreach (WorksheetPart inputWsPart in originalWsParts)
            {
                string origninalSheetId = wbPart.GetIdOfPart(inputWsPart);

                WorksheetPart replacementWsPart = wbPart.AddNewPart<WorksheetPart>();
                string replacementWsPartId = wbPart.GetIdOfPart(replacementWsPart);

                OpenXmlReader reader = OpenXmlReader.Create(inputWsPart);
                OpenXmlWriter writer = OpenXmlWriter.Create(replacementWsPart);

                while (reader.Read())
                {
                    logger.Debug(reader.ElementType.Name);
                    if (reader.ElementType == typeof(Cell) && reader.IsStartElement)
                    {
                        writer.WriteStartElement(reader);

                        // write the cell content, changing the formula and skipping the value
                        while (reader.Read() && !(reader.ElementType == typeof(Cell) && reader.IsEndElement))
                        {
                            if (reader.IsStartElement)
                            {
                                if (reader.ElementType == typeof(CellFormula))
                                {
                                    CellFormula element = reader.LoadCurrentElement() as CellFormula;
                                    element.Text = "SUM(1,2)";
                                    element.CalculateCell = true;
                                    writer.WriteElement(element);
                                }
                                else if (reader.ElementType != typeof(CellValue))
                                {
                                    writer.WriteStartElement(reader);
                                    string elementText = reader.GetText();
                                    if (!String.IsNullOrEmpty(elementText))
                                        writer.WriteString(elementText);
                                }
                            }
                            else if (reader.IsEndElement)
                            {
                                if (reader.ElementType != typeof(CellValue))
                                    writer.WriteEndElement();
                            }
                        }

                        writer.WriteEndElement();
                    }
                    else
                    {
                        if (reader.IsStartElement)
                        {
                            writer.WriteStartElement(reader);
                            string elementText = reader.GetText();
                            if (!String.IsNullOrEmpty(elementText))
                                writer.WriteString(elementText);
                        }
                        else if (reader.IsEndElement)
                        {
                            writer.WriteEndElement();
                        }
                    }
                }

                reader.Close();
                writer.Close();

                Sheet sheet = wbPart.Workbook.Descendants<Sheet>()
                    .Where(s => s.Id.Value.Equals(origninalSheetId)).First();
                sheet.Id.Value = replacementWsPartId;
                wbPart.DeletePart(inputWsPart);
            }
        }
    }

它在最简单的工作簿上工作得很好,但是当文件中的工作表内有图纸时,它会创建不可读的文件。 例如,如果我在Sheet1上有图纸,当Excel打开保存的文件时,它会抱怨该文件包含不可读的部分,并在其已删除的内容列表中显示图纸。

我解压缩了xlsx文件并比较了sheetX.xml文件,除了添加的x:前缀之外,它们是相同的。

显然,我错过了一些东西,但是阅读了我能找到的各种文档,没有任何事情发生在我身上。我相信原始工作表部分的引用尚未更新,但我没有看到工作簿中的任何图形后代。

非常欢迎任何帮助。

更新

我仔细查看了文件内容,xl文件夹中缺少两个文件夹:chartsdrawings

很明显,我错过了将这些代码添加到最终存档中的代码,但我还不能确定这是什么代码。

1 个答案:

答案 0 :(得分:0)

上面的代码在循环中创建了一个新的WorksheetPart,当意图始终是克隆输入部分时。

我因此寻找克隆工作表的方法,但ClonePart()上没有WorkbookPart方法。

对我来说幸运的是,其他人已经遇到过这个问题而且Brian Jones在他的博客上发挥过关于此问题的一部分:

https://blogs.msdn.microsoft.com/brian_jones/2009/02/19/how-to-copy-a-worksheet-within-a-workbook/

所以,我将using语句的开头改为:

using (SpreadsheetDocument document = SpreadsheetDocument.Open(Output, true), tmpDocument = SpreadsheetDocument.Create(new MemoryStream(), document.DocumentType))
{
    WorkbookPart wbPart = document.WorkbookPart;
    WorkbookPart tmpWbPart = tmpDocument.AddWorkbookPart();

然后,我现在拥有以下代码,而不是调用AddNewPart<WorksheetPart>

// can't directly clone, so add to the temporary workbook part and then back
// into the working workbook part
WorksheetPart tmpWsPart = tmpWbPart.AddPart(inputWsPart);
WorksheetPart replacementWsPart = wbPart.AddPart(tmpWsPart);
tmpWbPart.DeletePart(tmpWsPart);
tmpWsPart = null;

通过这些更改,我现在在工作表中有Drawing部分,xlsx文件中的相关文件夹。 因此,Excel在打开文件时不再抱怨,图表全部存在并更新。