在 C++ 中将演示文稿导出为 XAML

概述

本文说明如何使用 Aspose.Slides 将 PowerPoint 演示文稿导出为 XAML。它包括对 XAML 的简要介绍,展示如何使用默认设置将演示文稿保存为 XAML,并演示如何通过 XamlOptions 自定义导出,包括导出隐藏幻灯片。文章还回答了一些常见问题,涉及回退字体、XAML 堆栈兼容性以及隐藏幻灯片导出行为。

关于 XAML

XAML 是一种基于 XML 的标记语言,用于在 WPF(Windows Presentation Foundation)、UWP(Universal Windows Platform)和 Xamarin.Forms 等框架中描述用户界面。

您可以在可视化设计器中使用 XAML 文件,也可以直接编写和编辑标记。

使用默认选项将演示文稿导出为 XAML

以下 C++ 示例展示了如何使用默认设置将演示文稿导出为 XAML:

#include <DOM/Presentation.h>
#include <Export/Xaml/XamlOptions.h>

using namespace Aspose::Slides;
using namespace Aspose::Slides::Export::Xaml;

auto presentation = System::MakeObject<Presentation>(u"pres.pptx");
auto xamlOptions = System::MakeObject<XamlOptions>();
presentation->Save(xamlOptions);

默认情况下,导出的幻灯片保存在进程当前工作目录的 pres 子文件夹中,目录由 Directory::GetCurrentDirectory 返回。该文件夹会自动创建,所需的图像也会保存到该文件夹中。

输出文件夹名称取自源文件名(不含扩展名)。对于 pres.pptx,输出文件命名为 pres/Slide_1.xamlpres/Slide_2.xaml 等。即使为输入演示文稿传入绝对路径,输出文件夹也相对于当前工作目录创建,而不是与输入文件放在同一位置。

使用自定义选项将演示文稿导出为 XAML

使用 IXamlOptions 接口来控制 Aspose.Slides 将演示文稿导出为 XAML 的方式。

要将输出保存到自定义位置,请实现 IXamlOutputSaver 并将您的实现实例传递给 XamlOptionsset_OutputSaver 方法。

要在 XAML 输出中包含隐藏幻灯片,请向 set_ExportHiddenSlides 方法传递 true,如下 C++ 示例所示:

#include <DOM/Presentation.h>
#include <Export/Xaml/XamlOptions.h>

using namespace Aspose::Slides;
using namespace Aspose::Slides::Export::Xaml;

auto presentation = System::MakeObject<Presentation>(u"pres.pptx");
auto xamlOptions = System::MakeObject<XamlOptions>();
xamlOptions->set_ExportHiddenSlides(true);
presentation->Save(xamlOptions);

捕获所有生成的 XAML 工件

XAML 导出可以为每个导出的幻灯片生成一个 XAML 文档,并生成单独的图像和支持资源。将自定义的 IXamlOutputSaver 传递给 XamlOptions::set_OutputSaver,即可接收这些工件,而不是使用默认的文件系统保存器。使用接受 XAML 选项的 XAML 特定的 Presentation::Save 重载启动导出。

了解回调生命周期

导出器会为每个生成的工件分别调用 IXamlOutputSaver::Save

  • path 标识工件,可能包含相对目录。请保留此信息,因为 XAML 可能使用相对路径引用资源。
  • data 包含工件的字节。图像和其他二进制资源不能被解码为文本。
  • 保存器负责在返回前保留或持久化数据。示例中将每个字节数组复制到应用程序拥有的内存中。
  • 仅当演示文稿保存操作返回且每个回调均成功完成时,才视导出为成功。不要吞掉存储错误或启动未监视的后台写入。如果持久化在之后进行,则仅在该步骤也成功后才报告整体成功。

XamlOptions::set_ExportHiddenSlides 同样适用于自定义保存器。默认设置为 false,会排除隐藏幻灯片的 XAML 文档。将其设为 true 则会包含它们以及导出所需的所有资源。资源数量取决于演示文稿;不要假设每个幻灯片对应一个回调或回调顺序固定。

导出到内存并检查工件

此完整示例加载 pres.pptx,在一个 Dictionary<String, ArrayPtr<uint8_t» 中收集所有工件,并打印其名称、类型和字节数。它完全保留提供的名称。重复名称会导致收集失败,而不是静默覆盖工件。

#include <DOM/Presentation.h>
#include <Export/Xaml/IXamlOutputSaver.h>
#include <Export/Xaml/XamlOptions.h>
#include <system/array.h>
#include <system/collections/dictionary.h>
#include <system/console.h>
#include <system/string_comparer.h>
#include <system/io/path.h>
#include <system/text/encoding.h>

