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 提供的指针,我根本无法理解这一点。
我正在尝试使用 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 提供的指针,我根本无法理解这一点。