Управление гиперссылками презентации в Python

Введение

Гиперссылка связывает содержимое презентации с веб‑сайтом или расположением внутри презентации. В PowerPoint гиперссылки обычно служат для двух целей:

  • Открыть веб‑сайт из текста, фигуры или медиа‑кадра.
  • Перейти к другому слайду, например, из оглавления.

Aspose.Slides for Python via .NET позволяет добавлять эти ссылки, управлять их внешним видом и звуком, обновлять их свойства и удалять их. Приведённые ниже примеры показывают, как работать с гиперссылками на отдельных элементах и как получать доступ к гиперссылкам на уровне презентации, слайда или текстового кадра.

Добавить URL‑гиперссылки

Вы можете назначить URL веб‑сайта тексту, фигуре или медиа‑кадру. Элемент, к которому вы привязываете гиперссылка, определяет кликабельную область: часть текста связывает выбранный текст, а фигура или кадр связывают объект слайда.

Добавить URL‑гиперссылки к тексту

Чтобы связать текст с веб‑сайтом, назначьте объект Hyperlink свойству hyperlink_click части текста, как показано ниже. Только эта часть текста станет кликабельной.

import aspose.slides as slides

with slides.Presentation() as presentation:
    text_shape = presentation.slides[0].shapes.add_auto_shape(slides.ShapeType.RECTANGLE, 100, 100, 600, 50, False)
    text_shape.add_text_frame("Aspose: File Format APIs")
    portion_format = text_shape.text_frame.paragraphs[0].portions[0].portion_format
    portion_format.hyperlink_click = slides.Hyperlink("https://www.aspose.com/")
    portion_format.hyperlink_click.tooltip = "Explore Aspose file format APIs"
    portion_format.font_height = 32
    presentation.save("presentation-out.pptx", slides.export.SaveFormat.PPTX)

Добавить URL‑гиперссылки к фигурам и медиа‑кадрам

Чтобы сделать фигуру или кадр кликабельным, задайте её свойство hyperlink_click. Гиперссылка принадлежит самому объекту, а не части текста внутри него.

Тот же подход применяется к кадрам изображения, аудио и видео: назначьте гиперссылку кадру и при необходимости задайте tooltip ссылки.

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

import aspose.slides as slides

with slides.Presentation() as presentation:
    shape = presentation.slides[0].shapes.add_auto_shape(slides.ShapeType.RECTANGLE, 100, 100, 600, 50)
    shape.hyperlink_click = slides.Hyperlink("https://www.aspose.com/")
    shape.hyperlink_click.tooltip = "Explore Aspose file format APIs"
    presentation.save("presentation-out.pptx", slides.export.SaveFormat.PPTX)

Использовать гиперссылки для создания оглавления

Внутренние гиперссылки позволяют читателям переходить из оглавления к конкретному слайду. В следующем примере используется set_internal_hyperlink_click для привязки текста «Page 2» на первом слайде ко второму слайду.

import aspose.pydrawing as draw
import aspose.slides as slides

with slides.Presentation() as presentation:
    first_slide = presentation.slides[0]
    second_slide = presentation.slides.add_empty_slide(first_slide.layout_slide)
    table_of_contents = first_slide.shapes.add_auto_shape(slides.ShapeType.RECTANGLE, 40, 40, 300, 100)
    table_of_contents.fill_format.fill_type = slides.FillType.NO_FILL
    table_of_contents.line_format.fill_format.fill_type = slides.FillType.NO_FILL
    table_of_contents.text_frame.paragraphs.clear()
    paragraph = slides.Paragraph()
    paragraph.paragraph_format.default_portion_format.fill_format.fill_type = slides.FillType.SOLID
    paragraph.paragraph_format.default_portion_format.fill_format.solid_fill_color.color = draw.Color.black
    paragraph.text = "Title of slide 2 .......... "
    link_portion = slides.Portion()
    link_portion.text = "Page 2"
    link_portion.portion_format.hyperlink_manager.set_internal_hyperlink_click(second_slide)
    paragraph.portions.add(link_portion)
    table_of_contents.text_frame.paragraphs.add(paragraph)
    presentation.save("link_to_slide.pptx", slides.export.SaveFormat.PPTX)

