using DocumentService.Application.Common.Configuration; using DocumentService.Application.Common.Interfaces; using DocumentService.Domain.Common.Exceptions; using FluentValidation; using MediatR; using Microsoft.Extensions.Options; namespace DocumentService.Application.ExtractZugferd; /// /// Command to extract ZUGFeRD XML from a PDF document /// public record ExtractZugferdCommand : IRequest { /// /// PDF document stream. Must be positioned at the beginning (Position = 0). /// public required Stream PdfStream { get; init; } } /// /// Handler for ExtractZugferdCommand. /// Extracts ZUGFeRD XML from PDF and returns XML content /// public class ExtractZugferdCommandHandler( IPdfProcessor pdfProcessor, IOptions settings) : IRequestHandler { private readonly ZugferdSettings _settings = settings.Value; public async Task Handle(ExtractZugferdCommand request, CancellationToken cancellationToken) { // Get all attachments var attachmentInfo = await pdfProcessor.CheckAttachmentsAsync(request.PdfStream); // Find ZUGFeRD XML file using configured names and patterns var zugferdAttachment = attachmentInfo.Attachments.FirstOrDefault(a => _settings.ZugferdFileNames.Any(name => a.FileName.Equals(name, StringComparison.OrdinalIgnoreCase)) || _settings.ZugferdFileNamePatterns.Any(pattern => a.FileName.Contains(pattern, StringComparison.OrdinalIgnoreCase))); if (zugferdAttachment == null) { throw new NotFoundException("ZUGFeRD XML not found in PDF attachments"); } // Reset stream position for extraction request.PdfStream.Position = 0; // Extract all attachments as ZIP byte[] zipBytes = await pdfProcessor.ExtractAttachmentsAsync(request.PdfStream); // Find ZUGFeRD XML in ZIP using var zipStream = new MemoryStream(zipBytes); using var zipArchive = new System.IO.Compression.ZipArchive(zipStream, System.IO.Compression.ZipArchiveMode.Read); var zugferdEntry = zipArchive.Entries.FirstOrDefault(e => e.Name.Equals(zugferdAttachment.FileName, StringComparison.OrdinalIgnoreCase)); if (zugferdEntry == null) { throw new NotFoundException($"ZUGFeRD XML '{zugferdAttachment.FileName}' not found in extracted attachments"); } // Read XML content using var entryStream = zugferdEntry.Open(); using var reader = new StreamReader(entryStream); string xmlContent = await reader.ReadToEndAsync(cancellationToken); return new ZugferdExtractionResult { FileName = zugferdAttachment.FileName, XmlContent = xmlContent, FileSize = zugferdAttachment.SizeBytes }; } } /// /// Validator for ExtractZugferdCommand. /// public class ExtractZugferdCommandValidator : AbstractValidator { public ExtractZugferdCommandValidator() { RuleFor(x => x.PdfStream) .NotNull() .WithMessage("PDF stream is required"); } } /// /// Result DTO for ZUGFeRD extraction /// public record ZugferdExtractionResult { /// /// ZUGFeRD XML file name /// public required string FileName { get; init; } /// /// ZUGFeRD XML content as string /// public required string XmlContent { get; init; } /// /// File size in bytes /// public long FileSize { get; init; } }