【问题标题】:Apache POI - Retrieve text content between keywords in .doc file and conditionally render itApache POI - 检索 .doc 文件中关键字之间的文本内容并有条件地呈现它
【发布时间】:2021-11-06 20:47:06
【问题描述】:

我想在 .doc 文件中的两个关键字之间查找文本内容,并有条件地呈现或隐藏该文本内容。例如:

Lorem Ipsum 只是打印的虚拟文本${if condition} 和 排版行业。 Lorem Ipsum 已成为行业标准 自 1500 年代以来的虚拟文本 ${endif}

当我使用 Apache - POI 解析文档时,我希望能够以某种方式在文档中发现这些 blockquotes ${if condition} ${endif} 和有条件的每个内容在我要生成的下一个文档中呈现与否。

所以上面我解析后的文字应该有以下两种不同的形式:

1) 条件满足时

Lorem Ipsum 只是印刷和排版行业的虚拟文本。自 1500 年代以来,Lorem Ipsum 一直是业界标准的虚拟文本

2) 条件不满足时

Lorem Ipsum 只是虚拟文本

我尝试通过使用XWPFParagraph 对象然后XWPFRun 来做到这一点,但这不是可靠的方法,因为在不可预知的条件下,运行可能会在单词中间随机拆分。

您能否提出任何可靠的方法来实现我的用例?提前致谢。

【问题讨论】:

  • 嗨@PJFanning,感谢您的回复,是的,我知道该功能,但我认为它不足以满足我的用例,我想我需要类似setTextreplaceText 的功能
  • 解决Word 文本运行问题的内置方法是使用TextSegment。示例:stackoverflow.com/questions/65275097/…。但是XWPFParagraph.searchText 到目前为止有多个不同的问题。所以我怀疑到目前为止,如何在Word 文档中将一些文本内容替换为另一个文本内容是否存在“可靠的内置解决方案”。
  • 但是为什么要尝试替换文本内容呢?这不是使用文字处理软件应该做的事情。还有其他处理条件内容的可能性。使用具有邮件合并字段的邮件合并、使用表单字段、使用内容控制字段...并且要标记有条件的文本部分,应该使用书签而不是依赖特殊的文本内容。
  • 嗨@AxelRichter,感谢您的回复,我找到了一个解决方法,用于在我的自定义condition blockquotes 之间进行内容的条件渲染,我的主要“故障”仍然是我不知道的问题确定我的块引用将如何分成多个 runs 并且我事先不知道它们的形式以便使用 searchText 函数,因为条件将包含其中的表达式,形式如下:${if answerId=2} blablabla.... ${endif} 或 @987654339 @

标签: java ms-word apache-poi xwpf apache-poi-4


【解决方案1】:

以此为例(代码经过测试):

class ParagraphModifier {

    private final Pattern pIf = Pattern.compile("\\$\\{if\\s+(\\w+)\\}");
    private final Pattern pEIf = Pattern.compile("\\$\\{endif\\}");
    private final Function<String, Boolean> processor;

    public ParagraphModifier(Function<String, Boolean> processor) {
        this.processor = processor;
    }

    // Process

    static class Pair<K, V> {
        public K key;
        public V value;
        public Pair(K key, V value) {
            this.key = key;
            this.value = value;
        }
    }

    // https://stackoverflow.com/questions/23112924
    public static void cloneRun(XWPFRun clone, XWPFRun source) {
        CTRPr rPr = clone.getCTR().isSetRPr() ? clone.getCTR().getRPr() : clone.getCTR().addNewRPr();
        rPr.set(source.getCTR().getRPr());
        clone.setText(source.getText(0));
    }

    // Split runs in paragraph at a specific text offset and returns the run index
    int splitAtTextPosition(XWPFParagraph paragraph, int position) {
        List<XWPFRun> runs = paragraph.getRuns();
        int offset = 0;

        for (int i = 0; i < runs.size(); i++) {
            XWPFRun run = runs.get(i);
            String text = run.getText(0);
            int length = text.length();

            if (position >= (offset + length)) {
                offset += length;
                continue;
            }

            // Do split
            XWPFRun run2 = paragraph.insertNewRun(i + 1);
            cloneRun(run2, run);
            run.setText(text.substring(0, position - offset), 0);
            run2.setText(text.substring(position - offset), 0);
            return i + 1;
        }
        return -1;
    }

    String getParagraphText(XWPFParagraph paragraph) {
        StringBuilder sb = new StringBuilder("");
        for (XWPFRun run : paragraph.getRuns()) sb.append(run.getText(0));
        return sb.toString();
    }

    void removeRunsRange(XWPFParagraph paragraph, int from, int to) {
        int runs = paragraph.getRuns().size();
        to = Math.min(to, runs);
        for (int i = (to - 1); i >= from; i--) {
            paragraph.removeRun(i);
        }
    }

    Pair<Integer, String> extractToken(Pattern pattern, XWPFParagraph paragraph) {
        String text = getParagraphText(paragraph);
        Matcher matcher = pattern.matcher(text);

        if (matcher.find()) {
            int rStart = splitAtTextPosition(paragraph, matcher.start());
            int rEnd = splitAtTextPosition(paragraph, matcher.end());
            removeRunsRange(paragraph, rStart, rEnd);
            return new Pair<>(rStart, matcher.group());
        } else {
            return new Pair<>(-1, "");
        }
    }

    void applyParagraph(XWPFParagraph paragraph) {
        int lastIf = -1;

        while (true) {
            var tIf = extractToken(pIf, paragraph);
            if (tIf.key == -1) {
                break;
            }
            if (tIf.key < lastIf) {
                throw new IllegalStateException("If conditions can not be nested");
            }

            var tEIf = extractToken(pEIf, paragraph);
            if (tEIf.key == -1) {
                throw new IllegalStateException("If condition missing endif");
            }

            var m = pIf.matcher(tIf.value);
            var keep = m.find() && processor.apply(m.group(1));
            if (!keep) {
                removeRunsRange(paragraph, tIf.key, tEIf.key);
            }

            lastIf = tEIf.key;
        }
    }

    void apply(Iterable<XWPFParagraph> paragraphs) {
        for (XWPFParagraph p : paragraphs) {
            applyParagraph(p);
        }
    }

}

用法:

class Main {

    private static XWPFDocument loadDoc(String name) throws IOException, InvalidFormatException {
        String path = Main.class.getClassLoader().getResource(name).getPath();
        FileInputStream fis = new FileInputStream( path);
        return new XWPFDocument(OPCPackage.open(fis));
    }

    private static void saveDoc(String path, XWPFDocument doc) throws IOException {
        try (var fos = new FileOutputStream(path)) {
            doc.write(fos);
        }
    }

    public static void main (String[] args) throws Exception {
        var xdoc = loadDoc("test.docx");

        var pm = new ParagraphModifier(str -> str.toLowerCase().equals("true"));
        pm.apply(xdoc.getParagraphs());

        saveDoc("test.out.docx", xdoc);
    }
    
}

此解决方案不支持跨越段落的${if } 块(如果嵌套),也不支持表结构。扩展解决方案以支持它们应该很简单。

【讨论】:

  • 绝妙的解决方案! splitAtTextPosition 正是我所需要的!非常感谢您的跟进:)。您之前有使用过 Apache POI 库的经验吗?
  • 不是真的,但我过去曾用 PDF 做过类似的事情,我知道这有多痛苦 ;)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多