使用Open XML SDK检索内容控件时出现问题 [英] Problems retrieving content controls with Open XML sdk
问题描述
我正在开发一种将生成Word文档的解决方案.单词文档是根据具有定义的内容控件的模板文档生成的.当我的模板中只有一个内容控件时,一切对我都很好,但是在使用更多的内容控件扩展模板文档后,我遇到了异常.似乎我找不到内容控件.
I am developing a solution that will generate word-documents. The word-documents are generated on the basis of a template document which has defined content controls. Everything was working good for me when I had only one content control in my template, but after expanding the template document with more content controls, I am getting exceptions. It seems like I am not finding the content controls.
这是我的方法:
private void CreateReport(File file)
{
var byteArray = file.OpenBinary();
using (var mem = new MemoryStream())
{
mem.Write(byteArray, 0, byteArray.Length);
try
{
using (var wordDoc = WordprocessingDocument.Open(mem, true))
{
var mainPart = wordDoc.MainDocumentPart;
var firstName = mainPart.Document.Body.Descendants<SdtBlock>().Where
(r => r.SdtProperties.GetFirstChild<Tag>().Val == "FirstName").Single();
var t = firstName.Descendants<Text>().Single();
t.Text = _firstName;
var lastName = mainPart.Document.Body.Descendants<SdtBlock>().Where
(r => r.SdtProperties.GetFirstChild<Tag>().Val == "LastName").Single();
var t2= lastName.Descendants<Text>().Single();
t2.Text = _lastName;
mainPart.Document.Save();
SaveFileToSp(mem);
}
}
catch (FileFormatException)
{
}
}
}
这是我得到的例外:
System.Core.dll中发生类型'System.InvalidOperationException'的异常,但未在用户代码中处理.内部异常:空
An exception of type 'System.InvalidOperationException' occurred in System.Core.dll but was not handled in user code. Innerexception: Null
关于我如何编写更好的控件查找方法的任何提示?
Any tips for me on how I can write better method for finding controls?
推荐答案
您的问题是,在具有多个元素的序列上调用了Single()
的一个(或多个)调用.
Your issue is that one (or more) of your calls to Single()
is being called on a sequence that has more than one element. The documentation for Single()
states (emphasis mine):
返回序列中的唯一元素,如果序列中没有一个元素,则抛出异常.
在您的代码中,这可能在两种情况之一中发生.第一种是如果您有多个具有相同Tag
值的控件,例如,您在文档中可能会用标有"LastName"的两个控件来表示这一行
In your code this can happen in one of two scenarios. The first is if you have more than one control with the same Tag
value, for example you might have two controls in the document labelled "LastName" which would mean that this line
var lastName = mainPart.Document.Body.Descendants<SdtBlock>().Where
(r => r.SdtProperties.GetFirstChild<Tag>().Val == "LastName")
将返回两个元素.
第二个是您的内容控件中是否包含多个Text
元素,在这种情况下,此行
The second is if your content control has more than one Text
element in it in which case this line
var t = firstName.Descendants<Text>();
将返回多个元素.例如,如果我创建一个内容为"This is is a test"的控件,那么我最终得到的XML包含4个Text
元素:
would return multiple elements. For example if I create a control with the content "This is a test" I end up with XML which has 4 Text
elements:
<w:p w:rsidR="00086A5B" w:rsidRDefault="003515CB">
<w:r>
<w:rPr>
<w:rStyle w:val="PlaceholderText" />
</w:rPr>
<w:t xml:space="preserve">This </w:t>
</w:r>
<w:r>
<w:rPr>
<w:rStyle w:val="PlaceholderText" />
<w:i />
</w:rPr>
<w:t>is</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rStyle w:val="PlaceholderText" />
</w:rPr>
<w:t xml:space="preserve"> </w:t>
</w:r>
<w:r w:rsidR="00E1178E">
<w:rPr>
<w:rStyle w:val="PlaceholderText" />
</w:rPr>
<w:t>a test</w:t>
</w:r>
</w:p>
如何解决第一个问题取决于您是希望替换匹配的Tag
元素的 all 还是只替换一个特定的元素(例如第一个或最后一个).
How to get round the first issue depends on whether you wish to replace all of the matching Tag
elements or just one particular one (such as the first or last).
如果只想替换一个,可以将呼叫从Single()
更改为First()
或Last()
,但是我想您需要全部替换.在这种情况下,您需要为每个要替换的标签名称在每个匹配的元素周围循环.
If you want to replace just one you can change the call from Single()
to First()
or Last()
for example but I guess you need to replace them all. In that case you need to loop around each matching element for each tag name you wish to replace.
将呼叫移至Single()
将返回一个IEnumerable<SdtBlock>
,您可以反复进行替换:
Removing the call to Single()
will return an IEnumerable<SdtBlock>
which you can iterate around replacing each one:
IEnumerable<SdtBlock> firstNameFields = mainPart.Document.Body.Descendants<SdtBlock>().Where
(r => r.SdtProperties.GetFirstChild<Tag>().Val == "FirstName");
foreach (var firstName in firstNameFields)
{
var t = firstName.Descendants<Text>().Single();
t.Text = _firstName;
}
解决第二个问题要棘手一些.我认为最简单的解决方案是从内容中删除所有现有的段落,然后添加一个新的段落,其中包含您要输出的文本.
To get around the second problem is slightly more tricky. The easiest solution in my opinion is to remove all of the existing paragraphs from the content and then add a new one with the text you wish to output.
将其分解为一种方法可能是有意义的,因为有很多重复的代码-遵循以下几行原则应该可以做到这一点:
Breaking this out into a method probably makes sense as there's a lot of repeated code - something along these lines should do it:
private static void ReplaceTags(MainDocumentPart mainPart, string tagName, string tagValue)
{
//grab all the tag fields
IEnumerable<SdtBlock> tagFields = mainPart.Document.Body.Descendants<SdtBlock>().Where
(r => r.SdtProperties.GetFirstChild<Tag>().Val == tagName);
foreach (var field in tagFields)
{
//remove all paragraphs from the content block
field.SdtContentBlock.RemoveAllChildren<Paragraph>();
//create a new paragraph containing a run and a text element
Paragraph newParagraph = new Paragraph();
Run newRun = new Run();
Text newText = new Text(tagValue);
newRun.Append(newText);
newParagraph.Append(newRun);
//add the new paragraph to the content block
field.SdtContentBlock.Append(newParagraph);
}
}
然后可以从您的代码中调用以下代码:
Which can then be called from your code like so:
using (var wordDoc = WordprocessingDocument.Open(mem, true))
{
var mainPart = wordDoc.MainDocumentPart;
ReplaceTags(mainPart, "FirstName", _firstName);
ReplaceTags(mainPart, "LastName", _lastName);
mainPart.Document.Save();
SaveFileToSp(mem);
}
这篇关于使用Open XML SDK检索内容控件时出现问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!