使用documentformat.openxml

时间:2019-11-19 16:18:58

标签: ms-word openxml openxml-sdk

我有一个带有某些内容控件的Microsoft Word模板。它包含一个目录和一些其他信息。

在页面上,我设置了一些内容控件,希望从RESTful服务中插入新文本。如果RESTful服务返回数据(对象)数组,则需要基于Word模板在每个页面上复制信息。

关于如何使用Open XML SDK(DocumentFormat.OpenXml)做到这一点的任何想法?

编辑:

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

那么,如何在新文档中从同一模板创建多个页面?数据作为数组输入。

1 个答案:

答案 0 :(得分:2)

下面的示例代码(已通过单元测试并且可以工作)完成了您要实现的目标。它基于对问题和假设的以下解释:

  • “控件占位符”表示“富文本内容控件”,在Open XML术语中称为块级结构化文档标签(SDT),因此由Open XML SDK中的SdtBlock类表示。
  • 内容控件具有标签,这意味着相关的w:sdt元素具有诸如<w:tag="tagValue" />之类的孙元素。这些标签用于将从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,纯文本内容控件),则原理是一样的代替Paragraph实例中包含的w:p实例(SdtContentBlock元素),您将拥有Run中包含的w:r实例(SdtContentRun元素)实例。

更新2019-11-23:我的CodeSnippets GitHub存储库包含ContentControlWriterAltChunkAssemblyTests类的代码。后者显示了如何使用ContentControlWriter类。