Форматировать гиперссылки

Цвет

Свойство color_source объекта Hyperlink определяет, использует ли гиперссылка цвет гиперссылок презентации или форматирование части текста. Чтобы задать пользовательский цвет текста, выберите HyperlinkColorSource.PORTION_FORMAT и задайте цвет заливки части. Эта функция была добавлена в PowerPoint 2019; более старые версии её не поддерживают.

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

import aspose.pydrawing as draw
import aspose.slides as slides

with slides.Presentation() as presentation:
    colored_shape = presentation.slides[0].shapes.add_auto_shape(slides.ShapeType.RECTANGLE, 100, 100, 450, 50, False)
    colored_shape.add_text_frame("This hyperlink uses a custom color.")
    colored_portion_format = colored_shape.text_frame.paragraphs[0].portions[0].portion_format
    colored_portion_format.hyperlink_click = slides.Hyperlink("https://www.aspose.com/")
    colored_portion_format.hyperlink_click.color_source = slides.HyperlinkColorSource.PORTION_FORMAT
    colored_portion_format.fill_format.fill_type = slides.FillType.SOLID
    colored_portion_format.fill_format.solid_fill_color.color = draw.Color.red
    default_shape = presentation.slides[0].shapes.add_auto_shape(slides.ShapeType.RECTANGLE, 100, 200, 450, 50, False)
    default_shape.add_text_frame("This hyperlink uses the default color.")
    default_shape.text_frame.paragraphs[0].portions[0].portion_format.hyperlink_click = slides.Hyperlink("https://www.aspose.com/")
    presentation.save("presentation-out-hyperlink.pptx", slides.export.SaveFormat.PPTX)

Звук

Гиперссылка может воспроизводить звук при активации или останавливать уже воспроизводимый звук. Для настройки этих поведений используйте следующие свойства:

  • Hyperlink.sound указывает аудио, связанное с гиперссылкой.
  • Hyperlink.stop_sound_on_click управляет тем, останавливается ли при активации гиперссылка предыдущий звук.

Добавить звук к гиперссылке

В следующем примере загружается sampleaudio.wav и связывается с кнопкой на первом слайде. При нажатии кнопки воспроизводится звук и происходит переход к следующему слайду. Вторая фигура на этом слайде останавливает предыдущий звук при нажатии, не выполняя переход.

import aspose.slides as slides

with slides.Presentation() as presentation:
    with open("sampleaudio.wav", "rb") as audio_file:
        audio_data = audio_file.read()
    hyperlink_sound = presentation.audios.add_audio(audio_data)
    first_slide = presentation.slides[0]
    play_button = first_slide.shapes.add_auto_shape(slides.ShapeType.SOUND_BUTTON, 100, 100, 100, 50)
    play_button.hyperlink_click = slides.Hyperlink.next_slide
    if not play_button.hyperlink_click.stop_sound_on_click and play_button.hyperlink_click.sound is None:
        play_button.hyperlink_click.sound = hyperlink_sound

    second_slide = presentation.slides.add_empty_slide(first_slide.layout_slide)
    stop_button = second_slide.shapes.add_auto_shape(slides.ShapeType.RECTANGLE, 100, 100, 100, 50)
    stop_button.hyperlink_click = slides.Hyperlink.no_action
    stop_button.hyperlink_click.stop_sound_on_click = True
    presentation.save("hyperlink-sound.pptx", slides.export.SaveFormat.PPTX)

Извлечь звук из гиперссылки

В следующем примере открывается созданная выше презентация и аудио гиперссылки первой фигуры читается в память с помощью sound и binary_data.

