feat: Implement DevExpressPdfProcessor.ExtractAttachmentsAsync with ZIP packaging
This commit is contained in:
@@ -234,6 +234,68 @@ public class DevExpressPdfProcessor : IPdfProcessor
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extracts all embedded files from a PDF document and returns them as a ZIP archive.
|
||||||
|
/// Uses DevExpress PdfDocument.FileAttachments to retrieve attachment data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pdfStream">PDF content as stream (caller is responsible for disposal)</param>
|
||||||
|
/// <returns>ZIP archive byte array containing all extracted attachments</returns>
|
||||||
|
/// <exception cref="BadRequestException">Thrown when stream is empty</exception>
|
||||||
|
/// <exception cref="NotFoundException">Thrown when PDF contains no attachments</exception>
|
||||||
|
public async Task<byte[]> 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
|
#endregion
|
||||||
|
|
||||||
#region Private Helpers
|
#region Private Helpers
|
||||||
|
|||||||
Reference in New Issue
Block a user