【问题标题】:Problems retrieving content controls with Open XML sdk使用 Open XML sdk 检索内容控件时出现问题
【发布时间】:2015-05-14 15:21:32
【问题描述】:

我正在开发一种可以生成 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)
            {
            }
        }
    }

这是我得到的例外:

System.Core.dll 中出现“System.InvalidOperationException”类型的异常,但未在用户代码中处理。内部异常:空

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

【问题讨论】:

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


    【解决方案1】:

    您的问题是您对Single() 的一个(或多个)调用是在具有多个元素的序列上调用的。 documentation for Single() 状态(强调我的):

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

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

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

    将返回两个元素。

    第二个是如果您的内容控件中包含多个Text 元素,在这种情况下这一行

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

    会返回多个元素。例如,如果我创建一个内容为“This is 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&lt;SdtBlock&gt;,您可以反复替换每个IEnumerable&lt;SdtBlock&gt;

    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);
    }
    

    【讨论】:

    • 嗨,小皮。非常感谢您的宝贵意见。我试过这个,但得到: System.Core.dll 中发生“System.InvalidOperationException”类型的异常,但未在用户代码中处理附加信息:序列包含多个元素。内部异常:null。
    • 对不起@Ilyas,​​我没想到有多个Text 元素。请查看我的更新...
    • 谢谢您,先生,非常感谢您。如果我有足够的声誉,我会给你所有的帖子加分。我为此苦苦挣扎。现在我可以获得除第一个之外的所有标签。你知道如何包含第一个吗?
    • 感谢@Ilyas - 如果答案有帮助,请随时accept it。第一个标签 应该 像所有其他标签一样工作。尝试检查第一个标签的 tagFields 的值,看看是否找到它 - 不要忘记您的姓名搜索区分大小写。
    【解决方案2】:

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

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

    【讨论】:

    • 您好,感谢您提供的宝贵建议。你有任何关于如何循环内容控件的示例吗?
    • 您可以简单地循环 SdtBlock 的后代,就像在您的 linq 查询中一样 - mainPart.Document.Body.Descendants&lt;SdtBlock&gt;()
    • 你好。我不确定我是否理解。您能告诉我如何专门更改此代码以正确循环吗? :) var lastName = mainPart.Document.Body.Descendants().Where (r => r.SdtProperties.GetFirstChild().Val == "LastName").Single(); var t2= lastName.Descendants().Single(); t2.Text = _lastName;
    猜你喜欢
    • 1970-01-01
    • 2011-02-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-23
    相关资源
    最近更新 更多