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

标签 c# .net automation ms-word openxml

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

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

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

最佳答案

代码

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

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

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

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

您可能想知道为什么上述问题很重要。这一切都取决于插入的位置。如果书签的父级在表格中,目前上面的代码将尝试在表格中放置一个表格。这很好,但是由于 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。如果您对我之前的问题回答"is"并且想要在父表之后而不是像上面的代码那样在表内部执行插入,则可以使用 while 循环思想。如果是这种情况,上述问题将不再是问题,您可以使用以下内容替换该循环和 bool 值:
    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 的值。我做了一个表格并使用文档反射器工具来获取它。当您安装 Open Xml SDK 时,生产力工具(如果您安装了它们)将位于 C:\Program Files\Open XML Format SDK\V2.0\tools (或类似)。

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

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

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

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

代码片段

您可能还对 Open Xml 的代码片段感兴趣。有关片段列表,请查看 this blog post .您可以从这里下载它们:2007 Office System Sample: Open XML Format SDK 2.0 Code Snippets for Visual Studio 2008 .

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

关于c# - 在 Open XML SDK 中的单词书签后插入 OpenXmlElement,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1612511/

相关文章:

c# - WPF TextBox 事件 TextChanged PreviewTextInput 不触发

visual-studio - 通过包管理器控制台从解决方案中删除项目

.net - 发起TCP连接关闭后如何接收数据?

c# - 将 Func 转换为委托(delegate)

ruby - 如何在 watir 中定位特定图像?

c# - 创建文件快捷方式 (.lnk)

c# - vb.net C# 属性覆盖机制

c# - List 是值类型还是引用类型?

c# - Unity3D 中的 Time.time vs DateTime.UtcNow.Millisecond 性能?

c# - 将自定义对象添加到 JObject 时出错