Поиск и замена текста в презентациях PowerPoint на Android

Обзор

Aspose.Slides for Android via Java может искать, выделять и заменять текст в отдельном текстовом фрейме или во всей презентации. Каждая операция также может уведомлять приложение о каждом совпадении через обратный вызов результата. Это позволяет обновлять презентацию и одновременно создавать журнал аудита, содержащий найденный текст, его контекст, позицию, текстовый фрейм и номер слайда.

Эти возможности полезны для рецензирования, редактирования, проверки терминологии, очистки шаблонов и автоматизированных процессов отчётности.

В первых примерах ниже мы используем файл с именем “sample.pptx”, который содержит один текстовый блок на первом слайде со следующим текстом:

Пример текста

Выбор области поиска

Используйте методы интерфейса ITextFrame для ограничения операции одним текстовым фреймом. Используйте методы интерфейса IPresentation для обработки всего применимого текста в презентации.

Операция Один текстовый фрейм Вся презентация
Выделить буквальный текст ITextFrame.highlightText IPresentation.highlightText
Выделить совпадения регулярного выражения ITextFrame.highlightRegex IPresentation.highlightRegex
Заменить буквальный текст ITextFrame.replaceText IPresentation.replaceText
Заменить совпадения регулярного выражения ITextFrame.replaceRegex IPresentation.replaceRegex

Настройка сопоставления текста

Для операций с буквальным текстом используйте TextSearchOptions для управления сопоставлением:

Операции с регулярными выражениями используют Java Pattern, поэтому правила сопоставления, такие как чувствительность к регистру и границы слов, задаются выражением и его флагами.

Определение владельца текстового фрейма

Общие рабочие процессы обработки текста часто получают объект ITextFrame при поиске, замене, проверке или экспорте текста. Используйте ITextFrame.getParentShape и ITextFrame.getParentCell , чтобы определить, какой объект презентации владеет этим текстовым фреймом.

Ожидаемые значения зависят от владельца:

Владелец текстового фрейма getParentShape getParentCell
AutoShape или другая форма, содержащая текст Владелец IShape null
Ячейка таблицы null Владелец ICell

Оба метода предоставляют только навигацию для чтения. Их вызов не перемещает текстовый фрейм и не меняет его владельца. Общий код должен проверять оба значения на null и обрабатывать возможность, что ни один владелец недоступен.

Следующий пример использует SlideUtil.getAllTextFrames для перебора текстовых фреймов в презентации. Для фигур он выводит имя фигуры, тип в runtime 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 предоставляет связанный текстовый фрейм, исходный текст, найденный текст и позицию совпадения.

Обратный вызов не получает номер слайда напрямую. Ниже приведённая реализация получает его из родительского слайда и также обрабатывает текст, найденный в заметках слайда. Допускающий значение null тип 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 android.graphics.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);
    int substringHighlightColor = Color.rgb(173, 216, 230);

    // Выделить каждое вхождение "try" в текстовом фрейме.
    shape.getTextFrame().highlightText("try", substringHighlightColor, substringSearchOptions, callback);

    TextSearchOptions wholeWordSearchOptions = new TextSearchOptions();
    wholeWordSearchOptions.setWholeWordsOnly(true);
    wholeWordSearchOptions.setCaseSensitive(false);
    int wholeWordHighlightColor = Color.rgb(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();
}

Результат:

Выделенный текст

Выделение текста с помощью регулярных выражений

Метод ITextFrame.highlightRegex выделяет совпадения текста, найденные регулярным выражением, в текстовом фрейме.

Следующий код выделяет все слова, содержащие семь и более символов, и собирает каждое совпадение:

import com.aspose.slides.*;
import android.graphics.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();
}

Результат:

Выделенный текст с использованием регулярного выражения

Выделение текста во всей презентации

Используйте IPresentation.highlightText и IPresentation.highlightRegex для поиска во всех применимых текстовых фреймах презентации. Следующий пример выделяет буквальный термин и все адреса электронной почты, при этом сохраняет отдельные коллекции результатов для двух поисков.

import com.aspose.slides.*;
import android.graphics.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);

    int termHighlightColor = Color.rgb(255, 165, 0);
    presentation.highlightText("confidential", termHighlightColor, 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();
}

Если одно совпадение охватывает части с разным форматированием, проверьте результат, чтобы подтвердить, какое форматирование должно применяться к заменяемому тексту.

Замена текста во всей презентации

Используйте IPresentation.replaceText и IPresentation.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() + "'");
        }
    }
}

FAQ

Как выполнить поиск только в одном текстовом поле, а не во всей презентации?

Получите текстовый фрейм формы и вызовите ITextFrame.highlightText, ITextFrame.highlightRegex, ITextFrame.replaceText, или ITextFrame.replaceRegex для этого фрейма. Методы уровня презентации обрабатывают все применимые текстовые фреймы вместо этого.

Как сопоставить полные слова с учётом правильного регистра?

Установите TextSearchOptions.setWholeWordsOnly и TextSearchOptions.setCaseSensitive в true и передайте опции методу выделения или замены буквального текста. Для регулярных выражений определите границы слов и чувствительность к регистру непосредственно в Java Pattern.

Можно ли включить в поиск и замену текст из заметок слайдов?

Да. Установите TextSearchOptions.setIncludeNotes в true при использовании операции буквального текста уровня презентации. Реализация обратного вызова, приведённая выше, сопоставляет совпадение в слайде заметок с номером его родительского слайда.

Как создать отчёт, не сканируя презентацию второй раз?

Передайте реализацию IFindResultCallback в операцию выделения или замены. Обратный вызов получает каждое совпадение во время выполнения операции, поэтому приложение может сохранять исходный текст, найденный текст, позицию, текстовый фрейм и полученный номер слайда для последующей группировки или экспорта.

Сохраняет ли замена текста его форматирование?

ITextFrame.replaceText и ITextFrame.replaceRegex изменяют найденный текст внутри существующего текстового фрейма и сохраняют форматирование окружающих частей. Если совпадение охватывает части с разным форматированием, проверьте результат, чтобы убедиться, что замена использует требуемый стиль.