在 Java 中搜索和替换 PowerPoint 演示文稿中的文本

概述

Aspose.Slides for Java 可以在单个文本框或整个演示文稿中搜索、突出显示和替换文本。每个操作还可以通过结果回调通知应用程序每一次匹配。这使得在更新演示文稿的同时能够构建包含匹配文本、其上下文、位置、文本框和幻灯片编号的审计日志。

这些功能适用于审阅、脱敏、术语检查、模板清理以及自动化报告工作流。

在下面的第一个示例中,我们使用名为 “sample.pptx” 的文件,该文件在第一张幻灯片上包含一个仅有以下文本的文本框:

Sample text

选择搜索范围

使用ITextFrame的方法将操作限制在单个文本框。使用Presentation的方法处理演示文稿中的所有适用文本。

操作 单个文本框 整个演示文稿
高亮字面文本 ITextFrame.highlightText Presentation.highlightText
高亮正则表达式匹配 ITextFrame.highlightRegex Presentation.highlightRegex
替换字面文本 ITextFrame.replaceText Presentation.replaceText
替换正则表达式匹配 ITextFrame.replaceRegex Presentation.replaceRegex

配置文本匹配

对于字面文本操作,使用TextSearchOptions来控制匹配行为:

正则表达式操作使用 Java Pattern,因此大小写敏感性和单词边界等匹配规则由表达式本身及其标志决定。

识别文本框的所有者

通用的文本处理工作流在搜索、替换、验证或导出文本时常会收到一个ITextFrame。使用ITextFrame.getParentShapeITextFrame.getParentCell可以确定哪个演示对象拥有该文本框。

期望的返回值取决于所有者:

文本框所有者 getParentShape getParentCell
AutoShape 或其他含文本的形状 拥有者IShape null
表格单元格 null 拥有者ICell

两种方法均提供只读导航。调用它们不会移动文本框或更改其所有者。通用代码应检查两个返回值是否为null,并处理两者均不可用的情况。

下面的示例使用SlideUtil.getAllTextFrames遍历演示文稿中的所有文本框。对于形状,它报告形状名称、Java 运行时类型以及所在幻灯片;对于表格单元格,它报告零基的列行坐标以及所在幻灯片。

import com.aspose.slides.*;

Presentation presentation = new Presentation("presentation.pptx");
try {
    ITextFrame[] textFrames = SlideUtil.getAllTextFrames(presentation, false);

    for (ITextFrame textFrame : textFrames) {
        IShape ownerShape = textFrame.getParentShape();
        if (ownerShape != null) {
            String shapeName = ownerShape.getName().isEmpty() ? "(unnamed)" : ownerShape.getName();
            String shapeType = ownerShape.getClass().getSimpleName();
            IBaseSlide baseSlide = ownerShape.getSlide();
            String slideLabel;
            if (baseSlide instanceof ISlide) {
                slideLabel = "slide " + ((ISlide) baseSlide).getSlideNumber();
            } else if (baseSlide instanceof INotesSlide) {
                slideLabel = "notes for slide " + ((INotesSlide) baseSlide).getParentSlide().getSlideNumber();
            } else {
                slideLabel = baseSlide.getClass().getSimpleName();
            }
            System.out.println("Shape: " + shapeName + "; type: " + shapeType + "; " + slideLabel);
            continue;
        }

        ICell ownerCell = textFrame.getParentCell();
        if (ownerCell != null) {
            IBaseSlide baseSlide = ownerCell.getSlide();
            String slideLabel;
            if (baseSlide instanceof ISlide) {
                slideLabel = "slide " + ((ISlide) baseSlide).getSlideNumber();
            } else if (baseSlide instanceof INotesSlide) {
                slideLabel = "notes for slide " + ((INotesSlide) baseSlide).getParentSlide().getSlideNumber();
            } else {
                slideLabel = baseSlide.getClass().getSimpleName();
            }
            System.out.println("Table cell: column " + ownerCell.getFirstColumnIndex() + ", row " + ownerCell.getFirstRowIndex() + "; " + slideLabel);
            continue;
        }

        System.out.println("The text frame owner is not available as a shape or table cell.");
    }
} finally {
    presentation.dispose();
}

