如何使用Java中的Apache POI向Word文档添加项目符号

3

我有一个用作模板的Word文档。在这个模板中,我有一些包含预定义符号点的表格。现在我正在尝试用一组字符串替换占位符字符串。

我完全陷入了困境。我的简化方法如下。

        replaceKeyValue.put("[DescriptionOfItem]", new HashSet<>(Collections.singletonList("This is the description")));
        replaceKeyValue.put("[AllowedEntities]", new HashSet<>(Arrays.asList("a", "b")));
        replaceKeyValue.put("[OptionalEntities]", new HashSet<>(Arrays.asList("c", "d")));
        replaceKeyValue.put("[NotAllowedEntities]", new HashSet<>(Arrays.asList("e", "f")));

        try (XWPFDocument template = new XWPFDocument(OPCPackage.open(file))) {
            template.getTables().forEach(
                    xwpfTable -> xwpfTable.getRows().forEach(
                            xwpfTableRow -> xwpfTableRow.getTableCells().forEach(
                                    xwpfTableCell -> replaceInCell(replaceKeyValue, xwpfTableCell)
                            )
                    ));

            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            template.write(baos);
            return new ByteArrayResource(baos.toByteArray());
        } finally {
            if (file.exists()) {
                file.delete();
            }
        }

private void replaceInCell(Map<String, Set<String>> replacementsKeyValuePairs, XWPFTableCell xwpfTableCell) {
        for (XWPFParagraph xwpfParagraph : xwpfTableCell.getParagraphs()) {
            for (Map.Entry<String, Set<String>> replPair : replacementsKeyValuePairs.entrySet()) {
                String keyToFind = replPair.getKey();
                Set<String> replacementStrings = replacementsKeyValuePairs.get(keyToFind);
                if (xwpfParagraph.getText().contains(keyToFind)) {
                    replacementStrings.forEach(replacementString -> {
                        XWPFParagraph paragraph = xwpfTableCell.addParagraph();
                        XWPFRun run = paragraph.createRun();
                        run.setText(replacementString);
                    });
                }

        }
    }

我原本期望在当前单元格中添加更多的项目符号,是否有遗漏?该段落是包含占位符字符串和格式的那个。

感谢任何帮助!


更新:以下是模板的部分外观。我想自动搜索这些术语并替换它们。到目前为止,搜索正常进行。但尝试替换项目符号会导致无法定位的 NullPointer

使用字段会更容易吗?但我需要保留项目符号样式。

the word template


更新2:添加下载链接并更新了代码。如果我正在迭代段落,则似乎无法更改段落。我得到一个空指针。 WordTemplate下载链接


发生了什么事情? - Michiel Leegwater
你期望所有的字符串都在同一个单元格中还是每个字符串一个单元格?目前的输出是什么? - Arpan Kanthal
感谢您的快速帮助。我已经使用模板更新了问题的一部分。希望现在更清楚了。 - Cliff Pereira
如果您正在查看我的答案,您会看到一个最小可复现示例。同样的,我们需要您展示您正在使用的代码。请将Word模板上传到我们可以下载的某个地方。只有这样我们才能重现您的问题。 - Axel Richter
Axel,我更新了代码。它仍然不是一个最小的、可重现的例子。如果你需要的话,我会尝试在周一创建一个。我提供了一个模板的下载链接。希望这有所帮助。我还在编辑方面遇到困难。 非常感谢你的支持。 - Cliff Pereira
1个回答

4

由于 Microsoft Word 在存储文本时将其分成不同的运行方式非常“奇怪”,因此如果没有包含所有代码和相关的 Word 文档的完整示例,就无法回答这样的问题。除非所有添加或替换都是在字段(表单字段、内容控件或邮件合并字段)中进行,否则似乎不可能有一个通用可用的用于添加内容到 Word 文档的代码。

所以我下载了你的 WordTemplate.docx,它看起来像这样:

enter image description here

然后我运行了以下代码:

import java.io.*;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR;

import org.apache.xmlbeans.XmlCursor;

import java.util.*; 

import java.math.BigInteger;

public class WordReadAndRewrite {

 static void addItems(XWPFTableCell cell, XWPFParagraph paragraph, Set<String> items) {
  XmlCursor cursor = null;
  XWPFRun run = null;
  CTR cTR = null; // for a deep copy of the run's low level object

  BigInteger numID = paragraph.getNumID();
  int indentationLeft = paragraph.getIndentationLeft();
  int indentationHanging = paragraph.getIndentationHanging();

  boolean first = true;
  for (String item : items) {
   if (first) {
    for (int r = paragraph.getRuns().size()-1; r > 0; r--) {
     paragraph.removeRun(r);
    }
    run = (paragraph.getRuns().size() > 0)?paragraph.getRuns().get(0):null;
    if (run == null) run = paragraph.createRun();
    run.setText(item, 0);
    cTR = (CTR)run.getCTR().copy(); // take a deep copy of the run's low level object
    first = false;
   } else {
    cursor = paragraph.getCTP().newCursor();
    boolean thereWasParagraphAfter = cursor.toNextSibling(); // move cursor to next paragraph 
                                                             // because the new paragraph shall be **after** that paragraph
                                                             // thereWasParagraphAfter is true if there is a next paragraph, else false
    if (thereWasParagraphAfter) {
     paragraph = cell.insertNewParagraph(cursor); // insert new paragraph if there are next paragraphs in cell
    } else {
     paragraph = cell.addParagraph(); // add new paragraph if there are no other paragraphs present in cell
    }

    paragraph.setNumID(numID); // set template paragraph's numbering Id
    paragraph.setIndentationLeft(indentationLeft); // set template paragraph's indenting from left
    if (indentationHanging != -1) paragraph.setIndentationHanging(indentationHanging); // set template paragraph's hanging indenting
    run = paragraph.createRun();
    if (cTR != null) run.getCTR().set(cTR); // set template paragraph's run formatting
    run.setText(item, 0);
   }
  }
 }

 public static void main(String[] args) throws Exception {

  Map<String, Set<String>> replaceKeyValue = new HashMap<String, Set<String>>();
  replaceKeyValue.put("[AllowedEntities]", new HashSet<>(Arrays.asList("allowed 1", "allowed 2", "allowed 3")));
  replaceKeyValue.put("[OptionalEntities]", new HashSet<>(Arrays.asList("optional 1", "optional 2", "optional 3")));
  replaceKeyValue.put("[NotAllowedEntities]", new HashSet<>(Arrays.asList("not allowed 1", "not allowed 2", "not allowed 3")));

  XWPFDocument document = new XWPFDocument(new FileInputStream("WordTemplate.docx"));
  List<XWPFTable> tables = document.getTables();
  for (XWPFTable table : tables) {
   List<XWPFTableRow> rows = table.getRows();
   for (XWPFTableRow row : rows) {
    List<XWPFTableCell> cells = row.getTableCells();
    for (XWPFTableCell cell : cells) {

     int countParagraphs = cell.getParagraphs().size();
     for (int p = 0; p < countParagraphs; p++) { // do not for each since new paragraphs were added
      XWPFParagraph paragraph = cell.getParagraphArray(p);

      String placeholder = paragraph.getText();
      placeholder = placeholder.trim(); // this is the tricky part to get really the correct placeholder

      Set<String> items = replaceKeyValue.get(placeholder);
      if (items != null) {
       addItems(cell, paragraph, items);
      }
     }

    }
   }
  }

  FileOutputStream out = new FileOutputStream("Result.docx");
  document.write(out);
  out.close();
  document.close();

 }
}

Result.docx 看起来像这样:

enter image description here

该代码循环遍历Word文档中的表格单元格,并查找包含占位符的段落。这可能是棘手的部分,因为该占位符可能会被Word分割成不同的文本运行。如果找到了,则运行一个名为“addItems”的方法,该方法以找到的段落作为编号和缩进的模板(可能是不完整的)。然后将第一个新项目设置在找到段落的第一个文本运行中,并删除所有其他可能存在的文本运行。然后它确定是否必须插入新段落或将其添加到单元格中。为此,使用XmlCursor。在新插入或添加的段落中,放置其他项,并从占位符的段落中获取编号和缩进设置。
正如所说的,这段代码展示了如何实现功能的原则。要使其通用可用,必须进行大量扩展。在我看来,使用文本占位符在Word文档中进行文本替换的尝试并不是真正好的做法。用于变量文本的占位符应该是字段,可以是表单字段、内容控件或邮件合并字段。与文本占位符相比,字段的优点是Word知道它们是变量文本的实体。它不会因为多种奇怪的原因将它们拆分成多个文本运行。

嗨,Axel, 感谢您的快速回复。它有点起作用了。但是项目符号是添加在第二个项目符号之后,并且没有相同的样式。我已经更新了模板的问题并附上了一张图片。 - Cliff Pereira
哇!非常感谢你,Axel!非常感激你的帮助!代码中有一些很好的技巧,对我帮助很大! - Cliff Pereira

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接