Проблемы с извлечением элементов управления содержимым с помощью Open XML 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)
            {
            }
        }
    }

Это исключение, которое я получаю:

Исключение типа "System.InvalidOperationException" возникло в System.Core.dll, но не было обработано в коде пользователя. Innerexception: Null

Любые советы для меня, как я могу написать лучший метод для поиска элементов управления?

2 ответа

Решение

Ваша проблема в том, что один (или более) из ваших звонков Single() вызывается для последовательности, которая имеет более одного элемента. Документация для Single() состояния (акцент мой):

Возвращает единственный элемент последовательности и генерирует исключение, если в последовательности нет точно одного элемента.

В вашем коде это может произойти в одном из двух сценариев. Во-первых, если у вас есть более одного элемента управления с одинаковыми Tag значение, например, вы можете иметь два элемента управления в документе с пометкой "LastName", что будет означать, что эта строка

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

вернул бы два элемента.

Во-вторых, если ваш контент-контроль имеет более одного Text элемент в этом случае в этой строке

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

вернул бы несколько элементов. Например, если я создаю элемент управления с содержимым "Это тест", я получаю 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() вернет 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);
}

Я думаю твой Single() Метод вызывает исключение.

Когда у вас есть только один элемент управления контентом, Single() можно получить единственный доступный элемент. Но когда вы расширяете элементы управления контентом, ваш Single() метод может вызвать InvalidOperationException так как в последовательности более одного элемента. Если это так, попробуйте зациклить свой код и принимать по одному элементу за раз.

Другие вопросы по тегам