使用 Apache poi 从 word 文档中提取部分的最佳方法是什么?

How is the best way to extract a section from word document with Apache poi?

我在 java 11 中将 Apache poi (XWPF) 与 Springboot 一起使用。

我需要使用以下数字列表从 word 文档中提取第 2 节(标题和内容):

word_example

我想知道只获取第 2 节及其内容以创建仅包含该部分的新 word 文档的最佳方法是什么。

 private void extractAllParagraphs(){

    //Get documet
    XWPFDocument doc = new XWPFDocument(OPCPackage.open("path..."));

    String textPart = "";

    // loop all paragraphs
    List<XWPFParagraph> xwpfParagraphList = doc.getParagraphs();

    for (XWPFParagraph p : doc.getParagraphs()) {
        //Get paragraph runs
        List<XWPFRun> runs = p.getRuns();

        //Loop runs of the paragraph
        for(int i = 0; i<runs.size(); i++) {
            textPart += runs.get(i).toString();
        }
        System.out.println(textPart);
    }
}

谢谢。

XWPF of apache poi 不能很好地支持 Word 中的编号。所以处理编号并不是很简单。

在 Word 中编号的段落有 num-id 和文档中设置的编号级别。此 num-id 引用单独编号文档部分中的编号。那里定义了编号类型(十进制、字母、罗马...)和编号格式。段落的实际编号由num-id、编号级别、编号类型、编号格式和文档中具有相同num-id的段落数决定.所以在阅读Word文档时管理编号真的很复杂。

以下工作草案显示了 一个 示例,说明如何在使用 apache poi 阅读 Word 文档时管理编号。这是一个工作草案,以尽可能少的代码展示原理。它使用内存结构来存储文档中的编号级别计数器和前一个编号级别。代码被附加注释以显示它的作用。

import java.io.FileInputStream;
import org.apache.poi.xwpf.usermodel.*;

import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.math.BigInteger;

public class WordReader {

 //memory structure for storing the numbering level counter
 private Map<Integer, Map<Integer, Integer>> numIDLvlCnt = new HashMap<Integer, Map<Integer, Integer>>();
 //memory structure for storing the previous numbering level
 private Map<Integer, Integer> numIDPrevNumIlv = new HashMap<Integer,Integer>();

 private StringBuilder content = new StringBuilder();

 private void traverseBodyElements(List<IBodyElement> bodyElements, boolean crlf) throws Exception {
  for (IBodyElement bodyElement : bodyElements) {
   if (bodyElement instanceof XWPFParagraph) {
    XWPFParagraph paragraph = (XWPFParagraph)bodyElement;
    //System.out.println(paragraph);
    //ToDo: Do something with paragraph.
    String no = "";
    if (paragraph.getNumID() != null) { //if paragraph has numbering
     no = getCurrentNumber(paragraph);
    }
    //print paragraph, if numbered then with leading number
    content.append("<p>");
    if (no.length() > 0) content.append(no + " ");
    content.append(paragraph.getText());
    content.append("</p>");
    if (crlf) content.append("\r\n");
   } else if (bodyElement instanceof XWPFTable) {
    XWPFTable table = (XWPFTable)bodyElement;
    //System.out.println(table);
    content.append("<table>");
    content.append("\r\n");
    traverseTableRows(table.getRows());
    content.append("</table>");
    content.append("\r\n");
   } // ToDo: else ...
  }
 }

 private void traverseTableRows(List<XWPFTableRow> tableRows) throws Exception {
  for (XWPFTableRow tableRow : tableRows) {
   //System.out.println(tableRow);
   content.append("<tr>");
   traverseTableCells(tableRow.getTableICells());
   content.append("</tr>");
   content.append("\r\n");
  }
 }

 private void traverseTableCells(List<ICell> tableICells) throws Exception {
  for (ICell tableICell : tableICells) {
   if (tableICell instanceof XWPFTableCell) {
    XWPFTableCell tableCell = (XWPFTableCell)tableICell;
    //System.out.println(tableCell);
    content.append("<td>");
    traverseBodyElements(tableCell.getBodyElements(), false);
    content.append("</td>");
   } // ToDo: else ...
  }
 }

