代码之家  ›  专栏  ›  技术社区  ›  akosch

在open xml sdk中的word书签后插入openxmlement

  •  4
  • akosch  · 技术社区  · 16 年前

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

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

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

    1 回复  |  直到 14 年前
        1
  •  26
  •   Ahmad Mageed    16 年前

    代码

    一旦你有书签,你可以访问它的父元素并添加其他元素。

    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结构无效,可能会发生错误。原因是如果插入表是原始表的表元素中的最后一个元素,则在关闭TabelEL标签之后需要添加一个段落元素。如果在尝试用ms-word打开文档时发生此问题,您将立即发现。

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

    为此,我们可以添加到上面的代码(在父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());
    

    这将防止错误发生,并为您提供有效的openxml。如果您回答了前面的问题“是”,并希望在父表之后执行插入,而不是在表中执行上述代码,那么while循环思想可以使用。如果是这样的话,上述问题将不再是一个问题,您可以用以下方法替换该循环和布尔:

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

    这会一直重新分配父元素,直到它是主体级别的主包含元素。如果书签是在一个桌子上的段落,它会从段落到桌面,直到桌子,然后停在那里,因为桌子的父母是身体。在点Prime=表元素,我们可以在它之后插入。

    这应该包括一些不同的方法,取决于你的初衷。试穿之后,如果你需要澄清,请告诉我。

    文档反射器

    你可能想知道我是怎么决定的 GridColumn.Width 价值观。我做了一张桌子,用文档反射器工具得到它。当您安装open xml sdk时,生产力工具(如果您安装了它们)将位于 C:\Program Files\Open XML Format SDK\V2.0\tools (或类似)。

    了解*.docx格式(或任何打开的xml格式的文档)工作原理的最佳方法是使用document reflector工具打开现有文件。导航文档部分,并找到要复制的项。该工具向您显示用于生成整个文档的实际代码。这是您可以复制/粘贴到应用程序中生成类似结果的代码。通常你可以忽略所有的参考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 .

    安装后,您可以从“工具”“代码段管理器”菜单添加它们。选择“C”作为语言,单击“添加”按钮,然后导航到 PersonalFolder\Visual Studio 2008\code snippets\Visual C\open XML SDK2.0 for Microsoft Office 添加它们。在代码中,右键单击并选择“insert snippet”,然后选择所需的代码段。