Word 占位符替换问题

我参考了这个帖子,来实现我的占位符替换,但是发生了报错
https://forum.aspose.com/t/aspose-words-replacement-text-in-wrong-place-when-using-ireplacingcallback/211935
我经过 debug 发现第二次进这个 IReplacingCallback 对应的 run 是我第一次进入后删除的 run,导致了报错,正常情况下我删除这个 run 后,下次 IReplacingCallback 就不应该再匹配这个删除的 run了,为什么会这样,代码如下

public class ReplaceTest {
    private static final String ASPOSE_TAG_REGEX = "\\[(.*?)]";

    public static void main(String[] args) throws Exception {
        Document doc = new Document("aaa.docx");

        FindReplaceOptions options = new FindReplaceOptions();
        options.setReplacingCallback(new ReplaceTagsEvaluator());
        doc.getRange().replace(Pattern.compile(ASPOSE_TAG_REGEX), "", options);

        doc.save("out.docx");
    }

    private static Run splitRun(Run run, int position) throws Exception {
        Run afterRun = (Run)run.deepClone(true);
        afterRun.setText(run.getText().substring(position));
        run.setText(run.getText().substring(0, position));
        run.getParentNode().insertAfter(afterRun, run);
        return afterRun;
    }

    static class ReplaceTagsEvaluator implements IReplacingCallback {
        @Override
        public int replacing(ReplacingArgs e) throws Exception {
            Node currentNode = e.getMatchNode();

            if (e.getMatchOffset() > 0) {
                currentNode = splitRun((Run)currentNode, e.getMatchOffset());
            }

            ArrayList<Run> runs = new ArrayList<>();

            int remainingLength = e.getMatch().group().length();
            while (remainingLength > 0 &&
                   currentNode != null &&
                   currentNode.getText().length() <= remainingLength) {

                runs.add((Run)currentNode);
                remainingLength = remainingLength - currentNode.getText().length();

                do {
                    currentNode = currentNode.getNextSibling();
                } while (currentNode != null && currentNode.getNodeType() != NodeType.RUN);
            }

            if (currentNode != null && remainingLength > 0) {
                splitRun((Run)currentNode, remainingLength);
                runs.add((Run)currentNode);
            }

            DocumentBuilder builder = new DocumentBuilder((Document)e.getMatchNode().getDocument());
            builder.moveTo(runs.get(0));

            String match = e.getMatch().group();
            System.out.println("Match is: " + match);

            builder.write(e.getMatch().group(1));
            e.setReplacement("");

            for (Run run : runs) {
                run.remove();
            }

            return ReplaceAction.SKIP;
        }
    }
}

文件如下
aaa.docx (13.1 KB)

@myboy

在处理占位符替换时,您遇到的错误可能是由于在替换过程中对节点的操作导致的。根据您提供的代码,您在 replacing 方法中使用了 splitRun 方法来分割 Run 节点,这可能会导致在后续的替换过程中,某些 Run 节点被删除或变得不可用,从而引发 parentNodenull 的情况。

以下是一些可能的解决方案和建议:

  1. 检查节点的有效性:在访问 currentNode 之前,确保它不为 null。您可以在使用 currentNode.getParentNode() 之前添加一个检查,以避免在 currentNodenull 时调用方法。

  2. 避免在循环中修改集合:在 replacing 方法中,您在遍历 runs 列表时删除了 Run 节点。考虑在遍历完成后再进行删除操作,或者使用一个临时列表来存储需要删除的节点。

  3. 调试信息:在 splitRun 方法中添加调试信息,以便在运行时跟踪 Run 节点的状态。这可以帮助您确定在替换过程中哪些节点被删除或修改。

  4. 使用 DocumentBuilder 的方法:确保在使用 DocumentBuilder 进行写入操作时,您已经正确移动到目标节点。可以考虑在写入之前检查 runs 列表的大小和内容。

以下是一个修改后的示例,展示了如何在替换过程中更安全地处理节点:

@Override
public int replacing(ReplacingArgs e) throws Exception {
    Node currentNode = e.getMatchNode();

    if (e.getMatchOffset() > 0) {
        currentNode = splitRun((Run)currentNode, e.getMatchOffset());
    }

    ArrayList<Run> runs = new ArrayList<>();
    int remainingLength = e.getMatch().group().length();

    while (remainingLength > 0 && currentNode != null) {
        if (currentNode.getNodeType() == NodeType.RUN) {
            Run run = (Run) currentNode;
            runs.add(run);
            remainingLength -= run.getText().length();
            currentNode = currentNode.getNextSibling();
        } else {
            currentNode = currentNode.getNextSibling();
        }
    }

    // 处理剩余长度
    if (currentNode != null && remainingLength > 0) {
        splitRun((Run)currentNode, remainingLength);
        runs.add((Run)currentNode);
    }

    DocumentBuilder builder = new DocumentBuilder((Document)e.getMatchNode().getDocument());
    builder.moveTo(runs.get(0));
    builder.write(e.getMatch().group(1));
    e.setReplacement("");

    // 删除 Run 节点
    for (Run run : runs) {
        run.remove();
    }

    return ReplaceAction.SKIP;
}

请根据您的具体需求进行调整,并确保在调试时仔细检查每个节点的状态。希望这些建议能帮助您解决问题!

你的代码无法解决我的问题

@myboy 要解决该问题,请使用FindReplaceDirection.BACKWARD

Document doc = new Document("C:\\temp\\in.docx");
    
FindReplaceOptions options = new FindReplaceOptions();
options.setDirection(FindReplaceDirection.BACKWARD);
options.setReplacingCallback(new ReplaceTagsEvaluator());
doc.getRange().replace(Pattern.compile(ASPOSE_TAG_REGEX), "", options);
    
doc.save("C:\\temp\\out.docx");