import aspose.slides as slides

with slides.Presentation("hyperlink-sound.pptx") as presentation:
    if len(presentation.slides) > 0 and len(presentation.slides[0].shapes) > 0:
        hyperlink = presentation.slides[0].shapes[0].hyperlink_click
        sound = hyperlink.sound if hyperlink is not None else None
        if sound is not None:
            audio_data = sound.binary_data
            print(f"Extracted {len(audio_data)} bytes of hyperlink audio.")
        else:
            print("The first shape has no hyperlink sound.")
    else:
        print("The presentation has no first slide or shape to inspect.")

Настройки подсказки и взаимодействия

После назначения гиперссылки тексту или фигуре вы можете обновить следующие свойства [Hyperlink]:

  • [tooltip] задаёт текст, который пользователь может увидеть как подсказку к ссылке.
  • [target_frame] указывает целевой фрейм внутри родительского HTML‑фреймсета, если применимо.
  • [history] управляет тем, добавляется ли при активации ссылка в список просмотренных гиперссылок.
  • [highlight_click] управляет тем, будет ли гиперссылка выделена при нажатии.

Удалить гиперссылки из презентаций

Используйте [get_any_hyperlinks] для сбора контейнеров гиперссылок, включая ссылки на части текста, перед их изменением. В следующем примере удаляются оба типа активации с первого слайда. Чтобы удалить только один тип, вызовите только [remove_hyperlink_click] или [remove_hyperlink_mouse_over]; удаление действия клика не удаляет соответствующее действие наведения мышью.

import aspose.slides as slides

with slides.Presentation("pres.pptx") as presentation:
    if len(presentation.slides) > 0:
        containers = list(presentation.slides[0].hyperlink_queries.get_any_hyperlinks())
        for container in containers:
            container.hyperlink_manager.remove_hyperlink_click()
            container.hyperlink_manager.remove_hyperlink_mouse_over()
        presentation.save("pres-removed-hyperlinks.pptx", slides.export.SaveFormat.PPTX)
    else:
        print("The presentation has no slides to process.")

Для безусловного удаления [remove_all_hyperlinks] удаляет оба типа активации в выбранной области одним вызовом. Для выборочной очистки и охвата мастеров, макетов и заметок см. раздел Report, Sanitize, and Verify Hyperlinks.

Создать полный реестр гиперссылок

Перед распространением презентации выполните инвентаризацию её интерактивных действий и веб‑ссылок. [get_any_hyperlinks] возвращает объекты [IHyperlinkContainer], а не простой список URL‑строк. Проверьте как [hyperlink_click], так и [hyperlink_mouse_over] в каждом контейнере. Они независимы: один и тот же контейнер может содержать оба действия, поэтому полный отчёт может требовать до двух строк на контейнер.

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

Запрос областей презентации, слайда и текстового кадра

Класс [HyperlinkQueries] доступен через Presentation.hyperlink_queries, BaseSlide.hyperlink_queries, и TextFrame.hyperlink_queries. Каждая область поддерживает одинаковые запросы:

  • [get_hyperlink_clicks] возвращает контейнеры с действием клика.
  • [get_hyperlink_mouse_overs] возвращает контейнеры с действием наведения мышью.
  • [get_any_hyperlinks] возвращает контейнеры с одним или обоими действиями.

В следующем примере создаётся hyperlink-audit-input.pptx с внешней ссылкой клика, ссылкой наведения мышью на файл, внутренней навигацией по слайдам, ссылкой наведения мышью на текст и действием макроса. Он не выполняет ни одно из этих действий. Одни и те же три запроса работают во всех областях; количество относится к контейнерам, а не к сумме действий. Область текстового кадра исключает собственные ссылки охватывающей её фигуры.

import aspose.slides as slides