对于 SmartArt 内容,遍历ISmartArtNode.getShapes得到的形状,并访问每个ISmartArtShape.getTextFrame。文本框可以通过ITextFrame.getParentShape追溯到其关联的形状,而ITextFrame.getParentCell返回null。因此,示例中的形状分支同样处理来自 SmartArt 节点的文本。

使用回调收集匹配信息

实现IFindResultCallback即可在每一次匹配时收到通知。其IFindResultCallback.foundResult方法提供关联的文本框、源文本、匹配文本以及匹配位置。

回调不会直接收到幻灯片编号。下面的实现通过父幻灯片推导编号,并同时处理在幻灯片备注中找到的文本。可空的 Integer 允许相同的结果模型表示与其他幻灯片类型关联的文本。

import com.aspose.slides.*;
import java.util.ArrayList;
import java.util.List;

final class TextMatch {
    private final ITextFrame textFrame;
    private final String sourceText;
    private final String foundText;
    private final int textPosition;
    private final Integer slideNumber;

    TextMatch(ITextFrame textFrame, String sourceText, String foundText, int textPosition, Integer slideNumber) {
        this.textFrame = textFrame;
        this.sourceText = sourceText;
        this.foundText = foundText;
        this.textPosition = textPosition;
        this.slideNumber = slideNumber;
    }

    ITextFrame getTextFrame() {
        return textFrame;
    }

    String getSourceText() {
        return sourceText;
    }

    String getFoundText() {
        return foundText;
    }

    int getTextPosition() {
        return textPosition;
    }

    Integer getSlideNumber() {
        return slideNumber;
    }
}

final class TextSearchCallback implements IFindResultCallback {
    private final List<TextMatch> results = new ArrayList<TextMatch>();

    List<TextMatch> getResults() {
        return results;
    }

    @Override
    public void foundResult(ITextFrame textFrame, String sourceText, String foundText, int textPosition) {
        Integer slideNumber = getSlideNumber(textFrame);
        TextMatch result = new TextMatch(textFrame, sourceText, foundText, textPosition, slideNumber);
        results.add(result);
    }

    private Integer getSlideNumber(ITextFrame textFrame) {
        IShape parentShape = textFrame.getParentShape();
        ICell parentCell = textFrame.getParentCell();
        IBaseSlide parentSlide = parentShape != null ? parentShape.getSlide() : parentCell != null ? parentCell.getSlide() : textFrame.getSlide();

        if (parentSlide instanceof ISlide) {
            return ((ISlide) parentSlide).getSlideNumber();
        }

        if (parentSlide instanceof INotesSlide) {
            return ((INotesSlide) parentSlide).getParentSlide().getSlideNumber();
        }

        return null;
    }
}

对于替换操作,foundText 包含原始匹配文本,因此回调可以准确记录哪些词被替换。

高亮文本

使用ITextFrame.highlightText方法在文本框中高亮字面文本匹配。通过传入TextSearchOptions来控制搜索,并提供回调以收集匹配细节。

下面的代码示例先高亮所有 “try” 字符串的出现,然后仅高亮完整单词 “to”。两个搜索都将匹配结果报告给同一个回调。

import com.aspose.slides.*;
import java.awt.Color;

Presentation presentation = new Presentation("sample.pptx");
try {
    ISlide slide = presentation.getSlides().get_Item(0);
    IAutoShape shape = (IAutoShape) slide.getShapes().get_Item(0);
    TextSearchCallback callback = new TextSearchCallback();

    TextSearchOptions substringSearchOptions = new TextSearchOptions();
    substringSearchOptions.setCaseSensitive(false);
    Color substringHighlightColor = new Color(173, 216, 230);

    // 在文本框中高亮显示每个出现的 "try"。
    shape.getTextFrame().highlightText("try", substringHighlightColor, substringSearchOptions, callback);

    TextSearchOptions wholeWordSearchOptions = new TextSearchOptions();
    wholeWordSearchOptions.setWholeWordsOnly(true);
    wholeWordSearchOptions.setCaseSensitive(false);
    Color wholeWordHighlightColor = new Color(238, 130, 238);

    // 仅高亮完整单词 "to"。
    shape.getTextFrame().highlightText("to", wholeWordHighlightColor, wholeWordSearchOptions, callback);

    for (TextMatch result : callback.getResults()) {
        System.out.println("Found '" + result.getFoundText() + "' at position " +
                result.getTextPosition() + " on slide " + result.getSlideNumber() + ".");
    }

    presentation.save("highlighted_text.pptx", SaveFormat.Pptx);
} finally {
    presentation.dispose();
}

