【问题标题】:Creating a Table of Contents for a XWPFDocument with page numbers' indication为带有页码指示的 XWPFDocument 创建目录
【发布时间】:2020-10-09 15:13:11
【问题描述】:

我实际上是使用 Apache POI 生成一个 Word 文档,并且我需要自动创建一个目录 (TOC) 来引用段落及其页面指示。

这是我正在使用的代码(我省略了前置条件和内部方法的主体):

XWPFDocument doc = new XWPFDocument(OPCPackage.openOrCreate(new File(document)));

String strStyleId = "Index Style";
addCustomHeadingStyle(doc, strStyleId, 1);

XWPFParagraph documentControlHeading = doc.createParagraph();
changeText(documentControlHeading, "First try");
documentControlHeading.setAlignment(ParagraphAlignment.LEFT);
documentControlHeading.setPageBreak(true);
documentControlHeading.setStyle(strStyleId);

XWPFParagraph documentControlHeading1 = doc.createParagraph();
changeText(documentControlHeading1, "Second try");
documentControlHeading1.setAlignment(ParagraphAlignment.LEFT);
documentControlHeading1.setPageBreak(true);
documentControlHeading1.setStyle(strStyleId);

doc.createTOC();

当我打开生成的文档时,我得到了这个结果(见蓝色方块):

在左侧,我可以看到生成的 TOC。到目前为止,一切都很好。 然而,在文档的正文中,我只能看到一个静态文本“目录”,根本没有任何指示(段落和页面都没有)。我什至无法与之互动。

如果我点击菜单项“目录”(左上角的红色方块),就会生成我想要的“真实”目录(当然,按照箭头.. .).

我的问题是: 如何从代码中获得第二个结果(红色 TOC)?

非常感谢。

旁注:我什至尝试将doc.enforceUpdateFields(); 放在doc.createTOC(); 之后,但是TOC 的每个引用都以这种方式消失了。

@Sucy,我添加了您要求的方法。不过,不知道您是否会发现它们有用:

/*
 * Adds a custom style with the given indentation level at the given document.
 */
private static void addCustomHeadingStyle(XWPFDocument docxDocument, String strStyleId, int headingLevel) {

    CTStyle ctStyle = CTStyle.Factory.newInstance();
    ctStyle.setStyleId(strStyleId);

    CTString styleName = CTString.Factory.newInstance();
    styleName.setVal(strStyleId);
    ctStyle.setName(styleName);

    CTDecimalNumber indentNumber = CTDecimalNumber.Factory.newInstance();
    indentNumber.setVal(BigInteger.valueOf(headingLevel));

    // lower number > style is more prominent in the formats bar
    ctStyle.setUiPriority(indentNumber);

    CTOnOff onoffnull = CTOnOff.Factory.newInstance();
    ctStyle.setUnhideWhenUsed(onoffnull);

    // style shows up in the formats bar
    ctStyle.setQFormat(onoffnull);

    // style defines a heading of the given level
    CTPPr ppr = CTPPr.Factory.newInstance();
    ppr.setOutlineLvl(indentNumber);
    ctStyle.setPPr(ppr);

    XWPFStyle style = new XWPFStyle(ctStyle);

    // is a null op if already defined
    XWPFStyles styles = docxDocument.createStyles();

    style.setType(STStyleType.PARAGRAPH);
    styles.addStyle(style);

}

/*
 * Changes the text of a given paragraph.
 */
public static void changeText(XWPFParagraph p, String newText) {
    if (p != null) {
        List<XWPFRun> runs = p.getRuns();
        for (int i = runs.size() - 1; i >= 0; i--) {
            p.removeRun(i);
        }

        if (runs.size() == 0) {
            p.createRun();
        }

        XWPFRun run = runs.get(0);
        run.setText(newText, 0);
    }
}

【问题讨论】:

  • 你能给我看看你的 changeText() 和 addCustomHeadingStyle() 方法吗,谢谢!
  • @Sucy。我已经添加了它们。希望对您有用。
  • 你对my question有什么想法吗谢谢!
  • 也许我应该复制style.xml,但我不知道连接document.xml和styles.xml的关系,以及如何在document.xml中设置样式id来连接具体的文本部分风格。我应该付出巨大的努力来做到这一点。你有什么想法吗?
  • @Sucy 不,我很抱歉。我从在线讨论中导入了 addCustomHeadingStyle() 方法以实现样式,但我从未更深入地探索过样式。另外,我没有像你说的那样使用 xml 文件......对不起,我无法提供进一步的帮助。

