417 lines
17 KiB
VB.net
417 lines
17 KiB
VB.net
Imports System.Collections.Generic
|
|
Imports System.IO
|
|
Imports System.Reflection.Emit
|
|
Imports System.Xml
|
|
Imports System.Xml.Serialization
|
|
Imports System.Xml.XPath
|
|
Imports System.Xml.Xsl
|
|
Imports DigitalData.Modules.Interfaces.Exceptions
|
|
Imports DigitalData.Modules.Interfaces.ZUGFeRD
|
|
Imports DigitalData.Modules.Interfaces.ZUGFeRDInterface
|
|
Imports DigitalData.Modules.Logging
|
|
Imports GdPicture14
|
|
|
|
Public Class ZUGFeRDInterface
|
|
Private ReadOnly _logConfig As LogConfig
|
|
Private ReadOnly _logger As Logger
|
|
Private ReadOnly _Options As ZugferdOptions
|
|
|
|
' 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"
|
|
|
|
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
|
|
End Class
|
|
|
|
Public Class ZugferdResult
|
|
Public Property DataFileName As String
|
|
Public Property XPathObject As XPathDocument
|
|
Public Property SchemaObject As Object
|
|
Public Property Specification 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
|
|
End Class
|
|
|
|
''' <summary>
|
|
''' Create a new instance of ZUGFeRDInterface
|
|
''' </summary>
|
|
''' <param name="pLogConfig">A LogConfig object</param>
|
|
''' <param name="pGDPictureKey">A valid GDPicture License</param>
|
|
''' <param name="pOptions">Optional parameters to control various settings</param>
|
|
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
|
|
|
|
Public Function FilterPropertyMap(pPropertyMap As Dictionary(Of String, XmlItemProperty), pSpecification As String) As Dictionary(Of String, XmlItemProperty)
|
|
_logger.Debug("Filtering Property map for Specification [{0}]", pSpecification)
|
|
|
|
If pSpecification = ZUGFERD_SPEC_10 Then
|
|
_logger.Debug("Special Case [{0}], including [{1}]", ZUGFERD_SPEC_10, ZUGFERD_SPEC_DEFAULT)
|
|
Return pPropertyMap.
|
|
Where(Function(kv) kv.Value.Specification = pSpecification Or kv.Value.Specification = ZUGFERD_SPEC_DEFAULT).
|
|
ToDictionary(Function(kv) kv.Key, Function(kv) kv.Value)
|
|
Else
|
|
_logger.Debug("Using Specification [{0}]", pSpecification)
|
|
Return pPropertyMap.
|
|
Where(Function(kv) kv.Value.Specification = pSpecification).
|
|
ToDictionary(Function(kv) kv.Key, Function(kv) kv.Value)
|
|
End If
|
|
End Function
|
|
|
|
''' <summary>
|
|
''' Validates a ZUGFeRD File and extracts the XML Document from it
|
|
''' </summary>
|
|
''' <param name="Path"></param>
|
|
''' <exception cref="ZUGFeRDExecption"></exception>
|
|
Public Function ExtractZUGFeRDFileWithGDPicture(Path As String) As ZugferdResult
|
|
Dim oResult = ValidateZUGFeRDFileWithGDPicture(Path)
|
|
oResult = ValidateZUGFeRDDocument(oResult)
|
|
|
|
'If IsNothing(oResult.SchemaObject) Then
|
|
Throw New ZUGFeRDExecption(ErrorType.NoZugferd, "Datei ist keine ZUGFeRD Datei.")
|
|
'End If
|
|
|
|
Return SerializeZUGFeRDDocument(oResult)
|
|
End Function
|
|
|
|
''' <summary>
|
|
''' Validates a ZUGFeRD File and extracts the XML Document from it
|
|
''' </summary>
|
|
''' <param name="Stream"></param>
|
|
''' <exception cref="ZUGFeRDExecption"></exception>
|
|
Public Function ExtractZUGFeRDFileWithGDPicture(Stream As Stream) As ZugferdResult
|
|
Dim oResult = ValidateZUGFeRDFileWithGDPicture(Stream)
|
|
oResult = ValidateZUGFeRDDocument(oResult)
|
|
|
|
'If IsNothing(oResult.SchemaObject) Then
|
|
' Throw New ZUGFeRDExecption(ErrorType.NoZugferd, "Datei ist keine ZUGFeRD Datei.")
|
|
'End If
|
|
|
|
Return SerializeZUGFeRDDocument(oResult)
|
|
End Function
|
|
|
|
''' <summary>
|
|
''' Validates a ZUGFeRD File and extracts the XML Document from it
|
|
''' </summary>
|
|
''' <param name="pStream"></param>
|
|
''' <exception cref="ZUGFeRDExecption"></exception>
|
|
''' <returns>The embedded xml data as an XPath document</returns>
|
|
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 ZUGFeRD file with GDPicture")
|
|
_logger.Error(ex)
|
|
Throw ex
|
|
End Try
|
|
End Function
|
|
|
|
''' <summary>
|
|
''' Validates a ZUGFeRD File and extracts the XML Document from it
|
|
''' </summary>
|
|
''' <param name="pPath"></param>
|
|
''' <exception cref="ZUGFeRDExecption"></exception>
|
|
''' <returns>The embedded xml data as an XPath document</returns>
|
|
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 ZUGFeRD 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,
|
|
.XPathObject = 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
|
|
|
|
Private Class AllowedType
|
|
Public SchemaType As Type
|
|
Public Specification As String
|
|
End Class
|
|
|
|
Public Function ValidateZUGFeRDDocument(pResult As ZugferdResult) As ZugferdResult
|
|
Dim oNavigator As XPathNavigator = pResult.XPathObject.CreateNavigator()
|
|
Dim oNamespaceManager As New XmlNamespaceManager(oNavigator.NameTable)
|
|
|
|
oNamespaceManager.AddNamespace("ram", "urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:12")
|
|
oNamespaceManager.AddNamespace("rsm", "urn:ferd:CrossIndustryDocument:invoice:1p0")
|
|
|
|
' CurrencyCode Nodes
|
|
Try
|
|
Dim oCurrencyCodeIterator As XPathNodeIterator = oNavigator.
|
|
Select("//ram:InvoiceCurrencyCode | //ram:TaxCurrencyCode | //ram:TaxCurrencyCode | //ram:SourceCurrencyCode", oNamespaceManager)
|
|
|
|
While oCurrencyCodeIterator.MoveNext()
|
|
Dim oNode As XPathNavigator = oCurrencyCodeIterator.Current
|
|
Dim oValid = ValidateCurrencyCode(oNode.Value)
|
|
If oValid = False Then
|
|
pResult.ValidationErrors.Add(New ZugferdValidationError() With {
|
|
.ElementName = oNode.Name,
|
|
.ElementValue = oNode.Value,
|
|
.ErrorMessage = "Invalid CurrencyCode. Only 3-Character codes are allowed."
|
|
})
|
|
End If
|
|
End While
|
|
Catch ex As Exception
|
|
_logger.Error(ex)
|
|
End Try
|
|
|
|
' currencyID
|
|
Try
|
|
Dim oCurrencyIDIterator As XPathNodeIterator = oNavigator.Select("//*[@currencyID]")
|
|
|
|
While oCurrencyIDIterator.MoveNext()
|
|
Dim oNode As XPathNavigator = oCurrencyIDIterator.Current
|
|
Dim oCurrencyID As String = oNode.GetAttribute("currencyID", "")
|
|
|
|
' CurrencyID is optional per spec
|
|
If String.IsNullOrWhiteSpace(oCurrencyID) Then
|
|
Continue While
|
|
End If
|
|
|
|
Dim oValid = ValidateCurrencyCode(oCurrencyID)
|
|
If oValid = False Then
|
|
pResult.ValidationErrors.Add(New ZugferdValidationError() With {
|
|
.ElementName = oNode.Name,
|
|
.ElementValue = oCurrencyID,
|
|
.ErrorMessage = "Invalid currencyID. Only 3-Character codes or empty values are allowed."
|
|
})
|
|
End If
|
|
End While
|
|
Catch ex As Exception
|
|
_logger.Error(ex)
|
|
End Try
|
|
|
|
Return pResult
|
|
End Function
|
|
|
|
Private Function ValidateCurrencyCode(pValue As String) As Boolean
|
|
Dim oValueRegex As New Text.RegularExpressions.Regex("[A-Z]{3}")
|
|
|
|
If oValueRegex.IsMatch(pValue) = False Then
|
|
Return False
|
|
End If
|
|
|
|
Return True
|
|
End Function
|
|
|
|
Public Function SerializeZUGFeRDDocument(pResult As ZugferdResult) As ZugferdResult
|
|
Try
|
|
Dim oNavigator As XPathNavigator = pResult.XPathObject.CreateNavigator()
|
|
Dim oReader As XmlReader
|
|
|
|
Dim oObject As Object = Nothing
|
|
Dim oSpecification 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
|
|
})
|
|
End If
|
|
|
|
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
|
|
},
|
|
New AllowedType With {
|
|
.SchemaType = GetType(Version2_1_1.CrossIndustryInvoiceType),
|
|
.Specification = ZUGFERD_SPEC_2x
|
|
},
|
|
New AllowedType With {
|
|
.SchemaType = GetType(Version2_2_FacturX.CrossIndustryInvoiceType),
|
|
.Specification = ZUGFERD_SPEC_2x
|
|
}
|
|
})
|
|
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 = oNavigator.ReadSubtree()
|
|
|
|
oObject = oSerializer.Deserialize(oReader)
|
|
oSpecification = oType.Specification
|
|
|
|
_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.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 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
|