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);
}
}
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.
I've solved the mystery and, unfortunately (for people having the same problem), there's no good news. Apache POI's (please, consider jmarkmurphy's accepted answer).createTOC()
is bugged (to be honest, it seems a method whose implementation has been started but never completed in a proper way)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With