Search code examples
javaapache-poixwpfnumbered-list

Apache POI: How do you restart numbering on a numbered list in word document?


I'm trying to use Apache POI XWPF library to produce a report in a Word docx file.

My approach is to use an existing Word Document as a Styles template. Within the template I defined a style named "SRINumberList".

So to load the template and remove everything that's not in the Header or Footer:

protected void createDocFromTemplate() {
    try {
        document = new XWPFDocument(this.getClass().getResourceAsStream(styleTemplate));


        int pos = document.getBodyElements().size()-1;

        while (pos >= 0) {
            IBodyElement element = document.getBodyElements().get(pos);
            if (!EnumSet.of(BodyType.HEADER, BodyType.FOOTER).contains(element.getPartType())) {
                boolean success = document.removeBodyElement(pos);
                logger.log(Level.INFO, "Removed body element "+pos+": "+success);
            }
            pos--;
        }

    } catch (IOException e) {
        logger.log(Level.WARNING, "Not able to load style template", e);
        document = new XWPFDocument();
    }

}

Now within my document there are several different sections that contain a numbered lists. Each should be restart numbering from 1. This is the typical way I'm doing this:

if (itemStem.getItems().size() > 0) {
        p = document.createParagraph();
        p.setStyle(ParaStyle.StemAndItemTitle.styleId);
        final BigInteger bulletNum = newBulletNumber();

        run = p.createRun();
        run.setText("Sub Items");

        itemStem.getItems().stream().forEach(item -> {
            XWPFParagraph p2 = document.createParagraph();
            p2.setStyle(ParaStyle.NumberList.styleId);

            XWPFRun run2 = p2.createRun();
            run2.setText(item.getSubItemText());
        });
        p = document.createParagraph();
        p.createRun();
}

So this correctly applies the Style that contains the number format, but there is only a single sequence (1 ... to however many list items exit in the doc). For example:

Heading 1
1. item a
2. item b
3. item c

Heading 2
4. item a
5. item d
6. item g

But what I want is:

Heading 1
1. item a
2. item b
3. item c

Heading 2
1. item a
2. item d
3. item g

So basically I'm trying to figure out how to use the style I have but restart page numbering a various spots in the document. Can someone provide a sample of how this would work?


Solution

  • With some help from keil. I figured out the solution. I've posted a full working sample here: https://github.com/jimklo/apache-poi-sample

    The trick is that you need to reference the the AbstractNum of the Numbering style defined in the document when creating a new Num that restarts the numbering.

    Here are the highlights, however the key was having to determine what the AbstractNum ID is for the Style inside the document. It's seems unfortunate, that given this is just an XML doc, that there isn't some way to enumerate the existing Num's and AbstractNum's. If there is, I'd love to know the way to do that.

    /**
     * first discover all the numbering styles defined in the template.
     * a bit brute force since I can't find a way to just enumerate all the
     * abstractNum's inside the numbering.xml
     */
    protected void initNumberingStyles() {
        numbering = document.getNumbering();
    
        BigInteger curIdx = BigInteger.ONE;
        XWPFAbstractNum abstractNum;
    
        while ((abstractNum = numbering.getAbstractNum(curIdx)) != null) {
            if (abstractNum != null) {
                CTString pStyle = abstractNum.getCTAbstractNum().getLvlArray(0).getPStyle();
                if (pStyle != null) {
                    numberStyles.put(pStyle.getVal(), abstractNum);
                }
            }
            curIdx = curIdx.add(BigInteger.ONE);
        }
    
    }
    

    Now that we have a mapping from the Style to the AbstractNum, we can create a new Num that restarts via a LvlOverride and StartOverride.

    /**
     * This creates a new num based upon the specified numberStyle
     * @param numberStyle
     * @return
     */
    private XWPFNum restartNumbering(String numberStyle) {
        XWPFAbstractNum abstractNum = numberStyles.get(numberStyle);
        BigInteger numId = numbering.addNum(abstractNum.getAbstractNum().getAbstractNumId());
        XWPFNum num = numbering.getNum(numId);
        CTNumLvl lvlOverride = num.getCTNum().addNewLvlOverride();
        lvlOverride.setIlvl(BigInteger.ZERO);
        CTDecimalNumber number = lvlOverride.addNewStartOverride();
        number.setVal(BigInteger.ONE);
        return num;
    }
    

    And now you can just apply that NumID to the list you're creating.

    /**
     * This creates a five item list with a simple heading, using the specified style..
     * @param index
     * @param styleName
     */
    protected void createStyledNumberList(int index, String styleName) {
        XWPFParagraph p = document.createParagraph();
        XWPFRun run = p.createRun();
        run.setText(String.format("List %d: - %s", index, styleName));
    
        // restart numbering
        XWPFNum num = restartNumbering(styleName);
    
        for (int i=1; i<=5; i++) {
            XWPFParagraph p2 = document.createParagraph();
    
            // set the style for this paragraph
            p2.setStyle(styleName);
    
            // set numbering for paragraph
            p2.setNumID(num.getCTNum().getNumId());
            CTNumPr numProp = p2.getCTP().getPPr().getNumPr();
            numProp.addNewIlvl().setVal(BigInteger.ZERO);
    
            // set the text
            XWPFRun run2 = p2.createRun();
            run2.setText(String.format("Item #%d using '%s' style.", i, styleName));
        }
    
        // some whitespace
        p = document.createParagraph();
        p.createRun();
    
    }
    

    Again, overall I wouldn't have figured this out without the pointer that keil provided.