Apache POI:如何在 word 文档的编号列表中重新开始编号?

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

我正在尝试使用 Apache POI XWPF 库在 Word docx 文件中生成报告。

我的方法是使用现有的 Word 文档作为样式模板。在模板中,我定义了一个名为 "SRINumberList".

的样式

所以要加载模板并删除页眉或页脚中没有的所有内容:

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

}

现在在我的文档中有几个不同的部分包含一个编号列表。每个都应该从 1 重新开始编号。这是我这样做的典型方式:

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

所以这正确地应用了包含数字格式的样式,但只有一个序列(1 ... 到文档中存在的许多列表项)。例如:

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

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

但我想要的是:

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

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

所以基本上我想弄清楚如何使用我拥有的样式,但重新开始对文档中的各个位置进行页码编号。有人可以提供示例说明它是如何工作的吗?

我找到的唯一方法是覆盖 CTNum 中的级别。另一种方法可能是创建大量新摘要 numberings/styles,但是当您打开文档时,这会花费大量样式条目。

ArrayList<String> list = new ArrayList<String>();
list.add("SubItem 1");
list.add("SubItem 2");
list.add("SubItem 3");

XWPFNumbering numbering = document.getNumbering();
XWPFAbstractNum numAbstract =  numbering.getAbstractNum(BigInteger.ONE);

for (Integer nx = 1; nx < 3; nx++) {
    XWPFParagraph p = document.createParagraph();
    XWPFRun run = p.createRun();
    run.setText("Items " + nx.toString());
    //leveloverride (start the new numbering)
    BigInteger numId = numbering.addNum(numAbstract.getAbstractNum().getAbstractNumId());
    XWPFNum num = numbering.getNum(numId);
    CTNumLvl lvloverride = num.getCTNum().addNewLvlOverride();
    lvloverride.setIlvl(BigInteger.ZERO);
    CTDecimalNumber number = lvloverride.addNewStartOverride();
    number.setVal(BigInteger.ONE);

    for (String item : list) {
        XWPFParagraph p2 = document.createParagraph();
        p2.setNumID(num.getCTNum().getNumId());
        CTNumPr numProp = p2.getCTP().getPPr().getNumPr();
        numProp.addNewIlvl().setVal(BigInteger.ZERO);

        XWPFRun run2 = p2.createRun();
        run2.setText(item);
    }
}

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

的帮助下

诀窍是在创建重新开始编号的新Num时需要引用文档中定义的编号样式的AbstractNum。

这里是重点,但关键是必须确定文档中样式的 AbstractNum ID 是什么。这似乎很不幸,鉴于这只是一个 XML 文档,没有某种方法可以枚举现有的 Num 和 AbstractNum。如果有的话,我很想知道这样做的方法。

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

}

现在我们有了从 Style 到 AbstractNum 的映射,我们可以创建一个通过 LvlOverride 和 StartOverride 重新启动的新 Num。

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

现在您只需将该 NumID 应用到您正在创建的列表即可。

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

}

再说一次,如果没有 keil 提供的指针,我根本无法理解这一点。