使用 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。
我有一个包含一些内容控件的 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。