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 Public Property AllowZugferd_1_0_Schema As Boolean = True Public Property AllowZugferd_2_x_Schema 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 Tuple(Of String, Object) Dim oXmlDocument = ValidateZUGFeRDFileWithGDPicture(Path) If IsNothing(oXmlDocument.Item2) 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 Tuple(Of String, Object) Dim oXmlDocument = ValidateZUGFeRDFileWithGDPicture(Stream) If IsNothing(oXmlDocument.Item2) 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 Tuple(Of String, 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 Tuple(Of String, 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 Tuple(Of String, 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(f) ValidFilenames.Contains(f.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(f) AllowedFilenames.Contains(f.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.", oAllowedResult.FileName) End If Try Using oStream As New MemoryStream(oAllowedResult.FileContents) Return New Tuple(Of String, XPathDocument)(oAllowedResult.FileName, New XPathDocument(oStream)) End Using 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 Tuple(Of String, XPathDocument)) As Tuple(Of String, Object) Try Dim oNavigator As XPathNavigator = pDocument.Item2.CreateNavigator() Dim oReader As XmlReader Dim oObject As Object = Nothing Dim oAllowedTypes As New List(Of Type) If _Options.AllowZugferd_1_0_Schema Then oAllowedTypes.Add(GetType(ZUGFeRD.Version1_0.CrossIndustryDocumentType)) End If If _Options.AllowZugferd_2_x_Schema Then oAllowedTypes.AddRange(New List(Of Type) From { GetType(ZUGFeRD.Version2_0.CrossIndustryInvoiceType), GetType(ZUGFeRD.Version2_1_1.CrossIndustryInvoiceType), GetType(ZUGFeRD.Version2_2_FacturX.CrossIndustryInvoiceType) }) End If For Each oType In oAllowedTypes _logger.Debug("Trying Type [{0}]", oType.FullName) Dim oSerializer As New XmlSerializer(oType) Try oReader = oNavigator.ReadSubtree() oObject = 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 oObject Is Nothing Then Throw New ApplicationException("No Types matched the given document. Document could not be serialized.") End If Return New Tuple(Of String, Object)(pDocument.Item1, oObject) Catch ex As Exception _logger.Error(ex) Dim oMessage = "Datei ist eine ungültige ZUGFeRD Datei oder das Format wird nicht unterstüzt, oder das Format ist deaktiviert." Throw New ZUGFeRDExecption(ErrorType.NoValidZugferd, oMessage) End Try End Function End Class