使用 Open XML sdk 检索内容控件时出现问题
Problems retrieving content controls with Open XML sdk
我正在开发一个可以生成 word 文档的解决方案。 word文档是在定义了内容控件的模板文档的基础上生成的。当我的模板中只有一个内容控件时,一切对我来说都很好,但是在使用更多内容控件扩展模板文档后,我遇到了异常。好像我没有找到内容控件。
这是我的方法:
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)
{
}
}
}
这是我得到的异常:
An exception of type 'System.InvalidOperationException' occurred in System.Core.dll but was not handled in user code. Innerexception: Null
关于如何编写更好的查找控件的方法,有什么提示吗?
我认为您的 Single()
方法导致了异常。
当您只有一个内容控件时,Single()
可以获得唯一可用的元素。但是当您展开内容控件时,您的 Single()
方法可能会导致 InvalidOperationException
,因为序列中有多个元素。如果是这种情况,请尝试循环您的代码并一次取一个元素。
您的问题是您对 Single()
的一个(或多个)调用是在具有多个元素的序列上调用的。 Single()
的 documentation 状态(强调我的):
Returns the only element of a sequence, and throws an exception if there is not exactly one element in the sequence.
在您的代码中,这可能会在两种情况之一中发生。第一个是如果您有多个具有相同 Tag
值的控件,例如您可能在文档中有两个标记为 "LastName" 的控件,这意味着此行
var lastName = mainPart.Document.Body.Descendants<SdtBlock>().Where
(r => r.SdtProperties.GetFirstChild<Tag>().Val == "LastName")
会return两个元素。
第二种情况是,如果您的内容控件中有多个 Text
元素,在这种情况下,这一行
var t = firstName.Descendants<Text>();
会 return 多个元素。例如,如果我创建一个内容为 "This is a test" 的控件,我最终会得到 XML,其中有 4 个 Text
元素:
<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
元素还是只替换一个特定的元素(例如第一个或最后)。
如果您只想更换一个,您可以将呼叫从 Single()
更改为 First()
或 Last()
,但我想您需要全部更换。在这种情况下,您需要为要替换的每个标签名称循环每个匹配元素。
删除对 Single()
的调用将 return 一个 IEnumerable<SdtBlock>
,您可以循环替换每个调用:
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;
}
解决第二个问题稍微有点棘手。在我看来,最简单 的解决方案是从内容中删除所有现有段落,然后添加一个包含您希望输出的文本的新段落。
将其分解成一个方法可能是有意义的,因为有很多重复的代码 - 按照这些思路应该这样做:
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);
}
}
然后可以像这样从您的代码中调用它:
using (var wordDoc = WordprocessingDocument.Open(mem, true))
{
var mainPart = wordDoc.MainDocumentPart;
ReplaceTags(mainPart, "FirstName", _firstName);
ReplaceTags(mainPart, "LastName", _lastName);
mainPart.Document.Save();
SaveFileToSp(mem);
}
我正在开发一个可以生成 word 文档的解决方案。 word文档是在定义了内容控件的模板文档的基础上生成的。当我的模板中只有一个内容控件时,一切对我来说都很好,但是在使用更多内容控件扩展模板文档后,我遇到了异常。好像我没有找到内容控件。
这是我的方法:
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)
{
}
}
}
这是我得到的异常:
An exception of type 'System.InvalidOperationException' occurred in System.Core.dll but was not handled in user code. Innerexception: Null
关于如何编写更好的查找控件的方法,有什么提示吗?
我认为您的 Single()
方法导致了异常。
当您只有一个内容控件时,Single()
可以获得唯一可用的元素。但是当您展开内容控件时,您的 Single()
方法可能会导致 InvalidOperationException
,因为序列中有多个元素。如果是这种情况,请尝试循环您的代码并一次取一个元素。
您的问题是您对 Single()
的一个(或多个)调用是在具有多个元素的序列上调用的。 Single()
的 documentation 状态(强调我的):
Returns the only element of a sequence, and throws an exception if there is not exactly one element in the sequence.
在您的代码中,这可能会在两种情况之一中发生。第一个是如果您有多个具有相同 Tag
值的控件,例如您可能在文档中有两个标记为 "LastName" 的控件,这意味着此行
var lastName = mainPart.Document.Body.Descendants<SdtBlock>().Where
(r => r.SdtProperties.GetFirstChild<Tag>().Val == "LastName")
会return两个元素。
第二种情况是,如果您的内容控件中有多个 Text
元素,在这种情况下,这一行
var t = firstName.Descendants<Text>();
会 return 多个元素。例如,如果我创建一个内容为 "This is a test" 的控件,我最终会得到 XML,其中有 4 个 Text
元素:
<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
元素还是只替换一个特定的元素(例如第一个或最后)。
如果您只想更换一个,您可以将呼叫从 Single()
更改为 First()
或 Last()
,但我想您需要全部更换。在这种情况下,您需要为要替换的每个标签名称循环每个匹配元素。
删除对 Single()
的调用将 return 一个 IEnumerable<SdtBlock>
,您可以循环替换每个调用:
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;
}
解决第二个问题稍微有点棘手。在我看来,最简单 的解决方案是从内容中删除所有现有段落,然后添加一个包含您希望输出的文本的新段落。
将其分解成一个方法可能是有意义的,因为有很多重复的代码 - 按照这些思路应该这样做:
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);
}
}
然后可以像这样从您的代码中调用它:
using (var wordDoc = WordprocessingDocument.Open(mem, true))
{
var mainPart = wordDoc.MainDocumentPart;
ReplaceTags(mainPart, "FirstName", _firstName);
ReplaceTags(mainPart, "LastName", _lastName);
mainPart.Document.Save();
SaveFileToSp(mem);
}