Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4771b0b
Remove manually added reference to PdfSharp.dll, and replace with a N…
Feb 25, 2019
491a4ba
Change copyright year, and increase version to 2.2.5.0
Feb 25, 2019
c52a32d
Change readme to indicate it currently needs VS 2017 to build.
Feb 25, 2019
8a8285f
Update README.md, so that people actually know how to USE the thing
Feb 25, 2019
6672a90
Add to README about how you can split a single PDF file, or multiple …
Feb 27, 2019
cf6da49
. #5 Add VERSION file, which will be used by the app to check for ava…
Mar 5, 2019
dfd17b8
Implement ReSharper-suggested refactorings in Program.cs
Feb 26, 2019
ddbbfbb
Add .editorconfig file so I can do some refactoring without having to…
Feb 26, 2019
25f8a6a
Issue #1 (#2)
Mar 1, 2019
06de16f
Issue #3: Change program to use the Runner to actually do the work. (#4)
Mar 4, 2019
f60371a
Issue 5 - Implemented version checking, both on-demand and once every…
Mar 6, 2019
1023335
We needed a way to control the behaviour of the automated upgrade che…
Mar 7, 2019
6cbf1a3
Update version in all assemblies, in preparation for making a release.
Mar 7, 2019
ffe460d
Update VERSION file, and change README.md to indicate the new .NET 3.…
Mar 7, 2019
23d3b34
Set theme jekyll-theme-slate
Mar 11, 2019
d7bd294
Sanity check: I never actually added test IsUpgradeRequired_Should_Re…
Mar 11, 2019
f15936c
I #7: Handle exception when encryption not supported by PdfSharp
Aug 3, 2019
0ae80ff
Increase version number to 2.5.7.1
Aug 3, 2019
a3c1a11
Increase VERSION to 2.5.7.1
Aug 3, 2019
0d12a62
Update NuGet packages for MSTest
Sep 15, 2019
1042877
Upgrade target framework to 4.5
Mar 2, 2021
b8e6d45
Upgrade all Nuget packages
Mar 2, 2021
aab5687
Increase copyright year, version to 2.6.8.0
Mar 2, 2021
b36e7ba
Update VERSION and README to reflect new Framework version
Mar 2, 2021
7b6938a
Bump System.Text.RegularExpressions in /SplitPdf.UnitTests
dependabot[bot] Aug 4, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
root = true
[*]
indent_size=2
21 changes: 19 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
PdfSplit is a .NET console application for splitting PDF files into multiple files. Pass a PDF file to this app on the command line (or drag a PDF file onto the executable in Windows Explorer), and it will be split into multiple documents, one document per page.

The project uses the open source PDFsharp library, which can be downloaded from http://sourceforge.net/projects/pdfsharp/
Note: It was developed in Microsoft Visual Studio 2008 (although it currently needs Visual Studio 2017 to build), and targetting the .NET Framework 4.5. This is because the lady who I originally developed it for, to use at work, only has .NET 4.5 on her PC and the company policy didn't permit her to upgrade. Hence it needs to be left targetting that framework version.

Note: It was developed in Microsoft Visual Studio 2008, and targeting the .NET Framework 2.0. This is because the lady who I originally developed it for, to use at work, only has .NET 2.0 on her PC and the company policy didn't permit her to upgrade. Hence it needs to be left targetting that framework version.
Note 2: Although the executable only requires the .NET Framework 4.5 to run, there is a Unit Test project in the solution. In order to execute tests in that project, you require .NET Framework 4.6.1 on your machine. This, of course, shouldn't be a problem if you're running it from within Visual Studio 2017 or higher.

<h3>Usage</h3>
<ul><li>Split a single PDF file into many:<br/>
<code>SplitPdf.exe &lt;File&gt;</code></li>
<li>Split multiple PDF files into many (batching):<br/>
<code>SplitPdf.exe &lt;File1&gt; &lt;File2&gt; &lt;...&gt; &lt;FileN&gt;</code></li>
<li>Merge multiple PDF files into one (creates &lt;OutputFile&gt; at the end):<br/>
<code>SplitPdf.exe -m &lt;File1&gt; &lt;File2&gt; &lt;...&gt; &lt;FileN&gt; &lt;OutputFile&gt;</code></li></ul>

<h3>Checking for Upgrades</h3>
From version 2.5.6.0 onwards, SplitPdf will automatically check for upgrades every 14 days. You can configure this behaviour by editing SplitPdf.exe.config, and changing the following settings:
<ul><li>DaysBetweenUpgradeCheck (default: 14) - Set this to -1 to disable automatic checking for upgrades.</li>
<li>UpgradeCheckUrl - Don't modify this value, or the application will become unstable.</li>
<li>SecondsDelayAfterFindingUpgrades - If the application finds an upgrade, it pauses to give you time to copy the URL. This is the number of seconds it should wait for.</li></ul>

