使用Open XML sdk检索内容控件时出现问题

时间:2015-03-12 12:51:50

标签: ms-office sharepoint-2013 openxml openxml-sdk

我正在开发一个能够生成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)
            {
            }
        }
    }

这是我得到的例外:

  

类型&#39; System.InvalidOperationException&#39;的例外情况发生在System.Core.dll中但未在用户代码中处理。 Innerexception:Null

关于如何编写更好的方法来查找控件的任何提示?

2 个答案:

答案 0 :(得分:0)

我认为您的Single()方法导致异常。

当您只有一个内容控件时,Single()可以获得唯一可用的元素。但是,当您展开内容控件时,Single()方法可能会导致InvalidOperationException,因为序列中有多个元素。如果是这种情况,请尝试循环代码并一次取一个元素。

答案 1 :(得分:0)

您的问题是,对具有多个元素的序列调用Single()的一个(或多个)调用。 Single()州的documentation(强调我的):

  

返回序列的唯一元素,如果序列中没有恰好一个元素,则抛出异常。

在您的代码中,这可能发生在两种情况之一中。第一个是如果您有多个具有相同Tag值的控件,例如,您可能在标有“LastName”的文档中有两个控件,这意味着此行

var lastName = mainPart.Document.Body.Descendants<SdtBlock>().Where
                    (r => r.SdtProperties.GetFirstChild<Tag>().Val == "LastName")

将返回两个元素。

第二个是你的内容控件中有多个Text元素,在这种情况下这一行

var t = firstName.Descendants<Text>();

会返回多个元素。例如,如果我创建一个内容为“This a test”的控件,我最终会得到具有4个Text元素的XML:

<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()的调用将返回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);
}