From 32ca5cd0896e79efd29b50743afca5d9a73923f8 Mon Sep 17 00:00:00 2001 From: Marco von Rosenberg Date: Fri, 8 Nov 2024 11:37:08 +0100 Subject: [PATCH 1/3] upgrade to .NET 8 --- .gitignore | 1 + DocxMerge/DocxMerge.sln | 10 +- DocxMerge/DocxMerge/App.config | 6 -- DocxMerge/DocxMerge/DocxMerge.csproj | 91 ++----------------- DocxMerge/DocxMerge/ILMerge.props | 28 ------ DocxMerge/DocxMerge/ILMergeOrder.txt | 4 - .../DocxMerge/Properties/AssemblyInfo.cs | 36 -------- DocxMerge/DocxMerge/packages.config | 7 -- 8 files changed, 17 insertions(+), 166 deletions(-) delete mode 100644 DocxMerge/DocxMerge/App.config delete mode 100644 DocxMerge/DocxMerge/ILMerge.props delete mode 100644 DocxMerge/DocxMerge/ILMergeOrder.txt delete mode 100644 DocxMerge/DocxMerge/Properties/AssemblyInfo.cs delete mode 100644 DocxMerge/DocxMerge/packages.config diff --git a/.gitignore b/.gitignore index 2cfba4b..c55fb9f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ *.user *.userosscache *.sln.docstates +launchSettings.json # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs diff --git a/DocxMerge/DocxMerge.sln b/DocxMerge/DocxMerge.sln index f94c1db..8dc1bd9 100644 --- a/DocxMerge/DocxMerge.sln +++ b/DocxMerge/DocxMerge.sln @@ -1,9 +1,8 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.23107.0 +# Visual Studio Version 17 +VisualStudioVersion = 17.11.35312.102 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DocxMerge", "DocxMerge\DocxMerge.csproj", "{97EC5023-46FC-4027-BF4E-33A5E8890FA0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DocxMerge", "DocxMerge\DocxMerge.csproj", "{97EC5023-46FC-4027-BF4E-33A5E8890FA0}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -19,4 +18,7 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {876BE244-2A1E-4AF6-A521-50518B1C60A4} + EndGlobalSection EndGlobal diff --git a/DocxMerge/DocxMerge/App.config b/DocxMerge/DocxMerge/App.config deleted file mode 100644 index 88fa402..0000000 --- a/DocxMerge/DocxMerge/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/DocxMerge/DocxMerge/DocxMerge.csproj b/DocxMerge/DocxMerge/DocxMerge.csproj index 216953c..9f4c0f4 100644 --- a/DocxMerge/DocxMerge/DocxMerge.csproj +++ b/DocxMerge/DocxMerge/DocxMerge.csproj @@ -1,87 +1,16 @@ - - - - + - Debug - AnyCPU - {97EC5023-46FC-4027-BF4E-33A5E8890FA0} + net8.0 Exe - Properties - DocxMerge - DocxMerge - v4.5.2 - 512 - true - - + DocxMerge + Simple command line program to merge word documents + DocxMerge + Copyright © James Santiago 2015 + 2.0.0 + 2.0.0 - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\CommandLineParser.2.0.275-beta\lib\net45\CommandLine.dll - True - - - ..\packages\DocumentFormat.OpenXml.2.5\lib\DocumentFormat.OpenXml.dll - True - False - - - - - - - - - - - - - - - - - - - - - - + + - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - \ No newline at end of file diff --git a/DocxMerge/DocxMerge/ILMerge.props b/DocxMerge/DocxMerge/ILMerge.props deleted file mode 100644 index bd50e57..0000000 --- a/DocxMerge/DocxMerge/ILMerge.props +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - true - - - - - - - - - - - - - - - - - diff --git a/DocxMerge/DocxMerge/ILMergeOrder.txt b/DocxMerge/DocxMerge/ILMergeOrder.txt deleted file mode 100644 index 3fda7f5..0000000 --- a/DocxMerge/DocxMerge/ILMergeOrder.txt +++ /dev/null @@ -1,4 +0,0 @@ -# this file contains the partial list of the merged assemblies in the merge order -# you can fill it from the obj\CONFIG\PROJECT.ilmerge generated on every build -# and finetune merge order to your satisfaction - diff --git a/DocxMerge/DocxMerge/Properties/AssemblyInfo.cs b/DocxMerge/DocxMerge/Properties/AssemblyInfo.cs deleted file mode 100644 index 673d11a..0000000 --- a/DocxMerge/DocxMerge/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("DocxMerge")] -[assembly: AssemblyDescription("Simple command line program to merge word documents")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("DocxMerge")] -[assembly: AssemblyCopyright("Copyright © James Santiago 2015")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("97ec5023-46fc-4027-bf4e-33a5e8890fa0")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.2.0.0")] -[assembly: AssemblyFileVersion("1.2.0.0")] diff --git a/DocxMerge/DocxMerge/packages.config b/DocxMerge/DocxMerge/packages.config deleted file mode 100644 index aa8428b..0000000 --- a/DocxMerge/DocxMerge/packages.config +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file From 1fcc73dd3413dec1ca0d941a38fbc2405cc4edf4 Mon Sep 17 00:00:00 2001 From: Marco von Rosenberg Date: Fri, 8 Nov 2024 11:38:07 +0100 Subject: [PATCH 2/3] add single-file publish profiles and publish settings --- .gitignore | 4 ---- DocxMerge/DocxMerge/DocxMerge.csproj | 6 ++++++ .../PublishProfiles/SingleFileLinuxX64.pubxml | 14 ++++++++++++++ .../PublishProfiles/SingleFileWinX64.pubxml | 14 ++++++++++++++ 4 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 DocxMerge/DocxMerge/Properties/PublishProfiles/SingleFileLinuxX64.pubxml create mode 100644 DocxMerge/DocxMerge/Properties/PublishProfiles/SingleFileWinX64.pubxml diff --git a/.gitignore b/.gitignore index c55fb9f..b4638d3 100644 --- a/.gitignore +++ b/.gitignore @@ -131,10 +131,6 @@ publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml -# TODO: Comment the next line if you want to checkin your web deploy settings -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj # NuGet Packages *.nupkg diff --git a/DocxMerge/DocxMerge/DocxMerge.csproj b/DocxMerge/DocxMerge/DocxMerge.csproj index 9f4c0f4..e67587b 100644 --- a/DocxMerge/DocxMerge/DocxMerge.csproj +++ b/DocxMerge/DocxMerge/DocxMerge.csproj @@ -9,6 +9,12 @@ 2.0.0 2.0.0 + + true + false + false + bin\publish\$(TargetFramework)_$(RuntimeIdentifier) + diff --git a/DocxMerge/DocxMerge/Properties/PublishProfiles/SingleFileLinuxX64.pubxml b/DocxMerge/DocxMerge/Properties/PublishProfiles/SingleFileLinuxX64.pubxml new file mode 100644 index 0000000..b0e9175 --- /dev/null +++ b/DocxMerge/DocxMerge/Properties/PublishProfiles/SingleFileLinuxX64.pubxml @@ -0,0 +1,14 @@ + + + + Release + Any CPU + FileSystem + <_TargetId>Folder + net8.0 + false + linux-x64 + true + bin\publish\$(TargetFramework)_$(RuntimeIdentifier) + + \ No newline at end of file diff --git a/DocxMerge/DocxMerge/Properties/PublishProfiles/SingleFileWinX64.pubxml b/DocxMerge/DocxMerge/Properties/PublishProfiles/SingleFileWinX64.pubxml new file mode 100644 index 0000000..335c81b --- /dev/null +++ b/DocxMerge/DocxMerge/Properties/PublishProfiles/SingleFileWinX64.pubxml @@ -0,0 +1,14 @@ + + + + Release + Any CPU + FileSystem + <_TargetId>Folder + net8.0 + false + win-x64 + false + bin\publish\$(TargetFramework)_$(RuntimeIdentifier) + + \ No newline at end of file From 44ef305fb9b8463f5bcb6d9fb4e2401dae62b30a Mon Sep 17 00:00:00 2001 From: Marco von Rosenberg Date: Fri, 8 Nov 2024 12:10:58 +0100 Subject: [PATCH 3/3] code improvements made possible by .NET 8 --- DocxMerge/DocxMerge/DocxMerge.csproj | 1 + DocxMerge/DocxMerge/Options.cs | 35 ++-- DocxMerge/DocxMerge/Program.cs | 242 +++++++++++++-------------- 3 files changed, 131 insertions(+), 147 deletions(-) diff --git a/DocxMerge/DocxMerge/DocxMerge.csproj b/DocxMerge/DocxMerge/DocxMerge.csproj index e67587b..b68927c 100644 --- a/DocxMerge/DocxMerge/DocxMerge.csproj +++ b/DocxMerge/DocxMerge/DocxMerge.csproj @@ -8,6 +8,7 @@ Copyright © James Santiago 2015 2.0.0 2.0.0 + enable true diff --git a/DocxMerge/DocxMerge/Options.cs b/DocxMerge/DocxMerge/Options.cs index c067af2..317bfd1 100644 --- a/DocxMerge/DocxMerge/Options.cs +++ b/DocxMerge/DocxMerge/Options.cs @@ -1,28 +1,21 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using CommandLine; -using CommandLine.Text; +using CommandLine; -namespace DocxMerge +namespace DocxMerge; + +class Options { - class Options - { - [Option('i', "input", Required = true, HelpText = "A list of docx files to merge in order")] - public IEnumerable InputFiles { get; set; } + [Option('i', "input", Required = true, HelpText = "A list of docx files to merge in order")] + public IEnumerable InputFiles { get; set; } - [Option('o', "output", Required = false, HelpText = "Output file [Default: output.docx]")] - public string Output { get; set; } + [Option('o', "output", Required = false, HelpText = "Output file [Default: output.docx]")] + public string Output { get; set; } - [Option('f', "Force", Required = false, HelpText = "Replace output if already exists")] - public bool Force { get; set; } + [Option('f', "Force", Required = false, HelpText = "Replace output if already exists")] + public bool Force { get; set; } - [Option('r', "Repair-Spacing", Required = false, HelpText = "Replace single spacing after sentences removed from pandoc")] - public bool RepairSpacing { get; set; } + [Option('r', "Repair-Spacing", Required = false, HelpText = "Replace single spacing after sentences removed from pandoc")] + public bool RepairSpacing { get; set; } - [Option('v', "Verbose", Required = false, HelpText = "Show more information when executing")] - public bool Verbose { get; set; } - } + [Option('v', "Verbose", Required = false, HelpText = "Show more information when executing")] + public bool Verbose { get; set; } } diff --git a/DocxMerge/DocxMerge/Program.cs b/DocxMerge/DocxMerge/Program.cs index 3b713dc..0e64b14 100644 --- a/DocxMerge/DocxMerge/Program.cs +++ b/DocxMerge/DocxMerge/Program.cs @@ -1,153 +1,143 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; +using CommandLine; using DocumentFormat.OpenXml.Packaging; -using System.IO; +using System.Text.RegularExpressions; using System.Xml; using System.Xml.Linq; -using CommandLine; -namespace DocxMerge +namespace DocxMerge; + +static partial class Program { - class Program + static void Main(string[] args) { - static void Main(string[] args) + IEnumerable inputFiles = null; + string output = null; + bool verbose = false; + bool force = false; + bool repairSpacing = false; + + var results = Parser.Default.ParseArguments(args); + results.MapResult(options => { - IEnumerable inputFiles = null; - string output = null; - bool verbose = false; - bool force = false; - bool repairSpacing = false; - - var results = CommandLine.Parser.Default.ParseArguments(args); - results.MapResult(options => - { - inputFiles = options.InputFiles; - output = options.Output ?? "output.docx"; - verbose = options.Verbose; - force = options.Force; - repairSpacing = options.RepairSpacing; - return 0; - }, errors => - { - Environment.Exit(1); - return 1; // ._. - }); - + inputFiles = options.InputFiles; + output = options.Output ?? "output.docx"; + verbose = options.Verbose; + force = options.Force; + repairSpacing = options.RepairSpacing; + return 0; + }, _ => + { + Environment.Exit(1); + return 1; // ._. + }); - if (inputFiles.Count() < 2) ExitWithError("There must be at least two input files"); - foreach (var file in inputFiles) - { - if (!File.Exists(file)) - ExitWithError("Unable to find {0}", file); - } + if (inputFiles.Count() < 2) ExitWithError("There must be at least two input files"); + foreach (var file in inputFiles) + { + if (!File.Exists(file)) + ExitWithError("Unable to find {0}", file); + } - try { - if (verbose) Console.WriteLine("Creating initial document"); - File.Copy(inputFiles.First(), output, force); + try { + if (verbose) Console.WriteLine("Creating initial document"); + File.Copy(inputFiles.First(), output, force); - if (verbose) Console.WriteLine("Opening {0} for writing", output); - using (WordprocessingDocument doc = WordprocessingDocument.Open(output, true)) + if (verbose) Console.WriteLine("Opening {0} for writing", output); + using (WordprocessingDocument doc = WordprocessingDocument.Open(output, true)) + { + int fileId = 0; + foreach (var filepath in inputFiles.Skip(1)) { - int fileId = 0; - foreach (var filepath in inputFiles.Skip(1)) - { - if (verbose) Console.WriteLine("Adding {0} to {1}", filepath, output); - fileId++; - XNamespace w = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"; - XNamespace r = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"; - - string altChuckId = string.Format("AltChuckId{0}", fileId); - var mainPart = doc.MainDocumentPart; - var chunk = mainPart.AddAlternativeFormatImportPart( - "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml", - altChuckId); - - if (repairSpacing) - RepairSentenceSpacing(filepath, verbose); - - using (FileStream fileStream = File.Open(filepath, FileMode.Open)) - chunk.FeedData(fileStream); - var altChunk = new XElement(w + "altChunk", new XAttribute(r + "id", altChuckId)); - var mainDocumentXDoc = GetXDocument(doc); - - mainDocumentXDoc.Root.Element(w + "body").Elements(w + "p").Last().AddAfterSelf(altChunk); - SaveXDocument(doc, mainDocumentXDoc); - } + if (verbose) Console.WriteLine("Adding {0} to {1}", filepath, output); + fileId++; + XNamespace w = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"; + XNamespace r = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"; + + string altChuckId = string.Format("AltChuckId{0}", fileId); + var mainPart = doc.MainDocumentPart; + var chunk = mainPart.AddAlternativeFormatImportPart( + "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml", + altChuckId); + + if (repairSpacing) + RepairSentenceSpacing(filepath); + + using (FileStream fileStream = File.Open(filepath, FileMode.Open)) + chunk.FeedData(fileStream); + var altChunk = new XElement(w + "altChunk", new XAttribute(r + "id", altChuckId)); + var mainDocumentXDoc = GetXDocument(doc); + + mainDocumentXDoc.Root.Element(w + "body").Elements(w + "p").Last().AddAfterSelf(altChunk); + SaveXDocument(doc, mainDocumentXDoc); } - - if (verbose) Console.WriteLine("Successfully merged all documents"); } - catch (Exception ex) - { - if (verbose) ExitWithError(ex.ToString()); - else ExitWithError("DocxMerge failed to process the files: {0}", ex.Message); - } - } - private static void RepairSentenceSpacing(string filepath, bool verbose) + if (verbose) Console.WriteLine("Successfully merged all documents"); + } + catch (Exception ex) { - using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(filepath, true)) - { - string docText = null; - using (StreamReader sr = new StreamReader(wordDoc.MainDocumentPart.GetStream())) - { - docText = sr.ReadToEnd(); - } - - Regex regexText = new Regex(@"\. (?[A-Z])"); - var refIndex = docText.IndexOf("References"); - if (refIndex > 0) - { - var bodyText = docText.Substring(0, refIndex); - var references = docText.Substring(bodyText.Length); - docText = regexText.Replace(bodyText, ". ${a}") + references; - } - else - { - docText = regexText.Replace(docText, ". ${a}"); - } - - using (StreamWriter sw = new StreamWriter(wordDoc.MainDocumentPart.GetStream(FileMode.Create))) - { - sw.Write(docText); - } - } + if (verbose) ExitWithError(ex.ToString()); + else ExitWithError("DocxMerge failed to process the files: {0}", ex.Message); } + } - private static XDocument GetXDocument(WordprocessingDocument myDoc) + private static void RepairSentenceSpacing(string filepath) + { + using WordprocessingDocument wordDoc = WordprocessingDocument.Open(filepath, true); + string docText = null; + using (StreamReader sr = new(wordDoc.MainDocumentPart.GetStream())) { - // Load the main document part into an XDocument - XDocument mainDocumentXDoc; - using (Stream str = myDoc.MainDocumentPart.GetStream()) - using (XmlReader xr = XmlReader.Create(str)) - mainDocumentXDoc = XDocument.Load(xr); - return mainDocumentXDoc; + docText = sr.ReadToEnd(); } - private static void SaveXDocument(WordprocessingDocument myDoc, XDocument mainDocumentXDoc) + Regex regexText = RegexText(); + var refIndex = docText.IndexOf("References"); + if (refIndex > 0) { - // Serialize the XDocument back into the part - using (Stream str = myDoc.MainDocumentPart.GetStream( - FileMode.Create, FileAccess.Write)) - using (XmlWriter xw = XmlWriter.Create(str)) - mainDocumentXDoc.Save(xw); + var bodyText = docText[..refIndex]; + var references = docText[bodyText.Length..]; + docText = regexText.Replace(bodyText, ". ${a}") + references; } - - private static void ExitWithError(string message, params object[] args) + else { - var defaultColor = Console.ForegroundColor; - Console.ForegroundColor = ConsoleColor.Red; - if (args.Any()) - Console.WriteLine(message, args); - else - Console.WriteLine(message); - Console.ForegroundColor = defaultColor; - Environment.Exit(1); + docText = regexText.Replace(docText, ". ${a}"); } + using StreamWriter sw = new(wordDoc.MainDocumentPart.GetStream(FileMode.Create)); + sw.Write(docText); } + + private static XDocument GetXDocument(WordprocessingDocument myDoc) + { + // Load the main document part into an XDocument + XDocument mainDocumentXDoc; + using (Stream str = myDoc.MainDocumentPart.GetStream()) + using (XmlReader xr = XmlReader.Create(str)) + mainDocumentXDoc = XDocument.Load(xr); + return mainDocumentXDoc; + } + + private static void SaveXDocument(WordprocessingDocument myDoc, XDocument mainDocumentXDoc) + { + // Serialize the XDocument back into the part + using Stream str = myDoc.MainDocumentPart.GetStream( + FileMode.Create, FileAccess.Write); + using XmlWriter xw = XmlWriter.Create(str); + mainDocumentXDoc.Save(xw); + } + + private static void ExitWithError(string message, params object[] args) + { + var defaultColor = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Red; + if (args.Length != 0) + Console.WriteLine(message, args); + else + Console.WriteLine(message); + Console.ForegroundColor = defaultColor; + Environment.Exit(1); + } + + [GeneratedRegex(@"\. (?[A-Z])")] + private static partial Regex RegexText(); }