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

Add Linq-to-XML example #1101

Merged
merged 2 commits into from
Dec 16, 2021
Merged
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
14 changes: 12 additions & 2 deletions Open-XML-SDK.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31612.314
# Visual Studio Version 16
VisualStudioVersion = 16.0.31911.196
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0B53C729-D408-450D-B755-7A57184CDE93}"
ProjectSection(SolutionItems) = preProject
Expand Down Expand Up @@ -61,6 +61,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DocumentFormat.OpenXml.Gene
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DocumentFormat.OpenXml.Generator.Tests", "test\DocumentFormat.OpenXml.Generator.Tests\DocumentFormat.OpenXml.Generator.Tests.csproj", "{BAC36A8F-AA8E-4097-B2BA-2EFE559BEE62}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Linq", "Linq", "{C5AE39F0-A152-471A-B90E-B8F4E94AA6C2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SvgExample", "samples\Linq\SvgExample\SvgExample.csproj", "{DE06FC2B-EABB-4535-BC73-C51A32B5AC01}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DocumentFormat.OpenXml.Generator.Linq", "gen\DocumentFormat.OpenXml.Generator.Linq\DocumentFormat.OpenXml.Generator.Linq.csproj", "{A682CF61-B4D5-473B-AFB7-4DC87EC27C5B}"
EndProject
Global
Expand Down Expand Up @@ -125,6 +129,10 @@ Global
{BAC36A8F-AA8E-4097-B2BA-2EFE559BEE62}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BAC36A8F-AA8E-4097-B2BA-2EFE559BEE62}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BAC36A8F-AA8E-4097-B2BA-2EFE559BEE62}.Release|Any CPU.Build.0 = Release|Any CPU
{DE06FC2B-EABB-4535-BC73-C51A32B5AC01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DE06FC2B-EABB-4535-BC73-C51A32B5AC01}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DE06FC2B-EABB-4535-BC73-C51A32B5AC01}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DE06FC2B-EABB-4535-BC73-C51A32B5AC01}.Release|Any CPU.Build.0 = Release|Any CPU
{A682CF61-B4D5-473B-AFB7-4DC87EC27C5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A682CF61-B4D5-473B-AFB7-4DC87EC27C5B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A682CF61-B4D5-473B-AFB7-4DC87EC27C5B}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand All @@ -148,6 +156,8 @@ Global
{3B82C340-3B10-42BF-9A2C-2E2327A9CFD4} = {0782A132-968D-4BDD-911A-2C3074EAF886}
{E04F2FB4-DBC6-46DC-857C-F0E89B42F385} = {3653266D-2C88-4487-8977-839CB3E78A0A}
{BAC36A8F-AA8E-4097-B2BA-2EFE559BEE62} = {0782A132-968D-4BDD-911A-2C3074EAF886}
{C5AE39F0-A152-471A-B90E-B8F4E94AA6C2} = {7DAF7304-40CC-4180-88A5-9A89DD13C565}
{DE06FC2B-EABB-4535-BC73-C51A32B5AC01} = {C5AE39F0-A152-471A-B90E-B8F4E94AA6C2}
{A682CF61-B4D5-473B-AFB7-4DC87EC27C5B} = {3653266D-2C88-4487-8977-839CB3E78A0A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
Expand Down
201 changes: 201 additions & 0 deletions samples/Linq/SvgExample/Content/Dinosaur.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added samples/Linq/SvgExample/Content/EmptySlide.pptx
Binary file not shown.
38 changes: 38 additions & 0 deletions samples/Linq/SvgExample/GeneralTools.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using DocumentFormat.OpenXml.Packaging;
using Svg;
using System;
using System.Drawing.Imaging;
using System.IO;

namespace SvgExample
{
public static class GeneralTools
{
public static Stream ReadSvgAsPng(string svgPath)
{
var memoryStream = new MemoryStream();

SvgDocument svgDocument = SvgDocument.Open(svgPath);
using System.Drawing.Bitmap bitmap = svgDocument.Draw();
bitmap.Save(memoryStream, ImageFormat.Png);
memoryStream.Seek(0, SeekOrigin.Begin);

return memoryStream;
}

public static void AddImagePart(SlidePart slidePart, ImagePartType imagePartType, string id, Stream stream)
{
stream.Seek(0, SeekOrigin.Begin);
ImagePart imagePart = slidePart.AddImagePart(imagePartType, id);
imagePart.FeedData(stream);
}

public static string GetNewGuidString()
{
return string.Concat('{', Guid.NewGuid().ToString().ToUpperInvariant(), '}');
}
}
}
114 changes: 114 additions & 0 deletions samples/Linq/SvgExample/LinqToXmlTools.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using DocumentFormat.OpenXml.Linq;
using DocumentFormat.OpenXml.Packaging;
using System;
using System.IO;
using System.Linq;
using System.Xml.Linq;

#pragma warning disable SA1116 // Split parameters should start on line after declaration

namespace SvgExample
{
public static class LinqToXmlTools
{
public static void AddSvg(Stream stream, string svgPath, double percentageOfCy)
{
using PresentationDocument presentationDocument = PresentationDocument.Open(stream, true);
PresentationPart presentationPart = presentationDocument.PresentationPart ??
throw new InvalidOperationException(@"PresentationDocument is invalid.");

// Get relationship ID of first slide.
string sldRelId = presentationPart.GetXDocument()
.Elements(P.presentation)
.Elements(P.sldIdLst)
.Elements(P.sldId)
.Select(sldId => (string)sldId.Attribute(R.id)!)
.FirstOrDefault() ?? throw new InvalidOperationException(@"Presentation has no slides.");

// Get first slide's part.
var slidePart = (SlidePart)presentationPart.GetPartById(sldRelId);

// Determine unique relationship IDs for new image parts.
int partsCount = slidePart.Parts.Count();
string pngRelId = $"rId{++partsCount}";
string svgRelId = $"rId{++partsCount}";

// Add new image part with PNG image to slide part.
using Stream pngStream = GeneralTools.ReadSvgAsPng(svgPath);
GeneralTools.AddImagePart(slidePart, ImagePartType.Png, pngRelId, pngStream);

// Add new image part with SVG image to slide part.
using Stream svgStream = File.Open(svgPath, FileMode.Open, FileAccess.Read);
GeneralTools.AddImagePart(slidePart, ImagePartType.Svg, svgRelId, svgStream);

var picture =
new XElement(P.pic,
new XElement(P.nvPicPr,
new XElement(P.cNvPr,
new XAttribute(NoNamespace.id, ++partsCount),
new XAttribute(NoNamespace.name, "Picture 1"),
new XElement(A.extLst,
new XElement(A.ext,
new XAttribute(NoNamespace.uri, "{FF2B5EF4-FFF2-40B4-BE49-F238E27FC236}"),
new XElement(A16.creationId,
new XAttribute(XNamespace.Xmlns + "a16", A16.a16),
new XAttribute(NoNamespace.id, GeneralTools.GetNewGuidString()))))),
new XElement(P.cNvPicPr,
new XElement(A.picLocks,
new XAttribute(NoNamespace.noChangeAspect, "1"))),
new XElement(P.nvPr)),
new XElement(P.blipFill,
new XElement(A.blip,
new XAttribute(R.embed, pngRelId),
new XElement(A.extLst,
new XElement(A.ext,
new XAttribute(NoNamespace.uri, "{96DAC541-7B7A-43D3-8B79-37D633B846F1}"),
new XElement(ASVG.svgBlip,
new XAttribute(XNamespace.Xmlns + "asvg", ASVG.asvg),
new XAttribute(R.embed, svgRelId))))),
new XElement(A.stretch,
new XElement(A.fillRect))),
GetSpPr(presentationPart, percentageOfCy));

XElement spTree = slidePart.GetXDocument()
.Elements(P.sld)
.Elements(P.cSld)
.Elements(P.spTree)
.Single();

spTree.Add(picture);

slidePart.SaveXDocument();
}

private static XElement GetSpPr(PresentationPart part, double percentageOfCy)
{
XElement sldSz = part.GetXDocument()
.Elements(P.presentation)
.Elements(P.sldSz)
.Single();

var sldCx = (int)sldSz.Attribute(NoNamespace.cx)!;
var sldCy = (int)sldSz.Attribute(NoNamespace.cy)!;

var extentSize = (long)(sldCy * percentageOfCy);
long offsetX = (sldCx / 2) - (extentSize / 2);
long offsetY = (sldCy / 2) - (extentSize / 2);

return new XElement(P.spPr,
new XElement(A.xfrm,
new XElement(A.off,
new XAttribute(NoNamespace.x, offsetX),
new XAttribute(NoNamespace.y, offsetY)),
new XElement(A.ext,
new XAttribute(NoNamespace.cx, extentSize),
new XAttribute(NoNamespace.cy, extentSize))),
new XElement(A.prstGeom,
new XAttribute(NoNamespace.prst, "rect"),
new XElement(A.avLst)));
}
}
}
34 changes: 34 additions & 0 deletions samples/Linq/SvgExample/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.IO;

namespace SvgExample
{
public class Program
{
// Template slide and sample image
private const string EmptySlidePath = "Content\\EmptySlide.pptx";
private const string SvgImagePath = "Content\\Dinosaur.svg";

// Output paths
private const string LinqToXmlOutputPath = "LinqToXmlSlide.pptx";
private const string StronglyTypedOutputPath = "StronglyTypedSlide.pptx";

// Image scale as a percentage of the slide height
private const double PercentageOfCy = 0.5;

public static void Main()
{
// Add an SVG image, using the Linq-to-XML way.
File.Copy(EmptySlidePath, LinqToXmlOutputPath, true);
using FileStream linqToXmlStream = File.Open(LinqToXmlOutputPath, FileMode.Open, FileAccess.ReadWrite);
LinqToXmlTools.AddSvg(linqToXmlStream, SvgImagePath, PercentageOfCy);

// Add an SVG image, using the strongly typed way.
File.Copy(EmptySlidePath, StronglyTypedOutputPath, true);
using FileStream stronglyTypedStream = File.Open(StronglyTypedOutputPath, FileMode.Open, FileAccess.ReadWrite);
StronglyTypedTools.AddSvg(stronglyTypedStream, SvgImagePath, PercentageOfCy);
}
}
}
138 changes: 138 additions & 0 deletions samples/Linq/SvgExample/StronglyTypedTools.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Office2016.Drawing;
using DocumentFormat.OpenXml.Office2019.Drawing.SVG;
using DocumentFormat.OpenXml.Packaging;
using System;
using System.IO;
using System.Linq;

using Drawing = DocumentFormat.OpenXml.Drawing;
using Presentation = DocumentFormat.OpenXml.Presentation;

namespace SvgExample
{
public static class StronglyTypedTools
{
public static void AddSvg(Stream stream, string svgPath, double percentageOfCy)
{
using PresentationDocument presentationDocument = PresentationDocument.Open(stream, true);
PresentationPart presentationPart = presentationDocument.PresentationPart ??
throw new InvalidOperationException(@"PresentationDocument is invalid.");

// Get relationship ID of first slide.
string sldRelId = presentationPart
.Presentation
.SlideIdList?
.Elements<Presentation.SlideId>()
.Select(slideId => (string)slideId.RelationshipId!)
.FirstOrDefault() ?? throw new InvalidOperationException(@"Presentation has no slides.");

// Get first slide's part.
var slidePart = (SlidePart)presentationPart.GetPartById(sldRelId);

// Determine unique relationship IDs for new image parts.
int partsCount = slidePart.Parts.Count();
string pngRelId = $"rId{++partsCount}";
string svgRelId = $"rId{++partsCount}";

// Add new image part with PNG image to slide part.
using Stream pngStream = GeneralTools.ReadSvgAsPng(svgPath);
GeneralTools.AddImagePart(slidePart, ImagePartType.Png, pngRelId, pngStream);

// Add new image part with SVG image to slide part.
using Stream svgStream = File.Open(svgPath, FileMode.Open, FileAccess.Read);
GeneralTools.AddImagePart(slidePart, ImagePartType.Svg, svgRelId, svgStream);

// Create markup.
var picture =
new Presentation.Picture(
new Presentation.NonVisualPictureProperties(
new Presentation.NonVisualDrawingProperties(
new Drawing.NonVisualDrawingPropertiesExtensionList(
new Drawing.Extension(
new CreationId
{
Id = GeneralTools.GetNewGuidString(),
}.WithNamespaceDeclaration("a16", "http://schemas.microsoft.com/office/drawing/2014/main"))
{
Uri = "{FF2B5EF4-FFF2-40B4-BE49-F238E27FC236}",
}))
{
Id = (uint)++partsCount,
Name = "Picture 1",
},
new Presentation.NonVisualPictureDrawingProperties(
new Drawing.PictureLocks
{
NoChangeAspect = true,
}),
new Presentation.ApplicationNonVisualDrawingProperties()),
new Presentation.BlipFill(
new Drawing.Blip(
new Drawing.BlipExtensionList(
new Drawing.Extension(
new SVGBlip
{
Embed = svgRelId,
})
{
Uri = "{96DAC541-7B7A-43D3-8B79-37D633B846F1}",
}))
{
Embed = pngRelId,
},
new Drawing.Stretch(
new Drawing.FillRectangle())),
GetShapeProperties(presentationPart, percentageOfCy));

Presentation.ShapeTree shapeTree = slidePart
.Slide
.CommonSlideData?
.ShapeTree ?? throw new InvalidOperationException();

shapeTree.AppendChild(picture);
}

public static TOpenXmlElement WithNamespaceDeclaration<TOpenXmlElement>(
this TOpenXmlElement element,
string prefix,
string uri)
where TOpenXmlElement : OpenXmlElement
{
element.AddNamespaceDeclaration(prefix, uri);
return element;
}

private static Presentation.ShapeProperties GetShapeProperties(PresentationPart part, double percentageOfCy)
{
Presentation.SlideSize slideSize = part.Presentation.SlideSize!;
var slideCx = (int)slideSize.Cx!;
var slideCy = (int)slideSize.Cy!;

var extentSize = (long)(slideCy * percentageOfCy);
long offsetX = (slideCx / 2) - (extentSize / 2);
long offsetY = (slideCy / 2) - (extentSize / 2);

return new Presentation.ShapeProperties(
new Drawing.Transform2D(
new Drawing.Offset
{
X = offsetX,
Y = offsetY,
},
new Drawing.Extents
{
Cx = extentSize,
Cy = extentSize,
}),
new Drawing.PresetGeometry(
new Drawing.AdjustValueList())
{
Preset = Drawing.ShapeTypeValues.Rectangle,
});
}
}
}
Loading