Imports System.IO Imports System.Xml Imports System.Xml.Serialization Imports System.Xml.XPath Imports System.Xml.Xsl Imports DigitalData.Modules.Interfaces.Exceptions Imports DigitalData.Modules.Logging Imports GdPicture14 Public Class ZUGFeRDInterface Private ReadOnly _logConfig As LogConfig Private ReadOnly _logger As Logger Private ReadOnly _Options As ZugferdOptions Private ReadOnly ValidFilenames As New List(Of String) From { PDFEmbeds.ZUGFERD_XML_FILENAME.ToUpper, PDFEmbeds.FACTUR_X_XML_FILENAME_DE.ToUpper, PDFEmbeds.FACTUR_X_XML_FILENAME_FR.ToUpper } Private AllowedFilenames As New List(Of String) Public Enum ErrorType NoValidFile NoZugferd NoValidZugferd MissingProperties UnsupportedFormat UnknownError End Enum Public ReadOnly Property FileGroup As FileGroups Public ReadOnly Property PropertyValues As PropertyValues Public Class ZugferdOptions Public Property AllowFacturX_Filename As Boolean = True Public Property AllowXRechnung_Filename As Boolean = True End Class ''' ''' Create a new instance of ZUGFeRDInterface ''' ''' A LogConfig object ''' A valid GDPicture License ''' Optional parameters to control various settings Public Sub New(pLogConfig As LogConfig, pGDPictureKey As String, Optional pOptions As ZugferdOptions = Nothing) _logConfig = pLogConfig _logger = _logConfig.GetLogger() If pOptions Is Nothing Then _Options = New ZugferdOptions() Else _Options = pOptions End If ApplyFilenameOptions(_Options) FileGroup = New FileGroups(_logConfig) PropertyValues = New PropertyValues(_logConfig) Try Dim oLicenseManager As New LicenseManager oLicenseManager.RegisterKEY(pGDPictureKey) Catch ex As Exception _logger.Warn("GDPicture License could not be registered!") _logger.Error(ex) End Try End Sub Private Sub ApplyFilenameOptions(pOptions As ZugferdOptions) Dim oAllowedFilenames As List(Of String) = ValidFilenames If pOptions.AllowFacturX_Filename = False Then oAllowedFilenames = oAllowedFilenames. Except(New List(Of String) From {PDFEmbeds.FACTUR_X_XML_FILENAME_FR}).ToList() End If If pOptions.AllowXRechnung_Filename = False Then oAllowedFilenames = oAllowedFilenames. Except(New List(Of String) From {PDFEmbeds.FACTUR_X_XML_FILENAME_DE}).ToList() End If AllowedFilenames = oAllowedFilenames End Sub ''' ''' Validates a ZUGFeRD File and extracts the XML Document from it ''' ''' ''' Public Function ExtractZUGFeRDFileWithGDPicture(Path As String) As Object Dim oXmlDocument = ValidateZUGFeRDFileWithGDPicture(Path) If IsNothing(oXmlDocument) Then Throw New ZUGFeRDExecption(ErrorType.NoZugferd, "Datei ist keine ZUGFeRD Datei.") End If Return SerializeZUGFeRDDocument(oXmlDocument) End Function ''' ''' Validates a ZUGFeRD File and extracts the XML Document from it ''' ''' ''' Public Function ExtractZUGFeRDFileWithGDPicture(Stream As Stream) As Object Dim oXmlDocument = ValidateZUGFeRDFileWithGDPicture(Stream) If IsNothing(oXmlDocument) Then Throw New ZUGFeRDExecption(ErrorType.NoZugferd, "Datei ist keine ZUGFeRD Datei.") End If Return SerializeZUGFeRDDocument(oXmlDocument) End Function ''' ''' Validates a ZUGFeRD File and extracts the XML Document from it ''' ''' ''' ''' The embedded xml data as an XPath document Public Function ValidateZUGFeRDFileWithGDPicture(pStream As Stream) As XPathDocument Dim oEmbedExtractor = New PDFEmbeds(_logConfig) Try ' Extract XML attachments only! Dim oFiles = oEmbedExtractor.Extract(pStream, New List(Of String) From {"xml"}) ' Attachments are in this case the files that are embedded into a pdf file, ' like for example the zugferd-invoice.xml file Return HandleEmbeddedFiles(oFiles) Catch ex As ZUGFeRDExecption ' Don't log ZUGFeRD Exceptions here, they should be handled by the calling code. ' It also produces misleading error messages when checking if an attachment is a zugferd file. Throw ex Catch ex As Exception _logger.Error(ex) Throw ex End Try End Function ''' ''' Validates a ZUGFeRD File and extracts the XML Document from it ''' ''' ''' ''' The embedded xml data as an XPath document Public Function ValidateZUGFeRDFileWithGDPicture(pPath As String) As XPathDocument Dim oEmbedExtractor = New PDFEmbeds(_logConfig) Try ' Extract XML attachments only! Dim oFiles = oEmbedExtractor.Extract(pPath, New List(Of String) From {"xml"}) ' Attachments are in this case the files that are embedded into a pdf file, ' like for example the zugferd-invoice.xml file Return HandleEmbeddedFiles(oFiles) Catch ex As ZUGFeRDExecption ' Don't log ZUGFeRD Exceptions here, they should be handled by the calling code. ' It also produces misleading error messages when checking if an attachment is a zugferd file. Throw ex Catch ex As Exception _logger.Error(ex) Throw ex End Try End Function Private Function HandleEmbeddedFiles(pResults As List(Of PDFEmbeds.EmbeddedFile)) As XPathDocument Dim oXmlDocument As XPathDocument If pResults Is Nothing Then Throw New ZUGFeRDExecption(ErrorType.NoZugferd, "Datei ist keine ZUGFeRD Datei, weil die Attachments nicht gelesen werden konnten.") End If If pResults.Count = 0 Then Throw New ZUGFeRDExecption(ErrorType.NoZugferd, "Datei ist keine ZUGFeRD Datei, weil sie keine Attachments enthält.") End If ' Find the first file which filename matches the valid filenames for embedded invoice files Dim oValidResult As PDFEmbeds.EmbeddedFile = pResults. Where(Function(result) ValidFilenames.Contains(result.FileName.ToUpper)). FirstOrDefault() If oValidResult Is Nothing Then Throw New ZUGFeRDExecption(ErrorType.NoZugferd, "Datei ist keine ZUGFeRD Datei, weil keine entsprechende XML-Datei gefunden wurde.") End If ' Search the embedded files for the ones which are allowed as per the configuration. ' The config might say, allow ZUGFeRD but not Factur-X. Dim oAllowedResult As PDFEmbeds.EmbeddedFile = pResults. Where(Function(result) AllowedFilenames.Contains(result.FileName.ToUpper)). FirstOrDefault() If oAllowedResult Is Nothing Then Throw New ZUGFeRDExecption(ErrorType.UnsupportedFormat, "Datei ist eine ZUGFeRD Datei, aber das Format wird nicht unterstützt.") End If Try Using oStream As New MemoryStream(oAllowedResult.FileContents) oXmlDocument = New XPathDocument(oStream) End Using Return oXmlDocument Catch ex As ZUGFeRDExecption ' Don't log ZUGFeRD Exceptions here, they should be handled by the calling code. ' It also produces misleading error messages when checking if an attachment is a zugferd file. Throw ex Catch ex As Exception _logger.Error(ex) Throw New ZUGFeRDExecption(ErrorType.NoValidZugferd, "Datei ist eine ungültige ZUGFeRD Datei.") End Try End Function Public Function SerializeZUGFeRDDocument(pDocument As XPathDocument) As Object Try Dim oNavigator As XPathNavigator = pDocument.CreateNavigator() Dim oReader As XmlReader Dim oResult = Nothing Dim oTypes As New List(Of Type) From { GetType(ZUGFeRD.Version1_0.CrossIndustryDocumentType), GetType(ZUGFeRD.Version2_0.CrossIndustryInvoiceType), GetType(ZUGFeRD.Version2_1_1.CrossIndustryInvoiceType), GetType(ZUGFeRD.Version2_2_FacturX.CrossIndustryInvoiceType) } For Each oType In oTypes _logger.Debug("Trying Type [{0}]", oType.FullName) Dim oSerializer As New XmlSerializer(oType) Try oReader = oNavigator.ReadSubtree() oResult = oSerializer.Deserialize(oReader) _logger.Debug("Serializing with type [{0}] succeeded", oType.FullName) Exit For Catch ex As Exception _logger.Debug("Serializing with type [{0}] failed", oType.FullName) _logger.Debug(ex.Message) _logger.Error(ex.InnerException?.Message) End Try Next If oResult Is Nothing Then Throw New ApplicationException("No Types matched the given document. Document could not be serialized.") End If Return oResult Catch ex As Exception _logger.Error(ex) Throw New ZUGFeRDExecption(ErrorType.NoValidZugferd, "Datei ist eine ungültige ZUGFeRD Datei.") End Try End Function End Class