由于我正在编写相当大的xlsx文件,因此我按照本页推荐使用OpenXmlReader
和OpenXmlWriter
:
我唯一要做的就是更改现有单元格中的公式,并确保丢弃它们的值,以便在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文件夹中缺少两个文件夹:charts
和drawings
很明显,我错过了将这些代码添加到最终存档中的代码,但我还不能确定这是什么代码。
答案 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在打开文件时不再抱怨,图表全部存在并更新。