using namespace Aspose::Slides;
using namespace Aspose::Slides::Export::Xaml;
using namespace System;
using namespace System::Collections::Generic;
using namespace System::IO;
using namespace System::Text;

class InMemoryXamlExample
{
    class MemoryXamlSaver : public IXamlOutputSaver
    {
    public:
        using ArtifactDictionary = Dictionary<String, ArrayPtr<uint8_t>>;
        SharedPtr<ArtifactDictionary> Artifacts = MakeObject<ArtifactDictionary>(StringComparer::get_Ordinal());

        void Save(String path, ArrayPtr<uint8_t> data) override
        {
            auto retainedData = data->Clone();
            Artifacts->Add(path, retainedData);
        }
    };

public:
    static void Run()
    {
        auto saver = MakeObject<MemoryXamlSaver>();
        auto presentation = MakeObject<Presentation>(u"pres.pptx");
        auto options = MakeObject<XamlOptions>();
        options->set_OutputSaver(saver);
        options->set_ExportHiddenSlides(true);
        presentation->Save(options);

        auto inspectXamlText = false;
        for (const auto& artifact : saver->Artifacts)
        {
            auto extension = Path::GetExtension(artifact.get_Key()).ToLowerInvariant();
            auto isXaml = extension == u".xaml";
            auto isImage = extension == u".png" || extension == u".jpg" || extension == u".jpeg" || extension == u".gif" || extension == u".bmp" || extension == u".tif" || extension == u".tiff" || extension == u".svg";
            String kind = isXaml ? u"slide XAML" : isImage ? u"image" : u"supporting resource";
            Console::WriteLine(u"{0}: {1} bytes ({2})", artifact.get_Key(), artifact.get_Value()->get_Length(), kind);

            // 仅在需要文本检查时解码 XAML。
            if (isXaml && inspectXamlText)
            {
                auto markup = Encoding::get_UTF8()->GetString(artifact.get_Value());
                Console::WriteLine(markup);
            }
        }
    }
};

在您的应用程序中调用 InMemoryXamlExample::Run。扩展检查对于检查很有帮助;保留所有工件,包括不熟悉的资源类型。存储或传输时保持字节不变。仅在需要对 XAML 进行文本处理时,使用 UTF-8 编码的 Encoding::GetString

将收集的工件打包为 ZIP 存档

此独立示例收集导出内容,验证其名称,并将原始字节写入 ZIP 存档。唯一的存档名称用于区分并发的导出作业。ZIP 条目使用正斜杠并保留相对目录。危险名称或规范化后冲突的名称会在写入之前拒绝整个包。

#include <DOM/Presentation.h>
#include <Export/Xaml/IXamlOutputSaver.h>
#include <Export/Xaml/XamlOptions.h>
#include <system/array.h>
#include <system/collections/dictionary.h>
#include <system/console.h>
#include <system/string_comparer.h>
#include <system/guid.h>
#include <system/io/file_access.h>
#include <system/io/file_mode.h>
#include <system/io/file_stream.h>
#include <system/io/path.h>
#include <zip/zip_file.h>

using namespace Aspose::Slides;
using namespace Aspose::Slides::Export::Xaml;
using namespace System;
using namespace System::Collections::Generic;
using namespace System::IO;
using namespace Aspose::Zip;

class ZipXamlExample
{
    class CollectedXamlSaver : public IXamlOutputSaver
    {
    public:
        using ArtifactDictionary = Dictionary<String, ArrayPtr<uint8_t>>;
        SharedPtr<ArtifactDictionary> Artifacts = MakeObject<ArtifactDictionary>(StringComparer::get_Ordinal());