def print_counts(scope, queries):
    click_containers = queries.get_hyperlink_clicks()
    mouse_over_containers = queries.get_hyperlink_mouse_overs()
    all_containers = queries.get_any_hyperlinks()
    print(f"{scope}: click={len(click_containers)}, mouse-over={len(mouse_over_containers)}, any={len(all_containers)}")


with slides.Presentation() as presentation:
    slide = presentation.slides[0]
    destination = presentation.slides.add_empty_slide(slide.layout_slide)
    shape = slide.shapes.add_auto_shape(slides.ShapeType.RECTANGLE, 20, 20, 400, 60)
    shape.text_frame.text = "Click the text to go to slide 2"
    shape.hyperlink_manager.set_external_hyperlink_click("https://example.com/")
    shape.hyperlink_click.tooltip = "Public website"
    shape.hyperlink_manager.set_external_hyperlink_mouse_over("file:///C:/private/report.xlsx")

    portion_format = shape.text_frame.paragraphs[0].portions[0].portion_format
    portion_format.hyperlink_manager.set_internal_hyperlink_click(destination)
    portion_format.hyperlink_manager.set_external_hyperlink_mouse_over("https://example.com/help")
    macro_button = slide.shapes.add_auto_shape(slides.ShapeType.RECTANGLE, 20, 120, 200, 60)
    macro_button.hyperlink_manager.set_macro_hyperlink_click("ReviewPresentation")

    print_counts("Presentation", presentation.hyperlink_queries)
    print_counts("Slide 1", slide.hyperlink_queries)
    print_counts("Text frame", shape.text_frame.hyperlink_queries)
    presentation.save("hyperlink-audit-input.pptx", slides.export.SaveFormat.PPTX)

Для этого примера запросы презентации и слайда возвращают три контейнера клика, два контейнера наведения мышью и три контейнера с любым из действий. Запрос текстового кадра возвращает по одному контейнеру в каждой категории.

Классификация действий и целей

Используйте [Hyperlink.action_type] для определения типа действия перед определением его назначения. Значения [HyperlinkActionType] охватывают больше, чем веб‑навигацию:

Значения Смысл для аудита
HYPERLINK Внешняя гиперссылка; проверьте URL и её схему.
JUMP_SPECIFIC_SLIDE Внутренняя навигация к конкретному слайду.
JUMP_FIRST_SLIDE, JUMP_PREVIOUS_SLIDE, JUMP_NEXT_SLIDE, JUMP_LAST_SLIDE, JUMP_LAST_VIEWED_SLIDE Встроенная навигация в режиме показа, разрешается в контексте показа.
JUMP_END_SHOW, START_CUSTOM_SLIDE_SHOW Завершить текущий показ или запустить пользовательский показ.
START_MACRO Выполнить макрос.
START_PROGRAM Запустить программу.
OPEN_FILE, OPEN_PRESENTATION Открыть файл или другую презентацию; рассматривать отдельно от веб‑URL.
START_STOP_MEDIA Запустить или остановить воспроизведение медиа.
NO_ACTION, UNKNOWN Отсутствие навигационного действия или неизвестное действие, требующее проверки.

Чтение внешних целей производится из [external_url] и конкретные внутренние цели — из [target_slide]. Внутренние действия и встроенные команды могут не иметь внешнего URL; пустой URL не означает отсутствие действия у контейнера. Сохраняйте [external_url_original], когда он отличается от нормализованного URL, и включайте [tooltip], если он доступен.

Отчет, очистка и проверка гиперссылок

В следующем примере на Python читается существующая презентация (используйте файл, созданный выше), записывается hyperlink-audit.json, применяется политика, сохраняется hyperlink-sanitized.pptx и снова открывается для повторной проверки обоих типов активации. Он собирает контейнеры перед их изменением и запрашивает каждую область слайда один раз, чтобы избежать двойной обработки. Запросы презентации охватывают обычные слайды; для инвентаризации всего пакета пример запрашивает обычные слайды, мастера, макеты, заметки и мастера заметок и раздаточных материалов, если они присутствуют.

