在 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.xaml、pres/Slide_2.xaml,依此類推。即使您傳入絕對路徑的輸入簡報,輸出資料夾仍相對於目前工作目錄建立,而非與輸入檔案同階層。
使用自訂選項將簡報匯出為 XAML
使用 IXamlOptions 介面可控制 Aspose.Slides 如何將簡報匯出為 XAML。
若要將輸出儲存至自訂位置,請實作 IXamlOutputSaver 並將您的實作實例傳遞給 XamlOptions 的 set_OutputSaver 方法。
若要在 XAML 輸出中包含隱藏投影片,請將 true 傳遞給 set_ExportHiddenSlides 方法,範例如下 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 選項的 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();
// Save 會完成 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 解析,檢查其基於檔案的資源參照,如影像的
Source或ImageSource屬性。將每個相對 URI 以其所在 XAML 檔案的目錄為基礎解析,正規化得到的儲存名稱,並確認相應的字典鍵、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 進行控制;若不需要匯出隱藏投影片,請保持此選項為停用狀態。