        void Save(String path, ArrayPtr<uint8_t> data) override
        {
            auto retainedData = data->Clone();
            Artifacts->Add(path, retainedData);
        }
    };

public:
    static void Run()
    {
        auto saver = MakeObject<CollectedXamlSaver>();
        auto presentation = MakeObject<Presentation>(u"pres.pptx");
        auto options = MakeObject<XamlOptions>();
        options->set_OutputSaver(saver);
        options->set_ExportHiddenSlides(false);
        presentation->Save(options);

        auto entries = MakeObject<Dictionary<String, ArrayPtr<uint8_t>>>(StringComparer::get_OrdinalIgnoreCase());
        for (const auto& artifact : saver->Artifacts)
        {
            auto entryName = artifact.get_Key().Replace(u'\\', u'/');
            auto segments = entryName.Split(u'/');
            auto unsafeName = entryName.StartsWith(u"/", StringComparison::Ordinal) || entryName.Contains(u":");
            for (const auto& segment : segments)
            {
                unsafeName |= String::IsNullOrWhiteSpace(segment) || segment == u"." || segment == u"..";
            }

            if (unsafeName || entries->ContainsKey(entryName))
            {
                Console::WriteLine(u"Export rejected: unsafe or duplicate artifact name: {0}", artifact.get_Key());
                return;
            }
            entries->Add(entryName, artifact.get_Value());
        }

        auto jobId = Guid::NewGuid();
        auto archivePath = u"xaml-" + jobId.ToString(u"N") + u".zip";
        auto archive = MakeObject<ZipFile>();
        for (const auto& artifact : entries)
        {
            auto fileName = Path::GetFileName(artifact.get_Key());
            auto directoryName = Path::GetDirectoryName(artifact.get_Key()).Replace(u'\\', u'/');
            archive->AddEntry(fileName, directoryName, artifact.get_Value());
        }

        auto output = MakeObject<FileStream>(archivePath, FileMode::CreateNew, FileAccess::Write);
        archive->Save(output);
        output->Close();
        archive->Dispose();

        // 保存会完成 ZIP 目录的写入;在报告成功之前关闭文件。
        Console::WriteLine(u"Saved {0} artifacts to {1}", entries->get_Count(), archivePath);
    }
};

在您的应用程序中调用 ZipXamlExample::Run。示例使用 C++ 运行时的 Aspose::Zip::ZipFile 写入本地存档;导出器本身不写入松散的 XAML 或图像文件。对于远程存储,请将写入存档的阶段替换为对收集的字节数组的上传。可以使用导出作业标识符加完整相对工件名称作为 Blob 键,或将作业标识符、相对名称和二进制数据存储在数据库行中。仅在所有上传完成或数据库事务提交后才发布作业。如果持久化失败,清理部分输出。

对于大型演示文稿,自定义保存器可以将每个工件直接持久化到应用存储,从而避免在应用内存中保留整个导出的额外副本。导出器仍会在调用保存器之前在内存中收集所有生成的工件。请确保每个回调从导出器的角度是同步的:仅在目标接受字节后返回,并让失败传递给调用方。

保留资源名称并验证引用

  • 当目标需要时归一化路径分隔符,但保留相对目录。除非已知每个生成的名称都是唯一的且资源引用保持有效,否则不要仅使用 Path::GetFileName
  • 应用目标特定的名称验证。写入松散文件时,拒绝根路径和遍历段,使用 Path::GetFullPath 解析目标,并验证其保持在预期的导出目录之下,包括在包含检查中使用目录分隔符。使用没有可能重定向写入的符号链接的应用程序受控目录。
  • 为每个导出作业使用单独的保存器和存储命名空间。根据分隔符归一化后以及目标的大小写敏感规则检测冲突。
  • 在发布之前,将每个 XAML 文档解析为 XML,并检查其基于文件的资源引用,如图像的 SourceImageSource 属性。根据包含该 XAML 工件的目录解析每个相对 URI,归一化得到的存储名称,并确认对应的字典键、ZIP 条目或存储对象是否存在。将外部 URI 和 XAML 标记表达式与相对文件名分开处理。

例如,如果 pres/Slide_1.xaml 引用了 images/image1.png,则存储的资源必须以 pres/images/image1.png 的形式存在。仅保留 image1.png 会破坏该关系。对于对象存储,需在作业前缀下保留相同的布局,并使这些资源 URL 对 XAML 使用者可访问。重新打开已完成的 ZIP,验证条目名称和资源字节,并在目标 XAML 环境中加载代表性幻灯片,以确认图像能够正确解析。

常见问题

如果原始字体在机器上不可用,如何确保可预测的字体?

XamlOptions 中使用 set_DefaultRegularFont — 当原始字体缺失时,它将在导出期间作为回退字体使用。这并不保证生成的 XAML 会引用回退字体,或该字体在目标机器上可用。请确保 XAML 引用的字体在显示环境中可用。

导出的 XAML 仅面向 WPF,还是也可以用于其他 XAML 堆栈?

Aspose.Slides 通过其公共 API 导出 WPF XAML。对其他 XAML 堆栈(如 UWP 和 Xamarin.Forms)的兼容性不作保证。请在目标环境中测试生成的标记。

是否支持隐藏幻灯片,如何防止它们默认被导出?

默认情况下,隐藏幻灯片不会被包含。您可以通过 XamlOptions 中的 set_ExportHiddenSlides 来控制此行为——如果不需要导出隐藏幻灯片,请保持其禁用状态。