使用 documentformat.openxml 从包含多个页面的 word 模板创建一个新文档

create a new document from word template with multiple pages using documentformat.openxml

我有一个包含一些内容控件的 Microsoft Word 模板。它包含 table 的内容和一些额外的信息。

在页面上,我设置了一些内容控件,我想在其中插入来自 RESTful 服务的新文本。如果RESTful服务returns一个数据(对象)数组,我需要根据Word模板复制每个页面上的信息。

知道如何使用 Open XML SDK (DocumentFormat.OpenXml) 做到这一点吗?

编辑:

我在这里找到了这个 post,这很棒,但我不知道如何将数据数组应用到同一个模板的多个页面.

那么,如何在新文档中使用同一模板创建多个页面?数据以数组形式出现。

下面的示例代码(经过单元测试并且有效)可以实现您想要实现的目标。它基于以下对问题和假设的解释:

  • "Control place holders" 表示 "Rich Text content controls",在 Open XML 术语中称为块级结构化文档标签 (SDT),因此由 SdtBlock class 在打开 XML SDK 中。
  • 内容控件有标签,这意味着相关的 w:sdt 元素有像 <w:tag="tagValue" /> 这样的孙元素。这些标记用于 link 从 REST 服务接收到的数据到内容控件。
  • 数据作为 Dictionary<string, string> 提供,将标记值映射到内容控制文本(数据)。

一般方法是对 WordprocessingDocument 的主文档部分执行纯函数转换。 void WriteContentControls(WordprocessingDocument) 方法包装了最外层的纯函数转换 object TransformDocument(OpenXmlElement)。后者使用内部纯函数转换 object TransformSdtBlock(OpenXmlElement, string).

public class ContentControlWriter
{
    private readonly IDictionary<string, string> _contentMap;

    /// <summary>
    /// Initializes a new ContentControlWriter instance.
    /// </summary>
    /// <param name="contentMap">The mapping of content control tags to content control texts.
    /// </param>
    public ContentControlWriter(IDictionary<string, string> contentMap)
    {
        _contentMap = contentMap;
    }

    /// <summary>
    /// Transforms the given WordprocessingDocument by setting the content
    /// of relevant block-level content controls.
    /// </summary>
    /// <param name="wordDocument">The WordprocessingDocument to be transformed.</param>
    public void WriteContentControls(WordprocessingDocument wordDocument)
    {
        MainDocumentPart part = wordDocument.MainDocumentPart;
        part.Document = (Document) TransformDocument(part.Document);
    }

    private object TransformDocument(OpenXmlElement element)
    {
        if (element is SdtBlock sdt)
        {
            string tagValue = GetTagValue(sdt);
            if (_contentMap.TryGetValue(tagValue, out string text))
            {
                return TransformSdtBlock(sdt, text);
            }
        }

        return Transform(element, TransformDocument);
    }

    private static object TransformSdtBlock(OpenXmlElement element, string text)
    {
        return element is SdtContentBlock
            ? new SdtContentBlock(new Paragraph(new Run(new Text(text))))
            : Transform(element, e => TransformSdtBlock(e, text));
    }

    private static string GetTagValue(SdtElement sdt) => sdt
        .Descendants<Tag>()
        .Select(tag => tag.Val.Value)
        .FirstOrDefault();

    private static T Transform<T>(T element, Func<OpenXmlElement, object> transformation)
        where T : OpenXmlElement
    {
        var transformedElement = (T) element.CloneNode(false);
        transformedElement.Append(element.Elements().Select(e => (OpenXmlElement) transformation(e)));
        return transformedElement;
    }
}

即使细节有所不同(例如,关于如何将数据数组映射到特定内容控件),这也应该为实施您的特定解决方案提供足够的输入。此外,如果您不使用块级结构化文档标签(SdtBlock,富文本内容控件)而是使用内联级结构化文档标签(SdtRun,纯文本内容控件),原则是一样的。 SdtContentBlock 个实例中包含 Paragraph 个实例(w:p 个元素),SdtContentRun 个实例中包含 Run 个实例(w:r 个元素)实例。

2019-11-23 更新:我的 CodeSnippets GitHub repository contains the code for the ContentControlWriter and AltChunkAssemblyTests classes。后者显示了如何使用 ContentControlWriter class。