Отчёт регистрирует индекс слайда, начинающийся с единицы, и [slide_id], где это возможно. Сборщик сохраняет владелец‑слайд и область вместе с каждым возвращённым контейнером. У мастеров, макетов и заметок нет обычного индекса слайда и они идентифицируются по своей области. Контейнеры фигур и контейнеры форматирования текстовых частей помечаются отдельно; остальные типы контейнеров сохраняют своё имя типа во время выполнения. Каждый контейнер получает локальный ID отчёта, чтобы его два действия можно было сопоставить.

Отчёт использует консервативную политику проверки экспорта в PDF: помечать действия наведения мышью и всё, что не является внешней ссылкой или переходом к определённому слайду, как потенциально неподдерживаемое. Это лишь подсказка для проверки, а не тест возможностей или гарантия, что непомеченные ссылки выживут при экспорте. Поддерживаемый экспорт в PDF и HTML может сохранять гиперссылки в зависимости от действия, параметров экспорта и используемого просмотрщика. Растровые images и video не могут сохранять интерактивные гиперссылки; помечайте каждое действие при аудитe для этих форматов вывода.

import json
import sys
from urllib.parse import urlsplit
import aspose.slides as slides


def is_https(value):
    if not value or any(character.isspace() for character in value):
        return False
    try:
        uri = urlsplit(value)
        return uri.scheme.lower() == "https" and bool(uri.hostname)
    except ValueError:
        return False


def policy_violation(link):
    if link is None:
        return None
    if link.action_type == slides.HyperlinkActionType.JUMP_SPECIFIC_SLIDE:
        return "Missing target slide" if link.target_slide is None else None
    if link.action_type != slides.HyperlinkActionType.HYPERLINK:
        return "Action is not allowed"
    if not is_https(link.external_url):
        return "Normalized URL is not absolute HTTPS"
    original = link.external_url_original
    if original and not is_https(original):
        return "Original URL is not absolute HTTPS"
    return None


def slide_index(presentation, slide):
    if slide is not None:
        for index, candidate in enumerate(presentation.slides, start=1):
            if candidate.slide_id == slide.slide_id:
                return index
    return None


def collect_containers(presentation):
    # Запросить каждый слайд один раз, сохраняя его владельца для каждого контейнера.
    scopes = [("Slide", slide) for slide in presentation.slides]
    scopes.extend(("Master", master) for master in presentation.masters)
    scopes.extend(("Layout", layout) for layout in presentation.layout_slides)
    scopes.extend(("Notes", slide.notes_slide_manager.notes_slide) for slide in presentation.slides)
    scopes.append(("Notes master", presentation.master_notes_slide_manager.master_notes_slide))
    scopes.append(("Handout master", presentation.master_handout_slide_manager.master_handout_slide))
    found = []
    for scope, owner in scopes:
        if owner is not None:
            containers = list(owner.hyperlink_queries.get_any_hyperlinks())
            found.extend((container, scope, owner) for container in containers)
    return found


def add_row(rows, presentation, link, activation, container, container_id, scope, owner):
    if link is None:
        return
    target_slide = link.target_slide
    violation = policy_violation(link)
    if isinstance(container, slides.Shape):
        owner_type = "Shape"
    elif isinstance(container, slides.PortionFormat):
        owner_type = "Text portion"
    else:
        owner_type = type(container).__name__
    ordinary_action = link.action_type in (slides.HyperlinkActionType.HYPERLINK, slides.HyperlinkActionType.JUMP_SPECIFIC_SLIDE)
    original_url = link.external_url_original if link.external_url_original != link.external_url else None
    rows.append({
        "container_id": container_id,
        "slide_index": slide_index(presentation, owner) if scope == "Slide" else None,
        "slide_id": owner.slide_id,
        "scope": scope,
        "owner_type": owner_type,
        "activation": activation,
        "action_type": link.action_type.name,
        "external_url": link.external_url,
        "target_slide_index": slide_index(presentation, target_slide),
        "target_slide_id": target_slide.slide_id if target_slide is not None else None,
        "tooltip": link.tooltip,
        "original_external_url": original_url,
        "potentially_unsafe": violation is not None,
        "policy_violation": violation,
        "target_export": "PDF",
        "potentially_unsupported_by_export": activation == "mouse-over" or not ordinary_action,
    })


