Imports System.IO
Imports System.Xml
Imports System.Xml.Serialization
Imports DigitalData.Modules.Interfaces.Exceptions
Imports DigitalData.Modules.Interfaces.PDFEmbeds
Imports DigitalData.Modules.Interfaces.ZUGFeRD
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 _Validator As Validator
' These constants define the specification markers for the different
' zugferd document schema versions. These markers need to be used to
' define the property map in the database (column SPECIFICATION).
Public Const ZUGFERD_SPEC_DEFAULT = "DEFAULT"
Public Const ZUGFERD_SPEC_10 = "ZUGFERD_10"
Public Const ZUGFERD_SPEC_2x = "ZUGFERD_2x"
Public Const ZUGFERD_SPEC_2_3x = "ZUGFERD_2_3x"
Public Const UBL_SPEC_21 = "UBL_21"
Public Const XMLSCHEMA_ZUGFERD_10 = "Version1_0"
Public Const XMLSCHEMA_ZUGFERD_20 = "Version2_0"
Public Const XMLSCHEMA_ZUGFERD_211 = "Version2_1_1"
Public Const XMLSCHEMA_ZUGFERD_22 = "Version2_2_FacturX"
Public Const XMLSCHEMA_ZUGFERD_23 = "Version2_3_FacturX"
Public Const XMLSCHEMA_ZUGFERD_233 = "Version2_3_3_FacturX"
Public Const XMLSCHEMA_UBL_21_INVOICE = "UBL2_1_INVOICE"
Public Const XMLSCHEMA_UBL_21_CREDITNOTE = "UBL2_1_CREDITNOTE"
Public Const RECEIPT_TYPE_XML = "XML"
Public Const RECEIPT_TYPE_PDF = "PDF"
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
FileTooBig
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
Public Property AllowZugferd_2_3_x_Schema As Boolean = True
Public Property AllowPeppol_3_x_Schema As Boolean = False
End Class
Public Class ZugferdResult
Public Property DataFileName As String
Public Property XElementObject As XElement
Public Property SchemaObject As Object
Public Property Specification As String
Public Property UsedXMLSchema As String
'''
''' Dateityp des Belegs: PDF oder XML
'''
Public Property ReceiptFileType As String
Public Property ValidationErrors As New List(Of ZugferdValidationError)
End Class
Public Class ZugferdValidationError
Public ElementName As String
Public ElementValue As String
Public ErrorMessage As String
Public ErrorMessageDE As String
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()
_Validator = New Validator(_logConfig)
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
Public Function FilterPropertyMap(pPropertyMapList As List(Of XmlItemProperty), pSpecification As String) As Dictionary(Of String, XmlItemProperty)
_logger.Debug("Filtering Property map list for Specification [{0}]", pSpecification)
If pSpecification = ZUGFERD_SPEC_10 Then
Try
_logger.Debug("Special Case [{0}], including [{1}]", ZUGFERD_SPEC_10, ZUGFERD_SPEC_DEFAULT)
Dim countElements As Integer = pPropertyMapList.Where(Function(x) x.Specification = pSpecification Or x.Specification = ZUGFERD_SPEC_DEFAULT).Count
_logger.Debug("Property map list contains [{0}] elements for specification [{1}]", countElements, pSpecification)
Return pPropertyMapList.
Where(Function(kv) kv.Specification = pSpecification Or kv.Specification = ZUGFERD_SPEC_DEFAULT).
ToDictionary(Function(kv) kv.XMLPath, Function(kv) kv)
Catch ex As Exception
_logger.Error(ex)
End Try
Else
Try
Dim countElements As Integer = pPropertyMapList.Where(Function(x) x.Specification = pSpecification).Count
_logger.Debug("Property map list contains [{0}] elements for specification [{1}]", countElements, pSpecification)
Return pPropertyMapList.
Where(Function(kv) kv.Specification = pSpecification).
ToDictionary(Function(kv) kv.XMLPath, Function(kv) kv)
Catch ex As Exception
_logger.Error(ex)
End Try
End If
Return Nothing
End Function
Public Function GetSerializedXMLContentFromFile(oFileInfo As FileInfo) As ZugferdResult
Dim oResult = New ZugferdResult()
Try
Dim oFileSize As Integer = oFileInfo.Length
Dim oFileData As Byte() = File.ReadAllBytes(oFileInfo.FullName)
Dim oXmlFile As EmbeddedFile = New EmbeddedFile() With {
.FileName = oFileInfo.Name,
.FileContents = oFileData
}
Using oStream As New MemoryStream(oXmlFile.FileContents)
oResult = New ZugferdResult With {
.DataFileName = oXmlFile.FileName,
.XElementObject = XElement.Load(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 XML Datei.")
End Try
If oResult.ValidationErrors.Any() Then
Throw New ValidationException() With {
.ValidationErrors = oResult.ValidationErrors
}
End If
Return SerializeZUGFeRDDocument(oResult)
End Function
'''
''' Validates a ZUGFeRD File and extracts the XML Document from it
'''
'''
'''
Public Function ExtractZUGFeRDFileWithGDPicture(Path As String) As ZugferdResult
Dim oResult = ValidateZUGFeRDFileWithGDPicture(Path)
oResult = ValidateZUGFeRDDocument(oResult)
If oResult.ValidationErrors.Any() Then
Throw New ValidationException() With {
.ValidationErrors = oResult.ValidationErrors
}
End If
Return SerializeZUGFeRDDocument(oResult)
End Function
'''
''' Validates a ZUGFeRD File and extracts the XML Document from it
'''
'''
'''
Public Function ExtractZUGFeRDFileWithGDPicture(Stream As Stream) As ZugferdResult
Dim oResult = ValidateZUGFeRDFileWithGDPicture(Stream)
oResult = ValidateZUGFeRDDocument(oResult)
If oResult.ValidationErrors.Any() Then
_logger.Info("Validation found [{0}] errors", oResult.ValidationErrors.Count)
oResult.ValidationErrors.ForEach(
Sub(e) _logger.Info("Field [{0}] with value [{1}] has error: [{2}]", e.ElementName, e.ElementValue, e.ErrorMessage)
)
Throw New ValidationException() With {
.ValidationErrors = oResult.ValidationErrors
}
End If
Return SerializeZUGFeRDDocument(oResult)
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 ZugferdResult
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.Warn("Error while validating eInvoice file with GDPicture")
_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 ZugferdResult
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.Warn("Error while validating eInvoice file with GDPicture")
_logger.Error(ex)
Throw ex
End Try
End Function
Private Function HandleEmbeddedFiles(pResults As List(Of PDFEmbeds.EmbeddedFile)) As ZugferdResult
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 ZugferdResult With {
.DataFileName = oAllowedResult.FileName,
.XElementObject = XElement.Load(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
Private Class AllowedType
Public SchemaType As Type
Public Specification As String
Public XMLSchema As String
End Class
Public Function ValidateZUGFeRDDocument(pResult As ZugferdResult) As ZugferdResult
Return _Validator.ValidateZUGFeRDDocument(pResult)
End Function
Public Function SerializeZUGFeRDDocument(pResult As ZugferdResult) As ZugferdResult
Try
Dim oReader As XmlReader
Dim oObject As Object = Nothing
Dim oSpecification As String = Nothing
Dim oUsedXMLSchema As String = Nothing
Dim oAllowedTypes As New List(Of AllowedType)
If _Options.AllowZugferd_1_0_Schema Then
oAllowedTypes.Add(New AllowedType With {
.SchemaType = GetType(Version1_0.CrossIndustryDocumentType),
.Specification = ZUGFERD_SPEC_10,
.XMLSchema = XMLSCHEMA_ZUGFERD_10
})
End If
'' Reihenfolge 2.0 muss unverändert bleiben. Älteste Version immer zuerst!
If _Options.AllowZugferd_2_x_Schema Then
oAllowedTypes.AddRange(New List(Of AllowedType) From {
New AllowedType With {
.SchemaType = GetType(Version2_0.CrossIndustryInvoiceType),
.Specification = ZUGFERD_SPEC_2x,
.XMLSchema = XMLSCHEMA_ZUGFERD_20
},
New AllowedType With {
.SchemaType = GetType(Version2_1_1.CrossIndustryInvoiceType),
.Specification = ZUGFERD_SPEC_2x,
.XMLSchema = XMLSCHEMA_ZUGFERD_211
},
New AllowedType With {
.SchemaType = GetType(Version2_2_FacturX.CrossIndustryInvoiceType),
.Specification = ZUGFERD_SPEC_2x,
.XMLSchema = XMLSCHEMA_ZUGFERD_22
}
})
End If
'' Reihenfolge ab 2.3 geändert. Neuste Version immer zuerst
If _Options.AllowZugferd_2_3_x_Schema Then
oAllowedTypes.AddRange(New List(Of AllowedType) From {
New AllowedType With {
.SchemaType = GetType(Version2_3_3_FacturX.CrossIndustryInvoiceType),
.Specification = ZUGFERD_SPEC_2_3x,
.XMLSchema = XMLSCHEMA_ZUGFERD_233
},
New AllowedType With {
.SchemaType = GetType(Version2_3_FacturX.CrossIndustryInvoiceType),
.Specification = ZUGFERD_SPEC_2_3x,
.XMLSchema = XMLSCHEMA_ZUGFERD_23
}
})
End If
If _Options.AllowPeppol_3_x_Schema Then
oAllowedTypes.AddRange(New List(Of AllowedType) From {
New AllowedType With {
.SchemaType = GetType(UBL_21_Invoice.InvoiceType),
.Specification = UBL_SPEC_21,
.XMLSchema = XMLSCHEMA_UBL_21_INVOICE
},
New AllowedType With {
.SchemaType = GetType(UBL_21_CreditNote.CreditNoteType),
.Specification = UBL_SPEC_21,
.XMLSchema = XMLSCHEMA_UBL_21_CREDITNOTE
}
})
End If
For Each oType In oAllowedTypes
Dim oTypeName As String = oType.SchemaType.FullName
Dim oSerializer As New XmlSerializer(oType.SchemaType)
_logger.Debug("Trying Type [{0}]", oTypeName)
Try
oReader = pResult.XElementObject.CreateReader()
oObject = oSerializer.Deserialize(oReader)
oSpecification = oType.Specification
oUsedXMLSchema = oType.XMLSchema
_logger.Debug("Serializing with type [{0}] succeeded", oTypeName)
Exit For
Catch ex As Exception
_logger.Debug("Serializing with type [{0}] failed", oTypeName)
_logger.Debug(ex.Message)
If IsNothing(ex.InnerException) = False Then
_logger.Debug(ex.InnerException.Message)
End If
End Try
Next
If oObject Is Nothing Then
'Throw New ApplicationException("No Types matched the given document. Document could not be serialized.")
Throw New ZUGFeRDExecption(ErrorType.UnsupportedFormat, "Unsupported Format")
End If
pResult.Specification = oSpecification
pResult.UsedXMLSchema = oUsedXMLSchema
pResult.SchemaObject = oObject
Return pResult
Catch ex As ZUGFeRDExecption
_logger.Error(ex)
Throw ex
Catch ex As Exception
_logger.Error(ex)
Dim oMessage = "Datei ist eine ungültige eInvoice 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