At any time, you can force a manual upgrade check by executing:
<code>SplitPdf.exe -uc</code></li>
26 changes: 26 additions & 0 deletions SplitPdf.Engine/ArgumentValidationException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.Text;

namespace SplitPdf.Engine
{
public class ArgumentValidationException : ApplicationException
{
public ArgumentValidationException(string message) : base(message) { }

public static void ThrowWithUsageMessage()
=> ThrowWithUsageMessage(null);

public static void ThrowWithUsageMessage(string message)
{
var finalMessage = new StringBuilder(2);
if (message != null)
{
finalMessage.AppendLine(message);
finalMessage.AppendLine();
}

finalMessage.Append(ArgumentsInterpreter.UsageMessage);
throw new ArgumentValidationException(finalMessage.ToString());
}
}
}
63 changes: 63 additions & 0 deletions SplitPdf.Engine/ArgumentsInterpreter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System.Collections.Generic;

namespace SplitPdf.Engine
{
public class ArgumentsInterpreter
{
public const string UsageMessage = "Usage:\r\n" +
"======\r\n" +
"\r\n" +
"Split a single PDF file into many:\r\n" +
"\tSplitPdf.exe <File>\r\n" +
"Split multiple PDF files into many (batching):\r\n" +
"\tSplitPdf.exe <File1> <File2> <...> <FileN>\r\n" +
"Merge multiple PDF files into one (creates <OutputFile> at the end):\r\n" +
"\tSplitPdf.exe -m <File1> <File2> <...> <FileN> <OutputFile>";

public List<string> InputFiles { get; private set; }
public bool IsMergeEnabled { get; private set; }
public string MergeOutputFile { get; private set; }
public bool IsUpgradeCheckRequested { get; private set; }

public void ProcessArguments(string[] arguments)
{
if (arguments == null || arguments.Length == 0)
ArgumentValidationException.ThrowWithUsageMessage();

var firstFileNameIndex = 0;
// ReSharper disable once PossibleNullReferenceException
if (arguments[0].ToUpper() == "-M")
{
if (arguments.Length < 2)
ArgumentValidationException.ThrowWithUsageMessage("Nothing to merge.");

IsMergeEnabled = true;
firstFileNameIndex = 1;
}
else if (arguments[0].ToUpper() == "-UC")
{
if (arguments.Length > 1)
ArgumentValidationException.ThrowWithUsageMessage(
"If passed, -uc must be the only argument.");

IsUpgradeCheckRequested = true;
return;
}

InputFiles = new List<string>();
// ReSharper disable once PossibleNullReferenceException
for (var i = firstFileNameIndex; i < arguments.Length; i++)
{
if (IsMergeEnabled && i == arguments.Length - 1)
// Last argument is the Output File
MergeOutputFile = arguments[i];
else
InputFiles.Add(arguments[i]);
}

if (IsMergeEnabled && InputFiles.Contains(MergeOutputFile))
ArgumentValidationException.ThrowWithUsageMessage("Merge output file cannot be the same as one " +
"of the input files.");
}
}
}
71 changes: 71 additions & 0 deletions SplitPdf.Engine/ArgumentsValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace SplitPdf.Engine
{
// Note: This class can be unit tested separately, but in practice will not be used that way.
// It will be injected into the runner, and the runner will validate automatically. This is
// in preparation for a future enhancement, where we will have both a GUI and command line version
// of the application.
public class ArgumentsValidator
{
public void Validate(List<string> inputFiles)
=> Validate(inputFiles, null);
public void Validate(List<string> inputFiles, string mergeOutputFile)
{
if (inputFiles == null || inputFiles.Count < 1)
ArgumentValidationException.ThrowWithUsageMessage(
"Please pass at least one input file (two if merging).");

ThrowExceptionIfInputFilesNotFound(inputFiles);
if (!string.IsNullOrEmpty(mergeOutputFile))
{
ThrowExceptionIfLessThan2InputFiles(inputFiles);
ThrowExceptionIfInputFilesContainsOutputFile(inputFiles, mergeOutputFile);
ThrowExceptionIfMergeOutputFileExists(mergeOutputFile);
}
else
{
ThrowExceptionIfDuplicateInputFiles(inputFiles);
}
}

private void ThrowExceptionIfInputFilesNotFound(List<string> inputFiles)
{
foreach (var file in inputFiles)
if (!File.Exists(file))
ArgumentValidationException.ThrowWithUsageMessage($"File not found: {file}.");
}

private void ThrowExceptionIfInputFilesContainsOutputFile(ICollection<string> inputFiles,
string mergeOutputFile)
{
if (inputFiles.Contains(mergeOutputFile))
ArgumentValidationException.ThrowWithUsageMessage("Merge output file cannot be the same " +
"as one of the input files.");
}

private void ThrowExceptionIfMergeOutputFileExists(string mergeOutputFile)
{
if (File.Exists(mergeOutputFile))
ArgumentValidationException.ThrowWithUsageMessage(
$"Output file {mergeOutputFile} already exists, and will not be overwritten.");
}

private static void ThrowExceptionIfDuplicateInputFiles(ICollection<string> inputFiles)
{
var distinctList = inputFiles.Distinct();
if (distinctList.Count() != inputFiles.Count)
ArgumentValidationException.ThrowWithUsageMessage("Each file to split must be unique.");
}

public void ThrowExceptionIfLessThan2InputFiles(ICollection inputFiles)
{
if (inputFiles.Count < 2)
ArgumentValidationException.ThrowWithUsageMessage(
"Merge requires at least two input files and an output file.");
}
}
}
18 changes: 18 additions & 0 deletions SplitPdf.Engine/EncryptedPdfException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;

