【问题标题】:Generate DOCX from an existing DOCX in C#从 C# 中的现有 DOCX 生成 DOCX
【发布时间】:2021-05-10 18:36:15
【问题描述】:

我有一个需要生成 DOCX 的新项目。 我的客户为我提供了一个现有的 DOCX,我需要用数据库中的一些客户数据替换一些占位符。 好像这还不够具有挑战性,根据使用客户数据的某些条件,某些部分是可选的。所以我必须提供一些逻辑来完全省略 DOCX 的某些部分。

经过大量研究和一些 POC,我发现了一种新方法。 我已将 DOCX 保存为 Word XML 文档。这将创建一个包含所有内容的大型 XML 文件,甚至图像也被编码为 base64。之后,我将 XML 文件的内容复制到 T4 模板。这样做可以让我根据客户数据添加动态内容,并在我的代码中生成一个 Word XML 文档作为一个大字符串。

但现在我仍然坚持基于 Word XML 文档字符串再次创建 Docx。 我尝试过使用 OpenXml Sdk,但找不到任何关于如何执行此操作的真实文档。 经过一些实验,我最终得到了下面的代码,但它不解析 XML(根级别的数据无效。第 1 行,位置 1)。

作为第二次尝试,我尝试了another post 的一些建议,但这会导致另一个异常(XML 的内容无效,无法构造为元素。(参数'outerXml'))

有没有办法做到这一点,还是我应该离开 T4 模板并尝试另一种方法? T4 模板的另一个问题是一些图像的大小,它会导致一个长的 base64 字符串生成太多的行。我想我可以用占位符替换图像并在创建 XML 之前交换它们......

    public FileData CreateDocx(string title, string xml)
    {
        using (MemoryStream generatedDocument = new MemoryStream())
        {
            using (WordprocessingDocument package =
                WordprocessingDocument.Create(generatedDocument, WordprocessingDocumentType.Document))
            {
                var mainPart = package.AddMainDocumentPart();
                //First attempt
                //new Document(xml).Save(mainPart);

                var doc = new XmlDocument();
                doc.LoadXml(xml);
                new Document(doc.OuterXml).Save(mainPart);
            }

            return new FileData(title, generatedDocument.ToArray());
        }
    }

【问题讨论】:

标签: c# openxml docx t4 openxml-sdk


【解决方案1】:

根据 Thomas Weller 的反馈,我试用了DocX。这个库使打开/复制/创建 DOCX 文件变得更加容易。经过一番研究,我完全改变了我的方法。我最终使用现有的 DOCX 作为模板。

首先,我在需要从数据库中注入数据的段落中添加了占位符。为此,我使用了 {{CustomerName}} 之类的东西。通过使用 replaceText,我能够用正确的数据交换所有占位符。

完成此操作后,我添加了部分。这可以通过使用guide 在 Word 中轻松完成。添加部分后,我还添加了一个占位符来标记这些部分,因为您无法在 Word 中命名一个部分。所以我最终在部分的开头使用了 占位符,例如 {{SectionNationalCustomer}}。这使我可以使用 Linq 查询来查找我的部分,以搜索包含我的占位符的段落的所有部分。

一旦我收集了条件部分,我就可以通过遍历所有 SectionParagraphs删除它们来“删除”它们。完全删除这些部分似乎是不可能的。当该部分需要可见时,只需将占位符替换为空字符串即可。

我需要做的最后一件事是在文档中找到正确的表格。我通过使用新部分尝试了与以前相同的方法。但似乎 Section 对象的 Tables Collection 总是空的,即使其中有一个 Table。所以我需要另一种方法。我再次在表格的第一列中使用了一个唯一的占位符,例如 {{TableQuotation}}。然后我只是做了与部分相同的操作,并编写了一个 Linq 查询以通过查找具有正确占位符的段落来选择正确的表。

在这一切之后,我最终得到了一些看起来与此非常相似的代码:

using (var memoryStream = new MemoryStream())
{
    // Load  template document and make copy
    using (var template = DocX.Load("MyTemplate.docx"))
    {
        var document = template.Copy();

        //Swap placeholder with data
        document.ReplaceText("{{CustomerName}}", myData.CustomerName);

        //Hide or show section based on condition
        var section = document.Sections.FirstOrDefault(s => s.SectionParagraphs.Any(p => p.Text.StartsWith("{{SectionNationalCustomer}}")));
        if (myData.Customer.Address.National == true)
        {
            //Remove placeholder when section stays visible
            document.ReplaceText("{{SectionNationalCustomer}}", "");
        }
        else
        {
            //Remove contents of section
            foreach (var paragraph in section.SectionParagraphs)
            {
                document.RemoveParagraph(paragraph);
            }
        }

        //Find and edit table
        var table = document.Tables.FirstOrDefault(s => s.Paragraphs.Any(p => p.Text.Contains("{{TableQuotation}}")));
        document.ReplaceText("{{TableQuotation}}", "");
        table.RemoveRow(1);
        
        document.SaveAs(memoryStream);
    }

    return memoryStream.ToArray();
}

【讨论】:

  • 谢谢。请注意,DocX 仅供个人使用。
猜你喜欢
  • 2016-01-10
  • 1970-01-01
  • 2011-01-10
  • 1970-01-01
  • 1970-01-01
  • 2020-12-29
  • 1970-01-01
  • 2021-10-30
  • 1970-01-01
相关资源
最近更新 更多