标签: java apache-poi


【解决方案1】:

如您所见,XWPF 类是一项正在进行的工作,没有真正的总体架构。随着我们的工作,这将随着时间的推移而改变,但与此同时,您可以尝试通过这种方式将简单的 TOC 字段添加到段落中。

XWPFParagraph p;
...
// get or create your paragraph
....
CTP ctP = p.getCTP();
CTSimpleField toc = ctP.addNewFldSimple();
toc.setInstr("TOC \\h");
toc.setDirty(STOnOff.TRUE);

这将创建一个带有页面超链接的目录,当 Word 打开它时应该重新计算它,并且目录将基于预定义的 HeaderX 样式。

【讨论】:

  • 非常感谢!这确实是一个有价值的答案,我已经看到许多具有类似疑问的帖子仍未解决。我只需要做toc.setDirty(STOnOff.TRUE);,而不是toc.setDirty(STOnOff.Enum.TRUE);
  • 谢谢,我根据您的评论调整了答案。
  • 嘿,当我尝试这个时,我有两个问题: * 如果我没有设置默认文本 (toc.addNewR().addNewT().setStringValue("x");),则该字段不会包含在文档中 * toc.setDirty(STOnOff.TRUE);document.enforceUpdateFields(); 都不是似乎强制任何文字处理程序更新字段,你知道为什么吗?
【解决方案2】:

我已经解开了这个谜团,不幸的是(对于有同样问题的人),没有好消息。 Apache POI 的createTOC() 被窃听(老实说,这似乎是一个已经开始实施但从未以正确方式完成的方法)(请考虑 jmarkmurphy 接受的答案)。

Documentation 没有解释有关方法本身的任何内容(它只是报告签名,仅此而已),这是可疑的。

XWPFDocument的班级代码:

public void createTOC() {
    CTSdtBlock block = getDocument().getBody().addNewSdt();
    TOC toc = new TOC(block);
    for (XWPFParagraph par : this.paragraphs) {
        String parStyle = par.getStyle();
        if ((parStyle != null) && (parStyle.startsWith("Heading"))) try {
            int level = Integer.valueOf(parStyle.substring("Heading".length())).intValue();
            toc.addRow(level, par.getText(), 1, "112723803");
        } catch (NumberFormatException e) {
            e.printStackTrace();
        }
    }
}

Apache POI 搜索样式名为“HeadingX”的段落,其中 X 是一个数字。因此,例如,我的变量 strStyleId 应该被定为 Heading1。但这并不能解决问题。事实上,createTOC() 始终将 1 作为页码传递给 addRow() 方法,该方法始终将页面设置为 1,这种方式。为了动态地做到这一点,它绝对没有做任何事情。

这是最终的、无用的结果(如您所见,它也是一个“假”目录,而不是您可以使用问题中的红色方形按钮通过 Microsoft Word 创建的目录):

因此,无法动态检索 Word 文档的页码(正如我在其他帖子中看到的那样),遗憾的是,即使 Apache POI 似乎也无法做到这一点。

