在 Open XML SDK 中的单词书签后插入 OpenXmlElement

发布于 2024-08-09 06:08:10 字数 289 浏览 1 评论 0原文

我能够使用以下代码访问我的 Word 文档中的书签:

var res = from bm in mainPart.Document.Body.Descendants<BookmarkStart>()
                              where bm.Name == "BookmarkName"
                              select bm;

现在我想在此书签后插入一个段落和一个表格。我该怎么做? (示例代码将不胜感激)

I was able to access a bookmark in my word document using this code:

var res = from bm in mainPart.Document.Body.Descendants<BookmarkStart>()
                              where bm.Name == "BookmarkName"
                              select bm;

Now I want to insert a paragraph and a table after this bookmark. How do I do that? (example code would be appreciated)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

北风几吹夏 2024-08-16 06:08:10

代码

获得书签后,您可以访问其父元素并在其后添加其他项目。

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

上面的代码应该可以做到。不过,我会解释一些特殊情况。

请注意,它将尝试在书签的父元素之后插入。如果您的书签恰好是表格内段落的一部分,您期望什么行为?是否应该在该表内紧随其后附加新的段落和表格?或者应该在那张桌子之后做?

您可能想知道为什么上述问题很重要。这完全取决于插入发生的位置。如果书签的父级位于表中,则当前上述代码将尝试将表放置在表中。没关系,但是由于 OpenXml 结构无效,可能会出现错误。原因是,如果插入的表格是原始表格 TableCell 中的最后一个元素,则需要在结束 TableCell 标记之后添加一个 Paragraph 元素。如果您尝试在 MS Word 中打开文档时发生此问题,您会立即发现此问题。

解决方案是确定您是否确实在表中执行插入操作。

为此,我们可以添加到上面的代码(在父变量之后):

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

这应该可以防止错误发生并为您提供有效的 OpenXml。如果您对我之前的问题回答“是”并且想要在父表之后而不是像上面的代码那样在表内执行插入,则可以使用 while 循环的想法。如果是这种情况,上述问题将不再是一个问题,您可以用以下内容替换该循环和布尔值:

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

这将不断重新分配父级,直到它成为 Body 级别的主要包含元素。因此,如果书签位于表中的段落中,则它会从 Paragraph 到 TableCell 到 TableRow 到 Table,然后停在那里,因为 Table 的父级是 Body。此时parent = Table元素,我们可以在它后面插入。

这应该涵盖一些不同的方法,具体取决于您的初衷。如果您在尝试后需要任何说明,请告诉我。

文档反射器

您可能想知道我如何确定 GridColumn.Width 值。我制作了一个表格并使用 Document Reflector 工具来获取它。安装 Open Xml SDK 后,生产力工具(如果已安装)将位于 C:\Program Files\Open XML Format SDK\V2.0\tools (或类似位置)。

了解 *.docx 格式(或任何 Open Xml 格式的文档)如何工作的最佳方法是使用 Document Reflector 工具打开现有文件。导航文档部分,然后找到要复制的项目。该工具向您显示用于生成整个文档的实际代码。您可以将此代码复制/粘贴到您的应用程序中以生成类似的结果。通常您可以忽略所有参考 ID;你必须看一看并尝试一下才能找到感觉。

正如我所提到的,上面的表代码改编自示例文档。我向 docx 添加了一个简单的表格,然后在工具中打开它,并复制了该工具生成的代码(我删除了一些额外的内容以清理它)。这给了我一个添加表格的工作示例。

当您想知道如何编写生成某些内容(例如格式化表格和带有样式的段落等)的代码时,它特别有用。

请查看此链接以获取有关 SDK 中包含的其他工具的屏幕截图和信息:Open XML SDK 2.0 简介

代码片段

您可能还对 Open Xml 的代码片段感兴趣。有关代码段列表,请检查 这篇博文。您可以从此处下载它们: 2007 Office System 示例:适用于 Visual Studio 2008 的 Open XML 格式 SDK 2.0 代码片段

安装后,您可以从“工具”|“工具”添加它们。代码片段管理器菜单。选择 C# 作为语言,单击“添加”按钮,然后导航到 PersonalFolder\Visual Studio 2008\Code Snippets\Visual C#\Open XML SDK 2.0 for Microsoft Office 添加它们。在您的代码中,您可以右键单击并选择“插入片段”,然后选择您想要的片段。

Code

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文