namespace SplitPdf.Engine
{
public class EncryptedPdfException : Exception
{
public string FileName { get; set; }

public EncryptedPdfException(string fileName)
: base($"The PDF document, \"{fileName}\", was encrypted using an algorithm not yet " +
"supported by this program.\r\n" +
"\r\n" +
"Open the file and \"PDF Print\" it. Then try this operation again.")
{
FileName = fileName;
}
}
}
36 changes: 36 additions & 0 deletions SplitPdf.Engine/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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("SplitPdf.Engine")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("SplitPdf.Engine")]
[assembly: AssemblyCopyright("Copyright © Graham Downs 2021")]
[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("53740d81-616f-4a41-a403-c5239c9350fd")]

// 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("2.6.8.0")]
[assembly: AssemblyFileVersion("1.1.2.0")]
93 changes: 93 additions & 0 deletions SplitPdf.Engine/Runner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using System.Collections.Generic;
using System.IO;
using PdfSharp.Pdf;
using PdfSharp.Pdf.IO;

namespace SplitPdf.Engine
{
public class Runner
{
private readonly ArgumentsValidator _argumentsValidator;

public Runner(ArgumentsValidator argumentsValidator)
{
_argumentsValidator = argumentsValidator;
}

public delegate void RunnerProgressHandler(object sender, RunnerProgressEventArgs e);

public event RunnerProgressHandler Progress;

public void Run(List<string> inputFiles) => Run(inputFiles, null);
public void Run(List<string> inputFiles, string mergeOutputFile)
{
_argumentsValidator.Validate(inputFiles, mergeOutputFile);

if (string.IsNullOrEmpty(mergeOutputFile))
foreach (var file in inputFiles)
DoSplit(file);
else
DoMerge(inputFiles, mergeOutputFile);
}

private void DoSplit(string file)
{
var inputDocument = OpenPdfFile(file);
var destFolder = Path.GetDirectoryName(inputDocument.FullPath);
var destFileName = Path.GetFileNameWithoutExtension(file);
var destFileExtension = Path.GetExtension(file);
for (var i = 0; i < inputDocument.PageCount; i++)
{
var destFileNameFinal = $"{destFileName}-Page{i + 1}of{inputDocument.PageCount}{destFileExtension}";
Progress?.Invoke(this, new RunnerProgressEventArgs
{
ProgressMessage = $"Creating file: {destFileNameFinal}"
});
var outputDocument = new PdfDocument { Version = inputDocument.Version };
outputDocument.AddPage(inputDocument.Pages[i]);
outputDocument.Save($"{destFolder}\\{destFileNameFinal}");
}
}

private PdfDocument OpenPdfFile(string file)
{
try
{
return PdfReader.Open(file, PdfDocumentOpenMode.Import);
}
catch (PdfReaderException exception)
{
if (exception.Message != "The PDF document is protected with an encryption not " +
"supported by PDFsharp.")
throw;

throw new EncryptedPdfException(file);
}
}

private void DoMerge(List<string> inputFiles, string mergeOutputFile)
{
var outputDocument = new PdfDocument();
inputFiles.ForEach(file =>
{
Progress?.Invoke(this, new RunnerProgressEventArgs
{
ProgressMessage = $"Processing {file}"
});
var inputDocument = PdfReader.Open(file, PdfDocumentOpenMode.Import);
var count = inputDocument.PageCount;
for (var idx = 0; idx < count; idx++)
{
var page = inputDocument.Pages[idx];
outputDocument.AddPage(page);
}
});
Progress?.Invoke(this, new RunnerProgressEventArgs
{
ProgressMessage = $"Creating output file {mergeOutputFile}"
});
outputDocument.Save(mergeOutputFile);

}
}
}
9 changes: 9 additions & 0 deletions SplitPdf.Engine/RunnerProgressEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System;

namespace SplitPdf.Engine
{
public class RunnerProgressEventArgs : EventArgs
{
public string ProgressMessage { get; set; }
}
}
Loading