This commit is contained in:
amorfo77 committed 2026-10-22 17:44:03 +02:00
1 parent b6b52a691e
commit e161565ce1
5 files changed
+278 -123

No files matched your search

+25 -39
View File
@@ -128,48 +128,34 @@ namespace UtilitatPdf
if (list.Count == 0)
{
MessageBox.Show("Per a poder fusionar, has de seleccionar un fitxer com a mínim", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
else
if (string.IsNullOrWhiteSpace(MergeFileOut.Text))
{
string FinalPdfFile = MergeFileOut.Text;
if (string.IsNullOrEmpty(FinalPdfFile))
{
MessageBox.Show("El Fitxer de destí no pot estar buit.", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
else
{
if (Path.GetExtension(FinalPdfFile) != ".pdf")
{
FinalPdfFile += ".pdf";
}
FileInfo currentFile = new FileInfo(FinalPdfFile);
if (currentFile.Exists) File.Delete(FinalPdfFile);
if(list.Count == 1)
{
File.Copy(list[0].FileName, FinalPdfFile);
}
else
{
try
{
PdfMergeFiles mClas = new PdfMergeFiles(FinalPdfFile);
mClas.AddFiles(list);
mClas.Generate();
}
catch (IOException ex)
{
MessageBox.Show("S'ha produit un error quan s'intentava escriure el fitxer:\n" + ex.Message,"Error", MessageBoxButton.OK,MessageBoxImage.Error);
}
catch(Exception exc)
{
MessageBox.Show("S'ha produit un error desconegut:\n" + exc.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
MergeFileOut.Text = string.Empty;
list.Clear();
MessageBox.Show("S'han fusionat correctament el fitxers.", "Info", MessageBoxButton.OK, MessageBoxImage.Information);
}
MessageBox.Show("El fitxer de destí no pot estar buit.", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
try
{
string finalPdfFile = MergeFileOut.Text;
if (!string.Equals(Path.GetExtension(finalPdfFile), ".pdf", StringComparison.OrdinalIgnoreCase))
finalPdfFile += ".pdf";
var merger = new PdfMergeFiles(finalPdfFile);
merger.AddFiles(list);
merger.Generate();
}
catch (Exception ex)
{
MessageBox.Show("No s'han pogut fusionar els fitxers:\n" + ex.Message,
"Error", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
MergeFileOut.Text = string.Empty;
list.Clear();
MessageBox.Show("S'han fusionat correctament els fitxers.", "Info", MessageBoxButton.OK, MessageBoxImage.Information);
}
private void RemoveSign_Button_Click(object sender, RoutedEventArgs e)
{
+78 -84
View File
@@ -1,4 +1,5 @@
using System;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using iText.Kernel.Pdf;
@@ -8,104 +9,97 @@ namespace UtilitatPdf
{
public class PdfMergeFiles
{
private PdfDocument _pdfDocument = null;
private PdfDocument _pdfDocument2 = null;
private PdfMerger _pdfMerger = null;
private static string tmpFolder = System.IO.Path.GetTempPath();
private string _oldtmpFile = string.Empty;
private string _tmpFile = string.Empty;
private string _tmpFile2 = string.Empty;
private bool _mergeOK = false;
private string _FinalPdfFile = string.Empty;
private readonly List<string> _sourceFiles = new List<string>();
private readonly string _finalPdfFile;
private readonly bool _overwriteExisting;
public PdfMergeFiles(string FinalPdfFile, bool DeleteIfExists = true)
{
if (string.IsNullOrEmpty(FinalPdfFile))
{
throw new Exception();
}
else
{
_FinalPdfFile = FinalPdfFile;
if (DeleteIfExists)
{
if (new FileInfo(_FinalPdfFile).Exists) File.Delete(_FinalPdfFile);
}
}
if (string.IsNullOrWhiteSpace(FinalPdfFile))
throw new ArgumentException("El fitxer de destí no pot estar buit.", nameof(FinalPdfFile));
_finalPdfFile = Path.GetFullPath(FinalPdfFile);
_overwriteExisting = DeleteIfExists;
}
public bool AddFiles(ObservableCollection<PdfInfo> sourceFiles)
{
foreach (PdfInfo fitxer in sourceFiles)
{
if (!this.AddFile(fitxer.FileName))
{
return false;
}
}
if (sourceFiles == null) throw new ArgumentNullException(nameof(sourceFiles));
foreach (PdfInfo file in sourceFiles)
AddFile(file.FileName);
return true;
}
public bool AddFile(string PdfFilename)
{
checkVars(PdfFilename);
if (string.IsNullOrWhiteSpace(PdfFilename))
throw new ArgumentException("El fitxer d'origen no pot estar buit.", nameof(PdfFilename));
if (_mergeOK)
{
Random numr = new Random();
_oldtmpFile = tmpFolder + numr.Next(1, 99999).ToString() + "tmpfile.pdf";
try
{
_pdfDocument = new PdfDocument(new PdfReader(_tmpFile), new PdfWriter(_oldtmpFile), new StampingProperties().UseAppendMode());
_pdfDocument2 = new PdfDocument(new PdfReader(_tmpFile2));
_pdfMerger = new PdfMerger(_pdfDocument);
_pdfMerger.Merge(_pdfDocument2, 1, _pdfDocument2.GetNumberOfPages());
this.ClosePdfDocs();
}
catch (IOException)
{
throw new IOException();
}
catch(Exception)
{
throw new Exception();
}
finally
{
_tmpFile = _oldtmpFile;
_tmpFile2 = string.Empty;
_oldtmpFile = string.Empty;
_mergeOK = false;
}
}
_sourceFiles.Add(Path.GetFullPath(PdfFilename));
return true;
}
public void Generate()
{
File.Copy(_tmpFile, _FinalPdfFile);
}
private void ClosePdfDocs()
{
_pdfDocument?.Close();
_pdfDocument2?.Close();
_pdfMerger?.Close();
}
private void checkVars(string file)
{
if (string.IsNullOrEmpty(_tmpFile))
{
_tmpFile = file;
}
else
{
_tmpFile2 = file;
}
if (!string.IsNullOrEmpty(_tmpFile) && !string.IsNullOrEmpty(_tmpFile2)) _mergeOK = true;
}
if (_sourceFiles.Count == 0)
throw new InvalidOperationException("Has de seleccionar un fitxer com a mínim.");
// Validate all inputs before creating output or replacing an existing file.
foreach (string sourceFile in _sourceFiles)
{
if (string.Equals(sourceFile, _finalPdfFile, StringComparison.OrdinalIgnoreCase))
throw new IOException("El fitxer de destí no pot ser un dels fitxers d'origen.");
if (!File.Exists(sourceFile))
throw new FileNotFoundException("No s'ha trobat el fitxer d'origen.", sourceFile);
}
if (!_overwriteExisting && File.Exists(_finalPdfFile))
throw new IOException("El fitxer de destí ja existeix.");
// Keep the temporary file on the destination filesystem for atomic publication.
string temporaryFile = Path.Combine(Path.GetDirectoryName(_finalPdfFile),
".utilitatpdf-" + Guid.NewGuid().ToString("N") + ".tmp");
bool temporaryFileCreated = false;
try
{
using (var output = new FileStream(temporaryFile, FileMode.CreateNew, FileAccess.Write))
{
temporaryFileCreated = true;
if (_sourceFiles.Count == 1)
{
using (var input = File.OpenRead(_sourceFiles[0]))
input.CopyTo(output);
}
else
{
using (var document = new PdfDocument(new PdfWriter(output)))
{
var merger = new PdfMerger(document);
foreach (string sourceFile in _sourceFiles)
{
using (var source = new PdfDocument(new PdfReader(sourceFile)))
merger.Merge(source, 1, source.GetNumberOfPages());
}
}
}
}
// Publish only after all PDF documents and streams close successfully.
if (_overwriteExisting && File.Exists(_finalPdfFile))
File.Replace(temporaryFile, _finalPdfFile, null);
else
File.Move(temporaryFile, _finalPdfFile);
}
finally
{
if (temporaryFileCreated)
{
// Cleanup must not hide the original error or report success as failure.
try { File.Delete(temporaryFile); }
catch (IOException) { }
catch (UnauthorizedAccessException) { }
}
}
}
}
}
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="../../UtilitatPdf/PdfMergeFiles.cs" Link="PdfMergeFiles.cs" />
<Compile Include="../../UtilitatPdf/PdfInfo.cs" Link="PdfInfo.cs" />
<PackageReference Include="itext7" Version="7.1.13" />
</ItemGroup>
</Project>
+147
View File
@@ -0,0 +1,147 @@
using System;
using System.IO;
using System.Linq;
using iText.Kernel.Pdf;
using UtilitatPdf;
internal static class Program
{
private static string root;
private static int passed;
private static void Main()
{
root = Path.Combine(Path.GetTempPath(), "utilitatpdf-tests-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(root);
try
{
Run("single input is copied unchanged", () =>
{
string input = Pdf("single.pdf", 101);
string output = At("single-out.pdf");
Merge(output, input);
Equal(File.ReadAllBytes(input), File.ReadAllBytes(output));
});
Run("multiple inputs preserve page order", () =>
{
string a = Pdf("a.pdf", 101, 102);
string b = Pdf("b.pdf", 201);
string c = Pdf("c.pdf", 301, 302);
byte[] original = File.ReadAllBytes(a);
Merge(At("merged.pdf"), a, b, c);
using (var result = new PdfDocument(new PdfReader(At("merged.pdf"))))
{
int[] widths = { 101, 102, 201, 301, 302 };
Check(result.GetNumberOfPages() == widths.Length);
for (int i = 0; i < widths.Length; i++)
Check(result.GetPage(i + 1).GetPageSize().GetWidth() == widths[i]);
}
Equal(original, File.ReadAllBytes(a));
});
Run("same input and output is rejected", () =>
{
string a = Pdf("same.pdf", 100);
byte[] original = File.ReadAllBytes(a);
Fails(() => Merge(a, a));
Equal(original, File.ReadAllBytes(a));
});
Run("normalized source path is protected", () =>
{
string a = Pdf("normalized.pdf", 100);
byte[] original = File.ReadAllBytes(a);
Fails(() => Merge(At("./normalized.pdf"), a, At("b.pdf")));
Equal(original, File.ReadAllBytes(a));
});
Run("existing output survives corrupt later input", () =>
{
string output = Pdf("existing.pdf", 999);
byte[] original = File.ReadAllBytes(output);
File.WriteAllText(At("broken.pdf"), "not a PDF");
Fails(() => Merge(output, At("a.pdf"), At("broken.pdf")));
Equal(original, File.ReadAllBytes(output));
using (File.Open(At("a.pdf"), FileMode.Open, FileAccess.ReadWrite, FileShare.None)) { }
});
Run("failed merge creates no final output", () =>
{
string output = At("failed.pdf");
Fails(() => Merge(output, At("a.pdf"), At("broken.pdf")));
Check(!File.Exists(output));
});
Run("missing source preserves existing output", () =>
{
byte[] original = File.ReadAllBytes(At("existing.pdf"));
Fails(() => Merge(At("existing.pdf"), At("missing.pdf")));
Equal(original, File.ReadAllBytes(At("existing.pdf")));
});
Run("successful merge replaces existing output", () =>
{
Merge(At("existing.pdf"), At("a.pdf"), At("b.pdf"));
using (var result = new PdfDocument(new PdfReader(At("existing.pdf"))))
Check(result.GetNumberOfPages() == 3);
});
Run("single input replaces existing output", () =>
{
Merge(At("existing.pdf"), At("a.pdf"));
Equal(File.ReadAllBytes(At("a.pdf")), File.ReadAllBytes(At("existing.pdf")));
});
Run("overwrite disabled preserves destination", () =>
{
byte[] original = File.ReadAllBytes(At("existing.pdf"));
var merge = new PdfMergeFiles(At("existing.pdf"), false);
merge.AddFile(At("b.pdf"));
Fails(merge.Generate);
Equal(original, File.ReadAllBytes(At("existing.pdf")));
});
Run("empty input list preserves destination", () =>
{
byte[] original = File.ReadAllBytes(At("existing.pdf"));
Fails(new PdfMergeFiles(At("existing.pdf")).Generate);
Equal(original, File.ReadAllBytes(At("existing.pdf")));
});
Run("publish failure preserves destination directory", () =>
{
string output = At("directory.pdf");
Directory.CreateDirectory(output);
File.WriteAllText(Path.Combine(output, "keep.txt"), "keep");
Fails(() => Merge(output, At("a.pdf")));
Check(File.ReadAllText(Path.Combine(output, "keep.txt")) == "keep");
});
Console.WriteLine($"{passed} regression cases passed.");
}
finally { Directory.Delete(root, true); }
}
private static string At(string name) => Path.Combine(root, name);
private static string Pdf(string name, params int[] widths)
{
string path = At(name);
using (var pdf = new PdfDocument(new PdfWriter(path)))
foreach (int width in widths)
pdf.AddNewPage(new iText.Kernel.Geom.PageSize(width, 400));
return path;
}
private static void Merge(string output, params string[] inputs)
{
var merger = new PdfMergeFiles(output);
foreach (string input in inputs) merger.AddFile(input);
merger.Generate();
}
private static void Check(bool condition)
{
if (!condition) throw new Exception("Assertion failed.");
}
private static void Equal(byte[] expected, byte[] actual) => Check(expected.SequenceEqual(actual));
private static void Fails(Action action)
{
try { action(); }
catch (Exception) { return; }
throw new Exception("Expected the operation to fail.");
}
private static void Run(string name, Action action)
{
action();
Check(Directory.GetFiles(root, ".utilitatpdf-*.tmp").Length == 0);
Console.WriteLine("PASS " + name);
passed++;
}
}
+17
View File
@@ -0,0 +1,17 @@
# PDF merge regression checks
Run with the .NET 8 SDK:
```sh
dotnet run --project tests/PdfMergeRegression
```
This console harness links the production merge class and uses the same iText
7.1.13 dependency, without loading WPF. It checks page order, single-file copies,
source-path protection, failed merges, replacement, and temporary-file cleanup.
It does not change the application's .NET Core 3.1 target.
Also check the WPF application on Windows: after an invalid or inaccessible PDF,
the error must leave the selection and destination intact, without a success
message; after a successful merge, both fields should clear. Check uppercase
`.PDF` destinations and the single-input case as well.