【问题标题】:OpenXml: Table of contents update giving Error! Bookmark not definedOpenXml:目录更新给出错误!未定义书签
【发布时间】:2020-02-26 11:38:57
【问题描述】:

我正在开发一个 OpenXml 实用程序以将内容动态添加到 Word 文档中,然后我想更新目录。

        var byteTempArray = File.ReadAllBytes("Sample.docx");
        var _memoryStream = new MemoryStream();
        _memoryStream.Write(byteTempArray, 0, byteTempArray.Length);
        var _wordprocessingDocument = WordprocessingDocument.Open(_memoryStream, true);
        var bodyDoc = _wordprocessingDocument.MainDocumentPart.Document.Body;
        SimpleField f = new SimpleField
        {
            Instruction = "sdtContent",
            Dirty = true
        };
        bodyDoc.AppendChild(f);
        _wordprocessingDocument.MainDocumentPart.Document.Save();
        _wordprocessingDocument.Close();
        File.WriteAllBytes(Guid.NewGuid()+".docx", _memoryStream.ToArray());

我在文档中添加了一个 SimpleField,并将 Dirty 设置为 true。当我打开生成的文件时,它会显示下面的确认消息以更新目录。单击“是”会正确更新目录。

但问题是 错误!书签未定义。文本附加在文档底部。

知道如何在目录更新功能正常工作的情况下删除此错误消息。

【问题讨论】:

  • 我的回答是否有助于解决您的问题?

标签: c# openxml-sdk tableofcontents


【解决方案1】:

我对您尝试做的事情的理解是在您的文档中插入一个脏的w:fldSimple 元素(SimpleField 类),以使 Word 更新所有字段,包括目录,一旦用户使用 Microsoft Word 打开该文档.但是,如果您在更新字段后查看 Microsoft Word 生成的 Open XML 标记,您会发现您的w:fldSimple 是罪魁祸首。

我创建了以下代码来生成一个简单的 Word 文档,其中(几乎)只有一个字段:

[Fact]
public void CheckBookmarkNotDefined()
{
    using WordprocessingDocument wordDocument = WordprocessingDocument.Create(
        "SimpleField.docx", WordprocessingDocumentType.Document);

    MainDocumentPart part = wordDocument.AddMainDocumentPart();
    part.Document =
        new Document(
            new Body(
                new Paragraph(
                    new SimpleField
                    {
                        Instruction = "sdtContent",
                        Dirty = true
                    })));
}

以上代码在主文档部分创建了以下 Open XML 标记:

<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
  <w:body>
    <w:p>
      <w:fldSimple w:instr="sdtContent" w:dirty="true" />
    </w:p>
  </w:body>
</w:document>

请注意,w:fldSimple 元素包含在 w:p 元素(Paragraph 类)中,因为将其添加到 w:body 元素(Body 类)会产生无效的 Open XML 标记。

在 Microsoft Word 中打开该文档,更新字段并保存文档,使 Word 创建以下 Open XML 标记(我已经简化了一点):

<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
  <w:body>
    <w:p>
      <w:r>
        <w:fldChar w:fldCharType="begin"/>
      </w:r>
      <w:r>
        <w:instrText>sdtContent</w:instrText>
      </w:r>
      <w:r>
        <w:fldChar w:fldCharType="separate"/>
      </w:r>
      <w:r>
        <w:rPr>
          <w:b/>
          <w:bCs/>
        </w:rPr>
        <w:t>Error! Bookmark not defined.</w:t>
      </w:r>
      <w:r>
        <w:fldChar w:fldCharType="end"/>
      </w:r>
    </w:p>
    <w:sectPr>
      [Child elements removed for clarity]
    </w:sectPr>
  </w:body>
</w:document>

您可以看到 Word 已将简单字段(w:fldSimple 元素)转换为复杂字段,使用多个 w:fldChar 元素和一个以“sdtContent”为内容的w:instrText 元素。此外,您可以看到文本“错误!未定义书签”。作为该字段的结果。

Word 会创建该错误消息,因为您的说明文本 ("sdtContent") 不正确。如果您将"sdtContent" 替换为有效的字段名称,例如"AUTONUM",您将不会看到该错误消息。但是,在AUTONUM 的情况下,您显然会得到一个可见的字段结果。为避免这种情况,您可以使用REF 字段,但是您需要一个有效的书签。这是在以下示例中创建的:

[Fact]
public void CreateSimpleFieldForAutomaticUpdate()
{
    using WordprocessingDocument wordDocument = WordprocessingDocument.Create(
        "SimpleFieldRef.docx", WordprocessingDocumentType.Document);

    MainDocumentPart part = wordDocument.AddMainDocumentPart();
    part.Document =
        new Document(
            new Body(
                new Paragraph(
                    new Run(
                        new Text("Hello World!"))),
                new BookmarkStart { Id = "32767", Name = "_RefUpdate" },
                new BookmarkEnd { Id = "32767" },
                new Paragraph(
                    new SimpleField
                    {
                        Instruction = "REF _RefUpdate",
                        Dirty = true
                    }),
                new SectionProperties()));
}

您必须编写代码来插入 w:bookmarkStart 元素(BookmarkStart 类)、w:bookmarkEnd 元素(BookmarkEnd 类)和 w:p 元素与您的 w:fldSimple 子元素之前w:sectPr 元素(SectionProperties 类)。在我的示例中,我使用了一个大整数值作为书签 id,_RefUpdate 作为书签名称,Instruction 属性中也使用了该名称。

【讨论】:

  • 谢谢。但是,一旦字段变脏,Word 仍然会询问是否更新字段,无论指令的有效性如何,并且快速的网络搜索确认这是设计使然。
  • @Lucero,这是真的。你无法避免那个对话。我曾经考虑过 OP 利用的机制,但出于这个原因远离它。如果可以使 Word 更新诸如 TOC 和 REF 之类的字段,那就太好了,因为实现这是一项壮举。例如,对于 TOC,您需要一个布局引擎来确定页码。仅此一项就是大量的工作。
  • 我已经实现了一个 Markdown 到 WordprocessingML 转换器(因为我有很多 Markdown 需要进入文档)并且我在标题上实现了 _Toc 书签生成,以及一个后处理步骤然后它会像 Word 一样生成 TOC - 但页码为空,因为这确实需要太多的工作才能做到正确。但是,这消除了对话框,并且在打印时通常会更新该字段,并且仅更新页码也可以正常工作,所以我现在对这种方法感到满意。
猜你喜欢
  • 2013-04-04
  • 2018-12-19
  • 2014-12-26
  • 1970-01-01
  • 2019-09-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多