结果:

The highlighted text

使用正则表达式高亮文本

ITextFrame.highlightRegex方法可以在文本框中高亮正则表达式找到的匹配文本。

下面的代码高亮所有包含七个或更多字符的单词,并收集每一次匹配:

import com.aspose.slides.*;
import java.awt.Color;
import java.util.regex.Pattern;

Presentation presentation = new Presentation("sample.pptx");
try {
    ISlide slide = presentation.getSlides().get_Item(0);
    IAutoShape shape = (IAutoShape) slide.getShapes().get_Item(0);
    TextSearchCallback callback = new TextSearchCallback();
    Pattern regex = Pattern.compile("\\b[^\\s]{7,}\\b");

    shape.getTextFrame().highlightRegex(regex, Color.YELLOW, callback);

    presentation.save("highlighted_text_using_regex.pptx", SaveFormat.Pptx);
} finally {
    presentation.dispose();
}

结果:

The highlighted text using the regular expression

在整个演示文稿中高亮文本

使用Presentation.highlightTextPresentation.highlightRegex可以在演示文稿的所有适用文本框中进行搜索。下面的示例高亮一个字面词汇以及所有电子邮件地址,并为两次搜索分别维护独立的结果集合。

import com.aspose.slides.*;
import java.awt.Color;
import java.util.regex.Pattern;

Presentation presentation = new Presentation("presentation.pptx");
try {
    TextSearchCallback termCallback = new TextSearchCallback();
    TextSearchOptions searchOptions = new TextSearchOptions();
    searchOptions.setWholeWordsOnly(true);
    searchOptions.setCaseSensitive(false);

    presentation.highlightText("confidential", Color.ORANGE, searchOptions, termCallback);

    TextSearchCallback emailCallback = new TextSearchCallback();
    Pattern emailRegex = Pattern.compile(
            "\\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}\\b",
            Pattern.CASE_INSENSITIVE);

    presentation.highlightRegex(emailRegex, Color.YELLOW, emailCallback);
    presentation.save("highlighted_presentation.pptx", SaveFormat.Pptx);
} finally {
    presentation.dispose();
}

在文本框中替换文本

使用ITextFrame.replaceText进行字面文本替换,使用ITextFrame.replaceRegex进行基于模式的替换。这些方法在保持现有文本框的同时更新匹配的文本,保留周围部分的格式,而不是从纯字符串重新构建文本框。

下面的示例先统一一种拼写变体,然后替换版本标签。相同的回调记录两种操作匹配到的原始词汇。

import com.aspose.slides.*;
import java.util.regex.Pattern;

Presentation presentation = new Presentation("presentation.pptx");
try {
    ISlide slide = presentation.getSlides().get_Item(0);
    IAutoShape shape = (IAutoShape) slide.getShapes().get_Item(0);
    TextSearchCallback callback = new TextSearchCallback();
    TextSearchOptions searchOptions = new TextSearchOptions();
    searchOptions.setWholeWordsOnly(true);
    searchOptions.setCaseSensitive(false);

    shape.getTextFrame().replaceText("colour", "color", searchOptions, callback);

    Pattern versionRegex = Pattern.compile("\\bv\\d+(?:\\.\\d+)*\\b", Pattern.CASE_INSENSITIVE);
    shape.getTextFrame().replaceRegex(versionRegex, "current version", callback);

    presentation.save("updated_text_frame.pptx", SaveFormat.Pptx);
} finally {
    presentation.dispose();
}

如果一次匹配跨越了格式不同的片段,请检查输出以确认替换文本应采用哪种样式。

在整个演示文稿中替换文本

使用Presentation.replaceTextPresentation.replaceRegex可以将相同的操作应用于整个演示文稿。这对于模板清理、术语更新和脱敏非常有用。

