Creating a Table of Contents for a XWPFDocument wi

2019-01-09 16:04发布

问题:

I am actually generating a Word Document with Apache POI, and I need to automatically create a Table of Contents (TOC) that references the paragraphs, with their page's indication.

This is the code I am using (I omit preconditions and internal methods' body):

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

When I open the resulting document, I am getting this result (see blue squares):

In the left part, I can see the generated TOC. So far, so good. In the document's body, however, I can just see a static text "Table of Contents", with no indications at all (neither paragraphs nor pages). I cannot even interact with it.

If I'd click on the menu entry "Table of Contents" (red square on the upper-left corner), the "real" Table of Content that I want is being generated (follow the arrow, of course...).

My question is: how can I achieve the second result (red TOC) from code?

Thank you so much.

Side note: I even tried putting doc.enforceUpdateFields(); after doc.createTOC();, but every reference of the TOC disappears, this way.

@Sucy, I add the methods that you requested. Don't know if you can find them useful, though:

/*
 * 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);
    }
}

回答1:

The XWPF classes as you have seen are a work in progress, with no real overarching architecture. That will change over time as we work on it, but in the mean time you can try to add a simple TOC field to a paragraph in this way.

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

This will create a Table of contents with hyperlinks to the pages, it should be recalculated when Word opens it, and the table of contents will be based on predefined HeaderX styles.



回答2:

I've solved the mystery and, unfortunately (for people having the same problem), there's no good news. Apache POI's createTOC() is bugged (to be honest, it seems a method whose implementation has been started but never completed in a proper way) (please, consider jmarkmurphy's accepted answer).

The Documentation doesn't explain anything about the method itself (it just reports the signature, and nothing more), and that's suspect.

Watching at the XWPFDocument's class code:

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 searches for paragraphs having style named "HeadingX", with X being a number. So, my variable strStyleId should have been valorized as Heading1, as an example. But this doesn't solved the problem. In fact, createTOC() always passes 1 as the page number to the addRow() method, that always sets the page as 1, this way. It does absolutely nothing in order to get that dinamically.

That's the final, unuseful result (as you can see, it's also a "fake" TOC, and not the one that you can create via Microsoft Word using the red-squared button in the question):

So, page numbers for a Word document cannot be retrieved dinamically (as I read in other posts), and even Apache POI seems unable to do that, sadly.



回答3:

toc.setInstr("TOC \\h");

h switch has to be used with '\', not '/', because it works correctly only with '\'. More details about using TOC switches: Use Word's TOC field to fine-tune your table of contents