【问题标题】:Seperated text line in Apache POI XWPFRun objectApache POI XWPFRun 对象中的分隔文本行
【发布时间】:2020-02-04 15:34:26
【问题描述】:

我正在尝试使用 XWPFDocument 类将模板 DOCX 文档替换为 Apache POI。我在文档中有标签和一个JSON 文件来读取替换数据。我的问题是,当我将其扩展名更改为ZIP 文件并打开document.xml 时,DOCX 中的文本行似乎以某种方式分开。例如[MEMBER_CONTACT_INFO] 文本分别变为[MEMBER_CONTACT_INFO]POI 以相同的方式读取此内容,因为 DOCX 原件是这样的。这会在段落中创建 2 个XWPFRun 对象,分别将文本显示为[MEMBER_CONTACT_INFO]

我的问题是,有没有办法通过合并相关运行或类似的东西来强制POI 像 Word 一样运行?或者我该如何解决这个问题?我在替换时匹配运行文本,但我找不到我的标签,因为它被分成 2 个不同的运行对象。

最好的

【问题讨论】:

  • 我认为如果标签在表格中就会发生!
  • 没有人对此没有提示?

标签: apache-poi


【解决方案1】:

这一次浪费了我这么多时间......

基本上一个XWPFParagraph是由多个XWPFRun组成的,而XWPFRun是一个具有固定风格的传染性文本。

因此,当您尝试在 MS-Word 中编写类似“[PLACEHOLDER_NAME]”的内容时,它将创建一个 XWPFRun。但是,如果您以某种方式添加了更多内容,然后您返回并将“[PLACEHOLDER_NAME]”更改为其他内容,则永远无法保证它将保持为单个 XWPFRun,它很可能会拆分为两个运行。 AFAIK 这就是 MS-Word 的工作原理。

在这种情况下如何避免 Runs 的拆分?

解决方案:我知道有两种解决方案:

  1. 将文本“[PLACEHOLDER_NAME]”复制到记事本或其他东西。进行必要的修改并将其复制回去并将其而不是“[PLACEHOLDER_NAME]”粘贴到您的 Word 文件中,这样您的整个“[PLACEHOLDER_NAME]”将被替换为新文本,从而避免拆分 XWPFRuns。

  2. 选择“[PLACEHOLDER_NAME]”,然后单击 MS-Word 的“替换”选项并替换为“[Your-new-edited-placeholder]”,这将保证您的新占位符将使用单个 XWPFRun .

如果您必须再次更改新占位符,请按照步骤 1 或 2。

【讨论】:

  • 另一种解决方案是从 docx 存档中编辑 document.xml,使“[PLACEHOLDER_NAME]”位于 ... 标记中。
【解决方案2】:

这是修复单独文本行问题的 java 代码。它还将处理多格式字符串替换。

public static void replaceString(XWPFDocument doc, String search, String replace) throws Exception{
  for (XWPFParagraph p : doc.getParagraphs()) {
    List<XWPFRun> runs = p.getRuns();
    List<Integer> group = new ArrayList<Integer>();
    if (runs != null) {
      String groupText = search;
      for (int i=0 ; i<runs.size(); i++) {
        XWPFRun r = runs.get(i);
        String text = r.getText(0);
        if (text != null)
            if(text.contains(search)) {
              String safeToUseInReplaceAllString = Pattern.quote(search);
              text = text.replaceAll(safeToUseInReplaceAllString, replace);
              r.setText(text, 0);
            }
            else if(groupText.startsWith(text)){
              group.add(i);
              groupText = groupText.substring(text.length());
              if(groupText.isEmpty()){
                runs.get(group.get(0)).setText(replace, 0);
                for(int j = 1; j<group.size(); j++){
                  p.removeRun(group.get(j));
                }
                group.clear();
                groupText = search;
              }
            }else{
              group.clear();
              groupText = search;
            }
        }
    }
}
for (XWPFTable tbl : doc.getTables()) {
   for (XWPFTableRow row : tbl.getRows()) {
      for (XWPFTableCell cell : row.getTableCells()) {
         for (XWPFParagraph p : cell.getParagraphs()) {
            for (XWPFRun r : p.getRuns()) {
              String text = r.getText(0);
              if (text.contains(search)) {
                String safeToUseInReplaceAllString = Pattern.quote(search);
                text = text.replaceAll(safeToUseInReplaceAllString, replace);
                r.setText(text);
              }
            }
         }
      }
   }
}

}

【讨论】:

  • 这对我有帮助。带有 removeRun 的位对我不起作用,因为索引会随着您的删除而改变 - 用 p.removeRun(1) 替换该行可以解决这个问题,而且它很有效。
  • 我太仓促了,没有用足够的数据测试我的更改。相反,只需将 for 循环包装 removeRun 替换为相反的循环即可。即for(int j=group.size()-1; j&gt;=1; j--),它对我有用。
  • @Marcus 我有类似的问题,你能帮忙吗?如果你能帮忙的话。这里是 Q stackoverflow.com/q/65246636/13267143的链接。
  • 我遇到了同样的问题,并以此代码为灵感。我看到这个实现有两个问题:1)第一次运行可以从其他一些文本开始,然后包含搜索模式的一部分。 2) 最后一次运行可能包含不应删除的文本或可能包含新搜索模式的开始。否则,感谢您发布此代码 - 它给了我一些解决方案的想法。
【解决方案3】:

对我来说,它没有像我预期的那样工作(每次)。在我的例子中,我在文本中使用了 "${PLACEHOLDER}。首先,我们需要看看 Apache Poi 如何识别我们想要使用 Runs 迭代的每个段落。如果你更深入地了解 docx 文件构造,你会知道那个run 是具有相同字体样式/字体大小/颜色/粗体/斜体等的文本字符序列。这样,占位符有时被分成几部分,或者有时整个段落被识别为一个 Run,并且不可能遍历单词.
我所做的是在模板文档中粗体占位符名称。比通过 RUN 迭代时,我能够遍历整个占位符名称 ${PLACEHOLDER}。当我用

替换该值时
for (XWPFRun r : p.getRuns()) {
  String text = r.getText(0);
  if (text != null && text.contains("originalText")) {
     text = text.replace("originalText", "newText");
     r.setText(text,0);
     }
  }

我在 setText 之后只添加了 r.isBold(false);
这样,占位符被识别为不同的运行 -> 我可以替换特定的占位符,并且在已处理的文档中我没有粗体,只有纯文本。
对我来说,另一个优势是视觉上我' m 能够更快地在文本中找到占位符。 所以最后上面的循环看起来像这样:

for (XWPFRun r : p.getRuns()) {
      String text = r.getText(0);
      if (text != null && text.contains("originalText")) {
         text = text.replace("originalText", "newText");
         r.setText(text,0);
         r.isBold(false);
         }
      }

我希望它对某人有所帮助,而我为此花费了太多时间:)

【讨论】:

【解决方案4】:

几天前我也遇到了这个问题,但我找不到任何解决方案。我选择使用 PLACEHOLDER_NAME 而不是 [PLACEHOLDER_NAME]。这对我来说很好,它看起来像一个 XWPFRun 对象。

【讨论】:

    猜你喜欢
    • 2017-08-11
    • 2021-05-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多