 //set numbering level counter for current numbering ID and numbering level
 private void setNumIDLvlCnt(Integer numID, Integer numIlvl) {
  if (numID != null) {
   //get level counter for numbering ID
   Map<Integer, Integer> lvlCnt = numIDLvlCnt.get(numID);
   if (lvlCnt == null) { //if there is no level counter, create a new one
    lvlCnt = new HashMap<Integer, Integer>();
    numIDLvlCnt.put(numID, lvlCnt);
   }
   Integer prevNumIlv = numIDPrevNumIlv.get(numID);
   if (prevNumIlv == null) {
    prevNumIlv = 0;
    numIDPrevNumIlv.put(numID, prevNumIlv);
   }
   if (numIlvl != null) {
    //if this level is lower than the previous one, then all deeper level counters needs starting new
    if (numIlvl < prevNumIlv) {
     /*
     for(Iterator<Integer> iterator = lvlCnt.keySet().iterator(); iterator.hasNext(); ) {
      Integer ilvl = iterator.next();
      if (ilvl > numIlvl) {
       iterator.remove();
      }
     }
     */
     lvlCnt.keySet().removeIf(ilvl -> ilvl > numIlvl);
    }
    //get current counter for level
    Integer cnt = lvlCnt.get(numIlvl);
    if (cnt == null) { //if there is no counter, set 0
     lvlCnt.put(numIlvl, 0);
    }
    cnt = lvlCnt.get(numIlvl);
    lvlCnt.put(numIlvl, cnt + 1); //count up 1
    prevNumIlv = numIlvl; //set this level to be the previous level
    numIDPrevNumIlv.put(numID, prevNumIlv);
   }
  }
  //System.out.println(numIDLvlCnt);
  //System.out.println(numIDPrevNumIlv);
 }

 //get formatted number from number format and level counter
 private String getNoFromCount(String numFmt, Integer cnt) {
  String no = "";
  if ("DECIMAL".equalsIgnoreCase(numFmt)) {
   no = String.valueOf(cnt);
  } else if ("LOWERLETTER".equalsIgnoreCase(numFmt)) {
   no = Character.toString(96 + cnt); //should be done better
  } else if ("LOWERROMAN".equalsIgnoreCase(numFmt)) {
   String[] romans = new String[]{"", "i", "ii", "iii", "iv", "v"};
   if (cnt < romans.length) no = romans[cnt]; //should be done better
  } else if ("UPPERROMAN".equalsIgnoreCase(numFmt)) {
   String[] romans = new String[]{"", "I", "II", "III", "IV", "V"};
   if (cnt < romans.length) no = romans[cnt]; //should be done better
  } //ToDo: else ...
  return no;
 }

 //get current number from paragraph
 private String getCurrentNumber(XWPFParagraph paragraph) {
  String no = "";

  BigInteger numStartOverride = paragraph.getNumStartOverride(); //ToDo: to take into account
  //System.out.println(numStartOverride);

  //get numbering format
  String numFmt = paragraph.getNumFmt(); //decimal, lowerletter, roman, ..

  //get numbering ID
  BigInteger numID = paragraph.getNumID();
  //get current numbering level
  BigInteger numIlvl = paragraph.getNumIlvl();
  //set numbering level counter for current numbering ID and numbering level
  setNumIDLvlCnt(numID.intValue(), numIlvl.intValue());
  //get level counter for this numbering ID
  Map<Integer, Integer> lvlCnt = numIDLvlCnt.get(numID.intValue());
  //get numbering level text
  String numLevelText = paragraph.getNumLevelText(); // %1.%2.%3...
  no = numLevelText;
  for (Integer ilvl : lvlCnt.keySet()) {
   int i = ilvl + 1;
   //replace the placeholders %1, %2, %3, ... with formatted number from number format and level counter
   no = no.replace("%"+i, getNoFromCount(numFmt, lvlCnt.get(ilvl)));
  }
  return no;
 }

 public void read(String inFilePath) throws Exception {
  XWPFDocument document = new XWPFDocument(new FileInputStream(inFilePath));
  traverseBodyElements(document.getBodyElements(), true);
  document.close();
  System.out.println(content);
 }

 public static void main(String[] args) throws Exception {
  String inFilePath = "./WordDocument.docx";
  WordReader reader = new WordReader();
  reader.read(inFilePath);
 }
}

注意:由于您的问题询问的是最佳方式:这显示了一个方式。它是否是“最好的”在这里不予回答。关于“最好的方法”的问题总是基于意见,所以不是要在这里问的问题。参见 https://whosebug.com/help/on-topic