Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: public API for fluent presentation builder #82

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ _ReSharper.Caches/

.fake/
.paket/
AssemblyInfo.Generated.cs
AssemblyInfo.g.cs
TestFiles/PublishSlides/output/

.ionide/
temp/
temp/
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## [3.0.0-beta02] - January 8, 2025

- Public exposure of `IFluentPresentationBuilder` ergonomic API for the code behind `PresentationBuilder`

## [2.4.1] - January 4, 2025

- fix: Bug fix/document assembler paragraph properties not copied from template #91
Expand Down
2 changes: 0 additions & 2 deletions Clippit.Tests/Clippit.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
</PropertyGroup>
<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
using System.Xml.Linq;
using Clippit.PowerPoint;
using Clippit.PowerPoint.Fluent;
using DocumentFormat.OpenXml.Packaging;
using Xunit;

namespace Clippit.Tests.PowerPoint;

public partial class PresentationBuilderSlidePublishingTests
{
[Theory]
[ClassData(typeof(PublishingTestData))]
public async Task PublishUsingMemDocs(string sourcePath)
{
var fileName = Path.GetFileNameWithoutExtension(sourcePath);
var targetDir = Path.Combine(TargetDirectory, fileName);
if (Directory.Exists(targetDir))
Directory.Delete(targetDir, true);
Directory.CreateDirectory(targetDir);

await using var srcStream = File.Open(sourcePath, FileMode.Open);
var openSettings = new OpenSettings { AutoSave = false };
using var srcDoc = OpenXmlExtensions.OpenPresentation(srcStream, false, openSettings);
ArgumentNullException.ThrowIfNull(srcDoc.PresentationPart);

var slideNumber = 0;
var slidesIds = PresentationBuilderTools.GetSlideIdsInOrder(srcDoc);
foreach (var slideId in slidesIds)
{
var srcSlidePart = (SlidePart)srcDoc.PresentationPart.GetPartById(slideId);
var title = PresentationBuilderTools.GetSlideTitle(srcSlidePart.GetXElement());

using var stream = new MemoryStream();
using (var newDocument = PresentationBuilder.NewDocument(stream))
{
using (var builder = PresentationBuilder.Create(newDocument))
{
var newSlidePart = builder.AddSlidePart(srcSlidePart);

// Remove the show attribute from the slide element (if it exists)
var slideDocument = newSlidePart.GetXDocument();
slideDocument.Root?.Attribute(NoNamespace.show)?.Remove();
}

// Set the title of the new presentation to the title of the slide
newDocument.PackageProperties.Title = title;
}

var slideFileName = string.Concat(fileName, $"_{++slideNumber:000}.pptx");
await using var fs = File.Create(Path.Combine(targetDir, slideFileName));
stream.Position = 0;
await stream.CopyToAsync(fs, TestContext.Current.CancellationToken);

srcSlidePart.RemoveAnnotations<XDocument>();
srcSlidePart.UnloadRootElement();
}

Log.WriteLine($"GC Total Memory: {GC.GetTotalMemory(false) / 1024 / 1024} MB");
}

[Theory]
[ClassData(typeof(PublishingTestData))]
public async Task MergeAllPowerPointBack(string sourcePath)
{
var fileName = Path.GetFileNameWithoutExtension(sourcePath);
var targetDir = Path.Combine(TargetDirectory, fileName);
if (!Directory.Exists(targetDir))
Assert.Skip("Directory not found: " + targetDir);

var slides = Directory.GetFiles(targetDir, "*.pptx", SearchOption.TopDirectoryOnly);
if (slides.Length < 1)
Assert.Skip("Not enough slides to merge.");
Array.Sort(slides);

// Create a memory stream from the original presentation
using var ms = new MemoryStream();
await using (var fs = File.OpenRead(sourcePath))
await fs.CopyToAsync(ms, TestContext.Current.CancellationToken);

// Use the first slide as the base document
var setting = new OpenSettings { AutoSave = false };
using (var baseDoc = PresentationDocument.Open(ms, true, setting))
{
ArgumentNullException.ThrowIfNull(baseDoc.PresentationPart);

// Merge the remaining slides into the base document (one builder instance)
using (var builder = PresentationBuilder.Create(baseDoc))
{
foreach (var path in slides)
{
using var doc = PresentationDocument.Open(path, false, setting);
ArgumentNullException.ThrowIfNull(doc.PresentationPart);

// Add all slides in the correct order
foreach (var slidePath in PresentationBuilderTools.GetSlideIdsInOrder(doc))
{
var slidePart = (SlidePart)doc.PresentationPart.GetPartById(slidePath);
builder.AddSlidePart(slidePart);
}
}
}

baseDoc.PackageProperties.Title = $"{fileName} - Merged Deck X2";
}

// Save the merged document to a file
var resultFile = Path.Combine(TargetDirectory, $"{fileName}_MergedDeckX2.pptx");
ms.Position = 0;
await using var resFile = File.Create(resultFile);
await ms.CopyToAsync(resFile, TestContext.Current.CancellationToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Clippit.Tests.PowerPoint
{
public class PresentationBuilderSlidePublishingTests : TestsBase
public partial class PresentationBuilderSlidePublishingTests : TestsBase
{
private const string SourceDirectory = "../../../../TestFiles/PublishSlides/";
private const string TargetDirectory = "../../../../TestFiles/PublishSlides/output";
Expand Down Expand Up @@ -79,6 +79,7 @@ public void ExtractSlideWithExtendedChart()
using var srcStream = File.Open(sourcePath, FileMode.Open);
var openSettings = new OpenSettings { AutoSave = false };
using var srcDoc = OpenXmlExtensions.OpenPresentation(srcStream, false, openSettings);
ArgumentNullException.ThrowIfNull(srcDoc.PresentationPart);

var srcEmbeddingCount = srcDoc
.PresentationPart.SlideParts.SelectMany(slide => slide.ExtendedChartParts)
Expand All @@ -88,6 +89,7 @@ public void ExtractSlideWithExtendedChart()
var slide = PresentationBuilder.PublishSlides(srcDoc, Path.GetFileName(sourcePath)).First();
using var streamDoc = new OpenXmlMemoryStreamDocument(slide);
using var slideDoc = streamDoc.GetPresentationDocument(openSettings);
ArgumentNullException.ThrowIfNull(slideDoc.PresentationPart);

var slideEmbeddingCount = slideDoc
.PresentationPart.SlideParts.Select(slide => slide.ExtendedChartParts)
Expand Down Expand Up @@ -133,6 +135,7 @@ public void ExtractMasters(string fileName)

using var streamDoc = new OpenXmlMemoryStreamDocument(onlyMaster);
using var resDoc = streamDoc.GetPresentationDocument();
ArgumentNullException.ThrowIfNull(resDoc.PresentationPart);

Assert.Empty(resDoc.PresentationPart.SlideParts);
Assert.InRange(resDoc.PresentationPart.SlideMasterParts.Count(), 1, numberOfMasters);
Expand Down
3 changes: 2 additions & 1 deletion Clippit/Clippit.csproj.DotSettings
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=comparer/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=comparer/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=powerpoint_005Cfluent/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
Loading
Loading