Insert OpenXmlElement after word bookmark in Open XML SDK

http://stackoverflow.com/questions/1612511/insert-openxmlelement-after-word-bookmark-in-open-xml-sdk

Once you have the bookmark you can access its parent element and add the other items after it.

using (WordprocessingDocument document = WordprocessingDocument.Open(@"C:\Path\filename.docx", true))
{
    var mainPart = document.MainDocumentPart;
    var res = from bm in mainPart.Document.Body.Descendants<BookmarkStart>()
              where bm.Name == "BookmarkName"
              select bm;
    var bookmark = res.SingleOrDefault();
    if (bookmark != null)
    {
        var parent = bookmark.Parent;   // bookmark's parent element

        // simple paragraph in one declaration
        //Paragraph newParagraph = new Paragraph(new Run(new Text("Hello, World!")));

        // build paragraph piece by piece
        Text text = new Text("Hello, World!");
        Run run = new Run(new RunProperties(new Bold()));
        run.Append(text);
        Paragraph newParagraph = new Paragraph(run);

        // insert after bookmark parent
        parent.InsertAfterSelf(newParagraph);

        var table = new Table(
        new TableProperties(
            new TableStyle() { Val = "TableGrid" },
            new TableWidth() { Width = 0, Type = TableWidthUnitValues.Auto }
            ),
            new TableGrid(
                new GridColumn() { Width = (UInt32Value)1018U },
                new GridColumn() { Width = (UInt32Value)3544U }),
        new TableRow(
            new TableCell(
                new TableCellProperties(
                    new TableCellWidth() { Width = 0, Type = TableWidthUnitValues.Auto }),
                new Paragraph(
                    new Run(
                        new Text("Category Name"))
                )),
            new TableCell(
                new TableCellProperties(
                    new TableCellWidth() { Width = 4788, Type = TableWidthUnitValues.Dxa }),
                new Paragraph(
                    new Run(
                        new Text("Value"))
                ))
        ),
        new TableRow(
            new TableCell(
                new TableCellProperties(
                    new TableCellWidth() { Width = 0, Type = TableWidthUnitValues.Auto }),
                new Paragraph(
                    new Run(
                        new Text("C1"))
                )),
            new TableCell(
                new TableCellProperties(
                    new TableCellWidth() { Width = 0, Type = TableWidthUnitValues.Auto }),
                new Paragraph(
                    new Run(
                        new Text("V1"))
                ))
        ));

        // insert after new paragraph
        newParagraph.InsertAfterSelf(table);
    }

    // close saves all parts and closes the document
    document.Close();
}

The above code should do it. However, I'll explain some special circumstances.

Be aware that it will attempt the insertions after the parent element of the bookmark. What behavior do you expect if your bookmark happens to be part of a paragraph inside a table? Should it append the new paragraph and table right after it, within that table? Or should it do it after that table?

You might be wondering why the above questions matter. It all depends on where the insertion will occur. If the bookmark's parent is in a table, currently the above code would attempt to place a table within a table. That's fine, however an error might occur due to an invalid OpenXml structure. The reason is that if the inserted table was the last element in the original table's TableCell, there needs to be a Paragraph element added after the closing TableCell tag. You would promptly discover this issue if it occurred once you attempted to open the document in MS Word.

The solution is to determine whether you are indeed performing the insertion within a table.

To do so, we can add to the above code (after the parent var):

    var parent = bookmark.Parent;   // bookmark's parent element

    // loop till we get the containing element in case bookmark is inside a table etc.
    // keep checking the element's parent and update it till we reach the Body
    var tempParent = bookmark.Parent;
    bool isInTable = false;
    while (tempParent.Parent != mainPart.Document.Body)
    {
        tempParent = tempParent.Parent;
        if (tempParent is Table && !isInTable)
            isInTable = true;
    }

    // ... 

    newParagraph.InsertAfterSelf(table);  // from above sample
    // if bookmark is in a table, add a paragraph after table
    if (isInTable)
        table.InsertAfterSelf(new Paragraph());

That should prevent the error from occurring and give you valid OpenXml. The while loop idea can be used if you answered "yes" to my earlier question and wanted to perform the insertion after the parent table rather than inside the table as the above code would do. If that's the case, the above issue would no longer be a concern and you can replace that loop and boolean with the following:

    var parent = bookmark.Parent;   // bookmark's parent element
    while (parent.Parent != mainPart.Document.Body)
    {
        parent = parent.Parent;
    }

This keeps re-assigning the parent till it's the main containing element at the Body level. So if the bookmark was in a paragraph that was in a table, it would go from Paragraph to TableCell to TableRow to Table and stop there since the Table's parent is the Body. At that point parent = Table element and we can insert after it.

That should cover some different approaches, depending on your original intent. Let me know if you need any clarification after trying it out.

Document Reflector

You might be wondering how I determined the GridColumn.Width values. I made a table and used the Document Reflector tool to get it. When you installed the Open Xml SDK, the productivity tools (if you installed them) would be located in C:\Program Files\Open XML Format SDK\V2.0\tools (or similar).

The best way to learn how the *.docx format works (or any Open Xml formatted doc) is to open an existing file with the Document Reflector tool. Navigate the document part, and locate the items you want to replicate. The tool shows you the actual code used to generate the entire document. This is code you can copy/paste into your application to generate similar results. You can ignore all the reference IDs usually; you'll have to take a look and try it out to get a feel for it.

As I mentioned, the above Table code was adapted from a sample document. I added a simple table to a docx, then opened it in the tool, and copied the code generated by the tool (I removed some extras to clean it up). That gave me a working sample to add a table.

It is especially helpful when you want to know how to write code that generates something, such as formatted tables and paragraphs with styles etc.

Take a look at this link for screenshots and info on the other tools included in the SDK: An introduction to Open XML SDK 2.0.

Code Snippets

You might also be interested in the code snippets for Open Xml. For a list of snippets check this blog post. You can download them from here: 2007 Office System Sample: Open XML Format SDK 2.0 Code Snippets for Visual Studio 2008.

Once installed you would add them from Tools | Code Snippet Manager menu. Select C# for the language, click the Add button, and navigate to PersonalFolder\Visual Studio 2008\Code Snippets\Visual C#\Open XML SDK 2.0 for Microsoft Office to add them. From your code you would right-click and select "Insert Snippet" and select the one you want.


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值