From 26458a4017dc05ae61afd7bcc0214656329efe50 Mon Sep 17 00:00:00 2001 From: TekH Date: Tue, 21 Jul 2026 09:26:18 +0200 Subject: [PATCH] feat: Implement DevExpressPdfProcessor.ExtractAttachmentsAsync with ZIP packaging --- .../PdfProcessing/DevExpressPdfProcessor.cs | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/DocumentOperator.Infrastructure/Services/PdfProcessing/DevExpressPdfProcessor.cs b/DocumentOperator.Infrastructure/Services/PdfProcessing/DevExpressPdfProcessor.cs index 6a6d380..ed4118a 100644 --- a/DocumentOperator.Infrastructure/Services/PdfProcessing/DevExpressPdfProcessor.cs +++ b/DocumentOperator.Infrastructure/Services/PdfProcessing/DevExpressPdfProcessor.cs @@ -234,6 +234,68 @@ public class DevExpressPdfProcessor : IPdfProcessor ); } + /// + /// Extracts all embedded files from a PDF document and returns them as a ZIP archive. + /// Uses DevExpress PdfDocument.FileAttachments to retrieve attachment data. + /// + /// PDF content as stream (caller is responsible for disposal) + /// ZIP archive byte array containing all extracted attachments + /// Thrown when stream is empty + /// Thrown when PDF contains no attachments + public async Task ExtractAttachmentsAsync(Stream pdfStream) + { + // 1. Input Validation + ArgumentNullException.ThrowIfNull(pdfStream, nameof(pdfStream)); + + if (pdfStream.Length == 0) + { + throw new BadRequestException("PDF stream cannot be empty"); + } + + // Defensive validation: Seekable streams must be at Position = 0 + if (pdfStream.CanSeek && pdfStream.Position != 0) + { + throw new BadRequestException("PDF stream must be positioned at the beginning (Position = 0)."); + } + + // 2. Load PDF with DevExpress Document API + using var processor = new PdfDocumentProcessor(); + processor.LoadDocument(pdfStream); + + var document = processor.Document; + + // 3. Extract attachment data using DevExpress FileAttachments collection + var fileAttachments = document.FileAttachments; + + // 4. No attachments case + if (fileAttachments == null || !fileAttachments.Any()) + { + throw new NotFoundException("PDF does not contain any attachments"); + } + + // 5. Create ZIP archive in memory + using var zipStream = new MemoryStream(); + using (var zipArchive = new System.IO.Compression.ZipArchive(zipStream, System.IO.Compression.ZipArchiveMode.Create, leaveOpen: true)) + { + foreach (var attachment in fileAttachments) + { + // Get attachment metadata + string fileName = attachment.FileName ?? "unnamed"; + byte[] fileData = attachment.Data; + + // Create entry in ZIP + var entry = zipArchive.CreateEntry(fileName, System.IO.Compression.CompressionLevel.Optimal); + + // Write attachment data to ZIP entry + using var entryStream = entry.Open(); + await entryStream.WriteAsync(fileData, 0, fileData.Length); + } + } + + // 6. Return ZIP byte array + return zipStream.ToArray(); + } + #endregion #region Private Helpers