import com.aspose.slides.*;
import java.util.regex.Pattern;

Presentation presentation = new Presentation("presentation.pptx");
try {
    TextSearchCallback callback = new TextSearchCallback();
    TextSearchOptions searchOptions = new TextSearchOptions();
    searchOptions.setWholeWordsOnly(true);
    searchOptions.setCaseSensitive(true);

    presentation.replaceText("Contoso", "Example Corp", searchOptions, callback);

    Pattern accountNumberRegex = Pattern.compile("\\bACCT-\\d{6}\\b");
    presentation.replaceRegex(accountNumberRegex, "ACCT-REDACTED", callback);

    presentation.save("updated_presentation.pptx", SaveFormat.Pptx);
} finally {
    presentation.dispose();
}

对匹配进行分组以便报告

由于每个结果都存有幻灯片编号和文本框,应用程序可以按审计、报告或审阅工作流对匹配进行分组。下面的示例先按幻灯片再按文本框对收集到的结果进行分组:

import com.aspose.slides.ITextFrame;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

Map<Integer, Map<ITextFrame, List<TextMatch>>> matchesBySlide =
        new LinkedHashMap<Integer, Map<ITextFrame, List<TextMatch>>>();

for (TextMatch result : callback.getResults()) {
    Integer slideNumber = result.getSlideNumber();
    Map<ITextFrame, List<TextMatch>> matchesByTextFrame = matchesBySlide.get(slideNumber);

    if (matchesByTextFrame == null) {
        matchesByTextFrame = new LinkedHashMap<ITextFrame, List<TextMatch>>();
        matchesBySlide.put(slideNumber, matchesByTextFrame);
    }

    ITextFrame textFrame = result.getTextFrame();
    List<TextMatch> textFrameMatches = matchesByTextFrame.get(textFrame);

    if (textFrameMatches == null) {
        textFrameMatches = new java.util.ArrayList<TextMatch>();
        matchesByTextFrame.put(textFrame, textFrameMatches);
    }

    textFrameMatches.add(result);
}

for (Map.Entry<Integer, Map<ITextFrame, List<TextMatch>>> slideEntry : matchesBySlide.entrySet()) {
    String slideLabel = slideEntry.getKey() == null ? "Other" : slideEntry.getKey().toString();
    System.out.println("Slide: " + slideLabel);

    for (Map.Entry<ITextFrame, List<TextMatch>> textFrameEntry : slideEntry.getValue().entrySet()) {
        System.out.println("  Text frame: " + textFrameEntry.getKey().getText());

        for (TextMatch result : textFrameEntry.getValue()) {
            System.out.println("    '" + result.getFoundText() + "' at position " +
                    result.getTextPosition() + "; context: '" + result.getSourceText() + "'");
        }
    }
}

常见问题

如何仅在一个文本框中搜索,而不是整个演示文稿?

获取形状的文本框,然后对该文本框调用ITextFrame.highlightTextITextFrame.highlightRegexITextFrame.replaceText、或ITextFrame.replaceRegex;演示文稿级别的方法会处理所有适用的文本框。

如何匹配完整单词且区分大小写?

TextSearchOptions.setWholeWordsOnlyTextSearchOptions.setCaseSensitive设为true,并将这些选项传递给字面文本的高亮或替换方法。对于正则表达式,在 Java Pattern 本身中定义单词边界和大小写敏感性。

搜索和替换能否包括幻灯片备注中的文本?

可以。在使用演示文稿级别的字面文本操作时,将TextSearchOptions.setIncludeNotes设为true。上面的回调实现会把备注幻灯片中的匹配映射回其父幻灯片编号。

如何在不二次遍历演示文稿的情况下生成报告?

向高亮或替换操作传入IFindResultCallback实现。回调会在操作执行期间收到每一次匹配,因而应用程序可以立即存储源文本、匹配文本、位置、文本框以及派生的幻灯片编号,以便后续分组或导出。

替换文本是否会保留其格式?

ITextFrame.replaceTextITextFrame.replaceRegex在现有文本框内修改匹配的文本,并保留周围片段的格式。如果一次匹配跨越了不同格式的片段,请检查结果以确保替换使用了期望的样式。