在 C# 中从现有 DOCX 生成 DOCX

Generate DOCX from an existing DOCX in C#

我有一个新项目需要生成 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());
        }
    }

根据 Thomas Weller 的反馈,我试用了 DocX。这个库使 open/duplicate/create DOCX 文件变得更容易。经过一些研究,我完全改变了我的方法。我最终使用现有的 DOCX 作为模板。

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

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

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

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

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

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();
}