replace_external_clicks = True
replacement_url = "https://example.com/blocked-link"

with slides.Presentation("hyperlink-audit-input.pptx") as presentation:
    containers = collect_containers(presentation)
    rows = []
    for container_id, (container, scope, owner) in enumerate(containers, start=1):
        add_row(rows, presentation, container.hyperlink_click, "click", container, container_id, scope, owner)
        add_row(rows, presentation, container.hyperlink_mouse_over, "mouse-over", container, container_id, scope, owner)

    with open("hyperlink-audit.json", "w", encoding="utf-8") as report_file:
        json.dump(rows, report_file, indent=2)

    for container, scope, owner in containers:
        click = container.hyperlink_click
        if policy_violation(click) is not None:
            if replace_external_clicks and click.action_type == slides.HyperlinkActionType.HYPERLINK:
                container.hyperlink_manager.set_external_hyperlink_click(replacement_url)
            else:
                container.hyperlink_manager.remove_hyperlink_click()
        if policy_violation(container.hyperlink_mouse_over) is not None:
            container.hyperlink_manager.remove_hyperlink_mouse_over()

    presentation.save("hyperlink-sanitized.pptx", slides.export.SaveFormat.PPTX)

with slides.Presentation("hyperlink-sanitized.pptx") as reopened:
    remaining_containers = collect_containers(reopened)
    violations = 0
    for container, scope, owner in remaining_containers:
        if policy_violation(container.hyperlink_click) is not None:
            violations += 1
        if policy_violation(container.hyperlink_mouse_over) is not None:
            violations += 1
    print(f"Audit rows: {len(rows)}; prohibited actions after reopening: {violations}")
    if violations != 0:
        print("Verification failed: do not distribute the saved presentation.")
        sys.exit(1)

С созданным выше вводом отчёт содержит пять строк действий. Ссылка наведения мышью на файл и клик макроса удаляются, тогда как HTTPS‑ссылки и внутренняя навигация по слайдам сохраняются. Проверка выводит ноль запрещённых действий. Ввод, содержащий запрещённый внешний URL клика, также демонстрирует ветку замены. Контейнер с разрешённым кликом и запрещённым наведением мыши сохраняет действие клика.

Эта выборочная очистка отличается от [remove_all_hyperlinks], который удаляет оба типа активации во всей выбранной области независимо от политики. Проверка здесь рассматривает только действия гиперссылок; она не удаляет встроенные VBA‑проекты, OLE‑объекты или другое активное содержимое и не проверяет экспортированный PDF или HTML файл.

Часто задаваемые вопросы

Как создать ссылку на раздел или его первый слайд?

Разделы в PowerPoint группируют слайды, но внутренняя гиперссылка направлена на отдельный слайд. Чтобы создать навигацию к разделу, привяжите ссылку к первому слайду этого раздела.

Могу ли я привязать гиперссылку к элементам слайда‑мастера, чтобы она работала на всех слайдах?

Да. Элементы слайда‑мастера и макета поддерживают гиперссылки. Ссылки на этих элементах доступны во время показа слайдов на тех слайдах, которые используют соответствующий мастер или макет.

Сохранятся ли гиперссылки при экспорте в PDF, HTML, изображения или видео?

Поддерживаемый экспорт в PDF и HTML может сохранять гиперссылки; растровые изображения и видео — нет. Смотрите сведения об экспорте в разделе Report, Sanitize, and Verify Hyperlinks.