【发布时间】:2021-09-10 23:14:55
【问题描述】:
我们有一个代码将 Excel XLSX 文档加载到内存中,对其进行一些修改并将其保存回来。
XmlDocument doc = new XmlDocument();
doc.Load(pp.GetStream());
XmlNode rootNode = doc.DocumentElement;
if (rootNode == null) return;
ProcessNode(rootNode);
if (this.fileModified)
{
doc.Save(pp.GetStream(FileMode.Create, FileAccess.Write));
}
这在处理小文件时效果很好,但在处理一些大型 Excel 文件时会引发 OutOfMemory 异常。所以我们决定改变方法,使用XmlReader类来不一次将文件加载到内存中。
PackagePartCollection ppc = this.Package.GetParts();
foreach (PackagePart pp in ppc)
{
if (!this.xmlContentTypesXlsx.Contains(pp.ContentType)) continue;
using (XmlReader reader = XmlReader.Create(pp.GetStream()))
{
reader.MoveToContent();
while (reader.EOF == false)
{
XmlDocument doc;
XmlNode rootNode;
if (reader.NodeType == XmlNodeType.Element && reader.Name == "hyperlinks")
{
doc = new XmlDocument();
rootNode = doc.ReadNode(reader);
if (rootNode != null)
{
doc.AppendChild(rootNode);
ProcessNode(rootNode); // how can I save updated changes back to the file?
}
}
else if (reader.NodeType == XmlNodeType.Element && reader.Name == "row")
{
doc = new XmlDocument();
rootNode = doc.ReadNode(reader);
if (rootNode != null)
{
doc.AppendChild(rootNode);
ProcessNode(rootNode); // how can I save updated changes back to the file?
}
}
else
{
reader.Read();
}
}
}
}
这会逐个节点读取文件节点并处理我们需要的节点(并在那里更改一些值)。但是,我不确定如何将这些值更新回原始 Excel 文件。
我尝试将XmlWriter 与XmlReader 一起使用,但无法使其正常工作。有什么想法吗?
更新:
我尝试使用 cmets 部分的@dbc 建议,但对我来说似乎太慢了。对于大文件,它可能不会抛出 OutOfMemory 异常,但处理将需要很长时间。
PackagePartCollection ppc = this.Package.GetParts();
foreach (PackagePart pp in ppc)
{
if (!this.xmlContentTypesXlsx.Contains(pp.ContentType)) continue;
StringBuilder strBuilder = new StringBuilder();
using (XmlReader reader = XmlReader.Create(pp.GetStream()))
{
using (XmlWriter writer = this.Package.FileOpenAccess == FileAccess.ReadWrite ? XmlWriter.Create(strBuilder) : null)
{
reader.MoveToContent();
while (reader.EOF == false)
{
XmlDocument doc;
XmlNode rootNode;
if (reader.NodeType == XmlNodeType.Element && reader.Name == "hyperlinks")
{
doc = new XmlDocument();
rootNode = doc.ReadNode(reader);
if (rootNode != null)
{
doc.AppendChild(rootNode);
ProcessNode(rootNode);
writer?.WriteRaw(rootNode.OuterXml);
}
}
else if (reader.NodeType == XmlNodeType.Element && reader.Name == "row")
{
doc = new XmlDocument();
rootNode = doc.ReadNode(reader);
if (rootNode != null)
{
doc.AppendChild(rootNode);
ProcessNode(rootNode);
writer?.WriteRaw(rootNode.OuterXml);
}
}
else
{
WriteShallowNode(writer, reader); // Used from the @dbc's suggested stackoverflow answers
reader.Read();
}
}
writer?.Flush();
}
}
}
注意 1:我正在使用 StringBuilder 进行测试,但计划最终切换到临时文件。 注意 2:我尝试在每 100 个元素后刷新 XmlWriter,但它仍然很慢。
有什么想法吗?
【问题讨论】:
-
您可以进行从
XmlReader到XmlWriter的流式转换,例如如Edit a large XML file和Automating replacing tables from external files所示。 -
XmlReader是只进的,所以如果你想在必要时创建一个临时输出文件,你需要进行两次传递。 -
我会摆脱
writer?.WriteRaw(rootNode.OuterXml);并使用if (writer != null) { rootNode.WriteContentTo(writer); }。 LINQ-to-XML 也比XmlDocument快一点,所以你可以切换到那个。为什么你允许一个空的writer?如果你什么都不写,代码的目的是什么?您是否进行了两次通过,一次检查是否会修改任何内容?如果是这样,在您第一次通过时,只要找到需要修改的节点,您就可以返回true。 -
对不起,我的错误,我应该建议
XmlElement.WriteTo(XmlWriter)。我已经习惯了使用XElement,现在我对旧的 XML DOM 有点生疏了。 -
您可能应该为此提出另一个问题。有
XmlWriterSettings.OmitXmlDeclaration,因此一种选择可能是使用WriteShallowNode()手动复制根节点之前的节点。
标签: c# excel xml xlsx xmlreader