Как лучше всего извлечь раздел из документа Word с помощью Apache poi?

Я использую Apache poi (XWPF) с Springboot в java 11.

Мне нужно извлечь раздел номер 2 (название и содержание) из документа Word со следующим нумерованным списком:

слово_пример

Я хотел бы знать, как лучше всего получить только раздел 2 и его содержимое, чтобы создать новый текстовый документ только с этой частью.

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

спасибо.

Что вы пробовали? Можете ли вы поделиться своим кодом?

hfontanez 16.03.2022 14:23

В настоящее время он зацикливается и печатает текст. Есть ли способ получить доступ к разделу номер два через номер отсортированного списка?

Kane 16.03.2022 14:45

Прошло более 10 лет с тех пор, как я использовал XWPF, но я думаю, что вы можете получить нумерацию из объекта документа, если у вас есть нумерованный список.

hfontanez 16.03.2022 15:51

Спасибо за ответ. Я получил позицию, сравнив значение getNumLevelText() по абзацам

Kane 05.04.2022 10:15
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
4
76
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

XWPF of apache poi не очень хорошо поддерживает нумерацию в Word. Таким образом, обработка нумерации не совсем проста.

В Word нумерованные абзацы имеют num-id и уровень нумерации, установленный в документе. Этот num-id относится к нумерации в отдельной части документа нумерации. Там определяется тип нумерации (десятичная, буквенная, римская, ...) и формат нумерации. Фактическая нумерация абзацев определяется этим num-id, уровнем нумерации, типом нумерации, форматом нумерации и количество абзацев с одинаковым номерным идентификатором в документе. Поэтому очень сложно управлять нумерацией при чтении документа Word.

В следующем рабочем черновике показан пример один для управления нумерацией при чтении документа Word с помощью apache poi. Это рабочий проект, демонстрирующий принцип с использованием как можно меньшего количества кода. Он использует структуру памяти для хранения счетчика уровня нумерации и предыдущего уровня нумерации в документе. Код дополнительно прокомментирован, чтобы показать, что он делает.

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://stackoverflow.com/help/on-topic.

Спасибо за ваш ответ и за ваш совет. Наконец-то я получил нумерованный список по уровням (%1.%2.%3) В любом случае, ваш код очень полезен.

Kane 05.04.2022 10:10

Алекс, по вашему мнению, «лучший» — это вопрос мнения, и даже когда вы можете дать ему количественную оценку, «лучший» может не подходить для конкретной ситуации. Из-за этого такие ответы лучше опускать.

hfontanez 05.04.2022 15:07

Другие вопросы по теме