From c54f9029f14bd585129a6d8556035d8bfce6782e Mon Sep 17 00:00:00 2001 From: Sergey Tihon Date: Tue, 1 Oct 2024 19:02:20 +0200 Subject: [PATCH 1/7] hk: cleanup and refactoring with roslyn --- ...PresentationBuilderSlidePublishingTests.cs | 24 ++-- .../PowerPoint/PresentationBuilderTests.cs | 5 +- Clippit.Tests/PowerPoint/PtUtilTests.cs | 6 +- .../PowerPoint/FluentPresentationBuilder.cs | 108 +++++++----------- Clippit/PowerPoint/PresentationBuilder.cs | 13 +-- 5 files changed, 62 insertions(+), 94 deletions(-) diff --git a/Clippit.Tests/PowerPoint/PresentationBuilderSlidePublishingTests.cs b/Clippit.Tests/PowerPoint/PresentationBuilderSlidePublishingTests.cs index 78c4dd28..e62cef0f 100644 --- a/Clippit.Tests/PowerPoint/PresentationBuilderSlidePublishingTests.cs +++ b/Clippit.Tests/PowerPoint/PresentationBuilderSlidePublishingTests.cs @@ -10,13 +10,17 @@ namespace Clippit.Tests.PowerPoint { public class PresentationBuilderSlidePublishingTests : TestsBase { - public static string SourceDirectory = "../../../../TestFiles/PublishSlides/"; - public static string TargetDirectory = "../../../../TestFiles/PublishSlides/output"; + private const string SourceDirectory = "../../../../TestFiles/PublishSlides/"; + private const string TargetDirectory = "../../../../TestFiles/PublishSlides/output"; - public static IEnumerable GetData() + private class PublishingTestData : TheoryData { - var files = Directory.GetFiles(SourceDirectory, "*.pptx", SearchOption.TopDirectoryOnly); - return files.OrderBy(x => x).Select(path => new[] { path }); + public PublishingTestData() + { + var files = Directory.GetFiles(SourceDirectory, "*.pptx", SearchOption.TopDirectoryOnly); + foreach (var file in files.OrderBy(x => x)) + Add(file); + } } public PresentationBuilderSlidePublishingTests(ITestOutputHelper log) @@ -27,7 +31,7 @@ public PresentationBuilderSlidePublishingTests(ITestOutputHelper log) } [Theory] - [MemberData(nameof(GetData))] + [ClassData(typeof(PublishingTestData))] public void PublishUsingPublishSlides(string sourcePath) { var targetDir = Path.Combine(TargetDirectory, Path.GetFileNameWithoutExtension(sourcePath)); @@ -66,7 +70,7 @@ public void ExtractOneSlide(string fileName, int slideNumber) var document = new PmlDocument(file); var source = new SlideSource(document, slideNumber - 1, 1, true); - var slide = PresentationBuilder.BuildPresentation(new List { source }); + var slide = PresentationBuilder.BuildPresentation([source]); slide.FileName = document.FileName.Replace(".pptx", $"_{slideNumber:000}.pptx"); slide.SaveAs(Path.Combine(TargetDirectory, Path.GetFileName(slide.FileName))); @@ -126,7 +130,7 @@ public void ExtractMasters(string fileName) numberOfMasters = doc1.PresentationPart.SlideMasterParts.Count(); } - var onlyMaster = PresentationBuilder.BuildPresentation(new List { new(source, 0, 0, true) }); + var onlyMaster = PresentationBuilder.BuildPresentation([new(source, 0, 0, true)]); onlyMaster.FileName = fileName.Replace(".pptx", "_masterOnly.pptx"); onlyMaster.SaveAs(Path.Combine(TargetDirectory, onlyMaster.FileName)); @@ -146,9 +150,7 @@ public void ReassemblePresentationWithMaster(string fileName) var presentation = new PmlDocument(file); // generate presentation with all masters - var onlyMaster = PresentationBuilder.BuildPresentation( - new List { new(presentation, 0, 0, true) } - ); + var onlyMaster = PresentationBuilder.BuildPresentation([new(presentation, 0, 0, true)]); // publish slides with one-layout masters var slides = PresentationBuilder.PublishSlides(presentation); diff --git a/Clippit.Tests/PowerPoint/PresentationBuilderTests.cs b/Clippit.Tests/PowerPoint/PresentationBuilderTests.cs index 093a805d..470960c6 100644 --- a/Clippit.Tests/PowerPoint/PresentationBuilderTests.cs +++ b/Clippit.Tests/PowerPoint/PresentationBuilderTests.cs @@ -13,11 +13,8 @@ namespace Clippit.Tests.PowerPoint { - public class PresentationBuilderTests : TestsBase + public class PresentationBuilderTests(ITestOutputHelper log) : TestsBase(log) { - public PresentationBuilderTests(ITestOutputHelper log) - : base(log) { } - [Fact] public void PB001_Formatting() { diff --git a/Clippit.Tests/PowerPoint/PtUtilTests.cs b/Clippit.Tests/PowerPoint/PtUtilTests.cs index 07cccf8b..313c961e 100644 --- a/Clippit.Tests/PowerPoint/PtUtilTests.cs +++ b/Clippit.Tests/PowerPoint/PtUtilTests.cs @@ -18,9 +18,9 @@ public void PU001(string name) var sourceMht = new FileInfo(Path.Combine(sourceDir.FullName, name)); var src = File.ReadAllText(sourceMht.FullName); var p = MhtParser.Parse(src); - Assert.True(p.ContentType != null); - Assert.True(p.MimeVersion != null); - Assert.True(p.Parts.Length != 0); + Assert.NotNull(p.ContentType); + Assert.NotNull(p.MimeVersion); + Assert.NotEmpty(p.Parts); Assert.DoesNotContain(p.Parts, part => part.ContentType == null || part.ContentLocation == null); } } diff --git a/Clippit/PowerPoint/FluentPresentationBuilder.cs b/Clippit/PowerPoint/FluentPresentationBuilder.cs index 1ced4e56..a5a3ba69 100644 --- a/Clippit/PowerPoint/FluentPresentationBuilder.cs +++ b/Clippit/PowerPoint/FluentPresentationBuilder.cs @@ -20,8 +20,8 @@ internal sealed class FluentPresentationBuilder : IDisposable private SlideSize _slideSize; private bool _isDocumentInitialized; - private readonly List _mediaCache = new(); - private readonly List _slideMasterList = new(); + private readonly List _mediaCache = []; + private readonly List _slideMasterList = []; internal FluentPresentationBuilder(PresentationDocument presentationDocument) { @@ -182,16 +182,12 @@ private void CopyPresentationParts(PresentationDocument sourceDocument) // Copy theme for master var newThemePart = newMaster.AddNewPart(); newThemePart.PutXDocument(new XDocument(oldMaster.ThemePart.GetXDocument())); - CopyRelatedPartsForContentParts( - oldMaster.ThemePart, - newThemePart, - new[] { newThemePart.GetXDocument().Root } - ); + CopyRelatedPartsForContentParts(oldMaster.ThemePart, newThemePart, [newThemePart.GetXDocument().Root]); // Copy master newMaster.PutXDocument(new XDocument(oldMaster.GetXDocument())); - PBT.AddRelationships(oldMaster, newMaster, new[] { newMaster.GetXDocument().Root }); - CopyRelatedPartsForContentParts(oldMaster, newMaster, new[] { newMaster.GetXDocument().Root }); + PBT.AddRelationships(oldMaster, newMaster, [newMaster.GetXDocument().Root]); + CopyRelatedPartsForContentParts(oldMaster, newMaster, [newMaster.GetXDocument().Root]); newPresentation.Root.Add( new XElement( @@ -242,9 +238,7 @@ var legacyDocTextInfo in sourceDocument.PresentationPart.Parts.Where(p => rc.Remove(); newPresentation.Root.Add( listOfRootChildren.OrderBy(e => - PresentationBuilderTools.s_orderPresentation.ContainsKey(e.Name) - ? PresentationBuilderTools.s_orderPresentation[e.Name] - : 999 + PBT.s_orderPresentation.TryGetValue(e.Name, out var value) ? value : 999 ) ); } @@ -258,7 +252,7 @@ var legacyDocTextInfo in sourceDocument.PresentationPart.Parts.Where(p => /// /// List of all from source presentation.xml /// Modified copy of all elements - private IEnumerable SanitizeExtLst(IEnumerable extLstList) + private static IEnumerable SanitizeExtLst(IEnumerable extLstList) { foreach (var srcExtLst in extLstList) { @@ -282,7 +276,7 @@ private XElement CreateEmbeddedFontPart(PresentationDocument sourceDocument, XEl var oldFontPartId = (string)font.Element(fontXName).Attributes(R.id).FirstOrDefault(); if (!sourceDocument.PresentationPart.TryGetPartById(oldFontPartId, out var oldFontPart)) return null; - if (!(oldFontPart is FontPart)) + if (oldFontPart is not FontPart) throw new FormatException($"Part {oldFontPartId} is not {nameof(FontPart)}"); var fontPartType = oldFontPart.ContentType switch @@ -341,7 +335,7 @@ internal void AppendSlides(PresentationDocument sourceDocument, int start, int c uint newId = 256; var ids = newPresentation.Root.Descendants(P.sldId).Select(f => (uint)f.Attribute(NoNamespace.id)).ToList(); - if (ids.Any()) + if (ids.Count != 0) newId = ids.Max() + 1; var slideList = sourceDocument.PresentationPart.GetXDocument().Root.Descendants(P.sldId).ToList(); @@ -364,8 +358,8 @@ internal void AppendSlides(PresentationDocument sourceDocument, int start, int c SlideLayoutData.ScaleShapes(slideDocument, scaleFactor); - PBT.AddRelationships(slide, newSlide, new[] { newSlide.GetXDocument().Root }); - CopyRelatedPartsForContentParts(slide, newSlide, new[] { newSlide.GetXDocument().Root }); + PBT.AddRelationships(slide, newSlide, [newSlide.GetXDocument().Root]); + CopyRelatedPartsForContentParts(slide, newSlide, [newSlide.GetXDocument().Root]); CopyTableStyles(sourceDocument, newSlide); if (slide.NotesSlidePart is { } notesSlide) @@ -377,12 +371,8 @@ internal void AppendSlides(PresentationDocument sourceDocument, int start, int c newPart.AddPart(newSlide); if (_newDocument.PresentationPart.NotesMasterPart is not null) newPart.AddPart(_newDocument.PresentationPart.NotesMasterPart); - PBT.AddRelationships(notesSlide, newPart, new[] { newPart.GetXDocument().Root }); - CopyRelatedPartsForContentParts( - slide.NotesSlidePart, - newPart, - new[] { newPart.GetXDocument().Root } - ); + PBT.AddRelationships(notesSlide, newPart, [newPart.GetXDocument().Root]); + CopyRelatedPartsForContentParts(slide.NotesSlidePart, newPart, [newPart.GetXDocument().Root]); } var slideLayoutData = ManageSlideLayoutPart(sourceDocument, slide.SlideLayoutPart, scaleFactor); @@ -435,17 +425,13 @@ private void CopyNotesMaster(PresentationDocument sourceDocument) { var newThemePart = newMaster.AddNewPart(); newThemePart.PutXDocument(new XDocument(themePart.GetXDocument())); - CopyRelatedPartsForContentParts( - themePart, - newThemePart, - new[] { newThemePart.GetXDocument().Root } - ); + CopyRelatedPartsForContentParts(themePart, newThemePart, [newThemePart.GetXDocument().Root]); } // Copy master newMaster.PutXDocument(new XDocument(oldMaster.GetXDocument())); - PBT.AddRelationships(oldMaster, newMaster, new[] { newMaster.GetXDocument().Root }); - CopyRelatedPartsForContentParts(oldMaster, newMaster, new[] { newMaster.GetXDocument().Root }); + PBT.AddRelationships(oldMaster, newMaster, [newMaster.GetXDocument().Root]); + CopyRelatedPartsForContentParts(oldMaster, newMaster, [newMaster.GetXDocument().Root]); newPresentation.Root.Add( new XElement( @@ -508,7 +494,7 @@ private XElement FindCommentsAuthor(XElement comment, XDocument oldAuthors) { uint newId = 0; var ids = authors.Root.Descendants(P.cmAuthor).Select(f => (uint)f.Attribute(NoNamespace.id)).ToList(); - if (ids.Any()) + if (ids.Count != 0) newId = ids.Max() + 1; newAuthor = new XElement( @@ -653,8 +639,8 @@ var diagramReference in newContent OpenXmlPart newPart = newContentPart.AddNewPart(); newPart.GetXDocument().Add(oldPart.GetXDocument().Root); diagramReference.Attribute(R.dm).Value = newContentPart.GetIdOfPart(newPart); - PBT.AddRelationships(oldPart, newPart, new[] { newPart.GetXDocument().Root }); - CopyRelatedPartsForContentParts(oldPart, newPart, new[] { newPart.GetXDocument().Root }); + PBT.AddRelationships(oldPart, newPart, [newPart.GetXDocument().Root]); + CopyRelatedPartsForContentParts(oldPart, newPart, [newPart.GetXDocument().Root]); // lo attribute relId = diagramReference.Attribute(R.lo).Value; @@ -665,8 +651,8 @@ var diagramReference in newContent newPart = newContentPart.AddNewPart(); newPart.GetXDocument().Add(oldPart.GetXDocument().Root); diagramReference.Attribute(R.lo).Value = newContentPart.GetIdOfPart(newPart); - PBT.AddRelationships(oldPart, newPart, new[] { newPart.GetXDocument().Root }); - CopyRelatedPartsForContentParts(oldPart, newPart, new[] { newPart.GetXDocument().Root }); + PBT.AddRelationships(oldPart, newPart, [newPart.GetXDocument().Root]); + CopyRelatedPartsForContentParts(oldPart, newPart, [newPart.GetXDocument().Root]); // qs attribute relId = diagramReference.Attribute(R.qs).Value; @@ -677,8 +663,8 @@ var diagramReference in newContent newPart = newContentPart.AddNewPart(); newPart.GetXDocument().Add(oldPart.GetXDocument().Root); diagramReference.Attribute(R.qs).Value = newContentPart.GetIdOfPart(newPart); - PBT.AddRelationships(oldPart, newPart, new[] { newPart.GetXDocument().Root }); - CopyRelatedPartsForContentParts(oldPart, newPart, new[] { newPart.GetXDocument().Root }); + PBT.AddRelationships(oldPart, newPart, [newPart.GetXDocument().Root]); + CopyRelatedPartsForContentParts(oldPart, newPart, [newPart.GetXDocument().Root]); // cs attribute relId = diagramReference.Attribute(R.cs).Value; @@ -689,8 +675,8 @@ var diagramReference in newContent newPart = newContentPart.AddNewPart(); newPart.GetXDocument().Add(oldPart.GetXDocument().Root); diagramReference.Attribute(R.cs).Value = newContentPart.GetIdOfPart(newPart); - PBT.AddRelationships(oldPart, newPart, new[] { newPart.GetXDocument().Root }); - CopyRelatedPartsForContentParts(oldPart, newPart, new[] { newPart.GetXDocument().Root }); + PBT.AddRelationships(oldPart, newPart, [newPart.GetXDocument().Root]); + CopyRelatedPartsForContentParts(oldPart, newPart, [newPart.GetXDocument().Root]); } foreach ( @@ -768,7 +754,7 @@ var oleReference in newContent newChart.Add(oldChart.Root); chartReference.Attribute(R.id).Value = newContentPart.GetIdOfPart(newPart); PBT.CopyChartObjects(oldPart, newPart); - CopyRelatedPartsForContentParts(oldPart, newPart, new[] { newChart.Root }); + CopyRelatedPartsForContentParts(oldPart, newPart, [newChart.Root]); } } @@ -787,7 +773,7 @@ var oleReference in newContent newChart.Add(oldChart.Root); chartReference.Attribute(R.id).Value = newContentPart.GetIdOfPart(newPart); PBT.CopyExtendedChartObjects(oldPart, newPart); - CopyRelatedPartsForContentParts(oldPart, newPart, new[] { newChart.Root }); + CopyRelatedPartsForContentParts(oldPart, newPart, [newChart.Root]); } } @@ -806,7 +792,7 @@ var oleReference in newContent newXDoc.Add(oldXDoc.Root); userShape.Attribute(R.id).Value = newContentPart.GetIdOfPart(newPart); PBT.AddRelationships(oldPart, newPart, newContent); - CopyRelatedPartsForContentParts(oldPart, newPart, new[] { newXDoc.Root }); + CopyRelatedPartsForContentParts(oldPart, newPart, [newXDoc.Root]); } } @@ -861,13 +847,7 @@ var itemProps in oldPartIdPair9.OpenXmlPart.Parts.Where(p => } foreach (var soundReference in newContent.DescendantsAndSelf().Where(d => d.Name == A.audioFile)) - PresentationBuilderTools.CopyRelatedSound( - _newDocument, - oldContentPart, - newContentPart, - soundReference, - R.link - ); + PBT.CopyRelatedSound(_newDocument, oldContentPart, newContentPart, soundReference, R.link); if ( (oldContentPart is ChartsheetPart && newContentPart is ChartsheetPart) @@ -894,13 +874,7 @@ var soundReference in newContent || d.Name == PAV.srcMedia ) ) - PresentationBuilderTools.CopyRelatedSound( - _newDocument, - oldContentPart, - newContentPart, - soundReference, - R.embed - ); + PBT.CopyRelatedSound(_newDocument, oldContentPart, newContentPart, soundReference, R.embed); var vmlDrawingParts = oldContentPart switch { @@ -953,12 +927,8 @@ var soundReference in newContent } newVmlPart.PutXDocument(xd); - PBT.AddRelationships(vmlPart, newVmlPart, new[] { newVmlPart.GetXDocument().Root }); - CopyRelatedPartsForContentParts( - vmlPart, - newVmlPart, - new[] { newVmlPart.GetXDocument().Root } - ); + PBT.AddRelationships(vmlPart, newVmlPart, [newVmlPart.GetXDocument().Root]); + CopyRelatedPartsForContentParts(vmlPart, newVmlPart, [newVmlPart.GetXDocument().Root]); } catch (XmlException) { @@ -1264,7 +1234,7 @@ private ThemePart CopyThemePart(SlideMasterPart slideMasterPart, ThemePart oldTh SlideLayoutData.ScaleShapes(newThemeDoc, scaleFactor); newThemePart.PutXDocument(newThemeDoc); - CopyRelatedPartsForContentParts(oldThemePart, newThemePart, new[] { newThemePart.GetXDocument().Root }); + CopyRelatedPartsForContentParts(oldThemePart, newThemePart, [newThemePart.GetXDocument().Root]); if (_newDocument.PresentationPart.ThemePart is null) newThemePart = _newDocument.PresentationPart.AddPart(newThemePart); @@ -1327,8 +1297,8 @@ private SlideMasterPart CopySlideMasterPart(SlideMasterPart oldMasterPart, doubl SlideLayoutData.ScaleShapes(newMasterDoc, scaleFactor); newMaster.PutXDocument(newMasterDoc); - PBT.AddRelationships(oldMasterPart, newMaster, new[] { newMaster.GetXDocument().Root }); - CopyRelatedPartsForContentParts(oldMasterPart, newMaster, new[] { newMaster.GetXDocument().Root }); + PBT.AddRelationships(oldMasterPart, newMaster, [newMaster.GetXDocument().Root]); + CopyRelatedPartsForContentParts(oldMasterPart, newMaster, [newMaster.GetXDocument().Root]); _ = CopyThemePart(newMaster, oldMasterPart.ThemePart, scaleFactor); @@ -1378,8 +1348,8 @@ double scaleFactor SlideLayoutData.ScaleShapes(newLayoutDoc, scaleFactor); newLayout.PutXDocument(newLayoutDoc); - PBT.AddRelationships(oldSlideLayoutPart, newLayout, new[] { newLayout.GetXDocument().Root }); - CopyRelatedPartsForContentParts(oldSlideLayoutPart, newLayout, new[] { newLayout.GetXDocument().Root }); + PBT.AddRelationships(oldSlideLayoutPart, newLayout, [newLayout.GetXDocument().Root]); + CopyRelatedPartsForContentParts(oldSlideLayoutPart, newLayout, [newLayout.GetXDocument().Root]); var newMasterDoc = newSlideMasterPart.GetXDocument(); newMasterDoc @@ -1404,7 +1374,7 @@ private uint GetNextFreeId() .Root.Descendants(P.sldMasterId) .Select(f => (uint)f.Attribute(NoNamespace.id)) .ToList(); - if (masterIds.Any()) + if (masterIds.Count != 0) newId = Math.Max(newId, masterIds.Max()); foreach (var slideMasterData in _slideMasterList) @@ -1414,7 +1384,7 @@ private uint GetNextFreeId() .Root.Descendants(P.sldLayoutId) .Select(f => (uint)f.Attribute(NoNamespace.id)) .ToList(); - if (layoutIds.Any()) + if (layoutIds.Count != 0) newId = Math.Max(newId, layoutIds.Max()); } diff --git a/Clippit/PowerPoint/PresentationBuilder.cs b/Clippit/PowerPoint/PresentationBuilder.cs index 2a33a4a9..6066b1d9 100644 --- a/Clippit/PowerPoint/PresentationBuilder.cs +++ b/Clippit/PowerPoint/PresentationBuilder.cs @@ -65,7 +65,7 @@ public SlideSource(string fileName, int start, int count, bool keepMaster) } } - public static class PresentationBuilder + public static partial class PresentationBuilder { public static PmlDocument BuildPresentation(List sources) { @@ -88,6 +88,7 @@ public static IList PublishSlides(PmlDocument src) public static IEnumerable PublishSlides(PresentationDocument srcDoc, string fileName) { var slidesCount = srcDoc.PresentationPart.GetXElement().Descendants(P.sldId).Count(); + var slideNameRegex = SlideNameRegex(); for (var slideNumber = 0; slideNumber < slidesCount; slideNumber++) { using var streamDoc = OpenXmlMemoryStreamDocument.CreatePresentationDocument(); @@ -106,12 +107,7 @@ public static IEnumerable PublishSlides(PresentationDocument srcDoc var slideDoc = streamDoc.GetModifiedPmlDocument(); if (!string.IsNullOrWhiteSpace(fileName)) { - slideDoc.FileName = Regex.Replace( - fileName, - ".pptx", - $"_{slideNumber + 1:000}.pptx", - RegexOptions.IgnoreCase - ); + slideDoc.FileName = slideNameRegex.Replace(fileName, $"_{slideNumber + 1:000}.pptx"); } yield return slideDoc; @@ -164,5 +160,8 @@ private static void BuildPresentation(List sources, PresentationDoc sourceNum++; } } + + [GeneratedRegex(".pptx", RegexOptions.IgnoreCase, "en-US")] + private static partial Regex SlideNameRegex(); } } From 02d4affc2d9dd49c7272b0fd85e09458181c0fa3 Mon Sep 17 00:00:00 2001 From: Sergey Tihon Date: Sat, 5 Oct 2024 18:55:56 +0200 Subject: [PATCH 2/7] apply more fixes --- .../PowerPoint/FluentPresentationBuilder.cs | 4 +-- .../PowerPoint/PresentationBuilderTools.cs | 34 +++++++------------ Clippit/PowerPoint/SlidePartData.cs | 31 ++++++----------- 3 files changed, 23 insertions(+), 46 deletions(-) diff --git a/Clippit/PowerPoint/FluentPresentationBuilder.cs b/Clippit/PowerPoint/FluentPresentationBuilder.cs index a5a3ba69..41981371 100644 --- a/Clippit/PowerPoint/FluentPresentationBuilder.cs +++ b/Clippit/PowerPoint/FluentPresentationBuilder.cs @@ -237,9 +237,7 @@ var legacyDocTextInfo in sourceDocument.PresentationPart.Parts.Where(p => foreach (var rc in listOfRootChildren) rc.Remove(); newPresentation.Root.Add( - listOfRootChildren.OrderBy(e => - PBT.s_orderPresentation.TryGetValue(e.Name, out var value) ? value : 999 - ) + listOfRootChildren.OrderBy(e => PBT.OrderPresentation.TryGetValue(e.Name, out var value) ? value : 999) ); } diff --git a/Clippit/PowerPoint/PresentationBuilderTools.cs b/Clippit/PowerPoint/PresentationBuilderTools.cs index c2aad53e..87daecd8 100644 --- a/Clippit/PowerPoint/PresentationBuilderTools.cs +++ b/Clippit/PowerPoint/PresentationBuilderTools.cs @@ -44,7 +44,7 @@ internal static string GetSlideTitle(XElement slide) return paragraphText.ToString().Trim(); } - internal static readonly Dictionary s_orderPresentation = + internal static readonly Dictionary OrderPresentation = new() { { P.sldMasterIdLst, 10 }, @@ -155,10 +155,9 @@ internal static void CopyChartObjects(ChartPart oldChart, ChartPart newChart) else { //ExternalRelationship oldRelationship = oldChart.GetExternalRelationship(relId); - var oldRel = oldChart.ExternalRelationships.FirstOrDefault(h => h.Id == relId); - if (oldRel is null) - throw new PresentationBuilderInternalException("Internal Error 0007"); - + var oldRel = + oldChart.ExternalRelationships.FirstOrDefault(h => h.Id == relId) + ?? throw new PresentationBuilderInternalException("Internal Error 0007"); var newRid = Relationships.GetNewRelationshipId(); newChart.AddExternalRelationship(oldRel.RelationshipType, oldRel.Uri, newRid); dataReference.Attribute(R.id).Set(newRid); @@ -250,10 +249,9 @@ internal static void CopyExtendedChartObjects(ExtendedChartPart oldChart, Extend else { //ExternalRelationship oldRelationship = oldChart.GetExternalRelationship(relId); - var oldRel = oldChart.ExternalRelationships.FirstOrDefault(h => h.Id == relId); - if (oldRel is null) - throw new PresentationBuilderInternalException("Internal Error 0007"); - + var oldRel = + oldChart.ExternalRelationships.FirstOrDefault(h => h.Id == relId) + ?? throw new PresentationBuilderInternalException("Internal Error 0007"); var newRid = Relationships.GetNewRelationshipId(); newChart.AddExternalRelationship(oldRel.RelationshipType, oldRel.Uri, newRid); dataReference.Attribute(R.id).Set(newRid); @@ -368,9 +366,9 @@ IEnumerable newContent if (newPart.ExternalRelationships.Any(h => h.Id == relId)) continue; var newRid = Relationships.GetNewRelationshipId(); - var oldRel = oldPart.ExternalRelationships.FirstOrDefault(h => h.Id == relId); - if (oldRel is null) - throw new PresentationBuilderInternalException("Internal Error 0006"); + var oldRel = + oldPart.ExternalRelationships.FirstOrDefault(h => h.Id == relId) + ?? throw new PresentationBuilderInternalException("Internal Error 0006"); newPart.AddExternalRelationship(oldRel.RelationshipType, oldRel.Uri, newRid); UpdateContent(newContent, e.Name, relId, newRid); } @@ -951,15 +949,7 @@ internal static bool HasRelationship(this OpenXmlPart part, string relId) => || part.ExternalRelationships.Any(er => er.Id == relId); } - public class PresentationBuilderException : Exception - { - public PresentationBuilderException(string message) - : base(message) { } - } + public class PresentationBuilderException(string message) : Exception(message) { } - public class PresentationBuilderInternalException : Exception - { - public PresentationBuilderInternalException(string message) - : base(message) { } - } + public class PresentationBuilderInternalException(string message) : Exception(message) { } } diff --git a/Clippit/PowerPoint/SlidePartData.cs b/Clippit/PowerPoint/SlidePartData.cs index 2009dc08..486211dc 100644 --- a/Clippit/PowerPoint/SlidePartData.cs +++ b/Clippit/PowerPoint/SlidePartData.cs @@ -25,7 +25,7 @@ public virtual int CompareTo(SlidePartData other) { if (ReferenceEquals(this, other)) return 0; - if (ReferenceEquals(null, other)) + if (other is null) return 1; return string.Compare(ShapeXml, other.ShapeXml, StringComparison.Ordinal); } @@ -41,13 +41,13 @@ protected string NormalizeXml(string xml) private static readonly XNamespace s_relNs = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"; private static readonly XName[] s_noiseAttNames = - { + [ "smtClean", "dirty", "userDrawn", s_relNs + "id", s_relNs + "embed", - }; + ]; /// /// Remove OpenXml attributes that may occur on Layout/Master elements but does not affect rendering @@ -106,11 +106,9 @@ public static void ScaleShapes(XDocument openXmlPart, double scale) } // This class is used to prevent duplication of layouts and handle content modification - internal class SlideLayoutData : SlidePartData + internal class SlideLayoutData(SlideLayoutPart slideLayout, double scaleFactor) + : SlidePartData(slideLayout, scaleFactor) { - public SlideLayoutData(SlideLayoutPart slideLayout, double scaleFactor) - : base(slideLayout, scaleFactor) { } - protected override string GetShapeDescriptor(SlideLayoutPart slideLayout) { var sb = new StringBuilder(); @@ -123,27 +121,18 @@ protected override string GetShapeDescriptor(SlideLayoutPart slideLayout) } // This class is used to prevent duplication of themes and handle content modification - internal class ThemeData : SlidePartData + internal class ThemeData(ThemePart themePart, double scaleFactor) : SlidePartData(themePart, scaleFactor) { - public ThemeData(ThemePart themePart, double scaleFactor) - : base(themePart, scaleFactor) { } - protected override string GetShapeDescriptor(ThemePart themePart) => NormalizeXml(themePart.Theme.ThemeElements.OuterXml); } // This class is used to prevent duplication of masters and handle content modification - internal class SlideMasterData : SlidePartData + internal class SlideMasterData(SlideMasterPart slideMaster, double scaleFactor) + : SlidePartData(slideMaster, scaleFactor) { - public ThemeData ThemeData { get; } - public List SlideLayoutList { get; } - - public SlideMasterData(SlideMasterPart slideMaster, double scaleFactor) - : base(slideMaster, scaleFactor) - { - ThemeData = new ThemeData(slideMaster.ThemePart, scaleFactor); - SlideLayoutList = new List(); - } + public ThemeData ThemeData { get; } = new ThemeData(slideMaster.ThemePart, scaleFactor); + public List SlideLayoutList { get; } = []; protected override string GetShapeDescriptor(SlideMasterPart slideMaster) { From 20ab799ef48fe236f3830d050423427cf27d81ce Mon Sep 17 00:00:00 2001 From: Sergey Tihon Date: Sat, 5 Oct 2024 18:59:35 +0200 Subject: [PATCH 3/7] minor perf improvements --- Clippit/Internal/Relationships.cs | 2 +- Clippit/Internal/TextReplacer.cs | 38 ++++++++++++++++--------------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/Clippit/Internal/Relationships.cs b/Clippit/Internal/Relationships.cs index 41ce83bc..b5587fc2 100644 --- a/Clippit/Internal/Relationships.cs +++ b/Clippit/Internal/Relationships.cs @@ -5,5 +5,5 @@ namespace Clippit.Internal; internal static class Relationships { internal static string GetNewRelationshipId() => - "rcId" + Guid.NewGuid().ToString().Replace("-", "").Substring(0, 16); + string.Concat("rcId", Guid.NewGuid().ToString().Replace("-", "").AsSpan(0, 16)); } diff --git a/Clippit/Internal/TextReplacer.cs b/Clippit/Internal/TextReplacer.cs index 18a6c40c..600edd08 100644 --- a/Clippit/Internal/TextReplacer.cs +++ b/Clippit/Internal/TextReplacer.cs @@ -12,14 +12,9 @@ namespace Clippit.Internal { public class TextReplacer { - private class MatchSemaphore + private class MatchSemaphore(int matchId) { - public int MatchId { get; } - - public MatchSemaphore(int matchId) - { - MatchId = matchId; - } + public int MatchId { get; } = matchId; } private static XObject CloneWithAnnotation(XNode node) @@ -44,7 +39,10 @@ private static object WmlSearchAndReplaceTransform(XNode node, string search, st if (element.Name == W.p) { var contents = element.Descendants(W.t).Select(t => (string)t).StringConcatenate(); - if (contents.Contains(search) || (!matchCase && contents.ToUpper().Contains(search.ToUpper()))) + if ( + contents.Contains(search) + || (!matchCase && contents.Contains(search, System.StringComparison.CurrentCultureIgnoreCase)) + ) { var paragraphWithSplitRuns = new XElement( W.p, @@ -82,9 +80,10 @@ var pc in subRunArray if (matchCase) b = z.ParagraphChildProjection.Value != z.CharacterToCompare.ToString(); else - b = - z.ParagraphChildProjection.Value.ToUpper() - != z.CharacterToCompare.ToString().ToUpper(); + b = !z.ParagraphChildProjection.Value.Equals( + z.CharacterToCompare.ToString(), + System.StringComparison.CurrentCultureIgnoreCase + ); return b; }); var match = !dontMatch; @@ -141,7 +140,7 @@ var pc in subRunArray return (object)g; var textValue = g.Select(r => r.Element(W.t).Value).StringConcatenate(); XAttribute xs = null; - if (textValue[0] == ' ' || textValue[textValue.Length - 1] == ' ') + if (textValue[0] == ' ' || textValue[^1] == ' ') xs = new XAttribute(XNamespace.Xml + "space", "preserve"); return new XElement(W.r, g.First().Elements(W.rPr), new XElement(W.t, xs, textValue)); }) @@ -259,13 +258,15 @@ bool matchCase private static object PmlReplaceTextTransform(XNode node, string search, string replace, bool matchCase) { - var element = node as XElement; - if (element != null) + if (node is XElement element) { if (element.Name == A.p) { var contents = element.Descendants(A.t).Select(t => (string)t).StringConcatenate(); - if (contents.Contains(search) || (!matchCase && contents.ToUpper().Contains(search.ToUpper()))) + if ( + contents.Contains(search) + || (!matchCase && contents.Contains(search, System.StringComparison.CurrentCultureIgnoreCase)) + ) { var paragraphWithSplitRuns = new XElement( A.p, @@ -303,9 +304,10 @@ var pc in subRunArray if (matchCase) b = z.ParagraphChildProjection.Value != z.CharacterToCompare.ToString(); else - b = - z.ParagraphChildProjection.Value.ToUpper() - != z.CharacterToCompare.ToString().ToUpper(); + b = !z.ParagraphChildProjection.Value.Equals( + z.CharacterToCompare.ToString(), + System.StringComparison.CurrentCultureIgnoreCase + ); return b; }); var match = !dontMatch; From 8c90806e8f3354cb86a0aaa29a43dba2d33e4577 Mon Sep 17 00:00:00 2001 From: Sergey Tihon Date: Sat, 5 Oct 2024 19:02:40 +0200 Subject: [PATCH 4/7] fix: simplify relId gen code --- Clippit/Internal/Relationships.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Clippit/Internal/Relationships.cs b/Clippit/Internal/Relationships.cs index b5587fc2..f36b3cd2 100644 --- a/Clippit/Internal/Relationships.cs +++ b/Clippit/Internal/Relationships.cs @@ -4,6 +4,9 @@ namespace Clippit.Internal; internal static class Relationships { - internal static string GetNewRelationshipId() => - string.Concat("rcId", Guid.NewGuid().ToString().Replace("-", "").AsSpan(0, 16)); + internal static string GetNewRelationshipId() + { + var uid = Guid.NewGuid().ToString().Replace("-", "").AsSpan(0, 16); + return string.Concat("rcId", uid); + } } From fda0684d4877a9a7a5121dbb64dc9459f0775668 Mon Sep 17 00:00:00 2001 From: Sergey Tihon Date: Sat, 5 Oct 2024 19:07:03 +0200 Subject: [PATCH 5/7] update readme and remove gitpod --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8fe85e57..afa4fd7f 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -# Clippit [![NuGet Badge](https://buildstats.info/nuget/Clippit)](https://www.nuget.org/packages/Clippit) +# Clippit + +![NuGet Version](https://img.shields.io/nuget/v/Clippit) ![NuGet Downloads](https://img.shields.io/nuget/dt/Clippit) [![Build and Test](https://github.com/sergey-tihon/Clippit/actions/workflows/main.yml/badge.svg)](https://github.com/sergey-tihon/Clippit/actions/workflows/main.yml) ## Build Instructions -[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/sergey-tihon/Clippit) - ### Build Call `.\build.cmd` on Windows or `./build.sh` on other systems. @@ -13,7 +13,8 @@ Call `.\build.cmd` on Windows or `./build.sh` on other systems. ### Update docs - Install DocFx + - Windows : `choco install docfx -y` - MacOS: `brew install docfx` -- Run `docfx docs/docfx.json --serve` to start local copy of site/docs. \ No newline at end of file +- Run `docfx docs/docfx.json --serve` to start local copy of site/docs. From 1cfdad87785fbdc6c04dd22cfe4dfab534c0fee8 Mon Sep 17 00:00:00 2001 From: Sergey Tihon Date: Sat, 5 Oct 2024 20:11:40 +0200 Subject: [PATCH 6/7] cleanup readme --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index afa4fd7f..3eaf6deb 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,18 @@ # Clippit -![NuGet Version](https://img.shields.io/nuget/v/Clippit) ![NuGet Downloads](https://img.shields.io/nuget/dt/Clippit) - -[![Build and Test](https://github.com/sergey-tihon/Clippit/actions/workflows/main.yml/badge.svg)](https://github.com/sergey-tihon/Clippit/actions/workflows/main.yml) +![NuGet Version](https://img.shields.io/nuget/v/Clippit) ![NuGet Downloads](https://img.shields.io/nuget/dt/Clippit) [![Build and Test](https://github.com/sergey-tihon/Clippit/actions/workflows/main.yml/badge.svg)](https://github.com/sergey-tihon/Clippit/actions/workflows/main.yml) ## Build Instructions ### Build -Call `.\build.cmd` on Windows or `./build.sh` on other systems. +Run `./build.sh` on Unix systems or `.\build.cmd` on Windows. -### Update docs +### Documentation - Install DocFx - - Windows : `choco install docfx -y` - MacOS: `brew install docfx` + - Windows : `choco install docfx -y` - Run `docfx docs/docfx.json --serve` to start local copy of site/docs. From 87e6d5b3a087104ffb55baac67834d97df968ae2 Mon Sep 17 00:00:00 2001 From: Sergey Tihon Date: Sat, 5 Oct 2024 20:22:01 +0200 Subject: [PATCH 7/7] update xunit --- .gitpod.yml | 12 ------------ paket.lock | 24 ++++++++++++------------ 2 files changed, 12 insertions(+), 24 deletions(-) delete mode 100644 .gitpod.yml diff --git a/.gitpod.yml b/.gitpod.yml deleted file mode 100644 index 6462759f..00000000 --- a/.gitpod.yml +++ /dev/null @@ -1,12 +0,0 @@ -image: gitpod/workspace-dotnet - -vscode: - extensions: - - ionide.ionide-fsharp - - christian-kohler.path-intellisense - - PKief.material-icon-theme - - zhuangtongfa.material-theme - - eamodio.gitlens - -tasks: - - init: sudo apt-get update -y && sudo apt-get install -y libgdiplus && ./build.sh \ No newline at end of file diff --git a/paket.lock b/paket.lock index 7860ca32..760f91a5 100644 --- a/paket.lock +++ b/paket.lock @@ -28,19 +28,19 @@ NUGET System.Reflection.Metadata (8.0) System.Collections.Immutable (>= 8.0) System.Text.Encoding.CodePages (8.0) - xunit (2.9) - xunit.analyzers (>= 1.15) - xunit.assert (>= 2.9) - xunit.core (2.9) + xunit (2.9.2) + xunit.analyzers (>= 1.16) + xunit.assert (>= 2.9.2) + xunit.core (2.9.2) xunit.abstractions (2.0.3) xunit.analyzers (1.16) - xunit.assert (2.9) - xunit.core (2.9) - xunit.extensibility.core (2.9) - xunit.extensibility.execution (2.9) - xunit.extensibility.core (2.9) + xunit.assert (2.9.2) + xunit.core (2.9.2) + xunit.extensibility.core (2.9.2) + xunit.extensibility.execution (2.9.2) + xunit.extensibility.core (2.9.2) xunit.abstractions (>= 2.0.3) - xunit.extensibility.execution (2.9) - xunit.extensibility.core (2.9) - xunit.runner.console (2.9) + xunit.extensibility.execution (2.9.2) + xunit.extensibility.core (2.9.2) + xunit.runner.console (2.9.2) xunit.runner.visualstudio (2.8.2)