【讨论】:

    【解决方案3】:
    toc.setInstr("TOC \\h");
    

    h 开关必须与 '\' 一起使用,而不是 '/',因为它只能与 '\' 一起使用。更多关于使用 TOC 开关的细节:Use Word's TOC field to fine-tune your table of contents

    【讨论】:

    • 不要将链接发布为答案,而是添加一些文本来解释此答案如何帮助 OP 解决当前问题。谢谢
    【解决方案4】:

    最近我遇到了同样的问题,但我想添加一个深度=2 的 ToC(不包括标题 3)。我创建了一个副本并在 Word 文档中手动添加了 ToC,并将两者从 .docx 重命名为 .zip。

    在每个 zip 文档中,都有一个位于 document.zip/word/document.xml 中的 XML 文件。本文档为文档内容。

    我比较了这两个 XML 文件,发现 Word 在添加 ToC 时添加了以下值:

    <w:fldSimple w:instr="TOC \o "1-2" \h \z \u"/>
    

    我已经用这个更新了我的代码:

    CTP ctP = paragraph.getCTP();
    CTSimpleField toc = ctP.addNewFldSimple();
    toc.setInstr("TOC \\o \"1-2\" \\h \\z \\u");
    toc.setDirty(STOnOff.ON);
    

    当您打开文档时,Word 会要求您更新参考文献,并且 ToC 会变得完美。由于我希望生成过程完全自动化,因此我仍在努力。

    【讨论】:

      【解决方案5】:

      如果有人还在寻找答案,我会按照@jmarkmurphy 提供的建议进行操作。

      下面是工作代码

      import java.io.FileOutputStream;
      import java.math.BigInteger;
      
      import org.apache.poi.xwpf.usermodel.BreakType;
      import org.apache.poi.xwpf.usermodel.XWPFDocument;
      import org.apache.poi.xwpf.usermodel.XWPFParagraph;
      import org.apache.poi.xwpf.usermodel.XWPFRun;
      import org.apache.poi.xwpf.usermodel.XWPFStyle;
      import org.apache.poi.xwpf.usermodel.XWPFStyles;
      import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTDecimalNumber;
      import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTOnOff;
      import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP;
      import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPPr;
      import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSettings;
      import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSimpleField;
      import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTString;
      import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTStyle;
      import org.openxmlformats.schemas.wordprocessingml.x2006.main.STOnOff;
      import org.openxmlformats.schemas.wordprocessingml.x2006.main.STStyleType;
      
      public class ApachePOIWordTOCDemo {
      
          public static void main(String[] args) throws Exception {
      
          XWPFDocument doc = new XWPFDocument();
      
          doc.createTOC();
          addCustomHeadingStyle(doc, "heading 1", 1);
          addCustomHeadingStyle(doc, "heading 2", 2);
          addCustomHeadingStyle(doc, "heading 3", 3);
      
          // the body content
          XWPFParagraph paragraph = doc.createParagraph();
      
          CTP ctP = paragraph.getCTP();
          CTSimpleField toc = ctP.addNewFldSimple();
          toc.setInstr("TOC \\h");
          toc.setDirty(STOnOff.TRUE);
          
      
          XWPFRun run = paragraph.createRun();
      
          paragraph = doc.createParagraph();
          run = paragraph.createRun();
          run.setText("Heading 1");
          paragraph.setStyle("heading 1");
      
          paragraph = doc.createParagraph();
          run = paragraph.createRun();
          run.addBreak(BreakType.PAGE);
          run.setText("Heading 2");
          paragraph.setStyle("heading 2");
          
          paragraph = doc.createParagraph();
          run = paragraph.createRun();
          run.addBreak(BreakType.PAGE);
          run.setText("Heading 3");
          paragraph.setStyle("heading 3");
          
          FileOutputStream fos = new FileOutputStream("createTOC.docx");
          doc.write(fos);
          }
      
          private static void addCustomHeadingStyle(XWPFDocument docxDocument, String strStyleId, int headingLevel) {
      
          CTStyle ctStyle = CTStyle.Factory.newInstance();
          ctStyle.setStyleId(strStyleId);
      
          CTString styleName = CTString.Factory.newInstance();
          styleName.setVal(strStyleId);
          ctStyle.setName(styleName);
      
          CTDecimalNumber indentNumber = CTDecimalNumber.Factory.newInstance();
          indentNumber.setVal(BigInteger.valueOf(headingLevel));
      
          // lower number > style is more prominent in the formats bar
          ctStyle.setUiPriority(indentNumber);
      
          CTOnOff onoffnull = CTOnOff.Factory.newInstance();
          ctStyle.setUnhideWhenUsed(onoffnull);
      
          // style shows up in the formats bar
          ctStyle.setQFormat(onoffnull);
      
          // style defines a heading of the given level
          CTPPr ppr = CTPPr.Factory.newInstance();
          ppr.setOutlineLvl(indentNumber);
          ctStyle.setPPr(ppr);
      
          XWPFStyle style = new XWPFStyle(ctStyle);
      
          // is a null op if already defined
          XWPFStyles styles = docxDocument.createStyles();
      
          style.setType(STStyleType.PARAGRAPH);
          styles.addStyle(style);
      
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2019-05-28
        • 2010-11-08
        • 2011-07-31
        • 2013-12-18
        • 2012-10-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多